1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a dialog for specifying a filter rule. Can be used for either add or edit. 26 * @constructor 27 * @class 28 * This class presents a dialog which a user can use to add or edit a filter rule. 29 * A filter rule consists of conditions and actions (at least one of each). Different 30 * types of conditions and actions require different fields to specify them, so they 31 * are presented in a table in which all columns are not necessarily occupied. 32 * <p> 33 * First the HTML is laid out, then DWT objects that are needed for input are plugged 34 * in.</p> 35 * 36 * @author Conrad Damon 37 * 38 * @extends DwtDialog 39 */ 40 ZmFilterRuleDialog = function() { 41 42 DwtDialog.call(this, {parent:appCtxt.getShell(), className:"ZmFilterRuleDialog", title:ZmMsg.selectAddresses, id: "ZmFilterRuleDialog"}); 43 44 // set content 45 this.setContent(this._contentHtml()); 46 this._createControls(); 47 this._setConditionSelect(); 48 this._createTabGroup(); 49 50 // create these listeners just once 51 this._rowChangeLstnr = new AjxListener(this, this._rowChangeListener); 52 this._opsChangeLstnr = new AjxListener(this, this._opsChangeListener); 53 this._dateLstnr = new AjxListener(this, this._dateListener); 54 this._plusMinusLstnr = new AjxListener(this, this._plusMinusListener); 55 this._browseLstnr = new AjxListener(this, this._browseListener); 56 this._addrBookChangeLstnr = new AjxListener(this, this._addrBookChangeListener); 57 this._importanceChangeLstnr = new AjxListener(this, this._importanceChangeListener); 58 59 this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._okButtonListener)); 60 this.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._cancelButtonListener)); 61 this._conditionErrorFormatter = new AjxMessageFormat(ZmMsg.filterErrorCondition); 62 this._actionErrorFormatter = new AjxMessageFormat(ZmMsg.filterErrorAction); 63 }; 64 65 ZmFilterRuleDialog.prototype = new DwtDialog; 66 ZmFilterRuleDialog.prototype.constructor = ZmFilterRuleDialog; 67 68 // data keys 69 ZmFilterRuleDialog.ROW_ID = "_rowid_"; 70 ZmFilterRuleDialog.IS_CONDITION = "_condition_"; 71 ZmFilterRuleDialog.DO_ADD = "_add_"; 72 ZmFilterRuleDialog.BROWSE_TYPE = "_btype_"; 73 ZmFilterRuleDialog.DATA = "_data_"; 74 75 // character width of text inputs 76 ZmFilterRuleDialog.INPUT_NUM_CHARS = 15; 77 78 // button widths 79 ZmFilterRuleDialog.CHOOSER_BUTTON_WIDTH = 120; 80 ZmFilterRuleDialog.PLUS_MINUS_BUTTON_WIDTH = 20; 81 82 ZmFilterRuleDialog.CONDITIONS_INDEX = 0; 83 ZmFilterRuleDialog.prototype.toString = 84 function() { 85 return "ZmFilterRuleDialog"; 86 }; 87 88 /** 89 * Pops-up the dialog and displays either a given rule for editing, or a dummy 90 * rule that is the base for adding a new rule. 91 * 92 * @param {ZmFilterRule} rule the rule to edit 93 * @param {Boolean} editMode if <code>true</code>, we are editing a rule 94 * @param {ZmFilterRule} referenceRule the rule after which to add new rule 95 * @param {String} accountName the name of the account 96 */ 97 ZmFilterRuleDialog.prototype.popup = 98 function(rule, editMode, referenceRule, accountName, outgoing) { 99 // always make sure we have the right rules container in case of multi-mbox 100 this._rules = AjxDispatcher.run(outgoing ? "GetOutgoingFilterRules" : "GetFilterRules", accountName); 101 this._outgoing = outgoing; 102 this._rules.loadRules(); // make sure rules are loaded (for when we save) 103 this._inputs = {}; 104 this._rule = rule || ZmFilterRule.getDummyRule(); 105 this._editMode = editMode; 106 this._referenceRule = referenceRule; 107 this.setTitle(editMode ? ZmMsg.editFilter : ZmMsg.addFilter); 108 109 var nameField = Dwt.byId(this._nameInputId); 110 var name = rule ? rule.name : null; 111 nameField.value = name || ""; 112 113 var activeField = Dwt.byId(this._activeCheckboxId); 114 activeField.checked = (!rule || rule.active); 115 Dwt.setHandler(activeField, DwtEvent.ONCHANGE, AjxCallback.simpleClosure(this._activeChangeListener, this)); 116 117 var stopField = Dwt.byId(this._stopCheckboxId); 118 stopField.checked = (!editMode); 119 120 var checkAll = (rule && (rule.getGroupOp() == ZmFilterRule.GROUP_ALL)); 121 this._conditionSelect.setSelectedValue(checkAll ? ZmFilterRule.GROUP_ALL : ZmFilterRule.GROUP_ANY); 122 123 this._conditionsTabGroup.removeAllMembers(); 124 this._actionsTabGroup.removeAllMembers(); 125 126 this._renderTable(this._rule, true, this._conditionsTableId, this._rule.conditions, this._conditionsTabGroup); // conditions 127 this._renderTable(this._rule, false, this._actionsTableId, this._rule.actions, this._actionsTabGroup); // actions 128 this._addDwtObjects(); 129 130 DwtDialog.prototype.popup.call(this); 131 132 nameField.focus(); 133 }; 134 135 /** 136 * Pops-down the dialog. Clears the conditions and actions table before popdown 137 * so we don't keep adding to them. 138 */ 139 ZmFilterRuleDialog.prototype.popdown = 140 function() { 141 this._clearTables(); 142 DwtDialog.prototype.popdown.call(this); 143 }; 144 145 /** 146 * Gets the tab group member. 147 * 148 * @return {DwtTabGroup} the tab group 149 */ 150 ZmFilterRuleDialog.prototype.getTabGroupMember = 151 function() { 152 return this._tabGroup; 153 }; 154 155 /** 156 * Gets the HTML that forms the basic framework of the dialog. 157 * 158 * @private 159 */ 160 ZmFilterRuleDialog.prototype._contentHtml = 161 function() { 162 // identifiers 163 var id = this._htmlElId; 164 this._nameInputId = id+"_name"; 165 this._activeCheckboxId = id+"_active"; 166 this._groupSelectId = id+"_group"; 167 this._conditionId = id+"_condition"; 168 this._conditionsTableId = id+"_conditions"; 169 this._actionsTableId = id+"_actions"; 170 this._stopCheckboxId = id+"_stop"; 171 172 // content html 173 return AjxTemplate.expand("prefs.Pages#MailFilterRule", id); 174 }; 175 176 ZmFilterRuleDialog.prototype._createControls = 177 function() { 178 this._stopFiltersCheckbox = new DwtCheckbox({parent: this, id: this._stopCheckboxId, checked: true}); 179 this._stopFiltersCheckbox.replaceElement(document.getElementById(this._stopCheckboxId)); 180 this._stopFiltersCheckbox.setText(ZmMsg.stopFilterProcessing); 181 }; 182 183 ZmFilterRuleDialog.prototype._setConditionSelect = 184 function() { 185 var message = new DwtMessageComposite(this); 186 var callback = new AjxCallback(this, this._createConditionControl); 187 message.setFormat(ZmMsg.filterCondition, callback); 188 189 var conditionEl = Dwt.byId(this._htmlElId+"_condition"); 190 message.appendElement(conditionEl); 191 }; 192 193 ZmFilterRuleDialog.prototype._createConditionControl = 194 function(parent, segment, i) { 195 if (segment.getIndex() == 0) { 196 var format = segment.getSegmentFormat(); 197 var limits = format.getLimits(); 198 var formats = format.getFormats(); 199 var values = [ZmFilterRule.GROUP_ANY, ZmFilterRule.GROUP_ALL]; 200 201 var select = this._conditionSelect = new DwtSelect({parent:parent, id: "FilterRuleGroupCondition_" + ZmFilterRuleDialog.CONDITIONS_INDEX++}); 202 for (var i = 0; i < values.length; i++) { 203 // TODO: guard against badly specified message 204 select.addOption(formats[i].toPattern(), i == 0, values[i]); 205 }; 206 return select; 207 } 208 }; 209 210 ZmFilterRuleDialog.prototype._createTabGroup = 211 function() { 212 // create tabgroups 213 var id = this._htmlElId; 214 this._tabGroup = new DwtTabGroup(id); 215 this._conditionsTabGroup = new DwtTabGroup(id+"_conditions"); 216 this._actionsTabGroup = new DwtTabGroup(id+"_actions"); 217 218 // get basic controls 219 var MAX_VALUE = 100000; 220 var tabIndexes = {}; 221 var ids = [ this._nameInputId, this._activeCheckboxId, this._stopCheckboxId ]; 222 for (var i = 0; i < ids.length; i++) { 223 var el = Dwt.byId(ids[i]); 224 var tabIndex = el.getAttribute("tabindex") || MAX_VALUE - 5 - i; 225 tabIndexes[tabIndex] = el; 226 } 227 228 // add other controls 229 var el = Dwt.byId(this._conditionId); 230 var tabIndex = el.getAttribute("tabindex") || MAX_VALUE - 4; 231 tabIndexes[tabIndex] = this._conditionSelect; 232 233 // add tabgroups that will hold the conditions and actions 234 var el = Dwt.byId(this._conditionsTableId); 235 var tabIndex = el.getAttribute("tabindex") || MAX_VALUE - 3; 236 tabIndexes[tabIndex] = this._conditionsTabGroup; 237 238 var el = Dwt.byId(this._actionsTableId); 239 var tabIndex = el.getAttribute("tabindex") || MAX_VALUE - 2; 240 tabIndexes[tabIndex] = this._actionsTabGroup; 241 242 // add dialog buttons 243 tabIndexes[MAX_VALUE - 1] = this.getButton(DwtDialog.OK_BUTTON); 244 tabIndexes[MAX_VALUE] = this.getButton(DwtDialog.CANCEL_BUTTON); 245 246 // populate tabgroup 247 var keys = AjxUtil.keys(tabIndexes); 248 keys.sort(AjxUtil.byNumber); 249 for (var i = 0; i < keys.length; i++) { 250 this._tabGroup.addMember(tabIndexes[keys[i]]); 251 } 252 }; 253 254 /** 255 * Draws a table of conditions or actions. Returns the ID of the last row added. 256 * 257 * @param {ZmFilterRule} rule the source rule 258 * @param {Boolean} isCondition if <code>true</code>, we're drawing conditions (as opposed to actions) 259 * @param {String} tableId the DWT id representing the parent table 260 * @param {Object} rowData the meta data used to figure out which DWT widget to create 261 * @param {DwtTabGroup} tabGroup tab group for focus 262 * 263 * @private 264 */ 265 ZmFilterRuleDialog.prototype._renderTable = 266 function(rule, isCondition, tableId, rowData, tabGroup) { 267 var table = Dwt.byId(tableId); 268 var row; 269 for (var i in rowData) { 270 var data = rowData[i]; 271 if (isCondition && i == "condition") { continue; } 272 273 // don't show action if it's disabled 274 if (!isCondition) { 275 var actionIndex = ZmFilterRule.A_VALUE_MAP[i]; 276 if (!ZmFilterRule.checkPreconditions(ZmFilterRule.ACTIONS[actionIndex]) && actionIndex != ZmFilterRule.A_FORWARD) { continue; } 277 } 278 279 for (j = 0; j < data.length; j++) { 280 var rowId = Dwt.getNextId(); 281 this._enterTabScope(rowId); 282 try { 283 var html = this._getRowHtml(data[j], i, isCondition, rowId); 284 if (html) { 285 row = Dwt.parseHtmlFragment(html, true); 286 table.tBodies[0].appendChild(row); 287 tabGroup.addMember(this._getCurrentTabScope()); 288 } 289 } 290 finally { 291 this._exitTabScope(); 292 } 293 } 294 } 295 296 this._resetOperations(isCondition); 297 298 return (row ? row.id : null); 299 }; 300 301 /** 302 * Gets the HTML for a single condition or action row. 303 * 304 * @param {Object} data an object containing meta info about the filter rule condition or action 305 * @param {String} test the type of test condition (headerTest, sizeTest, bodyTest, etc) 306 * @param {Boolean} isCondition if <code>true</code>, we're rendering a condition row 307 * @param {String} rowId the unique ID representing this row 308 * 309 * @private 310 */ 311 ZmFilterRuleDialog.prototype._getRowHtml = 312 function(data, test, isCondition, rowId) { 313 var conf; 314 if (isCondition) { 315 conf = this._getConditionFromTest(test, data); 316 if (!conf) { 317 return ""; //see bug 85825 - encountered such a case if I had a socialcast filter before I removed socialcast code. 318 } 319 } else { 320 var actionId = ZmFilterRule.A_VALUE_MAP[test]; 321 conf = ZmFilterRule.ACTIONS[actionId]; 322 } 323 324 var html = []; 325 var i = 0; 326 327 this._inputs[rowId] = {}; 328 329 html[i++] = "<tr id='"; 330 html[i++] = rowId; 331 html[i++] = "'>"; 332 333 if (isCondition) { 334 this._inputs[rowId].isCondition = true; 335 html[i++] = this._createRowComponent(true, "subject", ZmFilterRule.CONDITIONS_LIST, data, test, rowId); 336 html[i++] = this._createRowComponent(conf, "subjectMod", conf.smOptions, data, test, rowId); 337 html[i++] = this._createRowComponent(conf, "ops", conf.opsOptions, data, test, rowId); 338 html[i++] = this._createRowComponent(conf, "value", conf.vOptions, data, test, rowId); 339 html[i++] = this._createRowComponent(conf, "valueMod", conf.vmOptions, data, test, rowId); 340 if (data && data.caseSensitive) { 341 this._inputs[rowId]["caseSensitive"] = {value: data.caseSensitive}; //save case sensitive value if it exists 342 } 343 } else { 344 if (test == ZmFilterRule.A_NAME_STOP) { 345 var stopField = Dwt.byId(this._stopCheckboxId); 346 stopField.checked = true; 347 return; 348 } 349 html[i++] = "<td><table class='filterActions'><tr>"; 350 if (conf) { 351 var options = this._outgoing ? ZmFilterRule.ACTIONS_OUTGOING_LIST : ZmFilterRule.ACTIONS_LIST; 352 html[i++] = this._createRowComponent(false, "name", options, data, test, rowId); 353 html[i++] = this._createRowComponent(conf, "param", conf.pOptions, data, test, rowId); 354 } 355 else { 356 //see if it's a actionReply or actionNotify filter and output readonly 357 if (actionId == ZmFilterRule.A_NOTIFY && data) { 358 var email = data.a; 359 var content = AjxUtil.isArray(data.content) ? data.content[0]._content : ""; 360 var maxBodySize = data.maxBodySize; 361 var subject = data.su; 362 363 html[i++] = "<td><table>"; 364 html[i++] = "<tr><td>" + ZmMsg.actionNotifyReadOnlyMsg + "</td></tr>"; 365 html[i++] = "<tr><td>" + ZmMsg.emailLabel + " " + email + " | " + subject + " | " + ZmMsg.maxBodySize + ": " + maxBodySize + "</td><tr>"; 366 html[i++] = "<tr><td>" + ZmMsg.body + ": " + content + "</td></tr></table></td>"; 367 } 368 else if (actionId == ZmFilterRule.A_REPLY && data) { 369 var content = AjxUtil.isArray(data.content) ? data.content[0]._content : ""; 370 html[i++] = "<td><table><tr><td>" + ZmMsg.actionReplyReadOnlyMsg + "</td></tr>"; 371 html[i++] = "<tr><td>" + ZmMsg.body + ": " + content + "</td></tr></table></td>"; 372 } 373 this.setButtonEnabled(DwtDialog.OK_BUTTON, false); 374 } 375 html[i++] = "</tr></table></td>"; 376 } 377 html[i++] = this._getPlusMinusHtml(rowId, isCondition); 378 html[i++] = "</tr>"; 379 380 return html.join(""); 381 }; 382 383 ZmFilterRuleDialog.prototype._getConditionFromTest = 384 function(test, data) { 385 var condition; 386 switch (test) { 387 case ZmFilterRule.TEST_ADDRESS: 388 condition = ZmFilterRule.C_ADDRESS_MAP[data.header]; 389 if (!condition) { // shouldn't get here 390 condition = ZmFilterRule.C_ADDRESS; 391 } 392 break; 393 case ZmFilterRule.TEST_HEADER_EXISTS: condition = ZmFilterRule.C_HEADER; break; 394 case ZmFilterRule.TEST_SIZE: condition = ZmFilterRule.C_SIZE; break; 395 case ZmFilterRule.TEST_DATE: condition = ZmFilterRule.C_DATE; break; 396 case ZmFilterRule.TEST_BODY: condition = ZmFilterRule.C_BODY; break; 397 case ZmFilterRule.TEST_ATTACHMENT: condition = ZmFilterRule.C_ATT; break; 398 case ZmFilterRule.TEST_MIME_HEADER: condition = ZmFilterRule.C_MIME_HEADER; break; 399 case ZmFilterRule.TEST_ADDRBOOK: condition = ZmFilterRule.C_ADDRBOOK; break; 400 case ZmFilterRule.TEST_INVITE: condition = ZmFilterRule.C_INVITE; break; 401 case ZmFilterRule.TEST_CONVERSATIONS: condition = ZmFilterRule.C_CONV; break; 402 case ZmFilterRule.TEST_SOCIAL: condition = ZmFilterRule.C_SOCIAL; break; 403 case ZmFilterRule.TEST_FACEBOOK: condition = ZmFilterRule.C_SOCIAL; break; 404 case ZmFilterRule.TEST_TWITTER: condition = ZmFilterRule.C_SOCIAL; break; 405 case ZmFilterRule.TEST_LINKEDIN: condition = ZmFilterRule.C_SOCIAL; break; 406 case ZmFilterRule.TEST_COMMUNITY: condition = ZmFilterRule.C_COMMUNITY; break; 407 case ZmFilterRule.TEST_COMMUNITY_REQUESTS: condition = ZmFilterRule.C_COMMUNITY; break; 408 case ZmFilterRule.TEST_COMMUNITY_CONTENT: condition = ZmFilterRule.C_COMMUNITY; break; 409 case ZmFilterRule.TEST_COMMUNITY_CONNECTIONS: condition = ZmFilterRule.C_COMMUNITY; break; 410 case ZmFilterRule.TEST_LIST: condition = ZmFilterRule.C_CONV; break; 411 case ZmFilterRule.TEST_BULK: condition = ZmFilterRule.C_CONV; break; 412 case ZmFilterRule.TEST_ME: condition = ZmFilterRule.C_ADDRBOOK; break; 413 case ZmFilterRule.TEST_RANKING: condition = ZmFilterRule.C_ADDRBOOK; break; 414 case ZmFilterRule.TEST_IMPORTANCE: condition = ZmFilterRule.C_CONV; break; 415 case ZmFilterRule.TEST_FLAGGED: condition = ZmFilterRule.C_CONV; break; 416 case ZmFilterRule.TEST_HEADER: 417 condition = ZmFilterRule.C_HEADER_MAP[data.header]; 418 if (!condition) { // means custom header 419 condition = ZmFilterRule.C_HEADER; 420 } 421 break; 422 } 423 424 //TODO: find a better way to do this. Preconditions for opsOptions? 425 if (condition == ZmFilterRule.C_SOCIAL) { 426 condition = ZmFilterRule.CONDITIONS[condition]; 427 condition.opsOptions = ZmFilterRule.getSocialFilters(); 428 return condition; 429 } 430 return (condition ? ZmFilterRule.CONDITIONS[condition] : null); 431 }; 432 433 ZmFilterRuleDialog.prototype._enterTabScope = 434 function(id) { 435 if (!this._tabScope) { 436 this._tabScope = []; 437 } 438 var tabGroup = new DwtTabGroup(id || Dwt.getNextId()); 439 this._tabScope.push(tabGroup); 440 return tabGroup; 441 }; 442 443 ZmFilterRuleDialog.prototype._getCurrentTabScope = 444 function() { 445 if (this._tabScope) { 446 return this._tabScope[this._tabScope.length - 1]; 447 } 448 }; 449 450 ZmFilterRuleDialog.prototype._exitTabScope = 451 function() { 452 return this._tabScope ? this._tabScope.pop() : null; 453 }; 454 455 /** 456 * Adds a new condition or action row to its table. 457 * 458 * @param {Boolean} isCondition if <code>true</code>, we're adding a condition row 459 * 460 * @private 461 */ 462 ZmFilterRuleDialog.prototype._addRow = 463 function(isCondition) { 464 var rule = ZmFilterRule.getDummyRule(); 465 var tableId, data, tabGroup; 466 if (isCondition) { 467 tableId = this._conditionsTableId; 468 data = rule.conditions; 469 tabGroup = this._conditionsTabGroup; 470 } else { 471 tableId = this._actionsTableId; 472 data = rule.actions; 473 tabGroup = this._actionsTabGroup; 474 } 475 var newRowId = this._renderTable(rule, isCondition, tableId, data, tabGroup); 476 this._addDwtObjects(newRowId); 477 }; 478 479 /** 480 * Removes a condition or action row from its table. Also cleans up any DWT 481 * objects the row was using. 482 * 483 * @param {String} rowId the ID of the row to remove 484 * @param {Boolean} isCondition if <code>true</code>, we're removing a condition row 485 * 486 * @private 487 */ 488 ZmFilterRuleDialog.prototype._removeRow = 489 function(rowId, isCondition) { 490 var row = Dwt.byId(rowId); 491 if (!row) { return; } 492 493 var table = Dwt.byId(isCondition ? this._conditionsTableId : this._actionsTableId); 494 var rows = table.rows; 495 for (var i = 0; i < rows.length; i++) { 496 if (rows[i] == row) { 497 table.deleteRow(i); 498 break; 499 } 500 } 501 this._removeDwtObjects(rowId); 502 delete this._inputs[rowId]; 503 }; 504 505 /** 506 * Creates an input widget and returns HTML for a table cell that will contain it. 507 * The config for a condition or action is based on its main operator; for conditions 508 * it's called subject ("from", "body", etc), and for actions it's just called the 509 * action ("keep", "fileinto", etc). Each one of those has its own particular inputs. 510 * This method creates one of those inputs. 511 * 512 * @param {Hash|Boolean} conf the config for this subject or action; boolean if rendering 513 * the actual subject or action (means "isCondition") 514 * @param {String} field the name of the input field 515 * @param {Array} options if the field type is a select, its options 516 * @param {Object} rowData the current value of the field, if any 517 * @param {String} testType the type of test condition (i.e. headerTest, attachmentTest, bodyTest, etc) 518 * @param {String} rowId the ID of the containing row 519 * 520 * @private 521 */ 522 ZmFilterRuleDialog.prototype._createRowComponent = 523 function(conf, field, options, rowData, testType, rowId) { 524 var tabGroup = this._getCurrentTabScope(); 525 526 var isCondition, type; 527 var isMainSelect = AjxUtil.isBoolean(conf); 528 if (isMainSelect) { 529 type = ZmFilterRule.TYPE_SELECT; 530 isCondition = conf; 531 } else { 532 type = conf[field]; 533 if (!type) { 534 return "<td></td>"; 535 } 536 } 537 538 var dataValue = this._getDataValue(isMainSelect, testType, field, rowData); 539 540 var id = Dwt.getNextId(); 541 if (type == ZmFilterRule.TYPE_INPUT) { 542 var inputFieldId = "FilterRuleDialog_INPUTFIELD_" + ZmFilterRuleDialog.CONDITIONS_INDEX++; 543 var inputId = "FilterRuleDialog_INPUT_" + ZmFilterRuleDialog.CONDITIONS_INDEX++; 544 var input = new DwtInputField({parent: this, type: DwtInputField.STRING, initialValue: dataValue, size: 20, id: inputFieldId, inputId: inputId}); 545 input.setData(ZmFilterRuleDialog.ROW_ID, rowId); 546 this._inputs[rowId][field] = {id: id, dwtObj: input}; 547 tabGroup.addMember(input.getTabGroupMember()); 548 } 549 else if (type == ZmFilterRule.TYPE_SELECT) { 550 var selectId = "FilterRuleDialog_SELECT_" + ZmFilterRuleDialog.CONDITIONS_INDEX++; 551 var select = new DwtSelect({parent:this, id: selectId}); 552 select.setData(ZmFilterRuleDialog.ROW_ID, rowId); 553 select.fixedButtonWidth(); 554 this._inputs[rowId][field] = {id: id, dwtObj: select}; 555 if (isMainSelect) { 556 select.setData(ZmFilterRuleDialog.IS_CONDITION, isCondition); 557 select.addChangeListener(this._rowChangeLstnr); 558 } 559 else if (field == "ops") { 560 if (testType == ZmFilterRule.TEST_HEADER) { 561 select.setData(ZmFilterRuleDialog.IS_CONDITION, isCondition); 562 select.addChangeListener(this._opsChangeLstnr); 563 } 564 else if (testType == ZmFilterRule.TEST_ADDRBOOK || testType == ZmFilterRule.TEST_ME) { 565 select.addChangeListener(this._addrBookChangeLstnr); 566 } 567 } 568 else if (field == "value") { 569 if (testType == ZmFilterRule.TEST_ADDRESS || testType == ZmFilterRule.TEST_ME) 570 { 571 select.setVisibility(false); //Don't show value "me" for address test 572 } 573 else if (testType == ZmFilterRule.TEST_CONVERSATIONS || testType == ZmFilterRule.TEST_LIST || testType == ZmFilterRule.TEST_BULK || testType == ZmFilterRule.TEST_IMPORTANCE || testType == ZmFilterRule.TEST_FLAGGED) { 574 select.addChangeListener(this._importanceChangeLstnr); 575 } 576 } 577 else if (field == "valueMod"){ 578 if (testType == ZmFilterRule.TEST_FLAGGED && (rowData.flagName == ZmFilterRule.READ || rowData.flagName == ZmFilterRule.PRIORITY)) { 579 var valueSelect = this._inputs[rowId]["value"].dwtObj; 580 var index = valueSelect.getIndexForValue(ZmFilterRule.IMPORTANCE); 581 valueSelect.setSelected(index); 582 } 583 else if (testType == ZmFilterRule.TEST_CONVERSATIONS || testType == ZmFilterRule.TEST_LIST || testType == ZmFilterRule.TEST_BULK || testType == ZmFilterRule.TEST_FLAGGED) { 584 select.setVisibility(false); 585 } 586 } 587 588 for (var i = 0; i < options.length; i++) { 589 var o = options[i]; 590 // skip if the action or this option is disabled 591 var okay = ZmFilterRule.checkPreconditions(ZmFilterRule.CONDITIONS[o] || ZmFilterRule.ACTIONS[o] || o); 592 if (!okay && (o !== ZmFilterRule.A_FORWARD || !rowData || !rowData.a)) { 593 continue; 594 } 595 596 var value, label; 597 if (isMainSelect) { 598 value = o; 599 label = isCondition ? ZmFilterRule.C_LABEL[o] : ZmFilterRule.A_LABEL[o]; 600 } else if (field == "ops") { 601 value = o; 602 label = ZmFilterRule.OP_LABEL[o]; 603 } else { 604 value = o.value; 605 label = o.label; 606 } 607 var selected = (dataValue && value && (value.toLowerCase() == dataValue.toLowerCase())); 608 if (value && value.toLowerCase()== "bcc" && !this._outgoing && !selected) { 609 continue; 610 } 611 select.addOption(new DwtSelectOptionData(value, label, selected)); 612 } 613 if (!select.getValue()) { 614 select.setSelected(0); 615 } 616 tabGroup.addMember(select.getTabGroupMember()); 617 } 618 else if (type == ZmFilterRule.TYPE_CALENDAR) { 619 // create button with calendar that hangs off menu 620 var dateId = "FilterRuleDialog_DATE_" + ZmFilterRuleDialog.CONDITIONS_INDEX++; 621 var dateButton = new DwtButton({parent:this, id: dateId}); 622 dateButton.setSize(ZmFilterRuleDialog.CHOOSER_BUTTON_WIDTH, Dwt.DEFAULT); 623 var date, dateText; 624 if (dataValue) { 625 date = new Date(dataValue); 626 dateText = AjxDateUtil.simpleComputeDateStr(date); 627 } else { 628 date = null; 629 dateText = ZmMsg.chooseDate; 630 } 631 dateButton.setText(dateText); 632 dateButton.setData(ZmFilterRuleDialog.DATA, date); 633 var calId = "FilterRuleDialog_CAL_" + ZmFilterRuleDialog.CONDITIONS_INDEX++; 634 var calMenu = new DwtMenu({parent:dateButton, style:DwtMenu.CALENDAR_PICKER_STYLE, id: calId}); 635 dateButton.setMenu(calMenu, true); 636 var cal = new DwtCalendar({parent:calMenu}); 637 cal.setSkipNotifyOnPage(true); 638 cal.addSelectionListener(this._dateLstnr); 639 cal.setDate(date || new Date()); 640 cal._dateButton = dateButton; 641 this._inputs[rowId][field] = {id: id, dwtObj: dateButton}; 642 tabGroup.addMember(dateButton.getTabGroupMember()); 643 } 644 else if (type == ZmFilterRule.TYPE_FOLDER_PICKER || type == ZmFilterRule.TYPE_TAG_PICKER) { 645 var buttonId = "FilterRuleDialog_BUTTON_" + ZmFilterRuleDialog.CONDITIONS_INDEX++; 646 var button = new DwtButton({parent:this, id: buttonId}); 647 var organizer; 648 if (dataValue) { 649 if (type == ZmFilterRule.TYPE_FOLDER_PICKER) { 650 var folderTree = appCtxt.getFolderTree(); 651 if (folderTree) { 652 dataValue = (dataValue.charAt(0) == '/') ? dataValue.substring(1) : dataValue; 653 organizer = folderTree.getByPath(dataValue, true); 654 } 655 } else { 656 var tagTree = appCtxt.getTagTree(); 657 if (tagTree) { 658 organizer = tagTree.getByName(dataValue); 659 } 660 } 661 } 662 var text = organizer ? AjxStringUtil.htmlEncode(organizer.getName(false, null, true)) : ZmMsg.browse; 663 button.setText(text); 664 button.setData(ZmFilterRuleDialog.BROWSE_TYPE, type); 665 button.setData(ZmFilterRuleDialog.DATA, dataValue); 666 this._inputs[rowId][field] = {id: id, dwtObj: button}; 667 button.addSelectionListener(this._browseLstnr); 668 tabGroup.addMember(button.getTabGroupMember()); 669 } 670 671 return "<td id='" + id + "'></td>"; 672 }; 673 674 ZmFilterRuleDialog.prototype._getDataValue = 675 function(isMainSelect, testType, field, rowData) { 676 var dataValue; 677 if (isMainSelect) { 678 switch (testType) { 679 case ZmFilterRule.TEST_HEADER: 680 dataValue = ZmFilterRule.C_HEADER_MAP[rowData.header]; 681 if (!dataValue) { // means custom header 682 dataValue = ZmFilterRule.C_HEADER; 683 } 684 break; 685 case ZmFilterRule.TEST_HEADER_EXISTS: dataValue = ZmFilterRule.C_HEADER; break; 686 case ZmFilterRule.TEST_SIZE: dataValue = ZmFilterRule.C_SIZE; break; 687 case ZmFilterRule.TEST_DATE: dataValue = ZmFilterRule.C_DATE; break; 688 case ZmFilterRule.TEST_BODY: dataValue = ZmFilterRule.C_BODY; break; 689 case ZmFilterRule.TEST_ATTACHMENT: dataValue = ZmFilterRule.C_ATT; break; 690 case ZmFilterRule.TEST_MIME_HEADER: dataValue = ZmFilterRule.C_MIME_HEADER; break; 691 case ZmFilterRule.TEST_ADDRBOOK: dataValue = ZmFilterRule.C_ADDRBOOK; break; 692 case ZmFilterRule.TEST_INVITE: dataValue = ZmFilterRule.C_INVITE; break; 693 case ZmFilterRule.TEST_CONVERSATIONS: dataValue = ZmFilterRule.C_CONV; break; 694 case ZmFilterRule.TEST_SOCIAL: dataValue = ZmFilterRule.C_SOCIAL; break; 695 case ZmFilterRule.TEST_FACEBOOK: dataValue = ZmFilterRule.C_SOCIAL; break; 696 case ZmFilterRule.TEST_TWITTER: dataValue = ZmFilterRule.C_SOCIAL; break; 697 case ZmFilterRule.TEST_LINKEDIN: dataValue = ZmFilterRule.C_SOCIAL; break; 698 case ZmFilterRule.TEST_COMMUNITY: dataValue = ZmFilterRule.C_COMMUNITY; break; 699 case ZmFilterRule.TEST_COMMUNITY_REQUESTS: dataValue = ZmFilterRule.C_COMMUNITY; break; 700 case ZmFilterRule.TEST_COMMUNITY_CONTENT: dataValue = ZmFilterRule.C_COMMUNITY; break; 701 case ZmFilterRule.TEST_COMMUNITY_CONNECTIONS: dataValue = ZmFilterRule.C_COMMUNITY; break; 702 case ZmFilterRule.TEST_ADDRESS: 703 dataValue = ZmFilterRule.C_ADDRESS_MAP[rowData.header]; 704 if (!dataValue) { 705 dataValue = ZmFilterRule.C_ADDRESS; 706 } 707 break; 708 case ZmFilterRule.TEST_LIST: dataValue = ZmFilterRule.C_CONV; break; 709 case ZmFilterRule.TEST_BULK: dataValue = ZmFilterRule.C_CONV; break; 710 case ZmFilterRule.TEST_ME: dataValue = ZmFilterRule.C_ADDRBOOK; break; 711 case ZmFilterRule.TEST_RANKING: dataValue = ZmFilterRule.C_ADDRBOOK; break; 712 case ZmFilterRule.TEST_IMPORTANCE: dataValue = ZmFilterRule.C_CONV; break; 713 case ZmFilterRule.TEST_FLAGGED: dataValue = ZmFilterRule.C_CONV; break; 714 // default returns action type 715 default: return ZmFilterRule.A_VALUE_MAP[testType]; 716 } 717 } else { 718 // conditions 719 if (testType == ZmFilterRule.TEST_HEADER) { 720 if (field == "subjectMod") { 721 dataValue = rowData.header; 722 } else if (field == "ops") { 723 dataValue = ZmFilterRule.OP_VALUE_MAP[rowData.stringComparison] == ZmFilterRule.OP_IS_READRECEIPT ? ZmFilterRule.OP_CONTAINS : 724 ZmFilterRule.OP_VALUE_MAP[rowData.stringComparison]; 725 if (dataValue && rowData.negative == "1") { 726 dataValue = ZmFilterRule.getNegativeComparator(dataValue); 727 } 728 } else if (field == "value") { 729 dataValue = rowData.value; 730 } 731 } 732 else if (testType == ZmFilterRule.TEST_HEADER_EXISTS) { 733 if (field == "subjectMod") { 734 dataValue = rowData.header; 735 } else if (field == "ops") { 736 dataValue = (rowData.negative == "1") 737 ? ZmFilterRule.OP_NOT_EXISTS 738 : ZmFilterRule.OP_EXISTS; 739 } else if (field == "value") { 740 dataValue = rowData.value; 741 } 742 } 743 else if (testType == ZmFilterRule.TEST_SIZE) { 744 if (field == "ops") { 745 dataValue = ZmFilterRule.OP_VALUE_MAP[rowData.numberComparison]; 746 if (dataValue && rowData.negative == "1") { 747 dataValue = ZmFilterRule.getNegativeComparator(dataValue); 748 } 749 } else if (field == "valueMod") { 750 var m = rowData.s ? rowData.s.match(/(\d+)([A-Z]*)/) : null; 751 dataValue = m ? ((!m[2]) ? "B" : m[2]) : null; 752 } else if (field == "value") { 753 dataValue = rowData.s ? rowData.s.match(/(\d+)/)[0] : null; 754 } 755 } 756 else if (testType == ZmFilterRule.TEST_DATE) { 757 if (field == "ops") { 758 dataValue = ZmFilterRule.OP_VALUE_MAP[rowData.dateComparison]; 759 if (dataValue && rowData.negative == "1") { 760 dataValue = ZmFilterRule.getNegativeComparator(dataValue); 761 } 762 } else if (field == "value") { 763 dataValue = rowData.d * 1000; 764 } 765 } 766 else if (testType == ZmFilterRule.TEST_BODY) { 767 if (field == "ops") { 768 dataValue = (rowData.negative == "1") 769 ? ZmFilterRule.OP_NOT_CONTAINS 770 : ZmFilterRule.OP_CONTAINS; 771 } else if (field == "value") { 772 dataValue = rowData.value; 773 } 774 } 775 else if (testType == ZmFilterRule.TEST_ATTACHMENT) { 776 if (field == "ops") { 777 dataValue = (rowData.negative == "1") 778 ? ZmFilterRule.OP_NOT_EXISTS 779 : ZmFilterRule.OP_EXISTS; 780 } 781 } 782 else if (testType == ZmFilterRule.TEST_LIST) { 783 if (field == "ops") { 784 dataValue = (rowData.negative == "1") 785 ? ZmFilterRule.OP_NOT_CONV 786 : ZmFilterRule.OP_CONV_IS; 787 } 788 else if (field == "value") { 789 dataValue = ZmFilterRule.C_LIST; 790 } 791 } 792 else if (testType == ZmFilterRule.TEST_BULK) { 793 if (field == "ops") { 794 dataValue = (rowData.negative == "1") 795 ? ZmFilterRule.OP_NOT_CONV 796 : ZmFilterRule.OP_CONV_IS; 797 } 798 else if (field == "value") { 799 dataValue = ZmFilterRule.C_BULK; 800 } 801 } 802 else if (testType == ZmFilterRule.TEST_CONVERSATIONS) { 803 if (field == "ops") { 804 dataValue = (rowData.negative == "1") 805 ? ZmFilterRule.OP_NOT_CONV 806 : ZmFilterRule.OP_CONV_IS; 807 } 808 else if (field == "value") { 809 dataValue = rowData.where; 810 } 811 } 812 else if (testType == ZmFilterRule.TEST_IMPORTANCE) { 813 if (field == "ops") { 814 dataValue = (rowData.negative == "1") 815 ? ZmFilterRule.OP_NOT_CONV 816 : ZmFilterRule.OP_CONV_IS; 817 } 818 else if (field == "value") { 819 dataValue = ZmFilterRule.IMPORTANCE; 820 } 821 else if (field == "valueMod") { 822 dataValue = rowData.imp; 823 } 824 } 825 else if (testType == ZmFilterRule.TEST_FLAGGED) { 826 if (field == "ops") { 827 dataValue = (rowData.negative == "1") 828 ? ZmFilterRule.OP_NOT_CONV 829 : ZmFilterRule.OP_CONV_IS; 830 } 831 else if (field == "value") { 832 dataValue = ZmFilterRule.FLAGGED; 833 } 834 else if (field == "valueMod") { 835 dataValue = rowData.flagName; 836 } 837 } 838 else if (testType == ZmFilterRule.TEST_FACEBOOK) { 839 dataValue = ZmFilterRule.OP_SOCIAL_FACEBOOK; 840 } 841 else if (testType == ZmFilterRule.TEST_TWITTER) { 842 dataValue = ZmFilterRule.OP_SOCIAL_TWITTER; 843 } 844 else if (testType == ZmFilterRule.TEST_LINKEDIN) { 845 dataValue = ZmFilterRule.OP_SOCIAL_LINKEDIN; 846 } 847 else if (testType == ZmFilterRule.TEST_INVITE) { 848 if (field == "ops") { 849 var isRequested = ZmFilterRule.OP_VALUE[ZmFilterRule.OP_IS_REQUESTED]; 850 var tmpValue = rowData.method && rowData.method[0]._content; 851 if (rowData.negative!=1) { 852 dataValue = (isRequested == tmpValue) 853 ? ZmFilterRule.OP_IS_REQUESTED 854 : ZmFilterRule.OP_IS_REPLIED; 855 }else { 856 dataValue = (isRequested == tmpValue) 857 ? ZmFilterRule.OP_NOT_REQUESTED 858 : ZmFilterRule.OP_NOT_REPLIED; 859 } 860 } 861 } 862 else if (testType == ZmFilterRule.TEST_ADDRBOOK) { 863 if (field == "subjectMod") { 864 dataValue = rowData.header; 865 } else if (field == "ops") { 866 dataValue = (rowData.negative == "1") 867 ? ZmFilterRule.OP_NOT_IN 868 : ZmFilterRule.OP_IN; 869 } else if (field == "value") { 870 dataValue = rowData.type; 871 } 872 } 873 else if (testType == ZmFilterRule.TEST_ADDRESS) { 874 if (field == "subjectMod") { 875 dataValue = rowData.header; 876 } else if (field == "ops") { 877 dataValue = ZmFilterRule.OP_VALUE_MAP[rowData.stringComparison] == ZmFilterRule.OP_IS_READRECEIPT ? ZmFilterRule.OP_CONTAINS : 878 ZmFilterRule.OP_VALUE_MAP[rowData.stringComparison]; 879 if (dataValue && rowData.negative == "1") { 880 dataValue = ZmFilterRule.getNegativeComparator(dataValue); 881 } 882 } else if (field == "value") { 883 dataValue = rowData.value; 884 } else if (field == "valueMod") { 885 dataValue = rowData.part; 886 } 887 } 888 else if (testType == ZmFilterRule.TEST_ME) { 889 if (field == "subjectMod") { 890 dataValue = rowData.header; 891 } else if (field == "ops") { 892 dataValue = (rowData.negative == "1") 893 ? ZmFilterRule.OP_NOT_ME 894 : ZmFilterRule.OP_IS_ME; 895 } else if (field == "value") { 896 dataValue = rowData.value; 897 } 898 } 899 else if (testType == ZmFilterRule.TEST_RANKING) { 900 if (field == "subjectMod") { 901 dataValue = rowData.header; 902 } else if (field == "ops") { 903 dataValue = (rowData.negative == "1") 904 ? ZmFilterRule.OP_NOT_IN 905 : ZmFilterRule.OP_IN; 906 } else if (field == "value") { 907 dataValue = ZmFilterRule.RANKING; 908 } 909 } 910 else if (testType == ZmFilterRule.TEST_MIME_HEADER) { 911 if (field == "ops") { 912 dataValue = (rowData.negative == "1") 913 ? ZmFilterRule.OP_NOT_READRECEIPT 914 : ZmFilterRule.OP_IS_READRECEIPT; 915 } 916 } 917 // actions 918 else if (testType == ZmFilterRule.A_NAME_FOLDER) { 919 dataValue = rowData.folderPath; 920 } 921 else if (testType == ZmFilterRule.A_NAME_FLAG) { 922 dataValue = rowData.flagName; 923 } 924 else if (testType == ZmFilterRule.A_NAME_TAG) { 925 dataValue = rowData.tagName; 926 } 927 else if (testType == ZmFilterRule.A_NAME_FORWARD) { 928 dataValue = rowData.a; 929 } 930 else if (ZmFilterRule.OP_COMMUNITY_MAP_R[testType]) { 931 dataValue = ZmFilterRule.OP_COMMUNITY_MAP_R[testType]; 932 } 933 } 934 935 return dataValue; 936 }; 937 938 /** 939 * Returns HTML for the + and - buttons at the end of each row. 940 * 941 * @param {String} rowId the ID of the row that gets the buttons 942 * @param {Boolean} isCondition the <code>true</code>, we're adding them to a condition row 943 * 944 * @private 945 */ 946 ZmFilterRuleDialog.prototype._getPlusMinusHtml = 947 function(rowId, isCondition) { 948 var tabGroup = this._getCurrentTabScope(); 949 var html = []; 950 var j = 0; 951 html[j++] = "<td width='1%'><table class='FilterAddRemoveButtons'><tr>"; 952 var buttons = ["Plus", "Minus"]; 953 for (var i = 0; i < buttons.length; i++) { 954 var b = buttons[i]; 955 var button = new DwtButton({parent:this}); 956 button.setImage(b); 957 button.setData(ZmFilterRuleDialog.ROW_ID, rowId); 958 button.setData(ZmFilterRuleDialog.IS_CONDITION, isCondition); 959 button.setData(ZmFilterRuleDialog.DO_ADD, (b == "Plus")); 960 button.addSelectionListener(this._plusMinusLstnr); 961 var id = Dwt.getNextId("TEST_"); 962 this._inputs[rowId][b] = {id: id, dwtObj: button}; 963 html[j++] = "<td id='"; 964 html[j++] = id; 965 html[j++] = "'></td>"; 966 tabGroup.addMember(button); 967 } 968 html[j++] = "</tr></table></td>"; 969 return html.join(""); 970 }; 971 972 /** 973 * If there's only one row, disable its Minus button (since removing it would 974 * leave the user with nothing). 975 * 976 * @param {Boolean} isCondition if <code>true</code>, we're checking a condition row 977 * 978 * @private 979 */ 980 ZmFilterRuleDialog.prototype._resetOperations = 981 function(isCondition) { 982 var tableId = isCondition ? this._conditionsTableId : this._actionsTableId; 983 var table = Dwt.byId(tableId); 984 var rows = table.rows; 985 if (!(rows && rows.length)) { return; } 986 987 var input = this._inputs[rows[0].id]; 988 if (input) { 989 var minusButton = input["Minus"].dwtObj; 990 if (rows.length == 1) { 991 minusButton.setEnabled(false); 992 } else { 993 minusButton.setEnabled(true); 994 } 995 } 996 }; 997 998 /** 999 * Update the inputs for a row based on the subject (condition), or action name. 1000 * The old row is removed, and a new row is created and inserted. 1001 * 1002 * @param {DwtEvent} ev the event (from {@link DwtSelect}) 1003 * 1004 * @private 1005 */ 1006 ZmFilterRuleDialog.prototype._rowChangeListener = 1007 function(ev) { 1008 var newValue = ev._args.newValue; 1009 var oldValue = ev._args.oldValue; 1010 var rowId = ev._args.selectObj.getData(ZmFilterRuleDialog.ROW_ID); 1011 var isCondition = ev._args.selectObj.getData(ZmFilterRuleDialog.IS_CONDITION); 1012 var tabGroup = isCondition ? this._conditionsTabGroup : this._actionsTabGroup; 1013 1014 // preserve op and value between header fields 1015 var comparator, dataValue; 1016 if (isCondition && (ZmFilterRule.IS_HEADER[oldValue] && ZmFilterRule.IS_HEADER[newValue])) { 1017 comparator = this._getInputValue(this._inputs[rowId], ZmFilterRule.CONDITIONS[oldValue], "ops"); 1018 dataValue = this._getInputValue(this._inputs[rowId], ZmFilterRule.CONDITIONS[oldValue], "value"); 1019 } 1020 else if (isCondition && (ZmFilterRule.IS_ADDRESS[oldValue] && ZmFilterRule.IS_ADDRESS[newValue])) { 1021 comparator = this._getInputValue(this._inputs[rowId], ZmFilterRule.CONDITIONS[oldValue], "ops"); 1022 dataValue = this._getInputValue(this._inputs[rowId], ZmFilterRule.CONDITIONS[oldValue], "value"); 1023 } 1024 1025 var row = Dwt.byId(rowId); 1026 var index = this._getIndexForRow(row, isCondition); 1027 var table = Dwt.byId(isCondition ? this._conditionsTableId : this._actionsTableId); 1028 this._removeDwtObjects(rowId); 1029 table.deleteRow(index); 1030 var newIndex = (index >= table.rows.length) ? null : index; // null means add to end 1031 1032 var test, data, subjectMod; 1033 if (isCondition) { 1034 test = ZmFilterRule.C_TEST_MAP[newValue]; 1035 if (test == ZmFilterRule.TEST_HEADER) { 1036 subjectMod = ZmFilterRule.C_HEADER_VALUE[newValue]; 1037 } 1038 else if (test == ZmFilterRule.TEST_ADDRESS) { 1039 subjectMod = ZmFilterRule.C_ADDRESS_VALUE[newValue]; 1040 } 1041 data = ZmFilterRule.getConditionData(test, comparator, dataValue, subjectMod); 1042 } else { 1043 test = ZmFilterRule.A_VALUE[newValue]; 1044 data = ZmFilterRule.getActionData(test); 1045 } 1046 1047 this._enterTabScope(rowId); 1048 try { 1049 var html = this._getRowHtml(data, test, isCondition, rowId); 1050 if (html) { 1051 row = Dwt.parseHtmlFragment(html, true); 1052 if (!row) { 1053 DBG.println(AjxDebug.DBG1, "Filter rule dialog: no row created!"); 1054 return; 1055 } 1056 table.tBodies[0].insertBefore(row, (newIndex != null) ? table.rows[newIndex] : null); 1057 this._addDwtObjects(row.id); 1058 this._resetOperations(isCondition); 1059 tabGroup.removeMember(DwtTabGroup.getByName(rowId)); 1060 tabGroup.addMember(this._getCurrentTabScope()); 1061 } 1062 } 1063 finally { 1064 this._exitTabScope(); 1065 } 1066 }; 1067 1068 /** 1069 * For the "Header Named" input only - hide the last input field (value) if the 1070 * selected op is "exists" or "does not exist", since those are unary ops which 1071 * don't take a value. 1072 * 1073 * @param {DwtEvent} ev the event (from {@link DwtSelect}) 1074 * 1075 * @private 1076 */ 1077 ZmFilterRuleDialog.prototype._opsChangeListener = 1078 function(ev) { 1079 var rowId = ev._args.selectObj.getData(ZmFilterRuleDialog.ROW_ID); 1080 var input = this._inputs[rowId]; 1081 if (!input) { return; } 1082 var newValue = ev._args.newValue; 1083 input["value"].dwtObj.setVisibility(!(newValue == ZmFilterRule.OP_EXISTS || newValue == ZmFilterRule.OP_NOT_EXISTS)); 1084 }; 1085 1086 ZmFilterRuleDialog.prototype._addrBookChangeListener = 1087 function(ev) { 1088 var rowId = ev._args.selectObj.getData(ZmFilterRuleDialog.ROW_ID); 1089 var input = this._inputs[rowId]; 1090 if (!input && !input["ops"] && !input["ops"].dwtObj) { 1091 return; 1092 } 1093 var value = input["ops"].dwtObj.getValue(); 1094 if (value == ZmFilterRule.OP_IS_ME || value == ZmFilterRule.OP_NOT_ME) { 1095 input["value"].dwtObj.setVisibility(false); 1096 } 1097 else { 1098 input["value"].dwtObj.setVisibility(true); 1099 } 1100 }; 1101 1102 ZmFilterRuleDialog.prototype._importanceChangeListener = 1103 function(ev) { 1104 var rowId = ev._args.selectObj.getData(ZmFilterRuleDialog.ROW_ID); 1105 var input = this._inputs[rowId]; 1106 if (!input && !input["value"] && !input["value"].dwtObj) { 1107 return; 1108 } 1109 var value = input["value"].dwtObj.getValue(); 1110 if (value == ZmFilterRule.IMPORTANCE) { 1111 input["valueMod"].dwtObj.setVisibility(true); 1112 } 1113 else { 1114 input["valueMod"].dwtObj.setVisibility(false); 1115 } 1116 }; 1117 1118 /** 1119 * Updates the calendar button text with a date that's just been selected. 1120 * 1121 * @param {DwtEvent} ev the event (from {@link DwtCalendar}) 1122 * 1123 * @private 1124 */ 1125 ZmFilterRuleDialog.prototype._dateListener = 1126 function(ev) { 1127 var cal = ev.item; 1128 if (!cal._dateButton) { return; } 1129 var date = ev.detail; 1130 var button = cal._dateButton; 1131 button.setText(AjxDateUtil.simpleComputeDateStr(date)); 1132 button.setData(ZmFilterRuleDialog.DATA, date); 1133 }; 1134 1135 /** 1136 * Adds or removes a condition/action row. 1137 * 1138 * @param {DwtEvent} ev the event 1139 * 1140 * @private 1141 */ 1142 ZmFilterRuleDialog.prototype._plusMinusListener = 1143 function(ev) { 1144 var button = ev.item; 1145 var isCondition = button.getData(ZmFilterRuleDialog.IS_CONDITION); 1146 var doAdd = button.getData(ZmFilterRuleDialog.DO_ADD); 1147 if (doAdd) { 1148 this._addRow(isCondition); 1149 } else { 1150 var rowId = button.getData(ZmFilterRuleDialog.ROW_ID); 1151 this._removeRow(rowId, isCondition); 1152 } 1153 this._resetOperations(isCondition); 1154 }; 1155 1156 /** 1157 * Pops up one of two dialogs, for choosing a folder or a tag. 1158 * 1159 * @param {DwtEvent} ev the event 1160 * 1161 * @private 1162 */ 1163 ZmFilterRuleDialog.prototype._browseListener = 1164 function(ev) { 1165 var type = ev.item.getData(ZmFilterRuleDialog.BROWSE_TYPE); 1166 var isFolder = (type == ZmFilterRule.TYPE_FOLDER_PICKER); 1167 var dialog = isFolder ? appCtxt.getChooseFolderDialog(ZmApp.MAIL) : appCtxt.getPickTagDialog(); 1168 var overviewId = isFolder ? dialog.getOverviewId(ZmApp.MAIL) : null; 1169 if (appCtxt.multiAccounts) { 1170 overviewId = [overviewId, "-", appCtxt.getActiveAccount().name, this.toString()].join(""); 1171 } 1172 1173 dialog.reset(); 1174 dialog.setTitle((type == ZmFilterRule.TYPE_FOLDER_PICKER) ? ZmMsg.chooseFolder : ZmMsg.chooseTag); 1175 dialog.registerCallback(DwtDialog.OK_BUTTON, this._browseSelectionCallback, this, [ev.item, dialog]); 1176 dialog.popup({overviewId:overviewId, appName:ZmApp.MAIL, forceSingle:true}); 1177 }; 1178 1179 /** 1180 * Changes the text of a button to the folder/tag that the user just chose. 1181 * 1182 * @param {DwtButton} button the browse button 1183 * @param {ZmDialog} dialog the folder or tag dialog that is popped up 1184 * @param {ZmOrganizer} organizer the folder or tag that was chosen 1185 * 1186 * @private 1187 */ 1188 ZmFilterRuleDialog.prototype._browseSelectionCallback = 1189 function(button, dialog, organizer) { 1190 var type = button.getData(ZmFilterRuleDialog.BROWSE_TYPE); 1191 var isFolder = (type == ZmFilterRule.TYPE_FOLDER_PICKER); 1192 if (organizer) { 1193 // Bug 24425, don't allow root folder selection 1194 if (isFolder && organizer.nId == ZmFolder.ID_ROOT) { return; } 1195 1196 button.setText(AjxStringUtil.htmlEncode(organizer.getName(false, null, true))); 1197 var value = isFolder 1198 ? organizer.getPath(false, false, null, true, true) 1199 : organizer.getName(false, null, true); 1200 button.setData(ZmFilterRuleDialog.DATA, value); 1201 } 1202 dialog.popdown(); 1203 }; 1204 1205 /** 1206 * If "save to sent" is disabled and we're an outgoing filter and the user chose to active the filter, display a warning with the option to turn the setting on 1207 * 1208 * @param {DwtEvent} ev the event 1209 * 1210 * @private 1211 */ 1212 ZmFilterRuleDialog.prototype._activeChangeListener = 1213 function(ev) { 1214 if (this._outgoing) { 1215 var target = DwtUiEvent.getTarget(ev); 1216 var active = target.checked; 1217 var cancelCallback = new AjxCallback(this, function(){target.checked = false;}); 1218 if (active) { 1219 var outgoingFilterController = ZmPreferencesApp.getFilterRulesController(this._outgoing); 1220 if (outgoingFilterContrller) { 1221 outgoingFilterController.handleBeforeFilterChange(null, cancelCallback); 1222 } 1223 } 1224 } 1225 }; 1226 1227 /** 1228 * Attaches input widgets to the DOM tree based on placeholder IDs. 1229 * 1230 * @param {String} rowId the ID of a single row to add inputs to 1231 * 1232 * @private 1233 */ 1234 ZmFilterRuleDialog.prototype._addDwtObjects = 1235 function(rowId) { 1236 for (var id in this._inputs) { 1237 if (rowId && (id != rowId)) { continue; } 1238 var row = this._inputs[id]; 1239 for (var f in row) { 1240 var field = row[f]; 1241 var el = (field.id && field.dwtObj) ? field.dwtObj.getHtmlElement() : null; 1242 if (el) { 1243 el.parentNode.removeChild(el); 1244 Dwt.byId(field.id).appendChild(el); 1245 el._rowId = id; 1246 } 1247 } 1248 } 1249 }; 1250 1251 /** 1252 * Destroys input widgets. 1253 * 1254 * @param {String} rowId the ID of a single row to clean up 1255 * 1256 * @private 1257 */ 1258 ZmFilterRuleDialog.prototype._removeDwtObjects = 1259 function(rowId) { 1260 for (var id in this._inputs) { 1261 if (rowId && (id != rowId)) continue; 1262 var row = this._inputs[id]; 1263 for (var f in row) { 1264 var field = row[f]; 1265 if (field.dwtObj) 1266 field.dwtObj.dispose(); 1267 } 1268 } 1269 }; 1270 1271 ZmFilterRuleDialog.prototype._cancelButtonListener = 1272 function(ev) { 1273 var filterRulesController = ZmPreferencesApp.getFilterRulesController(this._outgoing); 1274 if (filterRulesController) { 1275 //get index before loading rules to keep selection on cancel 1276 var sel = filterRulesController.getListView() ? filterRulesController.getListView().getSelection()[0] : null; 1277 var index = sel ? this._rules.getIndexOfRule(sel) : null; 1278 var callback = new AjxCallback(this, this._handleResponseLoadRules, [index]); 1279 this._rules.loadRules(true, callback); 1280 } 1281 this.popdown(); 1282 }; 1283 1284 ZmFilterRuleDialog.prototype._handleResponseLoadRules = 1285 function(index) { 1286 var filterRulesController = ZmPreferencesApp.getFilterRulesController(this._outgoing); 1287 if (filterRulesController) { 1288 filterRulesController.resetListView(index); 1289 } 1290 }; 1291 1292 /** 1293 * Saves the newly created/edited rule. 1294 * 1295 * @param {DwtEvent} ev the event 1296 */ 1297 ZmFilterRuleDialog.prototype._okButtonListener = 1298 function(ev) { 1299 1300 var rule = this._rule; 1301 var msg = null; 1302 var name = Dwt.byId(this._nameInputId).value; 1303 name = name.replace (/\s*$/,''); 1304 name = name.replace (/^\s*/,''); 1305 if (!name) { 1306 msg = ZmMsg.filterErrorNoName; 1307 } 1308 1309 var rule1 = this._rules.getRuleByName(name); 1310 if ( rule1 && (rule1 != rule)) { 1311 msg = ZmMsg.filterErrorNameExists; 1312 } 1313 if (msg) { 1314 var msgDialog = appCtxt.getMsgDialog(); 1315 msgDialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE); 1316 msgDialog.popup(); 1317 return; 1318 } 1319 1320 var active = Dwt.byId(this._activeCheckboxId).checked; 1321 var anyAll = this._conditionSelect.getValue(); 1322 1323 // adding a rule always starts with dummy 1324 1325 if (this._editMode) { 1326 var cachedRule = { 1327 name: rule.name, 1328 active: rule.active, 1329 conditions: rule.conditions, 1330 actions: rule.actions 1331 }; 1332 1333 rule.name = name; 1334 rule.active = active; 1335 rule.clearConditions(); 1336 rule.clearActions(); 1337 } else { 1338 rule = new ZmFilterRule(name, active); 1339 } 1340 rule.setGroupOp(anyAll); 1341 1342 // get input from tables so order is preserved 1343 var table = Dwt.byId(this._conditionsTableId); 1344 var rows = table.rows; 1345 for (var i = 0; i < rows.length; i++) { 1346 var c = this._getConditionFromRow(rows[i].id); 1347 if (msg = this._checkCondition(c)) { 1348 break; 1349 } else { 1350 rule.addCondition(c.testType, c.comparator, c.value, c.subjectMod, c.caseSensitive); 1351 } 1352 } 1353 if (!msg) { 1354 table = Dwt.byId(this._actionsTableId); 1355 rows = table.rows; 1356 for (var i = 0; i < rows.length; i++) { 1357 var action = this._getActionFromRow(rows[i].id); 1358 if (msg = this._checkAction(action)) { 1359 break; 1360 } 1361 rule.addAction(action.actionType, action.value); 1362 } 1363 } 1364 1365 if (msg) { 1366 // bug #35912 - restore values from cached rule 1367 if (cachedRule) { 1368 rule.name = cachedRule.name; 1369 rule.active = cachedRule.active; 1370 rule.conditions = cachedRule.conditions; 1371 rule.actions = cachedRule.actions; 1372 } 1373 1374 var msgDialog = appCtxt.getMsgDialog(); 1375 msgDialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE); 1376 msgDialog.popup(); 1377 return; 1378 } 1379 1380 var stopAction = Dwt.byId(this._stopCheckboxId).checked; 1381 if (stopAction) { 1382 rule.addAction(ZmFilterRule.A_STOP); 1383 } 1384 1385 var respCallback = new AjxCallback(this, this._handleResponseOkButtonListener); 1386 if (this._editMode) { 1387 this._rules._saveRules(this._rules.getIndexOfRule(rule), true, respCallback); 1388 } else { 1389 this._rules.addRule(rule, this._referenceRule, respCallback); 1390 } 1391 }; 1392 1393 ZmFilterRuleDialog.prototype._handleResponseOkButtonListener = 1394 function() { 1395 this.popdown(); 1396 }; 1397 1398 /** 1399 * Creates an Object based on the values of a condition row. 1400 * 1401 * @param {String} rowId the row ID 1402 * 1403 * @private 1404 */ 1405 ZmFilterRuleDialog.prototype._getConditionFromRow = 1406 function(rowId) { 1407 var inputs = this._inputs[rowId]; 1408 1409 var subject = inputs.subject.dwtObj.getValue(); 1410 var conf = ZmFilterRule.CONDITIONS[subject]; 1411 var comparator = this._getInputValue(inputs, conf, "ops"); 1412 var value = AjxStringUtil.trim(this._getInputValue(inputs, conf, "value")); 1413 var subjectMod = this._getInputValue(inputs, conf, "subjectMod"); 1414 var valueMod = this._getInputValue(inputs, conf, "valueMod"); 1415 var testType = ZmFilterRule.C_TEST_MAP[subject]; 1416 var caseSensitive = null; 1417 1418 if (testType == ZmFilterRule.TEST_HEADER) { 1419 if (subject == ZmFilterRule.C_HEADER && 1420 (comparator == ZmFilterRule.OP_EXISTS || 1421 comparator == ZmFilterRule.OP_NOT_EXISTS)) 1422 { 1423 testType = ZmFilterRule.TEST_HEADER_EXISTS; 1424 } 1425 else { 1426 if (subject != ZmFilterRule.C_HEADER) { 1427 subjectMod = ZmFilterRule.C_HEADER_VALUE[subject]; 1428 } 1429 } 1430 } 1431 else if (testType == ZmFilterRule.TEST_ADDRESS && subject) { 1432 subjectMod = ZmFilterRule.C_ADDRESS_VALUE[subject]; 1433 value += ";" + valueMod; //addressTest has value=email part=all|domain|localpart 1434 } 1435 else if (testType == ZmFilterRule.TEST_SIZE && valueMod && valueMod != "B") { 1436 value += valueMod; 1437 } 1438 // MIME header currently supports ZmMimeTable.MSG_READ_RECEIPT only. 1439 else if (testType == ZmFilterRule.TEST_MIME_HEADER) { 1440 subjectMod = "Content-Type"; 1441 value = ZmMimeTable.MSG_READ_RECEIPT; 1442 } 1443 else if (testType == ZmFilterRule.TEST_ADDRESS) { 1444 value += ";" + valueMod; //addressTest has value=email part=all|domain|localpart 1445 } 1446 else if (testType == ZmFilterRule.TEST_CONVERSATIONS && value == ZmFilterRule.IMPORTANCE) { 1447 value = valueMod; //importanceTest 1448 } 1449 1450 if (testType == ZmFilterRule.TEST_HEADER || testType == ZmFilterRule.TEST_MIME_HEADER || testType == ZmFilterRule.TEST_ADDRESS) { 1451 caseSensitive = inputs["caseSensitive"] ? inputs["caseSensitive"].value : null; 1452 } 1453 1454 return { testType:testType, comparator:comparator, value:value, subjectMod:subjectMod, caseSensitive:caseSensitive, subject: subject }; 1455 }; 1456 1457 /** 1458 * Returns an Object based on the values of an action row. 1459 * 1460 * @param {String} rowId the row ID 1461 * 1462 * @private 1463 */ 1464 ZmFilterRuleDialog.prototype._getActionFromRow = 1465 function(rowId) { 1466 var inputs = this._inputs[rowId]; 1467 var name = inputs.name.dwtObj.getValue(); 1468 var conf = ZmFilterRule.ACTIONS[name]; 1469 var value = this._getInputValue(inputs, conf, "param"); 1470 1471 return {actionType:name, value:value}; 1472 }; 1473 1474 /** 1475 * Retrieves the value of an input based on what type it is. For all but text 1476 * inputs, we can get it from a DWT object. 1477 * 1478 * @param {Object} inputs the the inputs for one row 1479 * @param {Object} conf the config info for this row's subject or action name 1480 * @param {String} field the current input field 1481 * 1482 * @private 1483 */ 1484 ZmFilterRuleDialog.prototype._getInputValue = 1485 function(inputs, conf, field) { 1486 var type = conf[field]; 1487 if (!type) { 1488 return null; 1489 } 1490 if (type == ZmFilterRule.TYPE_INPUT) { 1491 return inputs[field].dwtObj.getValue(); 1492 } 1493 if (type == ZmFilterRule.TYPE_SELECT) { 1494 return inputs[field].dwtObj.getValue(); 1495 } 1496 if (type == ZmFilterRule.TYPE_CALENDAR) { 1497 var date = inputs[field].dwtObj.getData(ZmFilterRuleDialog.DATA); 1498 if (!date) { 1499 return null; 1500 } 1501 return String(date.getTime() / 1000); 1502 } 1503 if (type == ZmFilterRule.TYPE_FOLDER_PICKER) { 1504 return inputs[field].dwtObj.getData(ZmFilterRuleDialog.DATA); 1505 } 1506 if (type == ZmFilterRule.TYPE_TAG_PICKER) { 1507 return inputs[field].dwtObj.getData(ZmFilterRuleDialog.DATA); 1508 } 1509 }; 1510 1511 /** 1512 * Given a row, returns its index in its containing table. 1513 * 1514 * @param row [element] a table row (TR) 1515 * @param isCondition [boolean] true if the row is a condition row 1516 * 1517 * @private 1518 */ 1519 ZmFilterRuleDialog.prototype._getIndexForRow = 1520 function(row, isCondition) { 1521 var table = Dwt.byId(isCondition ? this._conditionsTableId : this._actionsTableId); 1522 var rows = table.rows; 1523 for (var i = 0; i < rows.length; i++) { 1524 if (rows[i] == row) { return i; } 1525 } 1526 1527 return null; 1528 }; 1529 1530 /** 1531 * Buses tables, hopes to make it big in movies some day. 1532 * 1533 * @private 1534 */ 1535 ZmFilterRuleDialog.prototype._clearTables = 1536 function() { 1537 var list = [this._conditionsTableId, this._actionsTableId]; 1538 for (var i = 0; i < list.length; i++) { 1539 var table = Dwt.byId(list[i]); 1540 var tbody = table.tBodies[0]; 1541 while (tbody.firstChild != null) { 1542 this._removeDwtObjects(tbody.firstChild.id); 1543 tbody.removeChild(tbody.firstChild); 1544 } 1545 } 1546 }; 1547 1548 /** 1549 * Returns false if the condition has the necessary parts, an error message otherwise. 1550 * 1551 * @param condition [Object] condition 1552 * 1553 * @private 1554 */ 1555 ZmFilterRuleDialog.prototype._checkCondition = 1556 function(condition) { 1557 var conf = ZmFilterRule.CONDITIONS[condition.subject]; 1558 for (var f in conf) { 1559 var key = ZmFilterRule.CONDITIONS_KEY[f]; 1560 if (!key) { continue; } 1561 if ((key == "value") && (condition.subject == ZmFilterRule.C_HEADER) && 1562 (condition.comparator == ZmFilterRule.OP_EXISTS || condition.comparator == ZmFilterRule.OP_NOT_EXISTS)) { 1563 continue; // "Header Named" with "exists" doesn't take a value 1564 } 1565 if (conf[f] && !condition[key]) { 1566 return this._conditionErrorFormatter.format([ZmFilterRule.C_LABEL[condition.subject]]); 1567 } 1568 } 1569 }; 1570 1571 /** 1572 * Returns true if the action has the necessary parts, an error message otherwise. 1573 * 1574 * @param action [Object] action 1575 */ 1576 ZmFilterRuleDialog.prototype._checkAction = 1577 function(action) { 1578 var conf = ZmFilterRule.ACTIONS[action.actionType]; 1579 if (conf.param && !action.value) { 1580 return this._actionErrorFormatter.format([ZmFilterRule.A_LABEL[action.actionType]]); 1581 } 1582 if (conf.validationFunction && !conf.validationFunction(action.value)) { 1583 return conf.errorMessage; 1584 } 1585 }; 1586 1587 ZmFilterRuleDialog.prototype.isEditMode = 1588 function() { 1589 return this._editMode; 1590 }; 1591