1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @class
 26  * This class represents a panel used to modify the current search.
 27  * 
 28  * @param {hash}			params		a hash of parameters:
 29  * @param {DwtComposite}	parent		parent widget
 30  * @param {ZmController}	controller	search results controller
 31  * @param {constant}		resultsApp	name of app corresponding to type of results
 32  * 
 33  * TODO: Add change listeners to update filters as necessary, eg folders and tags.
 34  */
 35 ZmSearchResultsFilterPanel = function(params) {
 36 
 37 	params.className = params.className || "ZmSearchResultsFilterPanel";
 38 	params.posStyle = Dwt.ABSOLUTE_STYLE;
 39 	DwtComposite.apply(this, arguments);
 40 	
 41 	// Need to wait for ZmApp.* constants to have been defined
 42 	if (!ZmSearchResultsFilterPanel.BASIC_FILTER) {
 43 		ZmSearchResultsFilterPanel._initConstants();
 44 	}
 45 
 46 	this._controller = params.controller;
 47 	this._resultsApp = params.resultsApp;
 48 	this._viewId = this._controller.getCurrentViewId();
 49 	
 50 	// basic filters
 51 	this._checkbox = {};
 52 	
 53 	// advanced filters
 54 	this._menu		= {};
 55 	this._advancedFilterHandlers = {};
 56 	
 57 	this._createHtml();
 58 	this._addFilters();
 59 	this._addConditionals();
 60 };
 61 
 62 ZmSearchResultsFilterPanel.prototype = new DwtComposite;
 63 ZmSearchResultsFilterPanel.prototype.constructor = ZmSearchResultsFilterPanel;
 64 
 65 ZmSearchResultsFilterPanel.prototype.isZmSearchResultsFilterPanel = true;
 66 ZmSearchResultsFilterPanel.prototype.toString = function() { return "ZmSearchResultsFilterPanel"; };
 67 
 68 ZmSearchResultsFilterPanel.prototype.TEMPLATE = "share.Widgets#ZmSearchResultsFilterPanel";
 69 
 70 // used for element IDs
 71 ZmSearchResultsFilterPanel.BASIC	= "BasicFilter";
 72 ZmSearchResultsFilterPanel.ADVANCED	= "AdvancedFilter";
 73 
 74 // filter types
 75 ZmSearchResultsFilterPanel.ID_ATTACHMENT	= "ATTACHMENT";
 76 ZmSearchResultsFilterPanel.ID_FLAGGED		= "FLAGGED";
 77 ZmSearchResultsFilterPanel.ID_UNREAD		= "UNREAD";
 78 ZmSearchResultsFilterPanel.ID_TO			= "TO";
 79 ZmSearchResultsFilterPanel.ID_FROM			= "FROM";
 80 ZmSearchResultsFilterPanel.ID_DATE			= "DATE";
 81 ZmSearchResultsFilterPanel.ID_DATE_BRIEFCASE = "DATE_BRIEFCASE";
 82 ZmSearchResultsFilterPanel.ID_DATE_SENT		= "DATE_SENT";
 83 ZmSearchResultsFilterPanel.ID_SIZE			= "SIZE";
 84 ZmSearchResultsFilterPanel.ID_STATUS		= "STATUS";
 85 ZmSearchResultsFilterPanel.ID_TAG			= "TAG";
 86 ZmSearchResultsFilterPanel.ID_FOLDER		= "FOLDER";
 87 
 88 // filter can be used within any app
 89 ZmSearchResultsFilterPanel.ALL_APPS = "ALL";
 90 
 91 // ordered list of basic filters
 92 ZmSearchResultsFilterPanel.BASIC_FILTER_LIST = [
 93 	ZmSearchResultsFilterPanel.ID_ATTACHMENT,
 94 	ZmSearchResultsFilterPanel.ID_FLAGGED,
 95 	ZmSearchResultsFilterPanel.ID_UNREAD
 96 ];
 97 
 98 // ordered list of advanced filters
 99 ZmSearchResultsFilterPanel.ADVANCED_FILTER_LIST = [
100 	ZmSearchResultsFilterPanel.ID_FROM,
101 	ZmSearchResultsFilterPanel.ID_TO,
102 	ZmSearchResultsFilterPanel.ID_DATE,
103 	ZmSearchResultsFilterPanel.ID_DATE_BRIEFCASE,
104 	ZmSearchResultsFilterPanel.ID_DATE_SENT,
105 	ZmSearchResultsFilterPanel.ID_ATTACHMENT,
106 	ZmSearchResultsFilterPanel.ID_SIZE,
107 	ZmSearchResultsFilterPanel.ID_STATUS,
108 	ZmSearchResultsFilterPanel.ID_TAG,
109 	ZmSearchResultsFilterPanel.ID_FOLDER
110 ];
111 
112 ZmSearchResultsFilterPanel._initConstants =
113 function() {
114 
115 	// basic filters
116 	ZmSearchResultsFilterPanel.BASIC_FILTER = {};
117 	ZmSearchResultsFilterPanel.BASIC_FILTER[ZmSearchResultsFilterPanel.ID_ATTACHMENT] = {
118 		text:	ZmMsg.filterHasAttachment,
119 		term:	new ZmSearchToken("has", "attachment"),
120 		apps:	[ZmApp.MAIL, ZmApp.CALENDAR, ZmApp.TASKS]
121 	};
122 	ZmSearchResultsFilterPanel.BASIC_FILTER[ZmSearchResultsFilterPanel.ID_FLAGGED] = {
123 		text:	        ZmMsg.filterIsFlagged,
124 		term:	        new ZmSearchToken("is", "flagged"),
125 		precondition:   ZmSetting.FLAGGING_ENABLED
126 	};
127 	ZmSearchResultsFilterPanel.BASIC_FILTER[ZmSearchResultsFilterPanel.ID_UNREAD] = {
128 		text:	ZmMsg.filterisUnread,
129 		term:	new ZmSearchToken("is", "unread")
130 	};
131 	
132 	// advanced filters
133 	ZmSearchResultsFilterPanel.ADVANCED_FILTER = {};
134 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_FROM] = {
135 		text: 		ZmMsg.filterReceivedFrom,
136 		handler:	"ZmAddressSearchFilter",
137 		searchOp:	"from"
138 	};
139 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_TO] = {
140 		text: 		ZmMsg.filterSentTo,
141 		handler:	"ZmAddressSearchFilter",
142 		searchOp:	"to"
143 	};
144 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_DATE] = {
145 		text: 		ZmMsg.filterDate,
146 		handler:	"ZmApptDateSearchFilter",
147 		apps:		[ZmApp.CALENDAR, ZmApp.TASKS]
148 	};
149 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_DATE_SENT] = {
150 		text: 		ZmMsg.filterDateSent,
151 		handler:	"ZmDateSearchFilter"
152 	};
153 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_DATE_BRIEFCASE] = {
154 		text: 		ZmMsg.filterDate,
155 		handler:	"ZmDateSearchFilter",
156 		apps:       [ZmApp.BRIEFCASE]
157 	};
158 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_ATTACHMENT] = {
159 		text: 		ZmMsg.filterAttachments,
160 		handler:	"ZmAttachmentSearchFilter",
161 		searchOp:	"attachment",
162 		apps:		[ZmApp.MAIL, ZmApp.CALENDAR, ZmApp.TASKS]
163 	};
164 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_SIZE] = {
165 		text: 		ZmMsg.filterSize,
166 		handler:	"ZmSizeSearchFilter",
167 		apps:       [ZmApp.MAIL, ZmApp.BRIEFCASE]
168 	};
169 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_STATUS] = {
170 		text: 		ZmMsg.filterStatus,
171 		handler:	"ZmStatusSearchFilter",
172 		searchOp:	"is"
173 	};
174 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_TAG] = {
175 		text: 			ZmMsg.filterTag,
176 		handler:		"ZmTagSearchFilter",
177 		searchOp:		"tag",
178 		apps:			ZmSearchResultsFilterPanel.ALL_APPS,
179 		precondition:	appCtxt.getTagTree() && appCtxt.getTagTree().size() > 0
180 	};
181 	ZmSearchResultsFilterPanel.ADVANCED_FILTER[ZmSearchResultsFilterPanel.ID_FOLDER] = {
182 		text: 		ZmMsg.filterFolder,
183 		handler:	"ZmFolderSearchFilter",
184 		searchOp:	"in",
185 		noMenu:		true,						// has own menu to add to button
186 		apps:		ZmSearchResultsFilterPanel.ALL_APPS
187 	};
188 };
189 
190 ZmSearchResultsFilterPanel.CONDITIONALS = [
191 	ZmParsedQuery.COND_AND,
192 	ZmParsedQuery.COND_OR,
193 	ZmParsedQuery.COND_NOT,
194 	ZmParsedQuery.GROUP_OPEN,
195 	ZmParsedQuery.GROUP_CLOSE
196 ];
197 
198 
199 
200 
201 ZmSearchResultsFilterPanel.prototype._createHtml =
202 function() {
203 	this.getHtmlElement().innerHTML = AjxTemplate.expand(this.TEMPLATE, {id:this._htmlElId});
204 	this._basicPanel			= document.getElementById(this._htmlElId + "_basicPanel");
205 	this._basicContainer		= document.getElementById(this._htmlElId + "_basic");
206 	this._advancedPanel			= document.getElementById(this._htmlElId + "_advancedPanel");
207 	this._advancedContainer		= document.getElementById(this._htmlElId + "_advanced");
208 	this._conditionalsContainer	= document.getElementById(this._htmlElId + "_conditionals");
209 };
210 
211 // returns a list of filters that apply for the results' app
212 ZmSearchResultsFilterPanel.prototype._getApplicableFilters = function(filterIds, filterHash) {
213 	
214 	var filters = [];
215 	for (var i = 0; i < filterIds.length; i++) {
216 		var id = filterIds[i];
217 		var filter = filterHash[id];
218 		filter.index = i;
219 		if (!appCtxt.checkPrecondition(filter.precondition)) {
220 			continue;
221 		}
222 		var apps = (filter.apps == ZmSearchResultsFilterPanel.ALL_APPS) ? filter.apps : AjxUtil.arrayAsHash(filter.apps || [ZmApp.MAIL]);
223 		if ((filter.apps == ZmSearchResultsFilterPanel.ALL_APPS) || apps[this._resultsApp]) {
224 			filters.push({id:id, filter:filter});
225 		}
226 	}
227 	return filters;
228 };
229 
230 ZmSearchResultsFilterPanel.prototype._addFilters =
231 function() {
232 	var results = this._getApplicableFilters(ZmSearchResultsFilterPanel.BASIC_FILTER_LIST, ZmSearchResultsFilterPanel.BASIC_FILTER);
233 	Dwt.setVisible(this._basicPanel, (results.length > 0));
234 	for (var i = 0; i < results.length; i++) {
235 		var result = results[i];
236 		this._addBasicFilter(result.id, result.filter.text);
237 	}
238 	
239 	var results = this._getApplicableFilters(ZmSearchResultsFilterPanel.ADVANCED_FILTER_LIST, ZmSearchResultsFilterPanel.ADVANCED_FILTER);
240 	Dwt.setVisible(this._advancedPanel, (results.length > 0));
241 	this._addAdvancedFilters();
242 };
243 
244 ZmSearchResultsFilterPanel.prototype._addAdvancedFilters =
245 function(attTypes) {
246 	ZmSearchResultsFilterPanel.attTypes = attTypes;
247 	var results = this._getApplicableFilters(ZmSearchResultsFilterPanel.ADVANCED_FILTER_LIST, ZmSearchResultsFilterPanel.ADVANCED_FILTER);
248 	Dwt.setVisible(this._advancedPanel, (results.length > 0));
249 	for (var i = 0; i < results.length; i++) {
250 		var result = results[i];
251 		this._addAdvancedFilter(result.id, result.filter.text);
252 	}
253 };
254 
255 // basic filter is just an on/off checkbox
256 ZmSearchResultsFilterPanel.prototype._addBasicFilter =
257 function(id, text) {
258 	var cb = this._checkbox[id] = new DwtCheckbox({
259 				parent:			this,
260 				className:		"filter",
261 				parentElement:	this._basicContainer,
262 				id:				DwtId.makeId(ZmId.WIDGET_CHECKBOX, this._viewId, ZmSearchResultsFilterPanel.BASIC, id)
263 			});
264 	cb.setText(text);
265 	cb.addSelectionListener(this._checkboxListener.bind(this, id));
266 };
267 
268 ZmSearchResultsFilterPanel.prototype._checkboxListener =
269 function(id, ev) {
270 	var cb = this._checkbox[id];
271 	if (!cb) { return; }
272 	var filter = ZmSearchResultsFilterPanel.BASIC_FILTER[id];
273 	var term = filter && filter.term;
274 	if (term) {
275 		if (cb.isSelected()) {
276 			this._controller.addSearchTerm(term);
277 		}
278 		else {
279 			this._controller.removeSearchTerm(term);
280 		}
281 	}
282 };
283 
284 ZmSearchResultsFilterPanel.prototype._addAdvancedFilter =
285 function(id, text) {
286 
287 	var button, menu
288 	// button is what shows up in search panel
289 	button = new DwtButton({
290 				parent:			this,
291 				parentElement:	this._advancedContainer,
292 				id:				ZmId.getButtonId(this._viewId, id)
293 			});
294 	button.setText(text);
295 	
296 	// we want a wide button with dropdown on far right
297 	var buttonEl = button.getHtmlElement();
298 	var table = buttonEl && buttonEl.firstChild;
299 	if (table && table.tagName && (table.tagName.toLowerCase() == "table")) {
300 		table.style.width = "100%";
301 	}
302 
303 	var filter = ZmSearchResultsFilterPanel.ADVANCED_FILTER[id];
304 	// most filters start with a generic menu
305 	if (!filter.noMenu) {
306 		var params = {
307 			parent:	button,
308 			id:		ZmId.getMenuId(this._viewId, id),
309 			style:	DwtMenu.POPUP_STYLE
310 		};
311 		menu = new AjxCallback(this, this._createMenu, [params, id, filter]);
312 		button.setMenu({menu: menu, menuPopupStyle: DwtButton.MENU_POPUP_STYLE_CASCADE});
313 	}
314 	else {
315 		this._createFilter(button, id, filter);
316 	}
317 };
318 
319 ZmSearchResultsFilterPanel.prototype._createMenu =
320 function(params, id, filter, button) {
321 
322 	var menu = this._menu[id] = new DwtMenu(params);
323 	this._createFilter(menu, id, filter);
324 	return menu;
325 };
326 
327 ZmSearchResultsFilterPanel.prototype._createFilter =
328 function(parent, id, filter) {
329 	var handler = filter && filter.handler;
330 	var updateCallback = this.update.bind(this, id);
331 	var params = {
332 		parent:			parent,
333 		id:				id,
334 		viewId:			this._viewId,
335 		searchOp:		filter.searchOp,
336 		updateCallback:	updateCallback,
337 		resultsApp:		this._resultsApp
338 	}
339 	var filterClass = eval(handler);
340 	this._advancedFilterHandlers[id] = new filterClass(params);
341 };
342 
343 /**
344  * Updates the current search with the given search term. A check is done to see if any of the current
345  * search terms should be removed first. Some search operators should only appear once in a query (eg "in"),
346  * and some conflict with others (eg "is:read" and "is:unread").
347  * 
348  * @param {string}			id			filter ID
349  * @param {ZmSearchToken}	newTerms	search term(s)
350  * @param {boolean}			noPopdown	if true, don't popdown menu after update
351  */
352 ZmSearchResultsFilterPanel.prototype.update =
353 function(id, newTerms, noPopdown) {
354 	
355 	if (!id || !newTerms) { return; }
356 	
357 	newTerms = AjxUtil.toArray(newTerms);
358 	
359 	var curTerms = this._controller.getSearchTerms();
360 	if (curTerms && curTerms.length) {
361 		var ops = AjxUtil.arrayAsHash(AjxUtil.map(curTerms, function(a) { return a.op; }));
362 		var hasOr = ops[ZmParsedQuery.COND_OR];
363 		for (var i = 0; i < curTerms.length; i++) {
364 			var curTerm = curTerms[i];
365 			for (var j = 0; j < newTerms.length; j++) {
366 				var newTerm = newTerms[j];
367 				if (this._areExclusiveTerms(curTerm, newTerm, hasOr)) {
368 					this._controller.removeSearchTerm(curTerm, true);
369 				}
370 			}
371 		}
372 	}
373 	
374 	for (var i = 0; i < newTerms.length; i++) {
375 		this._controller.addSearchTerm(newTerms[i]);
376 	}
377 	
378 	if (this._menu[id] && !noPopdown) {
379 		this._menu[id].popdown();
380 	}
381 };
382 
383 /**
384  * Resets the filter panel by unchecking the basic filter checkboxes.
385  */
386 ZmSearchResultsFilterPanel.prototype.reset =
387 function() {
388 	for (var id in this._checkbox) {
389 		var cb = this._checkbox[id];
390 		if (cb) {
391 			cb.setSelected(false);
392 		}
393 	}
394 	//reset all the advanced filters.
395 	for (var i in this._advancedFilterHandlers) {
396 		var handler = this._advancedFilterHandlers[i];
397 		if (handler && handler.reset) {
398 			handler.reset();
399 		}
400 	}
401 };
402 
403 ZmSearchResultsFilterPanel.prototype.resetBasicFiltersToQuery =
404 function(query) {
405 	var filtersIds = ZmSearchResultsFilterPanel.BASIC_FILTER_LIST;
406 	for (var i = 0; i < filtersIds.length; i++) {
407 		var id = filtersIds[i];
408 		var cb = this._checkbox[id];
409 		if (!cb) {
410 			continue;
411 		}
412 		var filter = ZmSearchResultsFilterPanel.BASIC_FILTER[id];
413 		//checked if query has the filter term in it.
414 		cb.setSelected(query.indexOf(filter.term.toString()) !== -1);
415 	}
416 };
417 
418 ZmSearchResultsFilterPanel.prototype._areExclusiveTerms =
419 function(termA, termB, hasOr) {
420 	termA = this._translateTerm(termA);
421 	termB = this._translateTerm(termB);
422 	return (ZmParsedQuery.areExclusive(termA, termB) || (!hasOr && (termA.op == termB.op) && !ZmParsedQuery.isMultiple(termA)));
423 };
424 
425 // Treat "appt-start" like "before", "after", or "date" depending on its argument.
426 ZmSearchResultsFilterPanel.prototype._translateTerm =
427 function(term) {
428 	var newOp;
429 	if (term.op == "appt-start") {
430 		var first = term.arg.substr(0, 1);
431 		newOp = (first == "<") ? "before" : (first == ">") ? "after" : "date";
432 	}
433 	return newOp ? new ZmSearchToken(newOp, term.arg) : term;
434 };
435 
436 ZmSearchResultsFilterPanel.prototype._addConditionals =
437 function() {
438 	var conds = ZmSearchResultsFilterPanel.CONDITIONALS;
439 	for (var i = 0; i < conds.length; i++) {
440 		var cond = conds[i];
441 		var bubbleParams = {
442 			parent:			appCtxt.getShell(),
443 			parentElement:	this._conditionalsContainer,
444 			address:		cond,
445 			addClass:		ZmParsedQuery.COND_OP[cond] ? ZmParsedQuery.COND : ZmParsedQuery.GROUP
446 		};
447 		var bubble = new ZmAddressBubble(bubbleParams);
448 		bubble.addSelectionListener(this._conditionalSelectionListener.bind(this));
449 	}
450 };
451 
452 ZmSearchResultsFilterPanel.prototype._conditionalSelectionListener =
453 function(ev) {
454 	var bubble = ev.item;
455 	this._controller.addSearchTerm(new ZmSearchToken(bubble.address), true, true);
456 };
457 
458 /**
459  * Base class for widget that adds a term to the current search.
460  * 
461  * @param {hash}		params			hash of params:
462  * @param {DwtControl}	parent			usually a DwtMenu
463  * @param {string}		id				ID of filter
464  * @param {string}		searchOp		search operator for this filter (optional)
465  * @param {function}	updateCallback	called when value of filter (as a search term) has changed
466  * @param {constant}	resultsApp		name of app corresponding to type of results
467  */
468 ZmSearchFilter = function(params) {
469 	
470 	if (arguments.length == 0) { return; }
471 	
472 	this.parent = params.parent;
473 	this.id = params.id;
474 	this._viewId = params.viewId;
475 	this._searchOp = params.searchOp;
476 	this._updateCallback = params.updateCallback;
477 	this._resultsApp = params.resultsApp;
478 	
479 	this._setUi(params.parent);
480 };
481 
482 ZmSearchFilter.prototype.isZmSearchFilter = true;
483 ZmSearchFilter.prototype.toString = function() { return "ZmSearchFilter"; };
484 
485 
486 // used to store data with a menu item
487 ZmSearchFilter.DATA_KEY = "DATA";
488 
489 ZmSearchFilter.prototype._setUi = function(menu) {};
490 
491 // Default listener for click on menu item. Constructs a search term from the value
492 // of that item and the search op for the filter.
493 ZmSearchFilter.prototype._selectionListener =
494 function(ev) {
495 	var data = ev && ev.dwtObj && ev.dwtObj.getData(ZmSearchFilter.DATA_KEY);
496 	if (data && this._searchOp) {
497 		var term = new ZmSearchToken(this._searchOp, data);
498 		this._updateCallback(term);
499 	}
500 };
501 
502 
503 /**
504  * Allows the user to search by address or domain.
505  * 
506  * @param params
507  */
508 ZmAddressSearchFilter = function(params) {
509 	ZmSearchFilter.apply(this, arguments);
510 };
511 
512 ZmAddressSearchFilter.prototype = new ZmSearchFilter;
513 ZmAddressSearchFilter.prototype.constructor = ZmAddressSearchFilter;
514 
515 ZmAddressSearchFilter.prototype.isZmAddressSearchFilter = true;
516 ZmAddressSearchFilter.prototype.toString = function() { return "ZmAddressSearchFilter"; };
517 
518 // used for element IDs
519 ZmAddressSearchFilter.ADDRESS	= "address";
520 ZmAddressSearchFilter.DOMAIN	= "domain";
521 
522 // map search op to address type
523 ZmAddressSearchFilter.ADDR = {};
524 ZmAddressSearchFilter.ADDR["from"]	= AjxEmailAddress.FROM;
525 ZmAddressSearchFilter.ADDR["to"]	= AjxEmailAddress.TO;
526 
527 ZmAddressSearchFilter.INPUT_WIDTH = 25;
528 ZmAddressSearchFilter.NUM_DOMAINS_TO_FETCH = 100;
529 ZmAddressSearchFilter.NUM_DOMAINS_TO_SHOW = 10;
530 
531 ZmAddressSearchFilter.prototype._addInput =
532 function(menu, text, width) {
533 	var menuItem = new DwtMenuItem({
534 				parent:	menu,
535 				id:		ZmId.getMenuItemId(this._viewId, this.id, ZmAddressSearchFilter.ADDRESS)
536 			});
537 	menuItem.setText(text);
538 	var subMenu = new DwtMenu({
539 				parent:	menuItem,
540 				id:		ZmId.getMenuId(this._viewId, this.id, ZmAddressSearchFilter.ADDRESS),
541 				style:	DwtMenu.GENERIC_WIDGET_STYLE
542 			});
543 	menuItem.setMenu({menu: subMenu, menuPopupStyle: DwtButton.MENU_POPUP_STYLE_CASCADE});
544 	var input = new DwtInputField({
545 				parent:	subMenu,
546 				id:		DwtId.makeId(ZmId.WIDGET_INPUT, this._viewId, this.id, ZmAddressSearchFilter.ADDRESS),
547 				size:	width
548 			});
549 	return input;
550 };
551 
552 ZmAddressSearchFilter.prototype._addComboBox =
553 function(menu, text, width) {
554 	var menuItem = new DwtMenuItem({
555 				parent:	menu,
556 				id:		ZmId.getMenuItemId(this._viewId, this.id, ZmAddressSearchFilter.DOMAIN)
557 			});
558 	menuItem.setText(text);
559 	var subMenu = new DwtMenu({
560 				parent:	menuItem,
561 				id:		ZmId.getMenuId(this._viewId, this.id, ZmAddressSearchFilter.DOMAIN),
562 				style:	DwtMenu.GENERIC_WIDGET_STYLE
563 			});
564 	menuItem.setMenu({menu: subMenu, menuPopupStyle: DwtButton.MENU_POPUP_STYLE_CASCADE});
565 	var comboBox = new DwtComboBox({
566 				parent:			subMenu,
567 				id:		DwtId.makeId(ZmId.WIDGET_COMBOBOX, this._viewId, this.id, ZmAddressSearchFilter.ADDRESS),
568 				inputParams:	{size: width},
569 				maxRows: ZmAddressSearchFilter.NUM_DOMAINS_TO_SHOW,
570 				layout:DwtMenu.LAYOUT_SCROLL,
571 				autoScroll:true
572 			});
573 	comboBox.addChangeListener(this._domainChangeListener.bind(this));
574 	comboBox.input.addListener(DwtEvent.ONKEYUP, this._keyUpListener.bind(this));
575 	subMenu.addPopdownListener(comboBox.popdown.bind(comboBox));
576 	return comboBox;
577 };
578 
579 ZmAddressSearchFilter.prototype._initAutocomplete =
580 function() {
581 	if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) || appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.isOffline) {
582 		var params = {
583 			dataClass:			appCtxt.getAutocompleter(),
584 			matchValue:			ZmAutocomplete.AC_VALUE_EMAIL,
585 			compCallback:		this._acCompHandler.bind(this),
586 			separator:			"",
587 			contextId:			[this._viewId, this.id].join("-")
588 		};
589 		var aclv = new ZmAutocompleteListView(params);
590 		aclv.handle(this._addressBox.getInputElement());
591 	}
592 };
593 
594 // Process the filter if an address is autocompleted.
595 ZmAddressSearchFilter.prototype._acCompHandler =
596 function() {
597 	this._doUpdate(this._addressBox.getValue());
598 	this._addressBox.clear();
599 };
600 
601 
602 // Handles click on domain in the menu. Key events from the input will also
603 // come here. They are ignored.
604 ZmAddressSearchFilter.prototype._domainChangeListener =
605 function(ev) {
606 	// a menu item mouseup event will have dwtObj set
607 	if (ev && ev.dwtObj) {
608 		this._doUpdate(ev._args.newValue);
609 	}
610 };
611 
612 // Process the filter if Enter is pressed.
613 ZmAddressSearchFilter.prototype._keyUpListener =
614 function(ev) {
615 	var keyCode = DwtKeyEvent.getCharCode(ev);
616 	if (keyCode == 13 || keyCode == 3) {
617 		this._doUpdate(this._domainBox.getText());
618 	}
619 };
620 
621 ZmAddressSearchFilter.prototype._doUpdate =
622 function(address) {
623 	if (address) {
624 		var term = new ZmSearchToken(this._searchOp, address);
625 		this._updateCallback(term);
626 	}
627 };
628 
629 ZmAddressSearchFilter.prototype._setUi =
630 function(menu) {
631 	
632 	this._addressBox = this._addInput(menu, ZmMsg.address, ZmAddressSearchFilter.INPUT_WIDTH);
633 	this._initAutocomplete();
634 	
635 	if (!ZmSearchResultsFilterPanel.domains) {
636 		var domainList = new ZmDomainList();
637 		domainList.search("", ZmAddressSearchFilter.NUM_DOMAINS_TO_FETCH, this._addDomains.bind(this, menu));
638 	}
639 	else {
640 		this._addDomains(menu, ZmSearchResultsFilterPanel.domains);
641 	}
642 };
643 
644 ZmAddressSearchFilter.prototype._addDomains =
645 function(menu, domains) {
646 
647 	ZmSearchResultsFilterPanel.domains = domains;
648 	this._domainBox = this._addComboBox(menu, ZmMsg.domain, ZmAddressSearchFilter.INPUT_WIDTH);
649 	if (domains && domains.length) {
650 		for (var i = 0; i < domains.length; i++) {
651 			var domain = domains[i];
652 			var addrType = ZmAddressSearchFilter.ADDR[this._searchOp];
653 			if (domain.hasAddress(addrType)) {
654 				this._domainBox.add(domain.name, domain.name);
655 			}
656 		}
657 	}
658 };
659 
660 ZmAddressSearchFilter.prototype.reset =
661 function() {
662 	if (this._domainBox) {
663 		this._domainBox.setText('');
664 	}
665 	if (this._addressBox) {
666 		this._addressBox.setValue('');
667 	}
668 }
669 
670 /**
671  * Allows the user to search by date (before, after, or on a particular date).
672  * 
673  * @param params
674  */
675 ZmDateSearchFilter = function(params) {
676 
677 	this._calendar = {};	// calendar widgets
678 
679 	ZmSearchFilter.apply(this, arguments);
680 	
681 	this._formatter = AjxDateFormat.getDateInstance(AjxDateFormat.SHORT);
682 };
683 
684 ZmDateSearchFilter.prototype = new ZmSearchFilter;
685 ZmDateSearchFilter.prototype.constructor = ZmDateSearchFilter;
686 
687 ZmDateSearchFilter.prototype.isZmDateSearchFilter = true;
688 ZmDateSearchFilter.prototype.toString = function() { return "ZmDateSearchFilter"; };
689 
690 ZmDateSearchFilter.BEFORE	= "BEFORE";
691 ZmDateSearchFilter.AFTER	= "AFTER";
692 ZmDateSearchFilter.ON		= "ON";
693 
694 ZmDateSearchFilter.TEXT_KEY = {};
695 ZmDateSearchFilter.TEXT_KEY[ZmDateSearchFilter.BEFORE]	= "filterBefore";
696 ZmDateSearchFilter.TEXT_KEY[ZmDateSearchFilter.AFTER]	= "filterAfter";
697 ZmDateSearchFilter.TEXT_KEY[ZmDateSearchFilter.ON]		= "filterOn";
698 
699 ZmDateSearchFilter.OP = {};
700 ZmDateSearchFilter.OP[ZmDateSearchFilter.BEFORE]	= "before";
701 ZmDateSearchFilter.OP[ZmDateSearchFilter.AFTER]		= "after";
702 ZmDateSearchFilter.OP[ZmDateSearchFilter.ON]		= "date";
703 
704 ZmDateSearchFilter.prototype._createCalendar =
705 function(menu, type) {
706 	var menuItem = new DwtMenuItem({
707 				parent:	menu,
708 				id:		ZmId.getMenuItemId(this._viewId, this.id, type)
709 			}); 
710 	menuItem.setText(ZmMsg[ZmDateSearchFilter.TEXT_KEY[type]]);
711 	var subMenu = new DwtMenu({
712 				parent:	menuItem,
713 				id:		ZmId.getMenuId(this._viewId, this.id, type),
714 				style:	DwtMenu.CALENDAR_PICKER_STYLE
715 			});
716 	menuItem.setMenu({menu: subMenu, menuPopupStyle: DwtButton.MENU_POPUP_STYLE_CASCADE});
717 	var calendar = new DwtCalendar({
718 				parent:	subMenu,
719 				id:		DwtId.makeId(ZmId.WIDGET_CALENDAR, this._viewId, this.id, type)
720 			});
721 	calendar.addSelectionListener(this._doUpdate.bind(this, type));
722 	return calendar;
723 };
724 
725 ZmDateSearchFilter.prototype._doUpdate =
726 function(type) {
727 	var cal = this._calendar[type];
728 	var date = this._formatter.format(cal.getDate());
729 	var term = this._getSearchTerm(type, date);
730 	this._updateCallback(term, true);
731 };
732 
733 ZmDateSearchFilter.prototype._getTypes =
734 function() {
735 	return [
736 		ZmDateSearchFilter.BEFORE,
737 		ZmDateSearchFilter.AFTER,
738 		ZmDateSearchFilter.ON
739 	];
740 };
741 
742 ZmDateSearchFilter.prototype._getSearchTerm =
743 function(type, date) {
744 	return new ZmSearchToken(ZmDateSearchFilter.OP[type], date);
745 };
746 
747 ZmDateSearchFilter.prototype._setUi =
748 function(menu) {
749 	var calTypes = this._getTypes();
750 	for (var i = 0; i < calTypes.length; i++) {
751 		var calType = calTypes[i];
752 		this._calendar[calType] = this._createCalendar(menu, calType);
753 	}
754 };
755 
756 
757 
758 /**
759  * Allows the user to search for appts by date (before, after, or on a particular date).
760  * 
761  * @param params
762  */
763 ZmApptDateSearchFilter = function(params) {
764 	ZmDateSearchFilter.apply(this, arguments);
765 };
766 
767 ZmApptDateSearchFilter.prototype = new ZmDateSearchFilter;
768 ZmApptDateSearchFilter.prototype.constructor = ZmApptDateSearchFilter;
769 
770 ZmApptDateSearchFilter.prototype.isZmApptDateSearchFilter = true;
771 ZmApptDateSearchFilter.prototype.toString = function() { return "ZmApptDateSearchFilter"; };
772 
773 ZmApptDateSearchFilter.prototype._getSearchTerm =
774 function(type, date) {
775 	if (type == ZmDateSearchFilter.BEFORE) {
776 		return new ZmSearchToken("appt-start", "<" + date);
777 	}
778 	else if (type == ZmDateSearchFilter.AFTER) {
779 		return new ZmSearchToken("appt-end", ">" + date);
780 	}
781 	else if (type == ZmDateSearchFilter.ON) {
782 		return [new ZmSearchToken("appt-start", "<=" + date),
783 				new ZmSearchToken("appt-end", ">=" + date)];
784 	}
785 };
786 
787 
788 /**
789  * Allows the user to search by attachment type (MIME type).
790  * 
791  * @param params
792  */
793 ZmAttachmentSearchFilter = function(params) {
794 	ZmSearchFilter.apply(this, arguments);
795 };
796 
797 ZmAttachmentSearchFilter.prototype = new ZmSearchFilter;
798 ZmAttachmentSearchFilter.prototype.constructor = ZmAttachmentSearchFilter;
799 
800 ZmAttachmentSearchFilter.prototype.isZmAttachmentSearchFilter = true;
801 ZmAttachmentSearchFilter.prototype.toString = function() { return "ZmAttachmentSearchFilter"; };
802 
803 
804 ZmAttachmentSearchFilter.prototype._setUi =
805 function(menu) {
806 
807 	if (!ZmSearchResultsFilterPanel.attTypes) {
808 		var attTypeList = new ZmAttachmentTypeList();
809 		attTypeList.load(this._addAttachmentTypes.bind(this, menu));
810 	}
811 	else {
812 		this._addAttachmentTypes(menu, ZmSearchResultsFilterPanel.attTypes);
813 	}
814 };
815 	
816 ZmAttachmentSearchFilter.prototype._addAttachmentTypes =
817 function(menu, attTypes) {
818 
819 	ZmSearchResultsFilterPanel.attTypes = attTypes;
820 	var added = {};
821 	if (attTypes && attTypes.length) {
822 		for (var i = 0; i < attTypes.length; i++) {
823 			var attType = attTypes[i];
824 			if (added[attType.desc]) { continue; }
825 			var menuItem = new DwtMenuItem({
826 						parent:	menu,
827 						id:		ZmId.getMenuItemId(this._viewId, this.id, attType.type.replace("/", ":"))
828 					}); 
829 			menuItem.setText(attType.desc);
830 			menuItem.setImage(attType.image);
831 			menuItem.setData(ZmSearchFilter.DATA_KEY, attType.query || attType.type);
832 			added[attType.desc] = true;
833 		}
834 	}
835 	else {
836 		var menuItem = new DwtMenuItem({parent:	menu}); 
837 		menuItem.setText(ZmMsg.noAtt);
838 	}
839 	menu.addSelectionListener(this._selectionListener.bind(this));
840 };
841 
842 ZmAttachmentSearchFilter.prototype._selectionListener =
843 function(ev) {
844 	var data = ev && ev.dwtObj && ev.dwtObj.getData(ZmSearchFilter.DATA_KEY);
845 	if (data && this._searchOp) {
846 		var terms = [];
847 		if (data == ZmMimeTable.APP_ZIP  || data == ZmMimeTable.APP_ZIP2) {
848 			var other = (data == ZmMimeTable.APP_ZIP) ? ZmMimeTable.APP_ZIP2 : ZmMimeTable.APP_ZIP;
849 			terms.push(new ZmSearchToken(this._searchOp, data));
850 			terms.push(new ZmSearchToken(ZmParsedQuery.COND_OR));
851 			terms.push(new ZmSearchToken(this._searchOp, other));
852 		}
853 		else {
854 			terms.push(new ZmSearchToken(this._searchOp, data));
855 		}
856 		this._updateCallback(terms);
857 	}
858 };
859 
860 
861 /**
862  * Allows the user to search by size (larger or smaller than a particular size).
863  * 
864  * @param params
865  */
866 ZmSizeSearchFilter = function(params) {
867 	this._input = {};
868 	ZmSearchFilter.apply(this, arguments);
869 };
870 
871 ZmSizeSearchFilter.prototype = new ZmSearchFilter;
872 ZmSizeSearchFilter.prototype.constructor = ZmSizeSearchFilter;
873 
874 ZmSizeSearchFilter.prototype.isZmSizeSearchFilter = true;
875 ZmSizeSearchFilter.prototype.toString = function() { return "ZmSizeSearchFilter"; };
876 
877 ZmSizeSearchFilter.LARGER	= "LARGER";
878 ZmSizeSearchFilter.SMALLER	= "SMALLER";
879 
880 // used for element IDs
881 ZmSizeSearchFilter.UNIT	= "unit";
882 
883 ZmSizeSearchFilter.COMBO_INPUT_WIDTH = 2;
884 
885 ZmSizeSearchFilter.TYPES = [
886 		ZmSizeSearchFilter.LARGER,
887 		ZmSizeSearchFilter.SMALLER
888 ];
889 
890 ZmSizeSearchFilter.TEXT_KEY = {};
891 ZmSizeSearchFilter.TEXT_KEY[ZmSizeSearchFilter.LARGER]	= "filterLarger";
892 ZmSizeSearchFilter.TEXT_KEY[ZmSizeSearchFilter.SMALLER]	= "filterSmaller";
893 
894 ZmSizeSearchFilter.OP = {};
895 ZmSizeSearchFilter.OP[ZmSizeSearchFilter.LARGER]	= "larger";
896 ZmSizeSearchFilter.OP[ZmSizeSearchFilter.SMALLER]	= "smaller";
897 
898 ZmSizeSearchFilter.prototype._setUi =
899 function(menu) {
900 
901 	var types = ZmSizeSearchFilter.TYPES;
902 	for (var i = 0; i < types.length; i++) {
903 		var type = types[i];
904 		var menuItem = new DwtMenuItem({
905 					parent:	menu,
906 					id:		ZmId.getMenuItemId(this._viewId, this.id, type)
907 				});
908 		menuItem.setText(ZmMsg[ZmSizeSearchFilter.TEXT_KEY[type]]);
909 		var subMenu = new DwtMenu({
910 					parent:	menuItem,
911 					id:		ZmId.getMenuId(this._viewId, this.id, type),
912 					style:	DwtMenu.GENERIC_WIDGET_STYLE
913 				});
914 		subMenu.addClassName(this.toString() + "SubMenu");
915 		menuItem.setMenu({menu: subMenu, menuPopupStyle: DwtButton.MENU_POPUP_STYLE_CASCADE});
916 		var input = this._input[type] = new DwtInputField({
917 			parent:          subMenu,
918 			type:            DwtInputField.FLOAT,
919 			errorIconStyle:  DwtInputField.ERROR_ICON_LEFT,
920 			validationStyle: DwtInputField.CONTINUAL_VALIDATION,
921 			size:            5
922 		});
923 		input.setValidNumberRange(0, 1e6);
924 		var comboBox = new DwtComboBox({
925 			parent:			input,
926 			parentElement: input.getInputElement().parentNode,
927 			id:		DwtId.makeId(ZmId.WIDGET_COMBOBOX, this._viewId, this.id, type+ZmSizeSearchFilter.UNIT),
928 			inputParams:	{size: ZmSizeSearchFilter.COMBO_INPUT_WIDTH},
929 			useLabel: true,
930 			posStyle:DwtControl.ABSOLUTE_STYLE,
931 			className: this.toString() + "Combobox"
932 		});
933 		input.addListener(DwtEvent.ONKEYUP, this._keyUpListener.bind(this, type, comboBox));
934 		comboBox.addChangeListener(this._unitChangeListener.bind(this, type, comboBox));
935 		comboBox.add(ZmMsg.kb,"KB",true); //select kb as default value
936 		comboBox.add(ZmMsg.mb,"MB");
937 	}
938 };
939 
940 ZmSizeSearchFilter.prototype._unitChangeListener =
941 function(type, comboBox, ev) {
942 	var value = this._input[type].getValue();
943 	if (value && value != "") {
944 		var term = new ZmSearchToken(ZmSizeSearchFilter.OP[type], value + comboBox.getValue());
945 		this._updateCallback(term);
946 	}
947 };
948 
949 ZmSizeSearchFilter.prototype._keyUpListener =
950 function(type, comboBox, ev) {
951 	var keyCode = DwtKeyEvent.getCharCode(ev);
952 	var input = this._input[type];
953 	if (keyCode == 13 || keyCode == 3) {
954 		var errorMsg = input.getValidationError();
955 		if (errorMsg) {
956 			appCtxt.setStatusMsg(errorMsg, ZmStatusView.LEVEL_WARNING);
957 		} else {
958 			var term = new ZmSearchToken(ZmSizeSearchFilter.OP[type],
959 			                             input.getValue() + comboBox.getValue());
960 			this._updateCallback(term);
961 		}
962 	}
963 };
964 
965 
966 
967 /**
968  * Allows the user to search by various message statuses (unread, flagged, etc).
969  * 
970  * @param params
971  */
972 ZmStatusSearchFilter = function(params) {
973 	ZmSearchFilter.apply(this, arguments);
974 };
975 
976 ZmStatusSearchFilter.prototype = new ZmSearchFilter;
977 ZmStatusSearchFilter.prototype.constructor = ZmStatusSearchFilter;
978 
979 ZmStatusSearchFilter.prototype.isZmStatusSearchFilter = true;
980 ZmStatusSearchFilter.prototype.toString = function() { return "ZmStatusSearchFilter"; };
981 
982 
983 ZmStatusSearchFilter.prototype._setUi =
984 function(menu) {
985 	var values = ZmParsedQuery.IS_VALUES;
986 	for (var i = 0; i < values.length; i++) {
987 		var value = values[i];
988 		var menuItem = new DwtMenuItem({
989 					parent:	menu,
990 					id:		ZmId.getMenuItemId(this._viewId, this.id, value)
991 				}); 
992 		menuItem.setText(ZmMsg["QUERY_IS_" + value]);
993 		menuItem.setData(ZmSearchFilter.DATA_KEY, value);
994 	}
995 	menu.addSelectionListener(this._selectionListener.bind(this));
996 };
997 
998 
999 
1000 /**
1001  * Allows the user to search by tags.
1002  * 
1003  * @param params
1004  * TODO: filter should not show up if no tags
1005  */
1006 ZmTagSearchFilter = function(params) {
1007 	ZmSearchFilter.apply(this, arguments);
1008 
1009 	this._tagList = appCtxt.getTagTree();
1010 	if (this._tagList) {
1011 		this._tagList.addChangeListener(this._tagChangeListener.bind(this));
1012 	}
1013 };
1014 
1015 ZmTagSearchFilter.prototype = new ZmSearchFilter;
1016 ZmTagSearchFilter.prototype.constructor = ZmTagSearchFilter;
1017 
1018 ZmTagSearchFilter.prototype.isZmTagSearchFilter = true;
1019 ZmTagSearchFilter.prototype.toString = function() { return "ZmTagSearchFilter"; };
1020 
1021 ZmTagSearchFilter.prototype._setUi =
1022 function(menu) {
1023 
1024 	this._menu = menu;
1025 	var tags = appCtxt.getTagTree().asList();
1026 	if (tags && tags.length) {
1027 		for (var i = 0; i < tags.length; i++) {
1028 			var tag = tags[i];
1029 			if (tag.id == ZmOrganizer.ID_ROOT) { continue; }
1030 			var menuItem = new DwtMenuItem({
1031 						parent:	menu,
1032 						id:		ZmId.getMenuItemId(this._viewId, this.id, tag.id)
1033 					}); 
1034 			menuItem.setText(tag.getName());
1035 			menuItem.setImage(tag.getIconWithColor());
1036 			menuItem.setData(ZmSearchFilter.DATA_KEY, tag.getName(false, null, true));
1037 		}
1038 	}
1039 	menu.addSelectionListener(this._selectionListener.bind(this));
1040 };
1041 
1042 // for any change to tags, just re-render
1043 ZmTagSearchFilter.prototype._tagChangeListener =
1044 function(ev) {
1045 	if (this._menu) {
1046 		this._menu.removeChildren();
1047 		this._setUi(this._menu);
1048 	}
1049 };
1050 
1051 /**
1052  * Allows the user to search by folder.
1053  * 
1054  * @param params
1055  */
1056 ZmFolderSearchFilter = function(params) {
1057 	ZmSearchFilter.apply(this, arguments);
1058 	
1059 	// set button title to appropriate organizer type
1060 	if (!ZmFolderSearchFilter.TEXT_KEY) {
1061 		ZmFolderSearchFilter._initConstants();
1062 	}
1063 	var title = ZmMsg[ZmFolderSearchFilter.TEXT_KEY[this._resultsApp]] || ZmMsg.filterFolder;
1064 	params.parent.setText(title);
1065 };
1066 
1067 ZmFolderSearchFilter.prototype = new ZmSearchFilter;
1068 ZmFolderSearchFilter.prototype.constructor = ZmFolderSearchFilter;
1069 
1070 ZmFolderSearchFilter.prototype.isZmFolderSearchFilter = true;
1071 ZmFolderSearchFilter.prototype.toString = function() { return "ZmFolderSearchFilter"; };
1072 
1073 ZmFolderSearchFilter._initConstants =
1074 function(button) {
1075 	ZmFolderSearchFilter.TEXT_KEY = {};
1076 	ZmFolderSearchFilter.TEXT_KEY[ZmApp.MAIL]		= "filterFolder";
1077 	ZmFolderSearchFilter.TEXT_KEY[ZmApp.CALENDAR]	= "filterCalendar";
1078 	ZmFolderSearchFilter.TEXT_KEY[ZmApp.CONTACTS]	= "filterAddressBook";
1079 	ZmFolderSearchFilter.TEXT_KEY[ZmApp.TASKS]		= "filterTasksFolder";
1080 	ZmFolderSearchFilter.TEXT_KEY[ZmApp.BRIEFCASE]	= "filterBriefcase";
1081 };
1082 
1083 ZmFolderSearchFilter.prototype._setUi =
1084 function(button) {
1085 
1086 	// create menu for button
1087 	var moveMenu = this._moveMenu = new DwtMenu({
1088 				parent: button,
1089 				id:		ZmId.getMenuId(this._viewId, this.id)
1090 			});
1091 	moveMenu.getHtmlElement().style.width = "auto";
1092 	button.setMenu({menu: moveMenu, menuPopupStyle: DwtButton.MENU_POPUP_STYLE_CASCADE});
1093 
1094 	var chooser = this._folderChooser = new ZmFolderChooser({
1095 				parent:			moveMenu,
1096 				id:				DwtId.makeId(ZmId.WIDGET_CHOOSER, this._viewId, this.id),
1097 				hideNewButton:	true
1098 			});
1099 	var moveParams = this._getMoveParams(chooser);
1100 	chooser.setupFolderChooser(moveParams, this._selectionListener.bind(this));
1101 };
1102 
1103 ZmFolderSearchFilter.prototype._getMoveParams =
1104 function(dlg) {
1105 	return {
1106 		overviewId:		dlg.getOverviewId([this.toString(), this._resultsApp, this._viewId].join("_")),
1107 		treeIds:		[ZmApp.ORGANIZER[this._resultsApp]],
1108 		noRootSelect: 	true,
1109 		treeStyle:		DwtTree.SINGLE_STYLE
1110 	};
1111 };
1112 
1113 ZmFolderSearchFilter.prototype._selectionListener =
1114 function(folder) {
1115 	if (folder) {
1116 		var term = new ZmSearchToken("in", folder.createQuery(true));
1117 		this._updateCallback(term);
1118 		this._moveMenu.popdown();
1119 	}
1120 };
1121