1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  *
 27  * This file defines a Zimbra Application class.
 28  *
 29  */
 30 
 31 /**
 32  * Creates the application.
 33  * @class
 34  * This object represents a Zimbra Application. This class is a base class for application classes.
 35  * "App" is a useful abstraction for a set of related functionality, such as mail,
 36  * address book, or calendar. Looked at another way, an app is a collection of one or more controllers.
 37  * 
 38  * @param	{String}	name		the application name
 39  * @param	{DwtControl}	container	the control that contains components
 40  * @param	{ZmController}	parentController	the parent window controller (set by the child window)
 41  *
 42  */
 43 ZmApp = function(name, container, parentController) {
 44 
 45 	if (arguments.length == 0) return;
 46 	
 47 	this._name = name;
 48 	this._container = container;
 49 	this._parentController = parentController;
 50 	this._active = false;
 51 	this.currentSearch = null;
 52     this._defaultFolderId = null;   //reqd in case of external user
 53 
 54 	this._deferredFolders = [];
 55 	this._deferredFolderHash = {};
 56 	this._deferredNotifications = [];
 57 
 58 	this._sessionController		= {};
 59 	this._nextSessionId			= {};
 60 	this._curSessionId			= {};
 61 
 62 	ZmApp.DROP_TARGETS[name] = {};
 63 
 64 	this._defineAPI();
 65 	if (!parentController) {
 66 		this._registerSettings();
 67 	}
 68 	this._registerOperations();
 69 	this._registerItems();
 70 	this._registerOrganizers();
 71 	if (!parentController) {
 72 		this._setupSearchToolbar();
 73 	}
 74 	this._registerApp();
 75 
 76 };
 77 
 78 // app information ("_R" means "reverse map")
 79 
 80 // these are needed statically (before we get user settings)
 81 ZmApp.CLASS					= {};	// constructor for app class
 82 ZmApp.SETTING				= {};	// ID of setting that's true when app is enabled
 83 ZmApp.UPSELL_SETTING		= {};	// ID of setting that's true when app upsell is enabled
 84 ZmApp.LOAD_SORT				= {};	// controls order in which apps are instantiated
 85 ZmApp.BUTTON_ID				= {};	// ID for app button on app chooser toolbar
 86 
 87 // these are set via registerApp() in app constructor
 88 ZmApp.MAIN_PKG				= {};	// main package that composes the app
 89 ZmApp.NAME					= {};	// msg key for app name
 90 ZmApp.ICON					= {};	// name of app icon class
 91 ZmApp.TEXT_PRECEDENCE		= {};	// order for removing button text
 92 ZmApp.IMAGE_PRECEDENCE		= {};	// order for removing button image
 93 ZmApp.QS_ARG				= {};	// arg for 'app' var in QS to jump to app
 94 ZmApp.QS_ARG_R				= {};
 95 ZmApp.CHOOSER_TOOLTIP		= {};	// msg key for app view menu tooltip
 96 ZmApp.VIEW_TOOLTIP			= {};	// msg key for app tooltip
 97 ZmApp.DEFAULT_SEARCH		= {};	// type of item to search for in the app
 98 ZmApp.ORGANIZER				= {};	// main organizer for this app
 99 ZmApp.OVERVIEW_TREES		= {};	// list of tree IDs to show in overview
100 ZmApp.HIDE_ZIMLETS			= {};	// whether to show Zimlet tree in overview
101 ZmApp.SEARCH_TYPES			= {};	// list of types of saved searches to show in overview
102 ZmApp.SEARCH_TYPES_R		= {};
103 ZmApp.GOTO_ACTION_CODE		= {};	// key action for jumping to this app
104 ZmApp.GOTO_ACTION_CODE_R	= {};
105 ZmApp.NEW_ACTION_CODE		= {};	// default "new" key action
106 ZmApp.ACTION_CODES			= {};	// key actions that map to ops
107 ZmApp.ACTION_CODES_R		= {};
108 ZmApp.OPS					= {};	// IDs of operations for the app
109 ZmApp.OPS_R					= {};	// map of operation ID to app
110 ZmApp.QS_VIEWS				= {};	// list of views to handle in query string
111 ZmApp.TRASH_VIEW_OP			= {};	// menu choice for "Show Only ..." in Trash view
112 ZmApp.UPSELL_URL			= {};	// URL for content of upsell
113 //ZmApp.QUICK_COMMAND_TYPE	= {};
114 ZmApp.DROP_TARGETS			= {};	// drop targets (organizers) by item/organizer type
115 ZmApp.SEARCH_RESULTS_TAB	= {};	// whether to show search results in a tab
116 
117 // indexes to control order of appearance/action
118 ZmApp.CHOOSER_SORT			= {};	// controls order of apps in app chooser toolbar
119 ZmApp.DEFAULT_SORT			= {};	// controls order in which app is chosen as default start app
120 
121 ZmApp.ENABLED_APPS			= {};	// hash for quick detection if app is enabled
122 
123 // ordered lists of apps
124 ZmApp.APPS					= [];	// ordered list
125 ZmApp.DEFAULT_APPS			= [];	// ordered list
126 
127 ZmApp.OVERVIEW_ID			= "main";	// ID for main overview
128 
129 ZmApp.BATCH_NOTIF_LIMIT = 25;	// threshold for doing batched change notifications
130 
131 ZmApp.MAIN_SESSION			= "main";
132 ZmApp.HIDDEN_SESSION		= "hidden";
133 
134 /**
135  * Initializes the application.
136  *
137  * @private
138  */
139 ZmApp.initialize =
140 function() {
141 	if (appCtxt.get(ZmSetting.USE_KEYBOARD_SHORTCUTS)) {
142 		ZmApp.ACTION_CODES[ZmKeyMap.NEW_FOLDER]	= ZmOperation.NEW_FOLDER;
143 		ZmApp.ACTION_CODES[ZmKeyMap.NEW_TAG]	= ZmOperation.NEW_TAG;
144 	}
145 };
146 
147 /**
148  * Registers and stores information about an app. Note: Setting a value that evaluates to
149  * false (such as 0 or an empty string) will not do anything.
150  *
151  * @param {constant}	app				the app ID
152  * @param {Hash}	params			a hash of parameters
153  * @param params.mainPkg			[string]	main package that contains the app
154  * @param params.nameKey			[string]	msg key for app name
155  * @param params.icon				[string]	name of app icon class
156  * @param params.textPrecedence		[int]		order for removing button text
157  * @param params.imagePrecedence	[int]		order for removing button image
158  * @param params.chooserTooltipKey	[string]	msg key for app tooltip
159  * @param params.viewTooltipKey		[string]	msg key for app view menu tooltip
160  * @param params.defaultSearch		[constant]	type of item to search for in the app
161  * @param params.organizer			[constant]	main organizer for this app
162  * @param params.overviewTrees		[array]		list of tree IDs to show in overview
163  * @param params.hideZimlets		[boolean]	if true, hide Zimlet tree in overview
164  * @param params.searchTypes		[array]		list of types of saved searches to show in overview
165  * @param params.gotoActionCode		[constant]	key action for jumping to this app
166  * @param params.newActionCode		[constant]	default "new" action code
167  * @param params.actionCodes		[hash]		keyboard actions mapped to operations
168  * @param params.newItemOps			[hash]		IDs of operations that create a new item, and their text keys
169  * @param params.newOrgOps			[hash]		IDs of operations that create a new organizer, and their text keys
170  * @param params.qsViews			[array]		list of views to handle in query string
171  * @param params.chooserSort		[int]		controls order of apps in app chooser toolbar
172  * @param params.defaultSort		[int]		controls order in which app is chosen as default start app
173  * @param params.trashViewOp		[constant]	menu choice for "Show Only ..." in Trash view
174  * @param params.upsellUrl			[string]	URL for content of upsell
175  * @param params.searchResultsTab	[string]	if true, show search results in a tab
176  *
177  * @private
178  */
179 ZmApp.registerApp =
180 function(app, params) {
181 
182 	// TODO: why the ifs? this should only be called once per app
183 	if (params.mainPkg)				{ ZmApp.MAIN_PKG[app]			= params.mainPkg; }
184 	if (params.nameKey)				{ ZmApp.NAME[app]				= params.nameKey; }
185 	if (params.icon)				{ ZmApp.ICON[app]				= params.icon; }
186 	if (params.textPrecedence)		{ ZmApp.TEXT_PRECEDENCE[app]	= params.textPrecedence; }
187 	if (params.imagePrecedence)		{ ZmApp.IMAGE_PRECEDENCE[app]	= params.imagePrecedence; }
188 	if (params.chooserTooltipKey)	{ ZmApp.CHOOSER_TOOLTIP[app]	= params.chooserTooltipKey; }
189 	if (params.viewTooltipKey)		{ ZmApp.VIEW_TOOLTIP[app]		= params.viewTooltipKey; }
190 	if (params.defaultSearch)		{ ZmApp.DEFAULT_SEARCH[app]		= params.defaultSearch; }
191 	if (params.organizer)			{ ZmApp.ORGANIZER[app]			= params.organizer; }
192 	if (params.overviewTrees)		{ ZmApp.OVERVIEW_TREES[app]		= params.overviewTrees; }
193 	if (params.hideZimlets)			{ ZmApp.HIDE_ZIMLETS[app]		= params.hideZimlets; }
194 	if (params.searchTypes) 		{ ZmApp.SEARCH_TYPES[app]		= params.searchTypes; }
195 	if (params.gotoActionCode)		{ ZmApp.GOTO_ACTION_CODE[app]	= params.gotoActionCode; }
196 	if (params.newActionCode)		{ ZmApp.NEW_ACTION_CODE[app]	= params.newActionCode; }
197 	if (params.qsViews)				{ ZmApp.QS_VIEWS[app]			= params.qsViews; }
198 	if (params.chooserSort)			{ ZmApp.CHOOSER_SORT[app]		= params.chooserSort; }
199 	if (params.defaultSort)			{ ZmApp.DEFAULT_SORT[app]		= params.defaultSort; }
200 	if (params.trashViewOp)			{ ZmApp.TRASH_VIEW_OP[app]		= params.trashViewOp; }
201 	if (params.upsellUrl)			{ ZmApp.UPSELL_URL[app]			= params.upsellUrl; }
202 	//if (params.quickCommandType)	{ ZmApp.QUICK_COMMAND_TYPE[app]	= params.quickCommandType; }
203 	if (params.searchResultsTab)	{ ZmApp.SEARCH_RESULTS_TAB[app]	= params.searchResultsTab; }
204 
205 	if (params.searchTypes) {
206 		ZmApp.SEARCH_TYPES_R[app] = {};
207 		for (var i = 0; i < params.searchTypes.length; i++) {
208 			ZmApp.SEARCH_TYPES_R[app][params.searchTypes[i]] = true;
209 		}
210 	}
211 
212 	if (params.gotoActionCode) {
213 		ZmApp.GOTO_ACTION_CODE_R[params.gotoActionCode] = app;
214 	}
215 
216 	if (params.actionCodes) {
217 		for (var ac in params.actionCodes) {
218 			if (!ac) { continue; }
219 			ZmApp.ACTION_CODES_R[ac] = app;
220 			ZmApp.ACTION_CODES[ac] = params.actionCodes[ac];
221 		}
222 	}
223 
224     var appEnabled = appCtxt.get(ZmApp.SETTING[app]);
225 	if (params.newItemOps && appEnabled) {
226 		for (var op in params.newItemOps) {
227 			if (!op) { continue; }
228 			ZmApp.OPS_R[op] = app;
229 			ZmOperation.NEW_ITEM_OPS.push(op);
230 			ZmOperation.NEW_ITEM_KEY[op] = params.newItemOps[op];
231 		}
232 	}
233 	if (params.newOrgOps && appEnabled) {
234 		for (var op in params.newOrgOps) {
235 			if (!op) { continue; }
236 			ZmApp.OPS_R[op] = app;
237 			ZmOperation.NEW_ORG_OPS.push(op);
238 			ZmOperation.NEW_ORG_KEY[op] = params.newOrgOps[op];
239 		}
240 	}
241 
242 	if (params.qsViews) {
243 		for (var i = 0; i < params.qsViews.length; i++) {
244 			ZmApp.QS_VIEWS[params.qsViews[i]] = app;
245 		}
246 	}
247 
248     /* if (params.quickCommandType) {
249         ZmQuickCommand.itemTypes.push(params.quickCommandType);
250     } */
251 };
252 
253 
254 /**
255  * Runs the given function for all known (e.g. part of ZmApp.CLASS)
256  * app classes, passing args.
257  * NOTE: This runs class functions only, not instance (prototype) functions.
258  * @static
259  * @param funcName {String} The name of the function we will run on each
260  * application.
261  * @param mixed {mixed} 0 to n additional arguments are passed to funcName
262  * via apply.
263  */
264 ZmApp.runAppFunction =
265 function(funcName) {
266     var args;
267 
268     for (var appName in ZmApp.CLASS) {
269         var app = window[ZmApp.CLASS[appName]];
270         var func = app && app[funcName];
271         if (func && (typeof(func) == "function")) {
272             args = args || Array.prototype.slice.call(arguments, 1);
273             func.apply(app, args);
274         }
275     }
276 };
277 
278 
279 // Public instance methods
280 
281 /**
282  * Returns a string representation of the object.
283  *
284  * @return		{String}		a string representation of the object
285  */
286 ZmApp.prototype.toString =
287 function() {
288 	return "ZmApp";
289 };
290 
291 // Functions called during construction
292 ZmApp.prototype._defineAPI				= function() {};
293 ZmApp.prototype._registerSettings		= function() {};
294 ZmApp.prototype._registerOperations		= function() {};
295 ZmApp.prototype._registerItems			= function() {};
296 ZmApp.prototype._registerOrganizers		= function() {};
297 ZmApp.prototype._setupSearchToolbar		= function() {};
298 ZmApp.prototype._registerApp			= function() {};
299 ZmApp.prototype._registerPrefs			= function() {};						// called when Preferences pkg is loaded
300 
301 // Functions that apps can override in response to certain events
302 ZmApp.prototype.startup					= function(result) {};					// run during startup
303 ZmApp.prototype.preNotify				= function(notify) {};					// run before handling notifications
304 ZmApp.prototype.deleteNotify			= function(ids) {};						// run on delete notifications
305 ZmApp.prototype.createNotify			= function(list) {};					// run on create notifications
306 ZmApp.prototype.modifyNotify			= function(list) {};					// run on modify notifications
307 ZmApp.prototype.postNotify				= function(notify) {};					// run after handling notifications
308 ZmApp.prototype.refresh					= function(refresh) {};					// run when a <refresh> block arrives
309 ZmApp.prototype.handleOp				= function(op, params) {};				// handle an operation
310 
311 /**
312  * Gets the application name.
313  *
314  * @return	{String}	the name
315  */
316 ZmApp.prototype.getName =
317 function() {
318 	return this._name;
319 };
320 
321 /**
322  * Gets the application display name.
323  *
324  * @return	{String}	the display name
325  */
326 ZmApp.prototype.getDisplayName =
327 function() {
328 	return ZmMsg[ZmApp.NAME[this._name]] || ZmApp.NAME[this._name];
329 };
330 
331 /**
332  * Gets the initial search type.
333  *
334  * @return	{Object}	<code>null</code> since only set if different from the default
335  */
336 ZmApp.prototype.getInitialSearchType =
337 function() {
338 	return null;
339 };
340 
341 /**
342  * Gets the limit for the search triggered by the application launch or an overview click.
343  *
344  * @return	{int}	the limit
345  */
346 ZmApp.prototype.getLimit =
347 function(offset) {
348 	return appCtxt.get(ZmSetting.PAGE_SIZE);
349 };
350 
351 /**
352  * Sets the application view.
353  *
354  * @param		{String}	view		the view
355  * @see		ZmAppViewMgr
356  */
357 ZmApp.prototype.setAppView =
358 function(view) {
359 	appCtxt.getAppViewMgr().setAppView(this._name, view);
360 };
361 
362 /**
363  * Creates the application view.
364  *
365  * @param		{Hash}	params		a hash of parameters
366  * @see		ZmAppViewMgr
367  * @see		ZmAppViewMgr#createView
368  */
369 ZmApp.prototype.createView =
370 function(params) {
371 	params.appName = this._name;
372 	return appCtxt.getAppViewMgr().createView(params);
373 };
374 
375 /**
376  * Pushes the application view.
377  *
378  * @param	{String}	name	the view name
379  * @param	{Boolean}	force	<code>true</code> to force the view onto the stack
380  * @see		ZmAppViewMgr#pushView
381  */
382 ZmApp.prototype.pushView =
383 function(name, force) {
384 	return appCtxt.getAppViewMgr().pushView(name, force);
385 };
386 
387 /**
388  * Pops the application view.
389  *
390  * @param	{Boolean}	force	<code>true</code> to force the view off the stack
391  * @see		ZmAppViewMgr#popView
392  */
393 ZmApp.prototype.popView =
394 function(force, viewId, skipHistory) {
395 	return appCtxt.getAppViewMgr().popView(force, viewId, skipHistory);
396 };
397 
398 /**
399  * Sets the application view.
400  *
401  * @param	{String}	name	the view name
402  * @param	{Boolean}	force	<code>true</code> to force the view
403  * @see		ZmAppViewMgr#setView
404  */
405 ZmApp.prototype.setView =
406 function(name, force) {
407 	return appCtxt.getAppViewMgr().setView(name, force);
408 };
409 
410 /**
411  * Stages the application view.
412  *
413  * @param	{String}	name	the view name
414  * @see		ZmAppViewMgr#stageView
415  */
416 ZmApp.prototype.stageView =
417 function(name) {
418 	return appCtxt.getAppViewMgr().stageView(name);
419 };
420 
421 /**
422  * Adds a deferred folder.
423  *
424  * @param	{Hash}	params		a hash of parameters
425  */
426 ZmApp.prototype.addDeferredFolder =
427 function(params) {
428 	var id = params.obj && params.obj.id;
429 	if (id && !this._deferredFolderHash[id]) {
430 		this._deferredFolders.push(params);
431 		this._deferredFolderHash[id] = true;
432 		appCtxt.cacheSetDeferred(id, this._name);
433 	}
434 };
435 
436 /**
437  * Gets the remote folder ids.
438  *
439  * @param	{Object}	account		the account
440  * @return	{Array}		an array of {String} ids
441  */
442 ZmApp.prototype.getRemoteFolderIds =
443 function(account) {
444 	// XXX: optimize by caching this list? It would have to be cleared anytime
445 	// folder structure changes
446 	var list = [];
447 	if (appCtxt.getOverviewController(true)) {
448 		var type = ZmApp.ORGANIZER[this.getName()];
449 
450 		// first, make sure there aren't any deferred folders that need to be created
451 		if (this._deferredFolders.length) {
452 			this._createDeferredFolders(type);
453 		}
454 
455 		var tree = appCtxt.getFolderTree(account);
456 		var folders = tree ? tree.getByType(type) : [];
457 		for (var i = 0; i < folders.length; i++) {
458 			var folder = folders[i];
459 			if (folder.isRemote()) {
460 				list.push(folder.id);
461 			}
462 		}
463 	}
464 	return list;
465 };
466 
467 /**
468  * Creates the overview content for this app. The default implementation creates
469  * a {@link ZmOverview} with standard options. Other apps may want to use different
470  * options, or create a {@link DwtComposite} instead.
471  *
472  * @return	{String}	the content
473  */
474 ZmApp.prototype.getOverviewPanelContent =
475 function() {
476 	if (!this._overviewPanelContent) {
477 		var params = this._getOverviewParams();
478 		params.overviewId = this.getOverviewId();
479 		var ov = this._overviewPanelContent = appCtxt.getOverviewController().createOverview(params);
480 		ov.set(this._getOverviewTrees());
481 	}
482 
483 	return this._overviewPanelContent;
484 };
485 
486 
487 
488 /**
489  * Gets the overview container.
490  *
491  * @return	{ZmOverview}		the overview container
492  */
493 ZmApp.prototype.getOverviewContainer =
494 function(dontCreate) {
495 	if (!this._overviewContainer && !dontCreate) {
496 		var containerParams = {
497 			appName: this._name,
498 			containerId: ([ZmApp.OVERVIEW_ID, this._name].join("_")),
499 			posStyle: Dwt.ABSOLUTE_STYLE
500 		};
501 		var overviewParams = this._getOverviewParams();
502 		overviewParams.overviewTrees = this._getOverviewTrees();
503 
504 		this._overviewContainer = appCtxt.getOverviewController().createOverviewContainer(containerParams, overviewParams);
505 	}
506 
507 	return this._overviewContainer;
508 };
509 
510 /**
511  * Sets the overview tree to display overview content for this application.
512  *
513  * @param {Boolean}	reset		if <code>true</code>, clear the content first
514  */
515 ZmApp.prototype.setOverviewPanelContent =
516 function(reset) {
517 	if (reset) {
518 		this._overviewPanelContent = null;
519 		this._overviewContainer = null;
520 	}
521 
522 	// only set overview panel content if not in full screen mode
523 	var avm = appCtxt.getAppViewMgr();
524 	if (!avm.isFullScreen()) {
525 		Dwt.setLoadingTime(this.toString() + "-overviewPanel");
526 		var ov = ((appCtxt.multiAccounts && appCtxt.accountList.size() > 1) || this.getName() == ZmApp.VOICE)
527 			? this.getOverviewContainer()
528 			: this.getOverviewPanelContent();
529 		var components = {};
530 		components[ZmAppViewMgr.C_TREE] = ov;
531 		avm.setViewComponents(ZmAppViewMgr.APP, components, true, this.getName());
532 		Dwt.setLoadedTime(this.toString() + "-overviewPanel");
533 	}
534 };
535 
536 /**
537  * Gets the current overview, if any. Subclasses should ensure that a {@link ZmOverview} is returned.
538  *
539  * @return	{ZmOverview}	the overview
540  */
541 ZmApp.prototype.getOverview =
542 function() {
543 	var opc = appCtxt.getOverviewController();
544 	return opc && opc.getOverview(this.getOverviewId());
545 };
546 
547 /**
548  * Resets the current overview, preserving expansion.
549  *
550  * @param {String}		overviewId	the id of overview to reset
551  */
552 ZmApp.prototype.resetOverview =
553 function(overviewId) {
554 	var overview = overviewId ? appCtxt.getOverviewController().getOverview(overviewId) : this.getOverview();
555 	if (overview) {
556 		var expIds = [];
557 		var treeIds = overview.getTreeViews(), len = treeIds.length;
558 		for (var i = 0; i < len; i++) {
559 			var treeId = treeIds[i];
560 			var treeView = overview.getTreeView(treeId);
561 			if (treeView) {
562 				var items = treeView.getTreeItemList();
563 				var len1 = items.length;
564 				for (var j = 0; j < len1; j++) {
565 					var treeItem = items[j];
566 					if (treeItem._expanded) {
567 						expIds.push(treeItem._htmlElId);
568 					}
569 				}
570 			}
571 		}
572 		overview.clear();
573 		overview.set(this._getOverviewTrees());
574 		len = expIds.length;
575 		for (var i = 0; i < len; i++) {
576 			var treeItem = DwtControl.fromElementId(expIds[i]);
577 			if (treeItem && !treeItem._expanded) {
578 				treeItem.setExpanded(true);
579 			}
580 		}
581 	}
582 };
583 
584 /**
585  * Gets the overview id of the current {@link ZmOverview}, if any.
586  *
587  * @param	{ZmZimbraAccount}	account		the account
588  * @return	{String}	the id
589  */
590 ZmApp.prototype.getOverviewId =
591 function(account) {
592 	return appCtxt.getOverviewId([ZmApp.OVERVIEW_ID, this._name], account);
593 };
594 
595 /**
596  * Returns a hash of params with the standard overview options.
597  *
598  * @private
599  */
600 ZmApp.prototype._getOverviewParams =
601 function() {
602 	// Get the sorted list of overview trees.
603 	var treeIds = [];
604 	for (var id in ZmOverviewController.CONTROLLER) {
605 		treeIds.push(id);
606 	}
607 	var sortFunc = function(a, b) {
608 		return (ZmOrganizer.DISPLAY_ORDER[a] || 9999) - (ZmOrganizer.DISPLAY_ORDER[b] || 9999);
609 	};
610 	treeIds.sort(sortFunc);
611 
612 	return {
613 		posStyle:			Dwt.ABSOLUTE_STYLE,
614 		selectionSupported:	true,
615 		actionSupported:	true,
616 		dndSupported:		true,
617 		showUnread:			true,
618 		showNewButtons:		true,
619 		isAppOverview:		true,
620 		treeIds:			treeIds,
621 		appName:			this._name,
622 		account:			appCtxt.getActiveAccount()
623 	};
624 };
625 
626 /**
627  * Returns the list of trees to show in the overview for this app. Don't show
628  * Folders unless mail is enabled. Other organizer types won't be created unless
629  * their apps are enabled, so we don't need to check for them.
630  *
631  * @private
632  */
633 ZmApp.prototype._getOverviewTrees =
634 function() {
635 	var list = ZmApp.OVERVIEW_TREES[this._name] || [];
636 	var newList = [];
637 	for (var i = 0, count = list.length; i < count; i++) {
638 		if ((list[i] == ZmOrganizer.FOLDER && !appCtxt.get(ZmSetting.MAIL_ENABLED))) {
639 			continue;
640 		}
641 		newList.push(list[i]);
642 	}
643 
644 	if (!appCtxt.multiAccounts &&
645 		window[ZmOverviewController.CONTROLLER[ZmOrganizer.ZIMLET]] &&
646 		!ZmApp.HIDE_ZIMLETS[this._name])
647 	{
648 		newList.push(ZmOrganizer.ZIMLET);
649 	}
650 	return newList;
651 };
652 
653 /**
654  * Gets the number of active session controllers
655  *
656  * @return	{number} the number of active session controllers
657  */
658 ZmApp.prototype.getNumSessionControllers =
659 function(type) {
660     var controllers = this._sessionController[type] || [];
661     var activeCount = 0;
662     for (var id in controllers) {
663         if (!controllers[id].inactive) {
664             activeCount++;
665         }
666     }
667     return activeCount;
668 };
669 
670 /**
671  * Evaluates the controller class and returns the default view type from that controller.
672  *
673  * @param	{hash}							params						hash of params:
674  * @param	{string}						controllerClass				string name of controller class
675  * @param	{string}						sessionId					unique identifier for this controller
676  * @param 	{ZmSearchResultsController}		searchResultsController		containing controller
677  *
678  * @returns	{string}													default view type
679  */
680 ZmApp.prototype.getTypeFromController =
681 function(controllerClass) {
682 	var controller = eval(controllerClass);
683 	if (!controller.getDefaultViewType) {
684 		throw new AjxException("Session controller " + controllerClass + " must implement getDefaultViewType()");
685 	}
686 	return controller.getDefaultViewType();
687 };
688 
689 /**
690  * Returns a controller of the given type and class. If no sessionId is provided, then
691  * the controller's session ID will be an incremental number. If a sessionId is given,
692  * then a check is made for an existing controller with that session ID. If none is
693  * found, one is created and given that session ID.
694  * 
695  * @param	{hash}							params						hash of params:
696  * @param	{string}						controllerClass				string name of controller class
697  * @param	{string}						sessionId					unique identifier for this controller
698  * @param 	{ZmSearchResultsController}		searchResultsController		containing controller
699  */
700 ZmApp.prototype.getSessionController =
701 function(params) {
702 
703 	var type = this.getTypeFromController(params.controllerClass);
704 
705 	// track controllers of this type
706 	if (!this._sessionController[type]) {
707 		this._sessionController[type] = {};
708 		this._nextSessionId[type] = 1;
709 	}
710 
711 	// check if we've already created a session controller with the given ID
712 	var sessionId = params.sessionId;
713 	if (sessionId && this._sessionController[type][sessionId]) {
714 		return this._sessionController[type][sessionId];
715 	}
716 
717 	// re-use an inactive controller if possible
718 	var controller;
719 	if (!sessionId) {
720 		var controllers = this._sessionController[type];
721 		for (var id in controllers) {
722 			if (controllers[id].inactive && !controllers[id].isPinned && !controllers[id].isHidden) {
723 				controller = controllers[id];
724 				break;
725 			}
726 		}
727 	}
728 
729 	sessionId = (controller && controller.getSessionId()) || sessionId || String(this._nextSessionId[type]++);
730 
731 	if (!controller) {
732 		var ctlrClass = eval(params.controllerClass);
733 		controller = this._sessionController[type][sessionId] =
734 			new ctlrClass(this._container, this, type, sessionId, params.searchResultsController);
735 	}
736 	this._curSessionId[type] = sessionId;
737 	controller.inactive = false;
738 
739 	return controller;
740 };
741 
742 /**
743  * Deletes a controller of the given type, class, and sessionId.
744  *
745  * @param	{hash}							params						hash of params:
746  * @param	{string}						controllerClass				string name of controller class
747  * @param	{string}						sessionId					unique identifier for this controller
748  * @param 	{ZmSearchResultsController}		searchResultsController		containing controller
749  */
750 ZmApp.prototype.deleteSessionController =
751 function(params) {
752 	var type		= this.getTypeFromController(params.controllerClass);
753 	var sessionId	= params.sessionId;
754 
755 	if (!this._sessionController[type]) {
756 		return;
757 	}
758 	delete this._sessionController[type][sessionId];
759 };
760 
761 // returns the session ID of the most recently retrieved controller of the given type
762 ZmApp.prototype.getCurrentSessionId =
763 function(type) {
764 	return this._curSessionId[type];
765 };
766 
767 // returns a list of this app's controllers
768 ZmApp.prototype.getAllControllers =
769 function() {
770 
771 	var controllers = [];
772 	for (var viewType in this._sessionController) {
773 		var viewHash = this._sessionController[viewType];
774 		if (viewHash) {
775 			for (var viewId in viewHash) {
776 				var ctlr = viewHash[viewId];
777 				if (ctlr) {
778 					controllers.push(ctlr);
779 				}
780 			}
781 		}
782 	}
783 	
784 	return controllers;
785 };
786 
787 /**
788  * @private
789  */
790 ZmApp.prototype._addSettingsChangeListeners =
791 function() {
792 	if (!this._settingListener) {
793 		this._settingListener = new AjxListener(this, this._settingChangeListener);
794 	}
795 };
796 
797 /**
798  * @private
799  */
800 ZmApp.prototype._settingChangeListener =
801 function(ev) {
802 
803 };
804 
805 // Returns a hash of properties for the New Button
806 ZmApp.prototype.getNewButtonProps =
807 function() {
808 	return {};
809 };
810 
811 /**
812  * Gets the search parameters.
813  *
814  * @param {Hash}	params	a hash of arguments for the search
815  * @see		ZmSearchController
816  */
817 ZmApp.prototype.getSearchParams =
818 function(params) {
819 	return (params || {});
820 };
821 
822 /**
823  * Default function to run after an app's core package has been loaded. It assumes that the
824  * classes that define items and organizers for this app are in the core package.
825  *
826  * @private
827  */
828 ZmApp.prototype._postLoadCore =
829 function() {
830 	if (!appCtxt.isChildWindow) {
831 		this._setupDropTargets();
832 	}
833 };
834 
835 /**
836  * Default function to run after an app's main package has been loaded.
837  *
838  * @private
839  */
840 ZmApp.prototype._postLoad =
841 function(type) {
842 	if (type) {
843 		this._createDeferredFolders(type);
844 	}
845 	this._handleDeferredNotifications();
846     if(appCtxt.isExternalAccount()) {
847         this._handleExternalAccountSettings(type);
848     }
849 };
850 
851 
852 ZmApp.prototype.containsWritableFolder =
853 function() {
854     return appCtxt.isExternalAccount() ? (this._containsWritableFolder ? true : false) : true;
855 };
856 
857 ZmApp.prototype.getDefaultFolderId =
858 function() {
859     return this._defaultFolderId;
860 };
861 
862 /**
863  * @private
864  */
865 ZmApp.prototype._handleExternalAccountSettings =
866 function(type) {
867     //Handle the external account settings
868     var dataTree = appCtxt.getTree(type, appCtxt.getActiveAccount()),
869         folders = dataTree ? dataTree.getByType(type) : [],
870         len = folders.length,
871         folder,
872         i;
873     this._containsWritableFolder = false;
874     for (i=0; i<len; i++) {
875         folder = folders[i];
876         if(!this._defaultFolderId) { this._defaultFolderId = folder.id; }
877         if (folder.isPermAllowed(ZmOrganizer.PERM_WRITE)) {
878             this._containsWritableFolder = true;
879         }
880     }
881 };
882 
883 /**
884  * @private
885  */
886 ZmApp.prototype._setupDropTargets =
887 function() {
888 	var appTargets = ZmApp.DROP_TARGETS[this._name];
889 	for (var type in appTargets) {
890 		var targets = appTargets[type];
891 		for (var i = 0; i < targets.length; i++) {
892 			var orgType = targets[i];
893 			var ctlr = appCtxt.getOverviewController().getTreeController(orgType, true);
894 			var className = ZmList.ITEM_CLASS[type] || ZmOrganizer.ORG_CLASS[type];
895 			if (ctlr) {
896 				ctlr._dropTgt.addTransferType(className);
897 			} else {
898 				if (!ZmTreeController.DROP_SOURCES[orgType]) {
899 					ZmTreeController.DROP_SOURCES[orgType] = [];
900 				}
901 				ZmTreeController.DROP_SOURCES[orgType].push(className);
902 			}
903 		}
904 	}
905 };
906 
907 /**
908  * Disposes of the tree controllers (right now mainly gets rid of change listeners.
909  */
910 ZmApp.prototype.disposeTreeControllers =
911 function() {
912 
913 	var overviewController = appCtxt.getOverviewController(true); //see if overview controller was created (false param means it won't create it if not created)
914 	//this is created lazily in case of child window. There's nothing to do if it was not created.
915 	if (!overviewController) {
916 		return;
917 	}
918 
919 	var appTargets = ZmApp.DROP_TARGETS[this._name];
920 	for (var type in appTargets) {
921 		var targets = appTargets[type];
922 		for (var i = 0; i < targets.length; i++) {
923 			var orgType = targets[i];
924 			var treeController = overviewController.getTreeController(orgType, true);
925 			if (!treeController) {
926 				continue;
927 			}
928 			treeController.dispose();
929 		}
930 	}
931 };
932 
933 
934 /**
935  * @private
936  */
937 ZmApp.prototype.createDeferred = function() {
938 	var types = ZmOrganizer.APP2ORGANIZER[this._name] || [];
939 	for (var i = 0; i < types.length; i++) {
940 		var type = types[i];
941 		var packageName = ZmOrganizer.ORG_PACKAGE[type];
942 		AjxDispatcher.require(packageName);
943 		this._createDeferredFolders(type);
944 	}
945 };
946 
947 /**
948  * Lazily create folders received in the initial <refresh> block.
949  *
950  * @private
951  */
952 ZmApp.prototype._createDeferredFolders =
953 function(type) {
954 	for (var i = 0; i < this._deferredFolders.length; i++) {
955 		var params = this._deferredFolders[i];
956 		var folder = ZmFolderTree.createFolder(params.type, params.parent, params.obj, params.tree, params.path, params.elementType);
957         if (appCtxt.isExternalAccount() && folder.isSystem()) {
958             continue;
959         }
960 		params.parent.children.add(folder); // necessary?
961 		folder.parent = params.parent;
962 		ZmFolderTree._traverse(folder, params.obj, params.tree, params.path || []);
963 	}
964 	this._clearDeferredFolders();
965 };
966 
967 /**
968  * @private
969  */
970 ZmApp.prototype._clearDeferredFolders =
971 function() {
972 	this._deferredFolders = [];
973 	this._deferredFolderHash = {};
974 };
975 
976 /**
977  * Defer notifications if this app's main package has not been loaded.
978  * Returns true if notifications were deferred.
979  *
980  * @param type	[string]	type of notification (delete, create, or modify)
981  * @param data	[array]		list of notifications
982  *
983  * TODO: revisit use of MAIN_PKG, it's hokey
984  *
985  * @private
986  */
987 ZmApp.prototype._deferNotifications =
988 function(type, data) {
989 	var pkg = ZmApp.MAIN_PKG[this._name];
990 	if (pkg && !AjxDispatcher.loaded(pkg)) {
991 		this._deferredNotifications.push({type:type, data:data});
992 		return true;
993 	} else {
994 		this._noDefer = true;
995 		return false;
996 	}
997 };
998 
999 /**
1000  * @private
1001  */
1002 ZmApp.prototype._handleDeferredNotifications =
1003 function() {
1004 	var dns = this._deferredNotifications;
1005 	for (var i = 0; i < dns.length; i++) {
1006 		var dn = dns[i];
1007 		if (dn.type == "delete") {
1008 			this.deleteNotify(dn.data, true);
1009 		} else if (dn.type == "create") {
1010 			this.createNotify(dn.data, true);
1011 		} else if (dn.type == "modify") {
1012 			this.modifyNotify(dn.data, true);
1013 		}
1014 	}
1015 };
1016 
1017 /**
1018  * Notify change listeners with a list of notifications, rather than a single
1019  * item, so that they can optimize. For example, a list view can wait to
1020  * fix its alternation of dark and light rows until after all the moved ones
1021  * have been taken out, rather than after the removal of each row.
1022  *
1023  * @param mods	{Array}		list of notification objects
1024  */
1025 ZmApp.prototype._batchNotify =
1026 function(mods) {
1027 
1028 	if (!(mods && mods.length >= ZmApp.BATCH_NOTIF_LIMIT)) { return; }
1029 
1030 	var notifs = {}, item, gotOne = false;
1031 	for (var i = 0, len = mods.length; i < len; i++) {
1032 		var mod = mods[i];
1033 		item = appCtxt.cacheGet(mod.id);
1034 		if (item) {
1035 			var ev = item.notifyModify(mod, true);
1036 			if (ev) {
1037 				if (!notifs[ev]) {
1038 					notifs[ev] = [];
1039 				}
1040 				mod.item = item;
1041 				notifs[ev].push(mod);
1042 				gotOne = true;
1043 			}
1044 		}
1045 	}
1046 
1047 	if (!gotOne || !item) { return; }
1048 
1049 	var list = item.list;
1050 	if (!list) { return; }
1051 	list._evt.batchMode = true;
1052 	list._evt.item = item;	// placeholder - change listeners like it to be there
1053 	list._evt.items = null;
1054 	for (var ev in notifs) {
1055 		var details = {notifs:notifs[ev]};
1056 		list._notify(ev, details);
1057 	}
1058 };
1059 
1060 /**
1061  * Depending on "Always in New Window" option and whether Shift key is pressed,
1062  * determine whether action should be in new window or not.
1063  *
1064  * @private
1065  */
1066 ZmApp.prototype._inNewWindow =
1067 function(ev) {
1068 	if (appCtxt.isWebClientOffline()) {
1069 		return false;
1070 	}  else {
1071 		var setting = appCtxt.get(ZmSetting.NEW_WINDOW_COMPOSE);
1072 		return !ev ? setting : ((!setting && ev && ev.shiftKey) || (setting && ev && !ev.shiftKey));
1073 	}
1074 };
1075 
1076 /**
1077  * @private
1078  */
1079 ZmApp.prototype._handleCreateFolder =
1080 function(create, org) {
1081 	var parent = appCtxt.getById(create.l);
1082 	if (parent && (ZmOrganizer.VIEW_HASH[org][create.view])) {
1083 		parent.notifyCreate(create, "folder");
1084 		create._handled = true;
1085 	}
1086 };
1087 
1088 /**
1089  * @private
1090  */
1091 ZmApp.prototype._handleCreateLink =
1092 function(create, org) {
1093 	var parent = appCtxt.getById(create.l);
1094 	var view = create.view || "message";
1095 	if (parent && parent.supportsSharing() && (ZmOrganizer.VIEW_HASH[org][view])) {
1096 		parent.notifyCreate(create, "link");
1097 		create._handled = true;
1098 	}
1099 };
1100 
1101 // Abstract/protected methods
1102 
1103 /**
1104  * Launches the application.
1105  *
1106  * @param	{Hash}	params		a hash of parameters
1107  * @param	{AjxCallback}	callback		the callback
1108  */
1109 ZmApp.prototype.launch =
1110 function(params, callback) {
1111 	this.createDeferred();
1112     if (callback) {
1113         callback.run();
1114     }
1115 };
1116 
1117 /**
1118  * Activates the application.
1119  *
1120  * @param	{Boolean}	active	<code>true</code> if the application is active
1121  * @param	{string}	viewId	ID of view becoming active
1122  */
1123 ZmApp.prototype.activate =
1124 function(active, viewId) {
1125 	this._active = active;
1126 	if (active) {
1127 		appCtxt.getAppController().setNewButtonProps(this.getNewButtonProps());
1128 		this.setOverviewPanelContent();
1129 		this.stopAlert();
1130 		if (appCtxt.isWebClientOfflineSupported) {
1131 			this.resetWebClientOfflineOperations();
1132 		}
1133 		this._setRefreshButtonTooltip();
1134 	}
1135 };
1136 
1137 /**
1138  * Handle the common aspects of a transition from online to offline and offline to online, and also do so
1139  * when an app is activated
1140  */
1141 ZmApp.prototype.resetWebClientOfflineOperations =
1142 function() {
1143 	var isWebClientOnline = !appCtxt.isWebClientOffline();
1144 	var overview = this.getOverview();
1145 	if (overview) {
1146 		var zimletTreeView = overview.getTreeView(ZmOrganizer.ZIMLET);
1147 		if (zimletTreeView) {
1148 			zimletTreeView.setVisible(isWebClientOnline);
1149 		}
1150 		// enable/disable right click
1151 		overview.actionSupported = isWebClientOnline;
1152 		// enable/disable drag and drop
1153 		overview.dndSupported = isWebClientOnline;
1154 	}
1155 	// new button enable/disable
1156 	var newButton = appCtxt.getAppController().getNewButton();
1157 	if (newButton) {
1158 		if (ZmController._defaultNewId === ZmOperation.NEW_MESSAGE) {
1159 			newButton._setDropDownCellMouseHandlers(isWebClientOnline);
1160 		}
1161 		else {
1162 			newButton.setEnabled(isWebClientOnline);
1163 		}
1164 	}
1165 };
1166 
1167 /**
1168  * Checks if the application is active.
1169  *
1170  * @return	{Boolean}	<code>true</code> if the application is active
1171  */
1172 ZmApp.prototype.isActive =
1173 function() {
1174 	return this._active;
1175 };
1176 
1177 /**
1178  * Resets the application state.
1179  *
1180  * @return	{Boolean}	<code>true</code> if the application is active
1181  */
1182 ZmApp.prototype.reset =
1183 function(active) {
1184 };
1185 
1186 /**
1187  * Starts an alert on the application tab.
1188  * 
1189  */
1190 ZmApp.prototype.startAlert =
1191 function() {
1192 	AjxDispatcher.require("Alert");
1193 	this._alert = this._alert || new ZmAppAlert(this);
1194 	this._alert.start();
1195 };
1196 
1197 /**
1198  * Stops an alert on the application tab.
1199  */
1200 ZmApp.prototype.stopAlert =
1201 function() {
1202 	if (this._alert) {
1203 		this._alert.stop();
1204 	}
1205 };
1206 
1207 ZmApp.prototype._setRefreshButtonTooltip =
1208 function() {
1209 	if (appCtxt.refreshButton) {
1210 		appCtxt.refreshButton.setToolTipContent(this._getRefreshButtonTooltip());
1211 	}
1212 };
1213 
1214 /**
1215  * this is the default refresh button tooltip. overridden in Calendar. (see bug 85965)
1216  * @private
1217  */
1218 ZmApp.prototype._getRefreshButtonTooltip =
1219 function() {
1220 	 return ZmMsg.checkMailPrefUpdate;
1221 };
1222 
1223 /**
1224  * @private
1225  */
1226 ZmApp.prototype._notifyRendered =
1227 function() {
1228 	if (!this._hasRendered) {
1229 		appCtxt.getAppController().appRendered(this._name);
1230 		this._hasRendered = true;
1231 	}
1232 	this.stopAlert();
1233 };
1234 
1235 /**
1236  * @private
1237  */
1238 ZmApp.prototype._getExternalAccount =
1239 function() {
1240 
1241 	// bug #43464 - get the first non-local account that supports this app
1242 	var defaultAcct;
1243 	if (appCtxt.multiAccounts) {
1244 		var accounts = appCtxt.accountList.visibleAccounts;
1245 		for (var i = 0; i < accounts.length; i++) {
1246 			var acct = accounts[i];
1247 			if (acct.isMain) { continue; }
1248 
1249 			if (appCtxt.get(ZmApp.SETTING[this.name], null, acct)) {
1250 				defaultAcct = acct;
1251 				break;
1252 			}
1253 		}
1254 	}
1255 	return defaultAcct;
1256 };
1257 
1258 /**
1259  * Sets a hidden div for performance metrics.  Marks the time an app has been launched
1260  * @param appName {String}
1261  * @param date {Date}
1262  * @private
1263  */
1264 ZmApp.prototype._setLaunchTime = 
1265 function(appName, date) {
1266 	if (!window.isPerfMetric) {
1267 		return;
1268 	}
1269 	var id = appName + "_launched";
1270 	if (!date) {
1271 		date = new Date();
1272 	}
1273 	if (!document.getElementById(id)) {
1274 		var div = document.createElement("DIV");
1275 		div.id = id;
1276 		div.innerHTML = date.getTime();
1277 		div.style.display = "none";
1278 		document.body.appendChild(div);
1279 	}
1280 	if (window.appDevMode) {
1281 		console.profile(id);
1282 	}
1283 };
1284 
1285 /**
1286  * Sets a hidden div for performance metrics.  Marks the time an app has completed loading
1287  * @param appName {String}
1288  * @param date {Date}
1289  * @private
1290  */
1291 ZmApp.prototype._setLoadedTime = 
1292 function(appName, date) {
1293 	if (!window.isPerfMetric) {
1294 		return;
1295 	}
1296 	var id = appName + "_loaded";
1297 	if (!date) {
1298 		date = new Date();
1299 	}
1300 	if (!document.getElementById(id)) {
1301 		var div = document.createElement("DIV");
1302 		div.id = id;
1303 		div.innerHTML = date.getTime();
1304 		div.style.display = "none";
1305 		document.body.appendChild(div);
1306 	}
1307 	if (window.appDevMode) {
1308 		console.profileEnd();
1309 	}
1310 };
1311