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 an empty preferences page of the given type.
 26  * @constructor
 27  * @class
 28  * This class represents a single page of preferences available by selecting one of the
 29  * preference tabs. During construction, skeletal HTML is created. The preferences
 30  * are not added until the page becomes visible.
 31  *
 32  * @author Conrad Damon
 33  *
 34  * @param {DwtControl}	parent			the containing widget
 35  * @param {object}	section			the page
 36  * @param {ZmPrefController}	controller		the prefs controller
 37  *
 38  * @extends		DwtTabViewPage
 39  */
 40 ZmPreferencesPage = function(parent, section, controller, id) {
 41 	if (arguments.length == 0) return;
 42 
 43 	id = id || ("Prefs_Pages_" + section.id);
 44 	DwtTabViewPage.call(this, parent, "ZmPreferencesPage", null, id);
 45 
 46 	this._section = section;
 47 	this._controller = controller;
 48 
 49 	this.setScrollStyle(Dwt.SCROLL_Y);
 50 
 51 	this._title = [ZmMsg.zimbraTitle, controller.getApp().getDisplayName(), section.title].join(": ");
 52 
 53 	this._dwtObjects = {};
 54 	this._tabGroup = new DwtTabGroup(id);
 55 	this._rendered = false; // used by DwtTabViewPage
 56 };
 57 
 58 ZmPreferencesPage.prototype = new DwtTabViewPage;
 59 ZmPreferencesPage.prototype.constructor = ZmPreferencesPage;
 60 
 61 ZmPreferencesPage.prototype.toString =
 62 function () {
 63 	return "ZmPreferencesPage";
 64 };
 65 
 66 //
 67 // Constants
 68 //
 69 
 70 ZmPreferencesPage.IMPORT_FIELD_NAME = "importUpload";
 71 ZmPreferencesPage.IMPORT_TIMEOUT = 300;
 72 
 73 //
 74 // Public methods
 75 //
 76 
 77 ZmPreferencesPage.prototype._replaceControlElement =
 78 function(elemOrId, control) {
 79 	control.replaceElement(elemOrId);
 80 };
 81 
 82 ZmPreferencesPage.prototype._enterTabScope =
 83 function() {
 84 	if (!this._tabScopeStack) {
 85 		this._tabScopeStack = [];
 86 	}
 87 	var scope = {};
 88 	this._tabScopeStack.push(scope);
 89 	return scope;
 90 };
 91 
 92 ZmPreferencesPage.prototype._getCurrentTabScope =
 93 function() {
 94 	var stack = this._tabScopeStack;
 95 	return stack && stack[stack.length - 1];
 96 };
 97 
 98 ZmPreferencesPage.prototype._exitTabScope =
 99 function() {
100 	var stack = this._tabScopeStack;
101 	return stack && stack.pop();
102 };
103 
104 ZmPreferencesPage.prototype._addControlTabIndex =
105 function(elemOrId, control) {
106 };
107 
108 ZmPreferencesPage.prototype._addTabLinks =
109 function(elemOrId) {
110 	var elem = Dwt.byId(elemOrId);
111 	if (!elem) return;
112 
113 	var links = elem.getElementsByTagName("A");
114 	for (var i = 0; i < links.length; i++) {
115 		var link = links[i];
116 		if (!link.href) continue;
117 		this._addControlTabIndex(link, link);
118 	}
119 };
120 
121 /**
122  * Fills the page with preferences that belong to this page, if that has not been done
123  * already. Note this method is only called when the tab
124  * is selected and the page becomes visible.
125  *
126  */
127 ZmPreferencesPage.prototype.showMe =
128 function() {
129 	DwtTabViewPage.prototype.showMe.call(this);
130 
131 	Dwt.setTitle(this._title);
132 	this._controller._resetOperations(this._controller._toolbar, this._section.id);
133 
134 	if (this.hasRendered) {
135 		if (this._controller.isDirty(this._section.id)) {
136 			this._controller.setDirty(this._section.id, false);
137 		}
138 		return;
139 	}
140 
141 	this._dwtObjects = {}; // always reset in case account has changed
142 	this._createPageTemplate();
143 	this._createControls();
144 
145 	// find option headers and sections
146 	var elements = this.getHtmlElement().children;
147 
148 	AjxUtil.foreach(elements, function(el) {
149 		if (Dwt.hasClass(el, 'prefHeader')) {
150 			var header = el;
151 			header.setAttribute('role', 'heading');
152 			header.setAttribute('aria-level', 1);
153 			header.id = Dwt.getNextId('prefHeader')
154 		} else if (Dwt.hasClass(el, 'ZOptionsSectionTable')) {
155 			var sectiontable = el;
156 			var header = Dwt.getPreviousElementSibling(sectiontable);
157 			var sections = Dwt.byClassName('ZOptionsSectionMain', sectiontable);
158 
159 			if (!Dwt.hasClass(header, 'prefHeader')) {
160 				DBG.println(AjxDebug.DBG1, "pref section has no prefHeader:\n" +
161 				            sectiontable.outerHTML);
162 				return;
163 			}
164 
165 			// we only expect one section, but iterate over them, just in case
166 			AjxUtil.foreach(sections, function(section) {
167 				section.setAttribute('aria-labelledby', header.id);
168 				section.setAttribute('role', 'region');
169 			});
170 		}
171 	});
172 
173 	// find option fields
174 	var fields = Dwt.byClassName('ZOptionsField', this.getHtmlElement());
175 
176 	AjxUtil.foreach(fields, function(field) {
177 		field.setAttribute('role', 'group');
178 
179 		// find the label corresponding to this item and assign it as an ARIA
180 		// label
181 		var label = Dwt.getPreviousElementSibling(field);
182 
183 		if (!label) {
184 			DBG.println(AjxDebug.DBG1, "option field has no label " + Dwt.getId(field));
185 			return;
186 		}
187 
188 		label.setAttribute('role', 'heading');
189 		label.setAttribute('aria-level', 2);
190 
191 		field.setAttribute('aria-labelledby',
192 		                   Dwt.getId(label, 'ZOptionsLabel'));
193 	});
194 
195 	// find focusable children -- i.e. links and widgets -- but in the DOM
196 	// order, not in the order they were added as children
197 	var selector = [
198 		'[parentid="',
199 		this.getHTMLElId(),
200 		'"],',
201 		'A'
202 	].join('');
203 	var elements = this.getHtmlElement().querySelectorAll(selector);
204 
205 	for (var i = 0; i < elements.length; i++) {
206 		var element = elements[i];
207 		var control = DwtControl.fromElement(element);
208 
209 		// add the child to our tab group
210 		if (control && control.parent == this) {
211 			this._tabGroup.addMember(control.getTabGroupMember());
212 		} else if (DwtControl.findControl(element) === this) {
213 			this._makeFocusable(element);
214 			this._tabGroup.addMember(element);
215 		}
216 
217 		// find the ZOptionsField corresponding to this item and assign it as
218 		// an ARIA label
219 		var ancestors = Dwt.getAncestors(element, this.getHtmlElement());
220 		var field = null, label = null;
221 
222 		for (var j = 0; j < ancestors.length; j++) {
223 			var ancestor = ancestors[j];
224 			var ancestorSibling = Dwt.getPreviousElementSibling(ancestor);
225 
226 			// are we looking at an option field with a corresponding label?
227 			// please note that labels can have multiple classes, all of them
228 			// starting with ZOptionsLabel
229 			if (Dwt.hasClass(ancestor, 'ZOptionsField')) {
230 				field = ancestor;
231 			}
232 		}
233 
234 		if (!field) {
235 			DBG.println(AjxDebug.DBG1, "no field found for:\n" +
236 						element.outerHTML);
237 			continue;
238 		}
239 
240 		var label = Dwt.getPreviousElementSibling(field);
241 
242 		if (!label || !label.className.match(/\bZOptionsLabel/)) {
243 			DBG.println(AjxDebug.DBG1, "option field has no label:\n" +
244 						field.outerHTML);
245 			continue;
246 		}
247 
248 		if (!label.id) {
249 			label.id = Dwt.getNextId();
250 		}
251 
252 		label.setAttribute('role', 'heading');
253 		label.setAttribute('aria-level', 2);
254 	}
255 };
256 
257 ZmPreferencesPage.prototype._createPageTemplate =
258 function() {
259 	// expand template
260 	DBG.println(AjxDebug.DBG2, "rendering preferences page " + this._section.id);
261 	var templateId = this._section.templateId;
262 	this._createPageHtml(templateId, this._getTemplateData());
263 	this.setVisible(false); // hide until ready
264 };
265 
266 ZmPreferencesPage.prototype._getTemplateData =
267 function() {
268 	var data = {
269 		id: this._htmlElId,
270 		isEnabled: AjxCallback.simpleClosure(this._isEnabled, this),
271 		activeAccount: appCtxt.getActiveAccount()
272 	};
273 	data.expandField = AjxCallback.simpleClosure(this._expandField, this, data);
274 
275 	return data;
276 };
277 
278 ZmPreferencesPage.prototype._createControls =
279 function() {
280 	// create controls for prefs, if present in template
281 	this._prefPresent = {};
282 	this._enterTabScope();
283 	try {
284 		// add links to tab control list
285 		this._addTabLinks(this.getHtmlElement());
286 
287 		// add preference controls
288 		var prefs = this._section.prefs || [];
289 		var settings = appCtxt.getSettings();
290 
291 		for (var i = 0; i < prefs.length; i++) {
292 			var id = prefs[i];
293 			if (!id) { continue; }
294 			var pref = settings.getSetting(id);
295 
296 			// ignore if there is no container element
297 			var elem = document.getElementById([this._htmlElId, id].join("_"));
298 			if (!elem) { continue; }
299 
300 			// ignore if doesn't meet pre-condition
301 			var setup = ZmPref.SETUP[id];
302 
303 			if (!setup || !appCtxt.checkPrecondition(setup.precondition, setup.preconditionAny)) {
304 				continue;
305 			}
306 
307 			// perform load function
308 			if (setup.loadFunction) {
309 				setup.loadFunction(setup);
310 				if (setup.options.length <= 1) { continue; }
311 			}
312 
313 			// save the current value (for checking later if it changed)
314 			pref.origValue = this._getPrefValue(id);
315 			var value = this._getPrefValue(id, false);
316 
317 			this._prefPresent[id] = true;
318 			DBG.println(AjxDebug.DBG3, "adding pref " + pref.name + " / " + value);
319 
320 			// create form controls
321 			this._initControl(id, setup, value);
322 
323 			var control = null;
324 			var type = setup ? setup.displayContainer : null;
325 			if (type == ZmPref.TYPE_CUSTOM) {
326 				control = this._setupCustom(id, setup, value);
327 			}
328 			else if (type == ZmPref.TYPE_SELECT) {
329 				control = this._setupSelect(id, setup, value);
330 			}
331 			else if (type == ZmPref.TYPE_COMBOBOX) {
332 				control = this._setupComboBox(id, setup, value);
333 			}
334 			else if (type == ZmPref.TYPE_RADIO_GROUP) {
335 				control = this._setupRadioGroup(id, setup, value);
336 			}
337 			else if (type == ZmPref.TYPE_CHECKBOX) {
338 				control = this._setupCheckbox(id, setup, value);
339 			}
340 			else if (type == ZmPref.TYPE_INPUT || type == ZmPref.TYPE_TEXTAREA) {
341 				if (type == ZmPref.TYPE_TEXTAREA) {
342 					setup.rows = elem.getAttribute("rows") || setup.rows || 4;
343 					setup.cols = elem.getAttribute("cols") || setup.cols || 60;
344 					setup.wrap = elem.getAttribute("wrap") || setup.wrap || "on";
345 				}
346 				control = this._setupInput(id, setup, value);
347 			}
348 			else if (type == ZmPref.TYPE_STATIC) {
349 				control = this._setupStatic(id, setup, value);
350 			}
351 			else if (type == ZmPref.TYPE_COLOR) {
352 				control = this._setupColor(id, setup, value);
353 			}
354 			else if (type == ZmPref.TYPE_LOCALES) {
355                 //Fix for bug# 80762 - Based on multiple locale availability set the view as dropdown or label
356                 if(ZmLocale.hasChoices()) {
357                     control = this._setupLocales(id, setup, value);
358                 }
359                 else {
360                     //Part of bug# 80762. Sets view for a single locale and displays as a label
361                     control = this._setupLocaleLabel(id, setup, value);
362                 }
363 			}
364 			else if (type == ZmPref.TYPE_FONT) {
365 				control = this._setupMenuButton(id, value, ZmPreferencesPage.fontMap);
366 			}
367 			else if (type == ZmPref.TYPE_FONT_SIZE) {
368 				control = this._setupMenuButton(id, value, ZmPreferencesPage.fontSizeMap);
369 			}
370 			else if (type == ZmPref.TYPE_PASSWORD) {
371 				this._addButton(elem, setup.displayName, 50, new AjxListener(this, this._changePasswordListener), "CHANGE_PASSWORD");
372 			}
373 			else if (type == ZmPref.TYPE_IMPORT) {
374 				this._addImportWidgets(elem, id, setup);
375 			}
376 			else if (type == ZmPref.TYPE_EXPORT) {
377 				this._addExportWidgets(elem, id, setup);
378 			}
379 
380 			if (!control) {
381 				control = this.getFormObject(id);
382 			}
383 
384 			// add control to form
385 			if (control && control.isDwtControl) {
386 				this._replaceControlElement(elem, control);
387 				if (setup.initFunction) {
388 					setup.initFunction(control, value);
389 				}
390 				if (setup.changeFunction) {
391 					if (control.addChangeListener) {
392 						control.addChangeListener(setup.changeFunction);
393 					} else if (control.addSelectionListener) {
394 						control.addSelectionListener(setup.changeFunction);
395 					}
396 				}
397 			}
398 		}
399 
400 		// create special page buttons
401 		var defaultsRestore = document.getElementById([this._htmlElId,"DEFAULTS_RESTORE"].join("_"));
402 		if (defaultsRestore) {
403 			this._addButton(defaultsRestore, ZmMsg.restoreDefaults, 110, new AjxListener(this, this._resetListener));
404 		}
405 
406 		// create tab-group for all controls on the page
407 		this._addControlsToTabGroup(this._tabGroup);
408 	}
409 	finally {
410 		this._exitTabScope();
411 	}
412 
413 	// finish setup
414 	this.setVisible(true);
415 	this.hasRendered = true;
416 };
417 
418 ZmPreferencesPage.prototype._addControlsToTabGroup =
419 function(tabGroup) {
420 };
421 
422 ZmPreferencesPage.prototype.setFormObject =
423 function(id, object) {
424 	this._dwtObjects[id] = object;
425 };
426 
427 ZmPreferencesPage.prototype.getFormObject =
428 function(id) {
429 	return this._dwtObjects[id];
430 };
431 
432 /**
433  * Gets the value of the preference control.
434  *
435  * @param {String}		id		the preference id
436  * @param {Object}	[setup]		the preference descriptor
437  * @param {DwtControl}	[control]	the preference control
438  * @return	{String}	the value
439  */
440 ZmPreferencesPage.prototype.getFormValue =
441 function(id, setup, control) {
442 	setup = setup || ZmPref.SETUP[id];
443 	var value = null;
444 	var type = setup ? setup.displayContainer : null;
445 	if (type == ZmPref.TYPE_SELECT || type == ZmPref.TYPE_COMBOBOX ||
446 		type == ZmPref.TYPE_CHECKBOX ||
447 		type == ZmPref.TYPE_RADIO_GROUP || type == ZmPref.TYPE_COLOR ||
448 		type == ZmPref.TYPE_INPUT || type == ZmPref.TYPE_LOCALES ||
449 		type === ZmPref.TYPE_FONT || type === ZmPref.TYPE_FONT_SIZE) {
450 		var object = control || this.getFormObject(id);
451 		if (object) {
452 			if (type == ZmPref.TYPE_COLOR) {
453 				value = object.getColor();
454 			}
455 			else if (type == ZmPref.TYPE_CHECKBOX) {
456 				value = object.isSelected();
457 				if (setup.options) {
458 					value = setup.options[Number(value)];
459 				}
460 			}
461 			else if (type == ZmPref.TYPE_RADIO_GROUP) {
462 				value = object.getSelectedValue();
463 				if (value == "true" || value == "false") {
464 					value = (value == "true");
465 				}
466 			}
467 			else if (type == ZmPref.TYPE_LOCALES) {
468 				value = object._localeId;
469 			}
470 			else if (type === ZmPref.TYPE_FONT || type === ZmPref.TYPE_FONT_SIZE) {
471 				value = object._itemId;
472 			}
473 			else if (type == ZmPref.TYPE_COMBOBOX) {
474 				value = object.getValue() || object.getText();
475 			}
476 			else {
477 				value = object.getValue();
478 			}
479 		}
480 	}
481 	else {
482 		var prefId = [this._htmlElId, id].join("_");
483 		var element = document.getElementById(prefId);
484 		if (!element) return null;
485 		value = element.value;
486 	}
487 	return setup && setup.valueFunction ? setup.valueFunction(value) : value;
488 };
489 
490 /**
491  * Sets the value of the preference control.
492  *
493  * @param {String}	id		the preference id
494  * @param {Object}	value		the preference value
495  * @param {Object}	[setup]		the preference descriptor
496  * @param {DwtControl}	[control]	the preference control
497  */
498 ZmPreferencesPage.prototype.setFormValue =
499 function(id, value, setup, control) {
500 	setup = setup || ZmPref.SETUP[id];
501 	if (setup && setup.displayFunction) {
502 		value = setup.displayFunction(value);
503 	}
504 	if (setup && setup.approximateFunction) {
505 		value = setup.approximateFunction(value);
506 	}
507 	var type = setup ? setup.displayContainer : null;
508 	if (type == ZmPref.TYPE_SELECT || type == ZmPref.TYPE_COMBOBOX ||
509 		type == ZmPref.TYPE_CHECKBOX ||
510 		type == ZmPref.TYPE_RADIO_GROUP ||
511 		type == ZmPref.TYPE_COLOR) {
512 		var object = control || this.getFormObject(id);
513 		if (!object) { return value; }
514 
515 		if (type == ZmPref.TYPE_COLOR) {
516 			object.setColor(value);
517 		} else if (type == ZmPref.TYPE_CHECKBOX) {
518 			if (id == ZmSetting.OFFLINE_IS_MAILTO_HANDLER) {
519 				try { // add try/catch - see bug #33870
520 					if (window.platform && !window.platform.isRegisteredProtocolHandler("mailto")) {
521 						object.setSelected(false);
522 
523 						// this pref might have been set to true before. so we must set origValue = false
524 						// so that when user selects the checkbox, it will be considered "dirty"
525 						var setting = appCtxt.getSettings(appCtxt.accountList.mainAccount).getSetting(id);
526 						setting.origValue = false;
527 					} else {
528 						object.setSelected(true);
529 					}
530 					object.setEnabled(true);
531 				} catch(ex) {
532 					object.setEnabled(false);
533 					object.setSelected(false);
534 				}
535 			} else {
536 				object.setSelected(value);
537 			}
538 		} else if (type == ZmPref.TYPE_RADIO_GROUP) {
539 			object.setSelectedValue(value);
540 		} else if (type == ZmPref.TYPE_COMBOBOX) {
541 			object.setValue(value);
542 		} else {
543 			var curValue = object.getValue();
544 			if (value != null && (curValue != value)) {
545 				object.setSelectedValue(value);
546 			}
547 		}
548 	} else if (type == ZmPref.TYPE_INPUT) {
549 		var object = control || this.getFormObject(id);
550 		if (!object) { return value; }
551 
552 		var curValue = object.getValue();
553 		if (value != null && (curValue != value)) {
554 			object.setValue(value);
555 		}
556 	} else if (type == ZmPref.TYPE_LOCALES) {
557 		var object = this._dwtObjects[ZmSetting.LOCALE_NAME];
558 		if (!object) { return value; }
559 		this._showLocale(value, object);
560 	} else if (type == ZmPref.TYPE_FONT) {
561 		var object = this._dwtObjects[ZmSetting.FONT_NAME];
562 		if (!object) { return value; }
563 		this._showItem(value, ZmPreferencesPage.fontMap, object);
564 	} else if (type == ZmPref.TYPE_FONT_SIZE) {
565 		var object = this._dwtObjects[ZmSetting.FONT_SIZE];
566 		if (!object) { return value; }
567 		this._showItem(value, ZmPreferencesPage.fontSizeMap, object);
568 	} else {
569 		var prefId = [this._htmlElId, id].join("_");
570 		var element = control || document.getElementById(prefId);
571 		if (!element || element.value == value) { return value; }
572 
573 		element.value = value || "";
574 	}
575     return value;
576 };
577 
578 /**
579  * Gets the title.
580  *
581  * @return	{String}	the title
582  */
583 ZmPreferencesPage.prototype.getTitle =
584 function() {
585 	return this._title;
586 };
587 
588 ZmPreferencesPage.prototype.hasResetButton =
589 function() {
590 	return true;
591 };
592 
593 
594 ZmPreferencesPage.prototype.getTabGroupMember =
595 function() {
596 	return this._tabGroup;
597 };
598 
599 ZmPreferencesPage.prototype.reset =
600 function(useDefaults) {
601 	var settings = appCtxt.getSettings();
602 	var prefs = this._section.prefs || [];
603 	for (var i = 0; i < prefs.length; i++) {
604 		var id = prefs[i];
605 		if (!id) { continue; }
606 		var setup = ZmPref.SETUP[id];
607 		if (!setup) { continue; }
608 		var type = setup.displayContainer;
609 		if (type == ZmPref.TYPE_PASSWORD) { continue; } // ignore non-form elements
610 		var pref = settings.getSetting(id);
611 		var newValue = this._getPrefValue(id, useDefaults);
612 		this.setFormValue(id, newValue);
613 	}
614 
615 	if (!useDefaults) {
616 		this._controller.setDirty(this._section.id, false);
617 	}
618 };
619 
620 ZmPreferencesPage.prototype.resetOnAccountChange =
621 function() {
622 	this.hasRendered = false;
623 };
624 
625 /**
626  * Checks if the data is dirty.
627  *
628  * @return	{Boolean}	<code>true</code> if the data is dirty
629  */
630 ZmPreferencesPage.prototype.isDirty = function() { return false; };
631 ZmPreferencesPage.prototype.validate = function() {	return true; };
632 
633 /**
634  * Adds the modify command to the given batch command.
635  *
636  * @param	{ZmBatchCommand}		batchCmd		the batch command
637  */
638 ZmPreferencesPage.prototype.addCommand = function(batchCmd) {};
639 
640 //
641 // Protected methods
642 //
643 
644 ZmPreferencesPage.prototype._createPageHtml =
645 function(templateId, data) {
646 	if (AjxTemplate.require(templateId)) {
647 		this.getContentHtmlElement().innerHTML = AjxTemplate.expand(templateId, data);
648 	}
649 };
650 
651 /**
652  * Returns the value of the specified pref, massaging it if necessary.
653  *
654  * @param id			[constant]		pref ID
655  * @param useDefault	[boolean]		if true, use pref's default value
656  *
657  * @private
658  */
659 ZmPreferencesPage.prototype._getPrefValue =
660 function(id, useDefault) {
661 	var pref = appCtxt.getSettings().getSetting(id);
662 	return useDefault ? pref.getDefaultValue() : pref.getValue();
663 };
664 
665 // Add a button to the preferences page
666 ZmPreferencesPage.prototype._addButton =
667 function(parentIdOrElem, text, width, listener, id) {
668 	var params = {parent: this};
669 	if (id) {
670 		params.id = id;
671 	}
672 	var button = new DwtButton(params);
673 	button.setSize(width, Dwt.DEFAULT);
674 	button.setText(text);
675 	button.addSelectionListener(listener);
676 	this._replaceControlElement(parentIdOrElem, button);
677 	return button;
678 };
679 
680 ZmPreferencesPage.prototype._prepareValue =
681 function(id, setup, value) {
682 	if (setup.displayFunction) {
683 		value = setup.displayFunction(value);
684 	}
685 	if (setup.approximateFunction) {
686 		value = setup.approximateFunction(value);
687 	}
688 	return value;
689 };
690 
691 ZmPreferencesPage.prototype._initControl =
692 function(id, setup, value) {
693 	// sub-classes can override this to provide initialization
694 	// code *before* the actual control is constructed.
695 };
696 
697 ZmPreferencesPage.prototype._setupStatic =
698 function(id, setup, value) {
699 	var text = new DwtText(this);
700 	this.setFormObject(id, text);
701 	text.setText(value);
702 	return text;
703 };
704 
705 ZmPreferencesPage.prototype._setupSelect =
706 function(id, setup, value) {
707 	value = this._prepareValue(id, setup, value);
708 
709 	var params = {parent: this, id: "Prefs_Select_" + id};
710 	for (var p in setup.displayParams) {
711 		params[p] = setup.displayParams[p];
712 	}
713 	var selObj = new DwtSelect(params);
714 	this.setFormObject(id, selObj);
715 
716 	var options = setup.options || setup.displayOptions || setup.choices || [];
717 	var isChoices = Boolean(setup.choices);
718 	for (var j = 0; j < options.length; j++) {
719 		var optValue = isChoices ? options[j].value : options[j];
720 		var optLabel = isChoices ? options[j].label : setup.displayOptions[j];
721 		optLabel = ZmPreferencesPage.__formatLabel(optLabel, optValue);
722 		var optImage = setup.images ? setup.images[j] : null;
723 		var data = new DwtSelectOptionData(optValue, optLabel, false, null, optImage);
724 		selObj.addOption(data);
725 	}
726 
727 	selObj.setName(id);
728 	selObj.setSelectedValue(value);
729     selObj.dynamicButtonWidth();
730 
731 	return selObj;
732 };
733 
734 ZmPreferencesPage.prototype._setupComboBox =
735 function(id, setup, value) {
736 	value = this._prepareValue(id, setup, value);
737 
738 	var params = {parent: this, id: "Prefs_ComboBox_" + id};
739 
740 	var cboxObj = new DwtComboBox(params);
741 	this.setFormObject(id, cboxObj);
742 
743 	var options = setup.options || setup.displayOptions || setup.choices || [];
744 	var isChoices = Boolean(setup.choices);
745 	for (var j = 0; j < options.length; j++) {
746 		var optValue = isChoices ? options[j].value : options[j];
747 		var optLabel = isChoices ? options[j].label : setup.displayOptions[j];
748 		optLabel = ZmPreferencesPage.__formatLabel(optLabel, optValue);
749 		cboxObj.add(optLabel, optValue, optValue == value);
750 	}
751 
752 	cboxObj.setValue(value);
753 
754 	return cboxObj;
755 };
756 
757 ZmPreferencesPage.prototype._setupRadioGroup =
758 function(id, setup, value) {
759 	value = this._prepareValue(id, setup, value);
760 
761 	var params = {parent: this, id: "Prefs_RadioGroup_" + id};
762 	var container = new DwtComposite(params);
763 
764 	// build horizontally-oriented radio group, if needed
765 	var orient = setup.orientation || ZmPref.ORIENT_VERTICAL;
766 	var isHoriz = orient == ZmPref.ORIENT_HORIZONTAL;
767 	if (isHoriz) {
768 		var table, row, cell;
769 
770 		table = document.createElement("TABLE");
771 		table.className = "ZmRadioButtonGroupHoriz";
772 		table.border = 0;
773 		table.cellPadding = 0;
774 		table.cellSpacing = 0;
775 		container.getHtmlElement().appendChild(table);
776 
777 		row = table.insertRow(-1);
778 	}
779 
780 	// add options
781 	var options = setup.options || setup.displayOptions || setup.choices;
782 	var isChoices = setup.choices;
783 	var isDisplayString = AjxUtil.isString(setup.displayOptions);
784 	var inputId = setup.inputId;
785 	
786 	var radioIds = {};
787 	var selectedId;
788 	var name = Dwt.getNextId();
789 	for (var i = 0; i < options.length; i++) {
790 		var optValue = isChoices ? options[i].value : options[i];
791 		var optLabel = isChoices ? options[i].label : (isDisplayString ? setup.displayOptions : setup.displayOptions[i]);
792 		optLabel = ZmPreferencesPage.__formatLabel(optLabel, optValue);
793 		var isSelected = value == optValue;
794 
795 		var automationId  = AjxUtil.isArray(inputId) && inputId[i] ? inputId[i] : Dwt.getNextId();
796 		var radioBtn = new DwtRadioButton({parent:container, name:name, checked:isSelected, id: automationId});
797 		radioBtn.setText(optLabel);
798 		radioBtn.setValue(optValue);
799 
800 		var radioId = radioBtn.getInputElement().id;
801 		radioIds[radioId] = radioBtn;
802 		if (isSelected) {
803 			radioBtn.setSelected(true);
804             selectedId = radioId;
805 		}
806 
807 		if (setup.validationFunction) {
808 			var valueToCheck = setup.valueFunction ? setup.valueFunction(optValue) : optValue;
809 			if (!setup.validationFunction(valueToCheck)) {
810 				radioBtn.setEnabled(false);
811 			}
812 		}
813 
814 		if (isHoriz) {
815 			cell = row.insertCell(-1);
816 			cell.className = "ZmRadioButtonGroupCell";
817 			radioBtn.appendElement(cell);
818 		}
819 	}
820 
821 	// store radio button group
822 	this.setFormObject(id, new DwtRadioButtonGroup(radioIds, selectedId));
823 
824 	return container;
825 };
826 
827 ZmPreferencesPage.prototype._setupCheckbox =
828 function(id, setup, value) {
829 	var params = {parent: this, checked: value, id: "Prefs_Checkbox_" + id};
830 	var checkbox = new DwtCheckbox(params);
831 	this.setFormObject(id, checkbox);
832 	var text = setup.displayFunc ? setup.displayFunc() : setup.displayName;
833 	var cboxLabel = ZmPreferencesPage.__formatLabel(text, value);
834 	checkbox.setText(cboxLabel);
835 	checkbox.setSelected(value);
836 
837 	// TODO: Factor this out
838 	if (id == ZmSetting.MAIL_LOCAL_DELIVERY_DISABLED) {
839 		this._handleDontKeepCopyChange();
840 		checkbox.addSelectionListener(new AjxListener(this, this._handleDontKeepCopyChange));
841 	}
842 
843 	return checkbox;
844 };
845 
846 ZmPreferencesPage.prototype._setupInput =
847 function(id, setup, value) {
848 	value = this._prepareValue(id, setup, value);
849 	var params = {
850 		parent: this,
851 		type: setup.type || DwtInputField.STRING,
852 		initialValue: value,
853 		size: setup.cols || 40,
854 		rows: setup.rows,
855 		wrap: setup.wrap,
856 		maxLen:setup.maxLen,
857 		hint: setup.hint,
858 		label: setup.label,
859 		id: "Prefs_Input_" + id
860 	};
861 	var input = new DwtInputField(params);
862 	this.setFormObject(id, input);
863 	// TODO: Factor this out
864 	if (id == ZmSetting.MAIL_FORWARDING_ADDRESS) {
865 		this._handleDontKeepCopyChange();
866 	}
867 
868 	return input;
869 };
870 
871 ZmPreferencesPage.prototype._addImportWidgets =
872 function(containerDiv, settingId, setup) {
873 	var uri = appCtxt.get(ZmSetting.CSFE_UPLOAD_URI);
874 
875 	var importDivId = this._htmlElId+"_import";
876 	var isAddrBookImport = settingId == ZmSetting.IMPORT;
877 	var data = {
878 		id: importDivId,
879 		action: uri,
880 		name: ZmPreferencesPage.IMPORT_FIELD_NAME,
881 		label: isAddrBookImport ? ZmMsg.importFromCSVLabel : ZmMsg.importFromICSLabel
882 	};
883 	containerDiv.innerHTML = AjxTemplate.expand("prefs.Pages#Import", data);
884 
885 	this._uploadFormId = importDivId+"_form";
886 	this._attInputId = importDivId+"_input";
887 
888 	// setup pseudo tab group
889 	this._enterTabScope();
890 	var tabGroup = new DwtTabGroup(importDivId+"_x-tabgroup");
891 	try {
892 		// set up import button
893 	var buttonDiv = document.getElementById(importDivId+"_button");
894 	var btnLabel = setup ? setup.displayName : ZmMsg._import;
895 	this._importBtn = this._addButton(buttonDiv, btnLabel, 100, new AjxListener(this, this._importButtonListener));
896 	if (settingId) {
897 		this._importBtn.setData(Dwt.KEY_ID, settingId);
898 	}
899 
900 		// add other controls
901 		var inputEl = document.getElementById(this._attInputId);
902 		if (inputEl) {
903 			this._addControlTabIndex(inputEl, inputEl);
904 		}
905 		this._addTabLinks(containerDiv);
906 
907 		// add pseudo tab group
908 		this._addControlsToTabGroup(tabGroup);
909 	}
910 	finally {
911 		this._exitTabScope();
912 		this._addControlTabIndex(containerDiv, new ZmPreferencesPage.__hack_TabGroupControl(tabGroup));
913 	}
914 };
915 
916 ZmPreferencesPage.__hack_TabGroupControl =
917 function(tabGroup) {
918 	this.getTabGroupMember = function() { return tabGroup; };
919 };
920 
921 ZmPreferencesPage.prototype._setupColor =
922 function(id, setup, value) {
923 
924 	var params = {parent: this, id: "Prefs_ColorPicker_" + id};
925 	var picker = new DwtButtonColorPicker(params);
926 	picker.setImage("FontColor");
927 	picker.showColorDisplay(true);
928 	picker.setToolTipContent(ZmMsg.fontColor);
929 	picker.setColor(value);
930 
931 	this.setFormObject(id, picker);
932 
933 	return picker;
934 };
935 
936 ZmPreferencesPage.prototype._addExportWidgets =
937 function(containerDiv, settingId, setup) {
938 	var exportDivId = this._htmlElId+"_export";
939 	containerDiv.innerHTML = AjxTemplate.expand("prefs.Pages#Export", exportDivId);
940 
941     //Export Options
942 	var format = settingId == "CAL_EXPORT" ? "ics" : "csv";
943     var selFormat = null;
944     var optionsDiv = document.getElementById(exportDivId+"_options");
945     if(optionsDiv && (setup.options && setup.options.length > 0) ) {
946         var selFormat = this._setupSelect(settingId, setup);
947         this._replaceControlElement(optionsDiv, selFormat);
948     }
949 
950     //Export Button
951 	var buttonDiv = document.getElementById(exportDivId+"_button");
952 	buttonDiv.setAttribute("tabindex", containerDiv.getAttribute("tabindex"));
953 
954 	var btnLabel = setup.displayName || ZmMsg._export;
955 	var btn = this._addButton(buttonDiv, btnLabel, 110, new AjxListener(this, this._exportButtonListener, [format, selFormat]));
956 	btn.setData(Dwt.KEY_ID, settingId);
957 };
958 
959 ZmPreferencesPage.prototype._setupCustom =
960 function(id, setup, value) {
961 	DBG.println("TODO: override ZmPreferences#_setupCustom");
962 };
963 
964 ZmPreferencesPage.prototype._setupLocales =
965 function(id, setup, value) {
966 	var params = {parent: this, id: "Prefs_Locale_" + id};
967 	var button = new DwtButton(params);
968 	button.setSize(60, Dwt.DEFAULT);
969 	button.setMenu(new AjxListener(this, this._createLocalesMenu, [setup]));
970 	this._showLocale(value, button);
971 
972 	this._dwtObjects[id] = button;
973 
974 	return button;
975 };
976 
977 //Part of bug# 80762 - Display the single locale item as a read-only label
978 ZmPreferencesPage.prototype._setupLocaleLabel =
979 function(id, setup, value) {
980     var label = new DwtLabel({parent:this});
981     label.setSize(60, Dwt.DEFAULT);
982     this._showLocale(value, label);
983     this._dwtObjects[id] = label;
984     return label;
985 };
986 
987 ZmPreferencesPage.prototype._setupMenuButton =
988 function(id, value, itemMap) {
989 	var button = new DwtButton({parent:this});
990 	button.setSize(60, Dwt.DEFAULT);
991 	button.setMenu(new AjxListener(this, this._createMenu, [button, itemMap]));
992 	this._showItem(value, itemMap, button);
993 
994 	this._dwtObjects[id] = button;
995 
996 	return button;
997 };
998 
999 ZmPreferencesPage.prototype._showLocale =
1000 function(localeId, button) {
1001 	var locale = ZmLocale.localeMap[localeId] || ZmLocale.localeMap[localeId.substr(0, 2)];
1002 	button.setImage(locale ? locale.getImage() : null);
1003 	button.setText(locale ? locale.getNativeAndLocalName() : "");
1004 	button._localeId = localeId;
1005 };
1006 
1007 ZmPreferencesPage.prototype._createMenu =
1008 function(button, itemMap) {
1009 
1010 	var menu = new DwtMenu({parent:button});
1011 
1012 	var listener = this._itemSelectionListener.bind(this, button, itemMap);
1013 
1014 	for (var id in itemMap) {
1015 		var item = itemMap[id];
1016 		this._createMenuItem(menu, item.id, item.name, listener);
1017 	}
1018 	return menu;
1019 };
1020 
1021 ZmPreferencesPage.prototype._createLocalesMenu =
1022 function(setup) {
1023 
1024 	var button = this._dwtObjects[ZmSetting.LOCALE_NAME];
1025 	var result = new DwtMenu({parent:button});
1026 
1027 	var listener = new AjxListener(this, this._localeSelectionListener);
1028 	for (var language in ZmLocale.languageMap) {
1029 		var languageObj = ZmLocale.languageMap[language];
1030 		var locales = languageObj.locales;
1031 		if (!locales) {
1032 			this._createLocaleItem(result, languageObj, listener);
1033 		}
1034 		else if (locales.length > 0) {
1035 			/* show submenu even if just one item, for cases such as Portugeuse (Brasil), since we want country (locale) specific items in the submenu level */
1036 			var menuItem = new DwtMenuItem({parent:result, style:DwtMenuItem.CASCADE_STYLE});
1037 			menuItem.setText(ZmLocale.languageMap[language].getNativeAndLocalName());
1038 			var subMenu = new DwtMenu({parent:result, style:DwtMenu.DROPDOWN_STYLE});
1039 			menuItem.setMenu(subMenu);
1040 			for (var i = 0, count = locales.length; i < count; i++) {
1041 				this._createLocaleItem(subMenu, locales[i], listener);
1042 			}
1043 		}
1044 	}
1045 	return result;
1046 };
1047 
1048 ZmPreferencesPage.prototype._createMenuItem =
1049 function(parent, id, text, listener) {
1050 	var item = new DwtMenuItem({parent:parent});
1051 	item.setText(text);
1052 	item._itemId = id;
1053 	item.addSelectionListener(listener);
1054 	return item;
1055 };
1056 
1057 ZmPreferencesPage.prototype._createLocaleItem =
1058 function(parent, locale, listener) {
1059 	var result = new DwtMenuItem({parent:parent});
1060 	result.setText(locale.getNativeAndLocalName());
1061 	if (locale.getImage()) {
1062 		result.setImage(locale.getImage());
1063 	}
1064 	result._localeId = locale.id;
1065 	result.addSelectionListener(listener);
1066 	return result;
1067 };
1068 
1069 ZmPreferencesPage.prototype._showItem =
1070 function(itemId, itemMap, button) {
1071 	var item = itemMap[itemId];
1072 	button.setImage(item && item.image || null);
1073 	button.setText(item && item.name || "");
1074 	button._itemId = itemId;
1075 };
1076 
1077 ZmPreferencesPage.prototype._itemSelectionListener =
1078 function(button, itemMap, ev) {
1079 	var item = ev.dwtObj;
1080 	this._showItem(item._itemId, itemMap, button);
1081 };
1082 
1083 ZmPreferencesPage.prototype._localeSelectionListener =
1084 function(ev) {
1085 	var item = ev.dwtObj;
1086 	var button = this._dwtObjects[ZmSetting.LOCALE_NAME];
1087 	this._showLocale(item._localeId, button);
1088     this._showComposeDirection(item._localeId);
1089 };
1090 
1091 ZmPreferencesPage.prototype._handleDontKeepCopyChange = function(ev) {
1092 	var input = this.getFormObject(ZmSetting.MAIL_FORWARDING_ADDRESS);
1093 	var checkbox = this.getFormObject(ZmSetting.MAIL_LOCAL_DELIVERY_DISABLED);
1094 	if (input && checkbox) {
1095 		input.setRequired(checkbox.isSelected());
1096 	}
1097 };
1098 
1099 // Popup the change password dialog.
1100 ZmPreferencesPage.prototype._changePasswordListener =
1101 function(ev) {
1102 	appCtxt.getChangePasswordWindow(ev);
1103 };
1104 
1105 ZmPreferencesPage.prototype._exportButtonListener =
1106 function(format, formatSelectObj, ev) {
1107 	var settingId = ev.dwtObj.getData(Dwt.KEY_ID);
1108 
1109 	//Get Format
1110 	var subFormat = formatSelectObj? formatSelectObj.getValue() : null;
1111 
1112 	var dialog = appCtxt.getChooseFolderDialog();
1113 	dialog.reset();
1114 	dialog.registerCallback(DwtDialog.OK_BUTTON, this._exportOkCallback, this, [dialog, format, subFormat]);
1115 
1116 	var omit = {};
1117 	omit[ZmFolder.ID_TRASH] = true;
1118 	var overviewId = dialog.getOverviewId(settingId);
1119 
1120 	if (settingId == ZmSetting.EXPORT) {
1121 		AjxDispatcher.require(["ContactsCore", "Contacts"]);
1122 		dialog.popup({treeIds:			[ZmOrganizer.ADDRBOOK],
1123 					  overviewId:		overviewId,
1124 					  omit:				omit,
1125 					  title:			ZmMsg.chooseAddrBook,
1126 					  hideNewButton:	true,
1127 					  appName:			ZmApp.CONTACTS,
1128 					  description:		ZmMsg.chooseAddrBookToExport});
1129 	} else {
1130 		AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]);
1131 		dialog.popup({treeIds:			[ZmOrganizer.CALENDAR],
1132 					  overviewId:		overviewId,
1133 					  omit:				omit,
1134 					  title:			ZmMsg.chooseCalendar,
1135 					  hideNewButton:	true,
1136 					  appName:			ZmApp.CALENDAR,
1137 					  description:		ZmMsg.chooseCalendarToExport});
1138 	}
1139 };
1140 
1141 ZmPreferencesPage.prototype._importButtonListener =
1142 function(ev) {
1143 	var settingId = this._importBtn.getData(Dwt.KEY_ID);
1144 	var fileInput = document.getElementById(this._attInputId);
1145 	var val = fileInput ? AjxStringUtil.trim(fileInput.value) : null;
1146 
1147 	if (val) {
1148 		var dialog = appCtxt.getChooseFolderDialog();
1149 		dialog.reset();
1150 		dialog.setTitle(ZmMsg._import);
1151 		dialog.registerCallback(DwtDialog.OK_BUTTON, this._importOkCallback, this, dialog);
1152 
1153 		var overviewId = [this.toString(), settingId].join("-");
1154 		if (settingId == ZmSetting.IMPORT) {
1155 			AjxDispatcher.require(["ContactsCore", "Contacts"]);
1156 			var noNew = !appCtxt.get(ZmSetting.NEW_ADDR_BOOK_ENABLED);
1157 			var omit = {};
1158 			omit[ZmFolder.ID_TRASH] = true;
1159 			dialog.popup({treeIds:[ZmOrganizer.ADDRBOOK], title:ZmMsg.chooseAddrBook, overviewId: overviewId,
1160 						  description:ZmMsg.chooseAddrBookToImport, skipReadOnly:true, hideNewButton:noNew, omit:omit});
1161 		} else {
1162 			AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
1163 			dialog.popup({treeIds:[ZmOrganizer.CALENDAR], title:ZmMsg.chooseCalendar, overviewId: overviewId, description:ZmMsg.chooseCalendarToImport, skipReadOnly:true});
1164 		}
1165 	}
1166 	else {
1167 		var params = {
1168 			msg:	ZmMsg.importErrorMissingFile,
1169 			level:	ZmStatusView.LEVEL_CRITICAL
1170 		};
1171 		appCtxt.setStatusMsg(params);
1172 	}
1173 };
1174 
1175 ZmPreferencesPage.prototype._importOkCallback =
1176 function(dialog, folder) {
1177 	var rootId = ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT);
1178 	if (folder && folder.id && folder.id != rootId) {
1179 		dialog.popdown();
1180 		this._importBtn.setEnabled(false);
1181 
1182 		var callback = new AjxCallback(this, this._importDoneCallback, folder.id);
1183 		var um = appCtxt.getUploadManager();
1184 		window._uploadManager = um;
1185 		try {
1186 			um.execute(callback, document.getElementById(this._uploadFormId));
1187 		} catch (ex) {
1188 			if (ex.msg) {
1189 				var d = appCtxt.getMsgDialog();
1190 				d.setMessage(ex.msg, DwtMessageDialog.CRITICAL_STYLE);
1191 				d.popup();
1192 			}
1193 
1194 			this._importBtn.setEnabled(true);
1195 			return true;
1196 		}
1197 	}
1198 };
1199 
1200 ZmPreferencesPage.prototype._importDoneCallback =
1201 function(folderId, status, aid) {
1202 	var appCtlr = appCtxt.getAppController();
1203 	var settingId = this._importBtn.getData(Dwt.KEY_ID);
1204 
1205 	if (status == 200) {
1206 		appCtlr.setStatusMsg(ZmMsg.importingContacts);
1207 
1208 		// send the import request w/ the att Id to the server per import setting
1209 		if (settingId == ZmSetting.IMPORT)
1210 		{
1211 			var soapDoc = AjxSoapDoc.create("ImportContactsRequest", "urn:zimbraMail");
1212 			var method = soapDoc.getMethod();
1213 			method.setAttribute("ct", "csv"); // always "csv" for now
1214 			method.setAttribute("l", folderId);
1215 			var content = soapDoc.set("content", "");
1216 			content.setAttribute("aid", aid);
1217 		} else {
1218 			var soapDoc = AjxSoapDoc.create("ImportAppointmentsRequest", "urn:zimbraMail");
1219 			var method = soapDoc.getMethod();
1220 			method.setAttribute("ct", "ics");
1221 			method.setAttribute("l", folderId);
1222 			var content = soapDoc.set("content", "");
1223 			content.setAttribute("aid", aid);
1224 		}
1225 		var respCallback = new AjxCallback(this, this._handleResponseFinishImport, [aid, settingId]);
1226 		var errorCallback = new AjxCallback(this, this._handleErrorFinishImport);
1227 		appCtxt.getAppController().sendRequest({soapDoc:soapDoc, asyncMode:true,
1228 													  callback:respCallback, errorCallback:errorCallback,
1229 													  timeout:ZmPreferencesPage.IMPORT_TIMEOUT});
1230 	} else {
1231 		var msg = (status == AjxPost.SC_NO_CONTENT)
1232 			? ZmMsg.errorImportNoContent
1233 			: (AjxMessageFormat.format(ZmMsg.errorImportStatus, status));
1234         appCtlr.setStatusMsg(msg, ZmStatusView.LEVEL_CRITICAL);
1235 		this._importBtn.setEnabled(true);
1236 	}
1237 };
1238 
1239 ZmPreferencesPage.prototype._handleResponseFinishImport =
1240 function(aid, settingId, result) {
1241 	var msg;
1242 	if (settingId == ZmSetting.IMPORT) {
1243 		var resp = result.getResponse().ImportContactsResponse.cn[0];
1244 		msg = AjxMessageFormat.format(ZmMsg.contactsImportedResult, Number(resp.n));
1245 	} else {
1246 		var resp = result.getResponse().ImportAppointmentsResponse.appt[0];
1247 		msg = AjxMessageFormat.format(ZmMsg.apptsImportedResult, Number(resp.n));
1248 	}
1249 	appCtxt.getAppController().setStatusMsg(msg);
1250 	this._importBtn.setEnabled(true);
1251 };
1252 
1253 ZmPreferencesPage.prototype._handleErrorFinishImport =
1254 function(ex) {
1255 	this._importBtn.setEnabled(true);
1256 
1257 	if (ex.code == ZmCsfeException.MAIL_UNABLE_TO_IMPORT_CONTACTS ||
1258 		ex.code == ZmCsfeException.MAIL_UNABLE_TO_IMPORT_APPOINTMENTS)
1259 	{
1260 		var errDialog = appCtxt.getErrorDialog();
1261 		errDialog.setMessage(ex.getErrorMsg(), ex.msg, DwtMessageDialog.WARNING_STYLE);
1262 		errDialog.popup();
1263 		return true;
1264 	}
1265 	return false;
1266 };
1267 
1268 ZmPreferencesPage.prototype._exportOkCallback =
1269 function(dialog, format, subFormat, folder) {
1270 	var rootId = ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT);
1271 	if (folder && folder.id && folder.id != rootId) {
1272 		var portPrefix = (location.port == "" || location.port == "80")
1273 			? ""
1274 			: (":" + location.port);
1275 		var folderName = folder._systemName || AjxStringUtil.urlEncode(folder.getPath());
1276 		var username = appCtxt.multiAccounts ? (AjxStringUtil.urlComponentEncode(appCtxt.get(ZmSetting.USERNAME))) : "~";
1277 		var uri = [
1278 			location.protocol, "//", location.hostname, portPrefix, "/service/home/",
1279 			username, "/", folderName,
1280 			"?auth=co&fmt=", format,
1281 			subFormat ? "&"+format+"fmt="+subFormat : "" // e.g. csvfmt=zimbra-csv
1282 		].join("");
1283 		window.open(uri, "_blank");
1284 
1285 		dialog.popdown();
1286 	}
1287 };
1288 
1289 // Reset the form values to the pref defaults. Note that the pref defaults aren't the
1290 // values that the user last had, they're the values that the prefs have before the
1291 // user ever touches them.
1292 ZmPreferencesPage.prototype._resetListener =
1293 function(ev) {
1294 	this.reset(true);
1295 	appCtxt.setStatusMsg(ZmMsg.defaultsRestored);
1296 };
1297 
1298 /**
1299  * Returns true if any of the specified prefs are enabled (or have no preconditions).
1300  */
1301 ZmPreferencesPage.prototype._isEnabled = function(prefId1 /* ..., prefIdN */) {
1302 
1303 	for (var i = 0; i < arguments.length; i++) {
1304 		var prefId = arguments[i];
1305 
1306 		// setting not created (its app is disabled)
1307 		if (!prefId) { return false; }
1308 
1309 		if (!appCtxt.getActiveAccount().isMain && ZmSetting.IS_GLOBAL[prefId]) {
1310 			return false;
1311 		}
1312 
1313 		var setup = ZmPref.SETUP[prefId],
1314 			prefPrecondition = setup && setup.precondition;
1315 		if (appCtxt.checkPrecondition(prefPrecondition, prefPrecondition && setup.preconditionAny)) {
1316 			return true;
1317 		}
1318 	}
1319 	return false;
1320 };
1321 
1322 ZmPreferencesPage.prototype._expandField =
1323 function(data, prefId) {
1324 	var templateId = this._section.templateId.replace(/#.*$/, "#"+prefId);
1325 	return AjxTemplate.expand(templateId, data);
1326 };
1327 
1328 ZmPreferencesPage.prototype._showComposeDirection =
1329 function(localeId) {
1330     var button = this._dwtObjects[ZmSetting.COMPOSE_INIT_DIRECTION];
1331     var checkbox = this._dwtObjects[ZmSetting.SHOW_COMPOSE_DIRECTION_BUTTONS];
1332     if ( ZmLocale.RTLLANGUAGES.hasOwnProperty(localeId) ){
1333         button.setSelectedValue(ZmSetting.RTL);
1334         checkbox.setSelected(true);
1335     }
1336     else {
1337         button.setSelectedValue(ZmSetting.LTR);
1338         checkbox.setSelected(false);
1339     }
1340 };
1341 //
1342 // Private functions
1343 //
1344 
1345 /**
1346  * Formats a label. If the label contains a replacement parameter (e.g. {0}),
1347  * then it is formatted using AjxMessageFormat with the current value for this
1348  * label.
1349  *
1350  * @private
1351  */
1352 ZmPreferencesPage.__formatLabel =
1353 function(prefLabel, prefValue) {
1354 	prefLabel = prefLabel || "";
1355 	return prefLabel.match(/\{/) ? AjxMessageFormat.format(prefLabel, prefValue) : prefLabel;
1356 };
1357 
1358 ZmPreferencesPage.fontMap = {};
1359 
1360 ZmPreferencesPage._createMenuItem =
1361 function(id, name, itemMap) {
1362 	return itemMap[id] = {id: id, name: name};
1363 };
1364 
1365 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_SYSTEM, ZmMsg.fontSystem, ZmPreferencesPage.fontMap);
1366 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_MODERN, ZmMsg.fontModern, ZmPreferencesPage.fontMap);
1367 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_CLASSIC, ZmMsg.fontClassic, ZmPreferencesPage.fontMap);
1368 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_WIDE, ZmMsg.fontWide, ZmPreferencesPage.fontMap);
1369 
1370 ZmPreferencesPage.fontSizeMap = {};
1371 
1372 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_SIZE_SMALL, ZmMsg.fontSizeSmall, ZmPreferencesPage.fontSizeMap);
1373 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_SIZE_NORMAL, ZmMsg.fontSizeNormal, ZmPreferencesPage.fontSizeMap);
1374 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_SIZE_LARGE, ZmMsg.fontSizeLarge, ZmPreferencesPage.fontSizeMap);
1375 ZmPreferencesPage._createMenuItem(ZmSetting.FONT_SIZE_LARGER, ZmMsg.fontSizeExtraLarge, ZmPreferencesPage.fontSizeMap);
1376