1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates the filter rules view. 26 * @class 27 * This class represents the filters tab view in preferences application. 28 * 29 * @param {DwtComposite} parent the parent widget 30 * @param {ZmController} controller the controller 31 * 32 * @extends DwtTabViewPage 33 * 34 * @see ZmPreferencesApp 35 */ 36 ZmFilterRulesView = function(parent, controller) { 37 38 DwtTabViewPage.call(this, parent, "ZmPreferencesPage ZmFilterRulesView", Dwt.STATIC_STYLE); 39 40 this._controller = controller; 41 this._prefsController = AjxDispatcher.run("GetPrefController"); 42 43 this._rules = AjxDispatcher.run(controller.isOutgoing() ? "GetOutgoingFilterRules" : "GetFilterRules"); 44 45 var section = ZmPref.getPrefSectionWithPref(ZmSetting.FILTERS); 46 this._title = [ZmMsg.zimbraTitle, controller.getApp().getDisplayName(), section && section.title].join(": "); 47 48 this._rendered = false; 49 50 this._tabGroup = new DwtTabGroup(this._htmlElId); 51 }; 52 53 ZmFilterRulesView.prototype = new DwtTabViewPage; 54 ZmFilterRulesView.prototype.constructor = ZmFilterRulesView; 55 56 ZmFilterRulesView.prototype.toString = 57 function() { 58 return "ZmFilterRulesView"; 59 }; 60 61 ZmFilterRulesView.prototype.showMe = 62 function() { 63 Dwt.setTitle(this._title); 64 var section = ZmPref.getPrefSectionWithPref(ZmSetting.FILTERS); 65 66 this._prefsController._resetOperations(this._prefsController._toolbar, section && section.id); 67 //TODO: We got to optimize / avoid force-rendering logic for multi-account setup 68 if (this.hasRendered && !appCtxt.isOffline) { return; } 69 70 // create the html 71 var data = {id:this._htmlElId}; 72 this.getHtmlElement().innerHTML = AjxTemplate.expand("prefs.Pages#MailFilter", data); 73 74 // create toolbar 75 var toolbarEl = Dwt.byId(data.id + "_toolbar"); 76 if (toolbarEl) { 77 var buttons = this._controller.getToolbarButtons(); 78 this._toolbar = new ZmButtonToolBar({parent:this, buttons:buttons, posStyle:Dwt.STATIC_STYLE, 79 context:ZmId.VIEW_FILTER_RULES}); 80 this._toolbar.replaceElement(toolbarEl); 81 this._tabGroup.addMember(this._toolbar); 82 } 83 84 // create list view 85 var listViewEl = Dwt.byId(data.id + "_list"); 86 // add chooser 87 this._chooser = new ZmFilterRulesChooser(this._controller, {parent:this}); 88 this._chooser.reparentHtmlElement(listViewEl + "_chooser"); 89 var width = this._chooser.getWidth(this.parent); 90 var height = this._chooser.getHeight(this.parent); 91 this._chooser.resize(width, height); //still call this so the height is set correctly, but let's set the width to non specific values, to work better on resize: 92 //remove the width explicit setting and keep the current height setting. 93 this._chooser.sourceListView.setSize(Dwt.CLEAR, Dwt.DEFAULT); 94 this._chooser.targetListView.setSize(Dwt.CLEAR, Dwt.DEFAULT); 95 this._controller.initialize(this._toolbar, this._chooser.activeListView, this._chooser.notActiveListView); 96 this.hasRendered = true; 97 }; 98 99 /** 100 * Gets the title. 101 * 102 * @return {String} the title 103 */ 104 ZmFilterRulesView.prototype.getTitle = 105 function() { 106 return this._title; 107 }; 108 109 /** 110 * Gets the toolbar. 111 * 112 * @return {ZmButtonToolBar} the toolbar 113 */ 114 ZmFilterRulesView.prototype.getToolbar = 115 function() { 116 return this._toolbar; 117 }; 118 119 /** 120 * Gets the list view. 121 * 122 * @return {DwtListView} the list view 123 */ 124 ZmFilterRulesView.prototype.getListView = 125 function() { 126 return this._listView; 127 }; 128 129 /** 130 * Gets the tab group. 131 * 132 * @return {DwtTabGroup} the tab group 133 */ 134 ZmFilterRulesView.prototype.getTabGroupMember = 135 function() { 136 return this._tabGroup; 137 }; 138 139 // View is always in sync with rules 140 ZmFilterRulesView.prototype.reset = function() {}; 141 142 ZmFilterRulesView.prototype.resetOnAccountChange = 143 function() { 144 this.hasRendered = false; 145 }; 146 147 /** 148 * Creates a filter rule chooser. 149 * @class 150 * This class creates a specialized chooser for the filter rule list view. 151 * 152 * @param {ZmController} controller the filter rule controller 153 * @param {hash} params chooser params 154 * 155 * @extends DwtChooser 156 * 157 * @private 158 */ 159 ZmFilterRulesChooser = function(controller, params) { 160 this._controller = controller; 161 DwtChooser.call(this, params); 162 this._rules = AjxDispatcher.run(controller.isOutgoing() ? "GetOutgoingFilterRules" : "GetFilterRules"); 163 this._rules.addChangeListener(new AjxListener(this, this._changeListener)); 164 }; 165 166 ZmFilterRulesChooser.prototype = new DwtChooser; 167 ZmFilterRulesChooser.prototype.constructor = ZmFilterRulesChooser; 168 ZmFilterRulesChooser.MOVE_UP_BTN_ID = "__moveUp__"; 169 ZmFilterRulesChooser.MOVE_DOWN_BTN_ID = "__moveDown__"; 170 ZmFilterRulesChooser.CHOOSER_HEIGHT = 300; 171 ZmFilterRulesChooser.CHOOSER_WIDTH = 300; 172 ZmFilterRulesChooser.WIDTH_FUDGE = 111; //if button size not this sets the correct width 173 ZmFilterRulesChooser.HEIGHT_FUDGE = 200; 174 /** 175 * @private 176 */ 177 ZmFilterRulesChooser.prototype._createSourceListView = 178 function() { 179 return new ZmFilterChooserNotActiveListView(this); 180 }; 181 182 /** 183 * @private 184 */ 185 ZmFilterRulesChooser.prototype._createTargetListView = 186 function() { 187 return new ZmFilterChooserActiveListView(this); 188 }; 189 190 ZmFilterRulesChooser.prototype._initialize = 191 function() { 192 DwtChooser.prototype._initialize.call(this); 193 this._moveUpButtonId = Dwt.getNextId(); 194 this._moveUpButton = this._setupButton(ZmFilterRulesChooser.MOVE_UP_BTN_ID, this._moveUpButtonId, this._moveUpButtonDivId, ZmMsg.filterMoveUp); 195 this._moveUpButton.addSelectionListener(new AjxListener(this._controller, this._controller.moveUpListener)); 196 this._moveUpButton.setImage("UpArrow"); 197 this._moveUpButton.setEnabled(false); 198 this._moveDownButtonId = Dwt.getNextId(); 199 this._moveDownButton = this._setupButton(ZmFilterRulesChooser.MOVE_DOWN_BTN_ID, this._moveDownButtonId, this._moveDownButtonDivId, ZmMsg.filterMoveDown); 200 this._moveDownButton.addSelectionListener(new AjxListener(this._controller, this._controller.moveDownListener)); 201 this._moveDownButton.setImage("DownArrow"); 202 this._moveDownButton.setEnabled(false); 203 this._removeButton.setEnabled(false); 204 this._removeButton.setAlign(DwtLabel.IMAGE_RIGHT); 205 this._removeButton.setImage("RightDoubleArrow"); 206 this._removeButton.setEnabled(false); 207 this._transferButton = this._button[this._buttonInfo[0].id]; 208 this._transferButton.setImage("LeftDoubleArrow"); 209 this._transferButton.setEnabled(false); 210 this.notActiveListView = this.sourceListView; 211 this.activeListView = this.targetListView; 212 AjxUtil.foreach([this._moveUpButton, this._moveDownButton, this._removeButton, this._transferButton], 213 function(item){ 214 var htmlElement = item.getHtmlElement(); 215 if (htmlElement && htmlElement.firstChild) htmlElement.firstChild.style.width = "100%"; 216 }); 217 }; 218 219 ZmFilterRulesChooser.prototype._createHtml = 220 function() { 221 222 this._sourceListViewDivId = Dwt.getNextId(); 223 this._targetListViewDivId = Dwt.getNextId(); 224 this._buttonsDivId = Dwt.getNextId(); 225 this._removeButtonDivId = Dwt.getNextId(); 226 this._moveUpButtonDivId = Dwt.getNextId(); 227 this._moveDownButtonDivId = Dwt.getNextId(); 228 var data = { 229 targetDivId: this._targetListViewDivId, 230 sourceDivId: this._sourceListViewDivId, 231 buttonsDivId: this._buttonsDivId, 232 transferButtonId: this._buttonDivId[this._buttonInfo[0].id], 233 removeButtonId: this._removeButtonDivId, 234 moveUpButtonId: this._moveUpButtonDivId, 235 moveDownButtonId: this._moveDownButtonDivId 236 }; 237 this.getHtmlElement().innerHTML = AjxTemplate.expand("prefs.Pages#MailFilterListView", data); 238 }; 239 240 /** 241 * In general, we just re-display all the rules when anything changes, rather 242 * than trying to update a particular row. 243 * 244 * @param {DwtEvent} ev the event 245 * 246 * @private 247 */ 248 ZmFilterRulesChooser.prototype._changeListener = 249 function(ev) { 250 if (ev.type != ZmEvent.S_FILTER) { 251 AjxDebug.println(AjxDebug.FILTER, "FILTER RULES: ev.type is not S_FILTER; ev.type == " + ev.type); 252 return; 253 } 254 AjxDebug.println(AjxDebug.FILTER, "FILTER RULES: ev.type == " + ev.type); 255 if (ev.event == ZmEvent.E_MODIFY) { 256 this._controller.resetListView(ev.getDetail("index")); 257 AjxDebug.println(AjxDebug.FILTER, "FILTER RULES: MODIFY event, called resetListview"); 258 if (ev.source && ev.source.getNumberOfRules() == 0) { 259 this._enableButtons(); //disable transfer buttons 260 } 261 } 262 }; 263 264 /** 265 * Clicking a transfer button moves selected items to the target list. 266 * 267 * @param {DwtEvent} ev the click event 268 * 269 * @private 270 */ 271 ZmFilterRulesChooser.prototype._transferButtonListener = 272 function(ev) { 273 var button = DwtControl.getTargetControl(ev); 274 var sel = this.notActiveListView.getSelection(); 275 if (sel && sel.length) { 276 this.transfer(sel); 277 var list = this.notActiveListView.getList(); 278 if (list && list.size()) { 279 this._selectFirst(DwtChooserListView.SOURCE); 280 } else { 281 this._enableButtons(); 282 } 283 } 284 }; 285 286 /** 287 * Moves or copies items from the source list to the target list, paying attention 288 * to current mode. 289 * 290 * @param {AjxVector|array|Object|hash} list a list of items or hash of lists 291 * @param {string} id the ID of the transfer button that was used 292 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 293 */ 294 ZmFilterRulesChooser.prototype.transfer = 295 function(list, id, skipNotify) { 296 DwtChooser.prototype.transfer.call(this, list, id, skipNotify); 297 for (var i=0; i<list.length; i++) { 298 var rule = this._rules.getRuleByName(list[i].name); 299 if (rule) { 300 rule.active = true; 301 this._rules.moveToBottom(rule, true); 302 } 303 } 304 this._rules.saveRules(0, true); 305 }; 306 307 /** 308 * Removes items from target list, paying attention to current mode. Also handles button state. 309 * 310 * @param {AjxVector|array|Object|hash} list a list of items or hash of lists 311 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 312 */ 313 ZmFilterRulesChooser.prototype.remove = 314 function(list, skipNotify) { 315 DwtChooser.prototype.remove.call(this, list, skipNotify); 316 for (var i=0; i<list.length; i++) { 317 var rule = this._rules.getRuleByName(list[i].name); 318 if (rule) { 319 rule.active = false; 320 this._rules.moveToBottom(rule, true); 321 } 322 } 323 this._rules.saveRules(0, true); 324 }; 325 326 /** 327 * Removes an item from the target list. 328 * 329 * @param {Object} item the item to remove 330 * @param {boolean} skipNotify if <code>true</code>, don't notify listeners 331 * 332 * @private 333 */ 334 ZmFilterRulesChooser.prototype._removeFromTarget = 335 function(item, skipNotify) { 336 if (!item) return; 337 var list = this.activeListView.getList(); 338 if (!list) return; 339 if (!list.contains(item)) return; 340 341 this.activeListView.removeItem(item, skipNotify); 342 }; 343 344 /** 345 * Enable/disable buttons as appropriate. 346 * 347 * @private 348 */ 349 ZmFilterRulesChooser.prototype._enableButtons = 350 function(sForce, tForce) { 351 DwtChooser.prototype._enableButtons.call(this, sForce, tForce); 352 353 var activeEnabled = (sForce || (this.activeListView.getSelectionCount() > 0)); 354 var availableEnabled = (tForce || (this.notActiveListView.getSelectionCount() > 0)); 355 356 var listView = activeEnabled ? this.activeListView : this.notActiveListView; 357 if (listView.getSelectionCount() > 1 || listView._list.size() <= 1) { 358 this._moveUpButton.setEnabled(false); 359 this._moveDownButton.setEnabled(false); 360 } 361 else if (listView.getSelectionCount() == 1) { 362 var sel = listView.getSelection(); 363 var firstItem = listView._list.get(0); 364 var lastItem = listView._list.get(listView._list.size()-1); 365 if (firstItem && firstItem.id == sel[0].id) { 366 this._moveUpButton.setEnabled(false); 367 this._moveDownButton.setEnabled(true); 368 } 369 else if (lastItem && lastItem.id == sel[0].id) { 370 this._moveDownButton.setEnabled(false); 371 this._moveUpButton.setEnabled(true); 372 } 373 else { 374 this._moveDownButton.setEnabled(true); 375 this._moveUpButton.setEnabled(true); 376 } 377 } 378 }; 379 380 381 /** 382 * Single-click selects an item, double-click adds selected items to target list. 383 * 384 * @param {DwtEvent} ev the click event 385 * 386 * @private 387 */ 388 ZmFilterRulesChooser.prototype._sourceListener = 389 function(ev) { 390 if (this._activeButtonId == DwtChooser.REMOVE_BTN_ID) { 391 // single-click activates appropriate transfer button if needed 392 var id = this._lastActiveTransferButtonId ? this._lastActiveTransferButtonId : this._buttonInfo[0].id; 393 this._setActiveButton(id); 394 } 395 this.targetListView.deselectAll(); 396 this._enableButtons(); 397 }; 398 399 /** 400 * Single-click selects an item, double-click removes it from the target list. 401 * 402 * @param {DwtEvent} ev the click event 403 * 404 * @private 405 */ 406 ZmFilterRulesChooser.prototype._targetListener = 407 function(ev) { 408 this._setActiveButton(DwtChooser.REMOVE_BTN_ID); 409 this.sourceListView.deselectAll(); 410 this._enableButtons(); 411 412 }; 413 414 /** 415 * Calculates the chooser height based on the parent element height 416 * @param parent {DwtControl} parent 417 * @return {int} height 418 */ 419 ZmFilterRulesChooser.prototype.getHeight = 420 function(parent) { 421 if (!parent) { 422 return ZmFilterRulesChooser.CHOOSER_HEIGHT; 423 } 424 var height = parseInt(parent.getHtmlElement().style.height); 425 return height - ZmFilterRulesChooser.HEIGHT_FUDGE; 426 }; 427 428 /** 429 * calculates chooser width based on parent element width. 430 * @param parent {DwtControl} parent 431 * @return {int} width 432 */ 433 ZmFilterRulesChooser.prototype.getWidth = 434 function(parent) { 435 if (!parent) { 436 return ZmFilterRulesChooser.CHOOSER_WIDTH; 437 } 438 439 var widthFudge = ZmFilterRulesChooser.WIDTH_FUDGE; 440 var width = parseInt(parent.getHtmlElement().style.width); 441 var buttonsDiv = document.getElementById(this._buttonsDivId); 442 if (buttonsDiv) { 443 var btnSz = Dwt.getSize(buttonsDiv); 444 if (btnSz && btnSz.x > 0) { 445 widthFudge = ZmFilterRulesChooser.WIDTH_FUDGE - btnSz.x; 446 } 447 } 448 449 return width - widthFudge; 450 }; 451 452 /** 453 * Creates a source list view. 454 * @class 455 * This class creates a specialized source list view for the contact chooser. 456 * 457 * @param {DwtComposite} parent the contact picker 458 * 459 * @extends DwtChooserListView 460 * 461 * @private 462 */ 463 ZmFilterChooserActiveListView = function(parent) { 464 DwtChooserListView.call(this, {parent:parent, type:DwtChooserListView.SOURCE}); 465 this.setScrollStyle(Dwt.CLIP); 466 }; 467 468 ZmFilterChooserActiveListView.prototype = new DwtChooserListView; 469 ZmFilterChooserActiveListView.prototype.constructor = ZmFilterChooserActiveListView; 470 471 /** 472 * Returns a string representation of the object. 473 * 474 * @return {String} a string representation of the object 475 * @private 476 */ 477 ZmFilterChooserActiveListView.prototype.toString = 478 function() { 479 return "ZmFilterChooserActiveListView"; 480 }; 481 482 ZmFilterChooserActiveListView.prototype._getCellContents = 483 function(html, idx, item, field, colIdx, params) { 484 if (AjxEnv.isIE) { 485 var maxWidth = AjxStringUtil.getWidth(item); 486 html[idx++] = "<div style='float; left; overflow: visible; width: " + maxWidth + ";'>"; 487 html[idx++] = AjxStringUtil.htmlEncode(item.name); 488 html[idx++] = "</div>"; 489 } 490 else { 491 html[idx++] = AjxStringUtil.htmlEncode(item.name); 492 } 493 return idx; 494 }; 495 496 /** 497 * Only show active rules that have at least one valid action (eg, if the only action 498 * is "tag" and tagging is disabled, don't show the rule). 499 * 500 * @param list 501 * 502 * @private 503 */ 504 ZmFilterChooserActiveListView.prototype.set = 505 function(list) { 506 var list1 = new AjxVector(); 507 var len = list.size(); 508 for (var i = 0; i < len; i++) { 509 var rule = list.get(i); 510 if (rule.hasValidAction() && rule.active) { 511 list1.add(rule); 512 } 513 } 514 DwtListView.prototype.set.call(this, list1); 515 }; 516 517 ZmFilterChooserActiveListView.prototype._getHeaderList = 518 function() { 519 return [(new DwtListHeaderItem({text: ZmMsg.activeFilters}))]; 520 }; 521 522 523 /** 524 * Returns a string of any extra attributes to be used for the TD. 525 * 526 * @param item [object] item to render 527 * @param field [constant] column identifier 528 * @param params [hash]* hash of optional params 529 * 530 * @private 531 */ 532 ZmFilterChooserActiveListView.prototype._getCellAttrText = 533 function(item, field, params) { 534 return "style='position: relative; overflow: visible;'"; 535 }; 536 537 /** 538 * Creates the target list view. 539 * @class 540 * This class creates a specialized target list view for the contact chooser. 541 * 542 * @param {DwtComposite} parent the contact picker 543 * @extends DwtChooserListView 544 * 545 * @private 546 */ 547 ZmFilterChooserNotActiveListView = function(parent) { 548 DwtChooserListView.call(this, {parent:parent, type:DwtChooserListView.TARGET}); 549 this.setScrollStyle(Dwt.CLIP); 550 }; 551 552 ZmFilterChooserNotActiveListView.prototype = new DwtChooserListView; 553 ZmFilterChooserNotActiveListView.prototype.constructor = ZmFilterChooserNotActiveListView; 554 555 /** 556 * Returns a string representation of the object. 557 * 558 * @return {String} a string representation of the object 559 */ 560 ZmFilterChooserNotActiveListView.prototype.toString = 561 function() { 562 return "ZmFilterChooserNotActiveListView"; 563 }; 564 565 ZmFilterChooserNotActiveListView.prototype._getCellContents = 566 function(html, idx, item, field, colIdx, params) { 567 html[idx++] = AjxStringUtil.htmlEncode(item.name); 568 return idx; 569 }; 570 571 ZmFilterChooserNotActiveListView.prototype._getHeaderList = 572 function() { 573 return [(new DwtListHeaderItem({text: ZmMsg.availableFilters}))]; 574 }; 575 576 /** 577 * Only show non-active rules that have at least one valid action (eg, if the only action 578 * is "tag" and tagging is disabled, don't show the rule). 579 * 580 * @param list 581 * 582 * @private 583 */ 584 ZmFilterChooserNotActiveListView.prototype.set = 585 function(list) { 586 var list1 = new AjxVector(); 587 var len = list.size(); 588 for (var i = 0; i < len; i++) { 589 var rule = list.get(i); 590 if (rule.hasValidAction() && !rule.active) { 591 list1.add(rule); 592 } 593 } 594 DwtListView.prototype.set.call(this, list1); 595 }; 596 597 ZmFilterChooserNotActiveListView.prototype._getCellContents = 598 function(html, idx, item, field, colIdx, params) { 599 if (AjxEnv.isIE) { 600 var maxWidth = AjxStringUtil.getWidth(item); 601 html[idx++] = "<div style='float; left; overflow: visible; width: " + maxWidth + ";'>"; 602 html[idx++] = AjxStringUtil.htmlEncode(item.name); 603 html[idx++] = "</div>"; 604 } 605 else { 606 html[idx++] = AjxStringUtil.htmlEncode(item.name); 607 } 608 return idx; 609 }; 610 611 /** 612 * Returns a string of any extra attributes to be used for the TD. 613 * 614 * @param item [object] item to render 615 * @param field [constant] column identifier 616 * @param params [hash]* hash of optional params 617 * 618 * @private 619 */ 620 ZmFilterChooserNotActiveListView.prototype._getCellAttrText = 621 function(item, field, params) { 622 return "style='position: relative; overflow: visible;'"; 623 }; 624