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