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