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, 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) 2004, 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  * @overview
 26  * This file contains the application view manager class.
 27  * 
 28  */
 29 /**
 30  * Creates a layout manager from the given components.
 31  * @class
 32  * This class performs view and layout management. It expects there to be an HTML "skin" with
 33  * containers for various components. A container is an empty DIV with a known ID, so that we
 34  * can use it to place the corresponding component's content. A component is a widget; it is
 35  * the widget's HTML element that is positioned and sized based on the container's location and
 36  * size. The containers are part of the flow (they are positioned relatively), so their location
 37  * and size should be adjusted when necessary by the browser. The components are not children of
 38  * their containers within the DOM tree; they are children of the shell, and are positioned
 39  * absolutely. There appears to be a performance gain in keeping our HTML elements closer to the
 40  * top of the DOM tree, possibly because events do not propagate as far.
 41  * 
 42  * A handful of components are positioned statically. Those are generally the ones that appear
 43  * in the top row: banner, user info, etc. The positioning style is set through skin hints.
 44  * <br/>
 45  * <br/>
 46  * The following containers/components are supported:
 47  *
 48  * <ul>
 49  *  <li>banner: displays logo</li>
 50  *  <li>user info: user name</li>
 51  *  <li>quota: quota bar</li>
 52  *  <li>search bar: a text input and a few buttons</li>
 53  *  <li>search results toolbar: search tab only</li>
 54  *  <li>app chooser: a toolbar with buttons for changing apps</li>
 55  *  <li>new button: button for creating something new (email, contact, etc)</li>
 56  *  <li>tree: area on left that usually shows overview (folders, calendars, etc)</li>
 57  *  <li>tree footer: optionally displays mini-calendar</li>
 58  *  <li>top toolbar: a view-specific toolbar</li>
 59  *  <li>app content: used to present data to the user</li>
 60  *  <li>sash: a thin moveable vertical bar for resizing tree width</li>
 61  * </ul>
 62  *
 63  * <br/>
 64  * <br/>
 65  * In general, the app view manager responds to changes in the skin by having each of the
 66  * affected components adapt to its container's new location and/or size. That means that
 67  * we are dependent on the browser to relocate and resize the containers within the skin
 68  * properly.
 69  * <br/>
 70  * <br/>
 71  * The top and bottom toolbars and the app content are treated somewhat differently: they
 72  * come under the purview of "app view management". In general, an application represents a
 73  * view with a toolbar and a content area (which is often a list view). App view management
 74  * allows these views to be pushed and popped as if they were in a stack. That way, the views
 75  * only need be constructed once each.
 76  * <br/>
 77  * <br/>
 78  * The app view components are hidden and shown using two methods: z-index and relocation. 
 79  * Since every component hangs off the shell, it must have a z-index of at least Z_VIEW
 80  * (300) to be visible. It can be hidden by setting its z-index to Z_HIDDEN (100). Since
 81  * both IE and Firefox have display bugs related to the use of z-index, we use relocation as
 82  * well: a hidden component is positioned way off the screen. (In IE, SELECT fields don't
 83  * obey z-index, and in Firefox, the cursor bleeds through.) Note: the above was true in 2005,
 84  * and we haven't rewritten the app view manager substantially since then. Some day we may just
 85  * append the elements to their parent containers within the DOM, but until then we'll do
 86  * absolute positioning.
 87  * <br/>
 88  * <br/>
 89  * A view can open in a tab (in the row of app buttons) rather than replacing the current view. Those
 90  * are handled in essentially the same way (view push and pop), but they also manage the app button.
 91  * We currently manage only a single view in a tab.
 92  *
 93  * @author Conrad Damon
 94  * 
 95  * @param {DwtShell}		shell			the outermost containing element
 96  * @param {ZmController}	controller		the app controller
 97  * @param {Boolean}			isNewWindow		if <code>true</code>, we are a child window of the main app
 98  * @param {Boolean}			hasSkin			if <code>true</code>, the app has provided containing HTML
 99  */
100 ZmAppViewMgr = function(shell, controller, isNewWindow, hasSkin) {
101 
102 	ZmAppViewMgr._setContainerIds();
103 
104 	this._shell = shell;
105 	this._controller = controller;
106 	this._isNewWindow = isNewWindow;
107 	this._hasSkin = hasSkin;
108 
109 	this._shellSz = this._shell.getSize();
110 	this._shell.addControlListener(this._shellControlListener.bind(this));
111 	this._sashSupported = (window.skin && typeof window.skin.setTreeWidth == "function");
112 
113 	// history support
114 	if (appCtxt.get(ZmSetting.HISTORY_SUPPORT_ENABLED) && !isNewWindow && !AjxEnv.isPrism) {
115 		this._historyMgr = appCtxt.getHistoryMgr();
116 		this._historyMgr.addListener(this._historyChangeListener.bind(this));
117 	}
118 	this._hashViewId			= {};		// matches numeric hash to its view
119 	this._nextHashIndex			= 0;		// index for adding to browser history stack
120 	this._curHashIndex			= 0;		// index of current location in browser history stack
121 	this._noHistory				= false;	// flag to prevent history ops as result of programmatic push/pop view
122 	this._ignoreHistoryChange	= false;	// don't push/pop view as result of history.back() or history.forward()
123 
124 	this._lastViewId	= null;	// ID of previously visible view
125 	this._currentViewId	= null;	// ID of currently visible view
126 	this._hidden		= [];	// stack of views that aren't visible
127 	this._toRemove		= [];	// views to remove from hidden on next view push
128 
129 	this._view		= {};	// information about each view (components, controller, callbacks, app, etc)
130 	this._component	= {};	// component data (container, bounds, current control)
131 	this._app		= {};	// app info (current view)
132 	
133 	// reduce need for null checks
134 	this._emptyView = {component:{}, callback:{}, hide:{}};
135 	
136 	// Hashes keyed by tab ID
137 	this._viewByTabId = {};	// view for the given tab
138 	
139 	// view pre-emption
140 	this._pushCallback = this.pushView.bind(this);
141 	this._popCallback = this.popView.bind(this);
142 	
143 	// placeholder view
144 	this._createLoadingView();
145 };
146 
147 ZmAppViewMgr.prototype.isZmAppViewMgr = true;
148 ZmAppViewMgr.prototype.toString = function() { return "ZmAppViewMgr"; };
149 
150 // Components. A component must be a DwtControl. These component names must match the ones
151 // used in ZmSkin.
152 
153 // components that are visible by default
154 ZmAppViewMgr.C_BANNER					= "banner";
155 ZmAppViewMgr.C_USER_INFO				= "userInfo";
156 ZmAppViewMgr.C_QUOTA_INFO				= "quota";
157 ZmAppViewMgr.C_SEARCH					= "search";
158 ZmAppViewMgr.C_APP_CHOOSER				= "appChooser";
159 ZmAppViewMgr.C_TREE						= "tree";
160 ZmAppViewMgr.C_TOOLBAR_TOP				= "topToolbar";
161 ZmAppViewMgr.C_NEW_BUTTON				= "newButton";
162 ZmAppViewMgr.C_APP_CONTENT				= "main";
163 ZmAppViewMgr.C_SASH						= "sash";
164 
165 // components that are hidden by default
166 ZmAppViewMgr.C_TREE_FOOTER				= "treeFooter";
167 ZmAppViewMgr.C_SEARCH_RESULTS_TOOLBAR	= "searchResultsToolbar";
168 
169 // Components that make up the left nav, which we may want to hide
170 ZmAppViewMgr.LEFT_NAV = [ ZmAppViewMgr.C_NEW_BUTTON, ZmAppViewMgr.C_TREE, ZmAppViewMgr.C_TREE_FOOTER, ZmAppViewMgr.C_SASH ];
171 
172 // deprecated, unused, and obsolete components
173 
174 //ZmAppViewMgr.C_TOOLBAR_BOTTOM			= "bottomToolbar";
175 //ZmAppViewMgr.C_APP_CONTENT_FULL		= "fullScreen";
176 //ZmAppViewMgr.C_AD						= "adsrvc";
177 //ZmAppViewMgr.C_FOOTER					= "footer";
178 //ZmAppViewMgr.C_UNITTEST				= "unittest";
179 //ZmAppViewMgr.C_SEARCH_BUILDER			= "searchBuilder";
180 //ZmAppViewMgr.C_SEARCH_BUILDER_TOOLBAR	= "searchBuilderToolbar";
181 //ZmAppViewMgr.C_STATUS					= "status";
182 
183 // Constants used to control component mappings and visibility
184 ZmAppViewMgr.GLOBAL	= "Global";
185 ZmAppViewMgr.APP	= "App";
186 
187 // keys for getting container IDs
188 ZmAppViewMgr.CONT_ID_KEY = {};
189 
190 // callbacks
191 ZmAppViewMgr.CB_PRE_HIDE	= "PRE_HIDE";
192 ZmAppViewMgr.CB_POST_HIDE	= "POST_HIDE";
193 ZmAppViewMgr.CB_PRE_SHOW	= "PRE_SHOW";
194 ZmAppViewMgr.CB_POST_SHOW	= "POST_SHOW";
195 ZmAppViewMgr.CB_PRE_UNLOAD	= "PRE_UNLOAD";
196 ZmAppViewMgr.CB_POST_REMOVE	= "POST_REMOVE";
197 
198 // used to continue when returning from callbacks
199 ZmAppViewMgr.PENDING_VIEW = "ZmAppViewMgr.PENDING_VIEW";
200 
201 // history support
202 ZmAppViewMgr.BROWSER_BACK		= "BACK";
203 ZmAppViewMgr.BROWSER_FORWARD	= "FORWARD";
204 
205 ZmAppViewMgr.TAB_BUTTON_MAX_TEXT = 15;
206 
207 ZmAppViewMgr._setContainerIds =
208 function() {
209 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_BANNER]					= ZmId.SKIN_LOGO;
210 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_USER_INFO]				= ZmId.SKIN_USER_INFO;
211 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_QUOTA_INFO]				= ZmId.SKIN_QUOTA_INFO;
212 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_SEARCH]					= ZmId.SKIN_SEARCH;
213 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_SEARCH_RESULTS_TOOLBAR]	= ZmId.SKIN_SEARCH_RESULTS_TOOLBAR;
214 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_APP_CHOOSER]			= ZmId.SKIN_APP_CHOOSER;
215 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_TREE]					= ZmId.SKIN_TREE;
216 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_TREE_FOOTER]			= ZmId.SKIN_TREE_FOOTER;
217 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_TOOLBAR_TOP]			= ZmId.SKIN_APP_TOP_TOOLBAR;
218 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_NEW_BUTTON]				= ZmId.SKIN_APP_NEW_BUTTON;
219 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_APP_CONTENT]			= ZmId.SKIN_APP_MAIN;
220 	ZmAppViewMgr.CONT_ID_KEY[ZmAppViewMgr.C_SASH]					= ZmId.SKIN_SASH;
221 
222 	ZmAppViewMgr.ALL_COMPONENTS = AjxUtil.keys(ZmAppViewMgr.CONT_ID_KEY);
223 };
224 
225 
226 // Public methods
227 
228 /**
229  * Returns the requested component (widget) for the given view. The search is done
230  * in the following order:
231  * 		1. A component particular to that view
232  * 		2. A component associated with the view's app
233  * 		3. A global component	
234  * 
235  * @param {constant}	cid			component ID
236  * @param {constant}	viewId		view ID
237  */
238 ZmAppViewMgr.prototype.getViewComponent =
239 function(cid, viewId) {
240 	var view = this._view[viewId || this._currentViewId] || this._emptyView;
241 	var app = view.app || appCtxt.getCurrentAppName();
242 	var appView = this._view[app];
243 	var globalView = this._view[ZmAppViewMgr.GLOBAL];
244 	return ((view && view.component[cid]) ||
245 			(appView && appView.component[cid]) ||
246 			(globalView && globalView.component[cid]));
247 };
248 ZmAppViewMgr.prototype.getCurrentViewComponent = ZmAppViewMgr.prototype.getViewComponent;
249 
250 // Returns the view based on the ID, handling global and app views
251 ZmAppViewMgr.prototype._getView =
252 function(viewId, app) {
253 	var view;
254 	if (viewId == ZmAppViewMgr.GLOBAL) {
255 		view = this._view[viewId] || this.createView({ viewId:viewId }); 
256 	}
257 	else if (viewId == ZmAppViewMgr.APP) {
258 		viewId = app || appCtxt.getCurrentAppName();
259 		view = this._view[viewId] || this.createView({ viewId:viewId }); 
260 	}
261 	else {
262 		view = this._view[viewId || this._currentViewId] || this.createView({ viewId:viewId }); 
263 	}
264 	return view;
265 };
266 
267 /**
268  * Registers the given components with the app view manager, and optionally displays them.
269  *
270  * @param	{constant}	viewId		the view id
271  * @param	{hash}		components	a hash of component IDs and matching objects
272  * @param	{boolean}	show		if <code>true</code>, show the components
273  * @param	{constant}	app			name of app (for view ZmAppViewMgr.APP)
274  */
275 ZmAppViewMgr.prototype.setViewComponents =
276 function(viewId, components, show, app) {
277 
278 	DBG.println("avm", "-------------- SET components: " + AjxUtil.keys(components));
279 		
280 	// set up to add component to the appropriate map: global, app, or local
281 	var view = this._getView(viewId, app);
282 	if (!view) { return; }
283 
284 	var i = 0;
285 	var numComponents = AjxUtil.arraySize(components);
286 	for (var cid in components) {
287 		var comp = components[cid];
288 		if (!comp) { continue; }
289 		if (this.isHidden(cid, viewId)) { continue; }
290 		
291 		var doShow = show && !this.isHidden(cid, this._currentViewId);
292 		if (doShow) {
293 			// if we're replacing a visible component, hide the old one
294 			var oldComp = this._component[cid] && this._component[cid].control;
295 			if (oldComp && (oldComp != comp)) {
296 				this.showComponent(cid, false, oldComp);
297 			}
298 		}
299 		
300 		view.component[cid] = comp;
301 		
302 		if (this._hasSkin) {
303 			this.getContainer(cid, comp);
304 		}
305 
306 		this.displayComponent(cid, doShow, false, null, true);
307 
308 		// TODO: move this code
309 		if (cid == ZmAppViewMgr.C_SASH) {
310 			if (this._sashSupported){
311 				comp.registerCallback(this._appTreeSashCallback, this);
312 				if (appCtxt.get(ZmSetting.FOLDER_TREE_SASH_WIDTH)) {
313 					var newWidth =  appCtxt.get(ZmSetting.FOLDER_TREE_SASH_WIDTH);
314 					var oldWidth = skin.getTreeWidth();
315 					this._appTreeSashCallback(newWidth - oldWidth);
316 				}
317 			}
318 			comp.setCursor("default");
319 		}
320 		i++;
321 	}
322 	if (show) {
323 		this.fitAll();
324 	}
325 };
326 ZmAppViewMgr.prototype.addComponents = ZmAppViewMgr.prototype.setViewComponents;
327 
328 /**
329  * Returns true if the given component should be hidden. Checks local, app, and then
330  * global levels. At any level, the presence of a component trumps whether it is supposed
331  * to be hidden.
332  * 
333  * @param {constant}	cid			component ID
334  * @param {constant}	viewId		view ID
335  */
336 ZmAppViewMgr.prototype.isHidden =
337 function(cid, viewId) {
338 
339 	var view = this._view[viewId || this._currentViewId] || this._emptyView;
340 	var app = view.app || appCtxt.getCurrentAppName();
341 	var appView = this._view[app];
342 	var globalView = this._view[ZmAppViewMgr.GLOBAL];
343 	
344 	if		(view && view.component[cid])				{ return false; }	// view has comp
345 	else if (view && view.hide[cid])					{ return true; }	// view says hide
346 	else if (appView && appView.component[cid])			{ return false; }	// app has comp
347 	else if (appView && appView.hide[cid])				{ return true; }	// app says hide
348 	else if (globalView && globalView.component[cid])	{ return false; }	// global comp
349 	else												{ return globalView && globalView.hide[cid]; }	// global hide
350 };
351 
352 /**
353  * Sets whether the given components should be hidden. That setting can appear at any
354  * of three levels: global, app, or local.
355  * 
356  * @param	{constant}	viewId		the view id
357  * @param	{array}		cidList		list of component IDs
358  * @param	{boolean}	hide		if <code>true</code>, hide the components
359  * @param	{constant}	app			name of app (for view ZmAppViewMgr.APP)
360  */
361 ZmAppViewMgr.prototype.setHiddenComponents =
362 function(viewId, cidList, hide, app) {
363 
364 	cidList = AjxUtil.toArray(cidList);
365 
366 	// set up to add component to the appropriate map: global, app, or local
367 	var view = this._getView(viewId, app);
368 	if (!view) { return; }
369 
370 	for (var i = 0; i < cidList.length; i++) {
371 		view.hide[cidList[i]] = hide;
372 	}
373 };
374 
375 /**
376  * Shows or hides the skin element (not always the same as the container) for a given
377  * component.
378  * 
379  * @param {constant}	cid			the component ID
380  * @param {boolean}		show		if true, show the skin element; otherwise hide it
381  * @param {boolean}		noReflow	if true, tell skin to not refit all components
382  */
383 ZmAppViewMgr.prototype.showSkinElement =
384 function(cid, show, noReflow) {
385 	if (this._hasSkin) {
386 		DBG.println("avm", (show ? "SHOW " : "HIDE ") + "SKIN element for: " + cid);
387 		skin.show(cid, show, noReflow);
388 	}
389 };
390 
391 /**
392  * Shows or hides the given component. It may still need to be positioned.
393  * 
394  * @param {constant}	cid		the component ID
395  * @param {boolean}		show	if true, show the component; otherwise hide it
396  * @param {DwtControl}	comp	component (optional)
397  */
398 ZmAppViewMgr.prototype.showComponent =
399 function(cid, show, comp) {
400 	
401 	comp = comp || this.getViewComponent(cid);
402 	
403 	if (comp) {
404 		DBG.println("avm", (show ? "SHOW " : "HIDE ") + cid + " / " + comp.toString() + " / " + comp._htmlElId);
405 		if (show) {
406 			comp.zShow(true);
407 			comp.noTab = false;
408 		}
409 		else {
410 			if (comp.getPosition() == Dwt.ABSOLUTE_STYLE) {
411 				comp.setLocation(Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE);
412 			}
413 			comp.zShow(false);
414 			comp.noTab = true;
415 		}
416 	}
417 };
418 
419 /**
420  * Handles several tasks needed to make sure a component is actually visible.
421  * 
422  * @param {constant}	cid		the component ID
423  * @param {boolean}		show	if true, show the component; otherwise hide it
424  * @param {boolean}		doFit	if true, fit component to container
425  * @param {object}		comp	if provided, pass this to showComponent, so it does not just look for the cid in the current view (useful for previous view. see ZmAppViewMgr.prototype._setViewVisible)
426  * @param {boolean}		noReflow	if true, tell skin to not refit all components
427  */
428 ZmAppViewMgr.prototype.displayComponent =
429 function(cid, show, doFit, comp, noReflow) {
430 	this.showSkinElement(cid, show, noReflow);
431 	this.showComponent(cid, show, comp);
432 	if (doFit) {
433 		this._fitToContainer(cid);
434 	}
435 };
436 
437 /**
438  * Returns the requested container.
439  * 
440  * @param cid
441  * @param comp
442  */
443 ZmAppViewMgr.prototype.getContainer =
444 function(cid, comp) {
445 
446 	var component = this._component[cid] = this._component[cid] || {};
447 	
448 	if (!component.container) {
449 		var contId = ZmAppViewMgr.CONT_ID_KEY[cid];
450 		var contEl = document.getElementById(contId);
451 		if (!contEl) {
452 			// skin may want to omit certain containers
453 			DBG.println(AjxDebug.DBG2, "Skin container '" + contId + "' not found.");
454 			return null;
455 		}
456 		component.container = contEl;
457 		if (comp) {
458 			contEl.innerHTML = "";
459 
460 			// if the container has bounds, fit the component to it now to prevent resize flash
461 			var bounds = this._getContainerBounds(cid);
462 			var toolbarExists = Boolean(this.getViewComponent(ZmAppViewMgr.C_TOOLBAR_TOP));
463 			if (bounds) {
464 				DBG.println("avm", "SET BOUNDS " + cid + ": " + [bounds.x, bounds.y, bounds.width, bounds.height].join("/"));
465 				comp.setBounds(bounds.x, bounds.y, bounds.width, bounds.height, toolbarExists);
466 			}
467 		}
468 	}
469 	
470 	return component.container;
471 };
472 
473 /**
474  * Gets the ID of the view currently being displayed.
475  * 
476  * @return	{string}	the view id
477  */
478 ZmAppViewMgr.prototype.getCurrentViewId =
479 function() {
480 	return this._currentViewId;
481 };
482 
483 /**
484  * Gets the type of the view currently being displayed.
485  * 
486  * @return	{string}	the view type
487  */
488 ZmAppViewMgr.prototype.getCurrentViewType =
489 function() {
490 	var view = this._view[this._currentViewId];
491 	return view ? view.type : "";
492 };
493 
494 /**
495  * Gets the ID of the app view last displayed.
496  * 
497  * @return	{Object}	the last view
498  */
499 ZmAppViewMgr.prototype.getLastViewId =
500 function() {
501 	return this._lastViewId;
502 };
503 
504 /**
505  * Gets the main content object of the given view.
506  * 
507  * @return	{Object}	the current main content view object
508  */
509 ZmAppViewMgr.prototype.getCurrentView =
510 function(view) {
511 	return this.getViewComponent(ZmAppViewMgr.C_APP_CONTENT, view || this._currentViewId);
512 };
513 
514 /**
515  * Gets the current top-level view for the given app.
516  *
517  * @param {String}	app		the name of an app
518  * 
519  * @return	{string}	ID of the app's current view
520  */
521 ZmAppViewMgr.prototype.getAppView =
522 function(app) {
523 	return this._app[app] && this._app[app].viewId;
524 };
525 
526 /**
527  * Sets the current top-level view for the given app. Should be called by an app (or controller) that
528  * changes the top-level view of the app.
529  *
530  * @param {String}	app			the name of an app
531  * @param {string}	viewId		the view ID
532  */
533 ZmAppViewMgr.prototype.setAppView =
534 function(app, viewId) {
535 	if (!app || !viewId) { return; }
536 	var app = this._app[app];
537 	if (!app) {
538 		app = this._app[app] = {};
539 	}
540 	app.viewId = viewId;
541 };
542 
543 /**
544  * Returns a list of views of the given type. The views are the anonymous view objects used by the app view mgr.
545  * 
546  * @param {string}	type	a view type
547  * @param {boolean}	visible if true, only return visible views
548  */
549 ZmAppViewMgr.prototype.getViewsByType =
550 function(type, visible) {
551 	var list = [];
552 	for (var viewId in this._view) {
553 		var view = this._view[viewId];
554 		if (view.type == type && (!visible || view.visible)) {
555 			list.push(view);
556 		}
557 	}
558 	return list;
559 };
560 
561 /**
562  * Returns true if the given view is visible to the user.
563  *
564  * @param {string}  viewId  a view ID
565  *
566  * @returns {boolean}   true if the given view is visible to the user
567  */
568 ZmAppViewMgr.prototype.isVisible = function(viewId) {
569 
570     var view = this._view[viewId];
571 
572     return view && view.visible;
573 };
574 
575 /**
576  * Registers a set of elements comprising an app view.
577  *
578  * @param	{Hash}			params				a hash of parameters
579  * @param	{string}		params.viewId		the view ID
580  * @param	{string}		params.viewType		the view type
581  * @param	{String}		params.appName		the name of the owning app
582  * @param	{Hash}			params.elements		a hash of elements
583  * @param	{ZmController}	params.controller	controller responsible for this view
584  * @param	{Hash}			params.callbacks 	a hash of functions to call before/after this view is shown/hidden
585  * @param	{Boolean}		params.isAppView 	if <code>true</code>, this view is an app-level view
586  * @param	{Boolean}		params.isTransient	if <code>true</code>, this view does not go on the hidden stack
587  * @param	{Hash}			params.tabParams	the tab button params; view is opened in app tab instead of being stacked
588  * @param	{Hash}			params.hide			components that aren't displayed in this view
589  */
590 ZmAppViewMgr.prototype.createView =
591 function(params) {
592 
593 	params = params || {};
594 	var viewId = params.viewId;
595 	if (!viewId) { return null; }
596 	DBG.println(AjxDebug.DBG1, "createView: " + viewId);
597 
598 	var view = this._view[viewId] = {
599 		id:				viewId,
600 		type:			params.viewType || viewId,
601 		component:		params.elements || {},
602 		controller:		params.controller,
603 		callback:		params.callbacks || {},
604 		app:			params.appName,
605 		isAppView:		params.isAppView,
606 		isTransient:	params.isTransient,
607 		isFullScreen:	params.isFullScreen,
608 		hide:			AjxUtil.arrayAsHash(params.hide || [])
609 	};
610 
611 	if (params.appName && !this._app[params.appName]) {
612 		this._app[params.appName] = {};
613 	}
614 
615 	if (!this._isNewWindow && params.tabParams) {
616 		view.tabParams	= params.tabParams;
617 		view.isTabView = true;
618 		this._viewByTabId[params.tabParams.id] = viewId;
619 	}
620 
621 	// Accessibility - let AT know this is the main content area
622 	var mainView = params.isAppView && params.elements && params.elements[ZmAppViewMgr.C_APP_CONTENT],
623 		mainEl = mainView && mainView.getHtmlElement();
624 	if (mainEl) {
625 		mainEl.setAttribute("role", "main");
626 	}
627 
628 	return view;
629 };
630 
631 /**
632  * Makes the given view visible, pushing the previously visible one to the top of the
633  * hidden stack.
634  *
635  * @param {int}		viewId		the ID of the app view to push
636  * @param {Boolean}	force		if <code>true</code>, do not run callbacks
637  *
638  * @returns	{Boolean}	<code>true</code> if the view was pushed (is now visible)
639  */
640 ZmAppViewMgr.prototype.pushView =
641 function(viewId, force) {
642 
643 	if (!viewId) { return false; }
644 	DBG.println("avm", "------- PUSH view: " + viewId);
645 	
646 	viewId = this._viewByTabId[viewId] || viewId;
647 	var view = this._view[viewId] || this._emptyView;
648 	
649 	var isPendingView = (viewId == ZmAppViewMgr.PENDING_VIEW);
650 	if (!isPendingView && !view) {
651 		// view has not been created, bail
652 		return false;
653 	}
654 
655 	if (isPendingView) {
656 		viewId = this._pendingView;
657 	}
658 	DBG.println(AjxDebug.DBG1, "pushView: " + viewId);
659 
660 	var viewController = view.controller;
661 
662 	// if same view, no need to hide previous view or check for callbacks
663 	//also no need to make the view visible, it already is.
664 	if (viewId == this._currentViewId) {
665 		// make sure the new content has focus
666 		if (viewController) {
667 			viewController._restoreFocus();
668 		}
669 		return true;
670 	}
671 
672 	DBG.println(AjxDebug.DBG2, "hidden (before): " + this._hidden);
673 
674 	if (view.isTabView) {
675 		var tp = view.tabParams;
676 		var handled = tp && tp.tabCallback && tp.tabCallback.run(this._currentViewId, viewId);
677 		if (tp && !handled) {
678 			var ac = appCtxt.getAppChooser();
679 			var button = ac.getButton(tp.id);
680 			if (!button) {
681 				button = ac.addButton(tp.id, tp);
682 				button.setHoverImage("Close", "right");
683 			}
684 		}
685 	}
686 
687 	if (isPendingView) {
688 		DBG.println(AjxDebug.DBG1, "push of pending view: " + this._pendingView);
689 		force = true;
690 	}
691 
692 	var curView = this._view[this._currentViewId] || this._emptyView;
693 	if (!this._hideView(this._currentViewId, force || curView.isTabView, false, viewId)) {
694 		this._pendingAction = this._pushCallback;
695 		this._pendingView = viewId;
696 		return false;
697 	}
698 	this.setViewComponents(viewId, view.component);
699 
700 	var curViewController = curView.controller;
701 	var isTransient = curView.isTransient || (curViewController && curViewController.isTransient(this._currentViewId, viewId));
702 	if (this._currentViewId && (this._currentViewId != viewId) && !isTransient) {
703 		this._hidden.push(this._currentViewId);
704 	}
705 
706 	this._removeFromHidden(viewId);
707 	var temp = this._lastViewId;
708 	this._lastViewId = this._currentViewId;
709 	this._currentViewId = viewId;
710 	DBG.println(AjxDebug.DBG2, "app view mgr: current view is now " + this._currentViewId);
711 
712 	if (!this._showView(viewId, force, (viewId != this._currentViewId))) {
713 		this._currentViewId = this._lastViewId;
714 		this._lastViewId = temp;
715 		this._pendingAction = this._pushCallback;
716 		this._pendingView = viewId;
717 		return false;
718 	}
719 	DBG.println(AjxDebug.DBG2, "hidden (after): " + this._hidden);
720 
721 	// a view is being pushed - add it to browser history stack unless we're
722 	// calling this function as a result of browser Back or Forward
723 	if (this._noHistory) {
724 		DBG.println(AjxDebug.DBG2, "noHistory: push " + viewId);
725 		this._noHistory = false;
726 	} else {
727 		if (viewId != ZmId.VIEW_LOADING) {
728 			this._nextHashIndex++;
729 			this._curHashIndex = this._nextHashIndex;
730 			this._hashViewId[this._curHashIndex] = viewId;
731 			DBG.println(AjxDebug.DBG2, "adding to browser history: " + this._curHashIndex + "(" + viewId + ")");
732 			if (this._historyMgr) {
733 				this._historyMgr.add(this._curHashIndex);
734 			}
735 		}
736 	}
737 
738 	this._layout(this._currentViewId);
739 
740 	if (viewController && viewController.setCurrentViewId) {
741 		viewController.setCurrentViewId(viewId);
742 	}
743 	if (view.isAppView) {
744 		this.setAppView(view.app, viewId);
745 	}
746 	
747 	if (this._toRemove.length) {
748 		for (var i = 0; i < this._toRemove.length; i++) {
749 			this._removeFromHidden(this._toRemove[i]);
750 		}
751 		this._toRemove = [];
752 	}
753 
754 	return true;
755 };
756 
757 /**
758  * Hides the currently visible view, and makes the view on top of the hidden stack visible.
759  *
760  * @param	{Boolean}	force	if <code>true</code>, do not run callbacks (which check if popping is OK)
761  * @param	{int}	viewId	the view ID. Only pop if this is current view
762  * @returns	{Boolean}		<code>true</code> if the view was popped
763  */
764 ZmAppViewMgr.prototype.popView =
765 function(force, viewId, skipHistory) {
766 
767 	DBG.println("avm", "------- POP view: " + viewId);
768 	
769 	viewId = this._viewByTabId[viewId] || viewId;
770 	var view = this._view[viewId] || this._emptyView;
771 
772 	if (!this._currentViewId) {
773 		DBG.println(AjxDebug.DBG1, "ERROR: no view to pop");
774 		return false;
775 	}
776 
777 	var isPendingView = (force == ZmAppViewMgr.PENDING_VIEW);
778 	if (isPendingView) {
779 		viewId = force;
780 		force = true;
781 	}
782 
783 	// a tab view is the only type of non-current view we can pop; if it is not the
784 	// current view, push it first so that callbacks etc work as expected
785 	if (viewId && !isPendingView && (this._currentViewId != viewId)) {
786 		if (view.isTabView && (this._currentViewId != viewId)) {
787 			this.pushView(viewId);
788 		}
789 		else {
790 			return false;
791 		}
792 	}
793 
794 	// handle cases where there are no views in the hidden stack (entry via deep link)
795 	var noHide = false, noShow = false;
796 	var goToApp = null;
797 	var curView = this._view[this._currentViewId] || this._emptyView;
798 	if (!this._hidden.length && !this._isNewWindow) {
799 		noHide = !curView.isTabView;
800 		noShow = true;
801 		goToApp = appCtxt.getCurrentAppName() || appCtxt.startApp;
802 	}
803 
804 	DBG.println(AjxDebug.DBG1, "popView: " + this._currentViewId);
805 	DBG.println(AjxDebug.DBG2, "hidden (before): " + this._hidden);
806 	if (!this._hideView(this._currentViewId, force, noHide)) {
807 		this._pendingAction = this._popCallback;
808 		this._pendingView = null;
809 		return false;
810 	}
811 
812 	this._deactivateView(this._currentViewId);
813 
814 	if (curView.isTabView) {
815 		appCtxt.getAppChooser().removeButton(curView.tabParams.id);
816 		var callback = curView.callback[ZmAppViewMgr.CB_POST_REMOVE];
817 		if (callback) {
818 			callback.run();
819 		}
820 	}
821 	
822 	if (noShow) {
823 		if (goToApp) {
824 			this._controller.activateApp(ZmApp.MAIL);
825 		}
826 		return !noHide;
827 	}
828 
829 	this._lastViewId = this._currentViewId;
830 	this._currentViewId = this._hidden.pop();
831 
832 	// close this window if no more views exist and it's a child window
833 	if (!this._currentViewId && this._isNewWindow) {
834 		window.close();
835 		return false;
836 	}
837 
838 	DBG.println(AjxDebug.DBG2, "app view mgr: current view is now " + this._currentViewId);
839 	if (!this._showView(this._currentViewId, this._popCallback, null, force, true)) {
840 		DBG.println(AjxDebug.DBG1, "ERROR: pop with no view to show");
841 		return false;
842 	}
843 	this._removeFromHidden(this._currentViewId);
844 	DBG.println(AjxDebug.DBG2, "hidden (after): " + this._hidden);
845 	DBG.println(AjxDebug.DBG2, "hidden (" + this._hidden.length + " after pop): " + this._hidden);
846 
847 	// Move one back in the browser history stack so that we stay in sync, unless
848 	// we're calling this function as a result of browser Back
849 	if (this._historyMgr && !skipHistory) {
850 		if (this._noHistory) {
851 			DBG.println(AjxDebug.DBG2, "noHistory (pop)");
852 			this._noHistory = false;
853 		} else {
854 			this._ignoreHistoryChange = true;
855 			history.back();
856 		}
857 	}
858 
859 	this._layout(this._currentViewId);
860 
861 	return true;
862 };
863 
864 /**
865  * Makes the given view visible, and clears the hidden stack.
866  *
867  * @param 	{int}	viewId		the ID of the view
868  * @param 	{Boolean}	force		if <code>true</code>, ignore pre-emption callbacks
869  * @returns	{Boolean}	<code>true</code> if the view was set
870  */
871 ZmAppViewMgr.prototype.setView =
872 function(viewId, force) {
873 	DBG.println(AjxDebug.DBG1, "setView: " + viewId);
874 	var result = this.pushView(viewId, force);
875 	if (result) {
876 		for (var i = 0; i < this._hidden.length; i++) {
877 			this._deactivateView(this._hidden[i]);
878 		}
879 		this._hidden = [];
880 	}
881 	return result;
882 };
883 
884 /**
885  * Moves the given view to the top of the hidden stack, so that it will
886  * appear when the current view is popped.
887  * 
888  * @param {int}	viewId		the ID of the view
889  */
890 ZmAppViewMgr.prototype.stageView =
891 function(viewId) {
892 	DBG.println(AjxDebug.DBG1, "stageView: " + viewId);
893 	this._removeFromHidden(viewId);
894 	this._hidden.push(viewId);
895 };
896 
897 /**
898  * Checks if the view is the app view.
899  * 
900  * @param	{int}	viewId	the view id
901  * @return	{Boolean}	<code>true</code> if the view is the app view
902  */
903 ZmAppViewMgr.prototype.isAppView =
904 function(viewId) {
905 	var view = this._view[viewId || this._currentViewId] || this._emptyView;
906 	return view.isAppView;
907 };
908 
909 /**
910  * Returns true if the view is full screen.
911  * 
912  * @param	{constant}	viewId		the view id
913  * @return	{boolean}	<code>true</code> if full screen
914  */
915 ZmAppViewMgr.prototype.isFullScreen =
916 function(viewId) {
917 	var view = this._view[viewId || this._currentViewId] || this._emptyView;
918 	return view.isFullScreen;
919 };
920 
921 /**
922 * Shows the view that was waiting for return from a popped view's callback. Typically, the
923 * popped view's callback will have put up some sort of dialog, and this function would be
924 * called by a listener on a dialog button.
925 *
926 * @param {Boolean}	show		if <code>true</code>, show the pending view
927 */
928 ZmAppViewMgr.prototype.showPendingView =
929 function(show) {
930 	if (show && this._pendingAction) {
931 		this._pendingAction.run(ZmAppViewMgr.PENDING_VIEW);
932 	}
933 
934 	// If a pop shield has been dismissed and we're not going to show the
935 	// pending view, and we got here via press of browser Back/Forward button,
936 	// then undo that button press so that the browser history is correct.
937 	if (!show) {
938 		if (this._browserAction == ZmAppViewMgr.BROWSER_BACK) {
939 			this._ignoreHistoryChange = true;
940 			history.forward();
941 		} else if (this._browserAction == ZmAppViewMgr.BROWSER_FORWARD) {
942 			this._ignoreHistoryChange = true;
943 			history.back();
944 		}
945 		this._browserAction = null;
946 	}
947 	this._pendingAction = this._pendingView = null;
948 };
949 
950 /**
951  * Fits all components to the container.
952  */
953 ZmAppViewMgr.prototype.fitAll =
954 function() {
955 	this._shell.relayout();
956 	this._fitToContainer(ZmAppViewMgr.ALL_COMPONENTS);
957 };
958 
959 /**
960  * Gets the currently pending view waiting to get pushed.
961  * 
962  * @return	{Object}	the pending view id
963  */
964 ZmAppViewMgr.prototype.getPendingViewId = 
965 function() {
966 	return this._pendingView;
967 };
968 
969 /**
970  * Updates and shows the current view title in the title bar.
971  */
972 ZmAppViewMgr.prototype.updateTitle = 
973 function() {
974 	this._setTitle(this._currentViewId);
975 };
976 
977 /**
978  * Sets the tab title.
979  * 
980  * @param	{int}	viewId	the view id
981  * @param	{String}	text	the title
982  */
983 ZmAppViewMgr.prototype.setTabTitle =
984 function(viewId, text) {
985 	var view = this._view[viewId || this._currentViewId] || this._emptyView;
986 	var tp = view.tabParams;
987 	var button = !appCtxt.isChildWindow && tp && appCtxt.getAppChooser().getButton(tp.id);
988 	if (button) {
989 		button.setText(AjxStringUtil.htmlEncode(text));
990 	}
991 };
992 
993 /**
994  * Checks if it is OK to unload the app (for example, user logs out, navigates away, closes browser).
995  * 
996  * @return	{Boolean}	<code>true</code> if OK to unload the app
997  */
998 ZmAppViewMgr.prototype.isOkToUnload =
999 function() {
1000 	for (var viewId in this._view) {
1001 		var view = this._view[viewId];
1002 		var callback = view && view.callback && view.callback[ZmAppViewMgr.CB_PRE_UNLOAD];
1003 		if (callback) {
1004 			DBG.println(AjxDebug.DBG2, "checking if ok to unload " + viewId);
1005 			var okToContinue = callback.run(viewId);
1006 			if (!okToContinue) { return false; }
1007 		}
1008 	}
1009 	return true;
1010 };
1011 
1012 // Private methods
1013 
1014 /**
1015  * @private
1016  */
1017 ZmAppViewMgr.prototype._createLoadingView =
1018 function() {
1019 	this.loadingView = new DwtControl({parent:this._shell, className:"DwtListView",
1020 									   posStyle:Dwt.ABSOLUTE_STYLE, id:ZmId.LOADING_VIEW});
1021 	var el = this.loadingView.getHtmlElement();
1022 	el.innerHTML = AjxTemplate.expand("share.App#Loading", this._htmlElId);
1023 	var elements = {};
1024 	elements[ZmAppViewMgr.C_APP_CONTENT] = this.loadingView;
1025 	this.createView({viewId:ZmId.VIEW_LOADING, elements:elements});
1026 };
1027 
1028 /**
1029  * Locates and sizes the given list of components to fit within their containers.
1030  * 
1031  * @private
1032  */
1033 ZmAppViewMgr.prototype._fitToContainer =
1034 function(cidList, isIeTimerHack) {
1035 	
1036 	var cidList = AjxUtil.toArray(cidList);
1037 
1038 	for (var i = 0; i < cidList.length; i++) {
1039 		var cid = cidList[i];
1040 		DBG.println(AjxDebug.DBG3, "fitting to container: " + cid);
1041 		var cont = this.getContainer(cid);
1042 		if (cont) {
1043 			var comp = this.getViewComponent(cid);
1044 			if (comp && !this.isHidden(cid, this._currentViewId)) {
1045 				var position = this._getComponentPosition(cid);
1046 				var isStatic = (position == Dwt.STATIC_STYLE);
1047 				
1048 				// reset position if skin overrides default of absolute
1049 				var compEl = comp.getHtmlElement();
1050 				if (position) {
1051 					compEl.style.position = position;
1052 				}
1053 
1054 				var component = this._component[cid];
1055 				if (isStatic) {
1056 					if (compEl.parentNode != cont) {
1057 						DBG.println("avm", "APPEND " + cid);
1058 						cont.appendChild(compEl);
1059 					}
1060 					if (comp.adjustSize) {
1061 						comp.adjustSize();
1062 					}
1063 				} else {
1064 					var contBds = Dwt.getBounds(cont);
1065 					// take insets (border + padding) into account
1066 					var insets = Dwt.getInsets(cont);
1067 					Dwt.insetBounds(contBds, insets);
1068 					
1069 					// save bounds
1070 					component.bounds = contBds;
1071 					var toolbarExists = Boolean(this._component[ZmAppViewMgr.C_TOOLBAR_TOP].control);
1072 					DBG.println("avm", "FIT " + cid + ": " + [contBds.x, contBds.y, contBds.width, contBds.height].join("/"));
1073 					comp.setBounds(contBds.x, contBds.y, contBds.width, contBds.height, toolbarExists);
1074 				}
1075 				component.control = comp;
1076 			}
1077 		}
1078 	}
1079 
1080 	if (window.DBG && DBG.getDebugLevel() >= AjxDebug.DBG2) {
1081 		this._debugShowMetrics(cidList);
1082 	}
1083 };
1084 
1085 /**
1086  * @private
1087  */
1088 ZmAppViewMgr.prototype._getComponentPosition =
1089 function(cid) {
1090 	return appCtxt.getSkinHint(cid, "position");
1091 };
1092 
1093 /**
1094  * @private
1095  */
1096 ZmAppViewMgr.prototype._getContainerBounds =
1097 function(cid) {
1098 	// ignore bounds for statically laid-out components
1099 	var position = this._getComponentPosition(cid);
1100 	if (position == Dwt.STATIC_STYLE) { return null; }
1101 
1102 	var container = this.getContainer(cid);
1103 	if (container) {
1104 		var bounds = Dwt.getBounds(container);
1105 		// take insets (border + padding) into account
1106 		var insets = Dwt.getInsets(container);
1107 		Dwt.insetBounds(bounds, insets);
1108 		return bounds;
1109 	}
1110 	return null;
1111 };
1112 
1113 /**
1114  * Performs manual layout of the components, absent a containing skin. Currently assumes
1115  * that there will be a top toolbar and app content.
1116  * 
1117  * @private
1118  */
1119 ZmAppViewMgr.prototype._layout =
1120 function(view) {
1121 	// if skin, elements already laid out by being placed in their containers
1122 	if (this._hasSkin) { return; }
1123 	
1124 	var topToolbar = this.getViewComponent(ZmAppViewMgr.C_TOOLBAR_TOP);
1125 	if (topToolbar) {
1126 		var sz = topToolbar.getSize();
1127 		var height = sz.y ? sz.y : topToolbar.getHtmlElement().clientHeight;
1128 		topToolbar.setBounds(0, 0, this._shellSz.x, height);
1129 	}
1130 	var appContent = this.getCurrentView();
1131 	if (appContent) {
1132 		appContent.setBounds(0, height, this._shellSz.x, this._shellSz.y - height, Boolean(topToolbar));
1133 	}
1134 };
1135 
1136 /**
1137  * Tries to hide the given view. First checks to see if the view has a callback
1138  * for when it is hidden. The callback must return true for the view to be hidden.
1139  * 
1140  * @private
1141  */
1142 ZmAppViewMgr.prototype._hideView =
1143 function(viewId, force, noHide, newViewId) {
1144 
1145 	if (!viewId) { return true; }
1146 
1147 	var view = this._view[viewId] || this._emptyView;
1148 	var okToContinue = true;
1149 	var callback = view.callback[ZmAppViewMgr.CB_PRE_HIDE];
1150 	if (callback) {
1151 		DBG.println(AjxDebug.DBG2, "hiding " + viewId);
1152 		okToContinue = callback.run(viewId, force, newViewId);
1153 	}
1154 	if (okToContinue) {
1155 		if (!noHide) {
1156 			this._setViewVisible(viewId, false);
1157 		}
1158         if (appCtxt.get(ZmSetting.USE_KEYBOARD_SHORTCUTS)) {
1159 		    appCtxt.getKeyboardMgr().clearKeySeq();
1160         }
1161 		DBG.println(AjxDebug.DBG2, viewId + " hidden");
1162 		callback = view.callback[ZmAppViewMgr.CB_POST_HIDE];
1163 		if (callback) {
1164 			callback.run(viewId, newViewId);
1165 		}
1166 	}
1167 
1168 	return okToContinue;
1169 };
1170 
1171 /**
1172  * Makes the given view visible.
1173  * 
1174  * @private
1175  */
1176 ZmAppViewMgr.prototype._showView =
1177 function(viewId, force, isNewView) {
1178 
1179 	if (!viewId) { return true; }
1180 	
1181 	var view = this._view[viewId] || this._emptyView;
1182 	var okToContinue = true;
1183 	var callback = view.callback[ZmAppViewMgr.CB_PRE_SHOW];
1184 	if (callback) {
1185 		DBG.println(AjxDebug.DBG2, "showing " + viewId);
1186 		okToContinue = callback.run(viewId, isNewView, force);
1187 	}
1188 	if (okToContinue) {
1189 		this._setViewVisible(viewId, true);
1190 		DBG.println(AjxDebug.DBG2, viewId + " shown");
1191 		callback = view.callback[ZmAppViewMgr.CB_POST_SHOW];
1192 		if (callback) {
1193 			callback.run(viewId, isNewView);
1194 		}
1195 	}
1196 	appCtxt.notifyZimlets("onShowView", [viewId, isNewView]);
1197 
1198 	return okToContinue;
1199 };
1200 
1201 /**
1202  * Shows or hides the components of a view.
1203  * 
1204  * @private
1205  */
1206 ZmAppViewMgr.prototype._setViewVisible =
1207 function(viewId, show) {
1208 
1209 	DBG.println("avm", "-------------- " + (show ? "SHOW " : "HIDE ") + viewId);
1210 
1211 	var view = this._view[viewId] || this._emptyView;
1212 	view.visible = show;
1213 	
1214 	if (show) {
1215 
1216 		for (var i = 0; i < ZmAppViewMgr.ALL_COMPONENTS.length; i++) {
1217 			var cid = ZmAppViewMgr.ALL_COMPONENTS[i];
1218 			var oldComp = this.getViewComponent(cid, this._lastViewId);
1219 			if (oldComp) {
1220 				this.displayComponent(cid, false, null, oldComp, true);
1221 			}
1222 			var comp = this.getViewComponent(cid, viewId);
1223 			if (comp) {
1224                 this.displayComponent(cid, !this.isHidden(cid, viewId), null, comp, true);
1225 			}
1226 		}
1227 
1228 		// fit the components now that we're done messing with the skin
1229 		if (this._hasSkin) {
1230 			this.fitAll();
1231 		}
1232 		
1233 		this._setTitle(viewId);
1234 		
1235 		if (view.isTabView) {
1236 			var tabId = view.tabParams.id;
1237 			this._controller.setActiveTabId(tabId);
1238 		}
1239 		
1240 		if (view.app) {
1241 			this._controller.setActiveApp(view);
1242 		}
1243 	}
1244 	else {
1245 		// hiding a view is lightweight - just hide the component widgets
1246 		for (var cid in view.component) {
1247 			this.showComponent(cid, false);
1248 		}
1249 		// hide the app components too - if we're not changing apps, they will reappear
1250 		// when the new view is shown. Done this way since this._lastViewId is not yet set.
1251 		var appView = this._view[view.app];
1252 		if (appView) {
1253 			for (var cid in appView.component) {
1254 				this.showComponent(cid, false);
1255 			}
1256 		}
1257 	}
1258 };
1259 
1260 /**
1261  * Removes a view from the hidden stack.
1262  * 
1263  * @private
1264  */
1265 ZmAppViewMgr.prototype._removeFromHidden =
1266 function(view) {
1267 	AjxUtil.arrayRemove(this._hidden, view);
1268 };
1269 
1270 /**
1271  * Tells a view's components that it has been hidden.
1272  * 
1273  * @private
1274  */
1275 ZmAppViewMgr.prototype._deactivateView =
1276 function(viewId) {
1277 	viewId = viewId || this._currentViewId;
1278 	var view = this._view[viewId] || this._emptyView;
1279 	for (var cid in view.component) {
1280 		var comp = this.getViewComponent(cid, viewId);
1281 		if (comp && comp.deactivate) {
1282 			comp.deactivate();
1283 		}
1284 	}
1285 };
1286 
1287 /**
1288  * Sets the browser title based on the view's APP_CONTENT component
1289  * @private
1290  */
1291 ZmAppViewMgr.prototype._setTitle =
1292 function(view) {
1293 	var content = this.getCurrentView();
1294 	if (content && content.getTitle) {
1295 		var title = content.getTitle();
1296 		Dwt.setTitle(title ? title : ZmMsg.zimbraTitle);
1297 	}
1298 };
1299 
1300 // Listeners
1301 
1302 /**
1303  * Handles shell resizing event.
1304  * 
1305  * @private
1306  */
1307 ZmAppViewMgr.prototype._shellControlListener =
1308 function(ev) {
1309 
1310 	if (ev.oldWidth != ev.newWidth || ev.oldHeight != ev.newHeight) {
1311 		this._shellSz.x = ev.newWidth;
1312 		this._shellSz.y = ev.newHeight;
1313 		var deltaWidth = ev.newWidth - ev.oldWidth;
1314 		var deltaHeight = ev.newHeight - ev.oldHeight;
1315 		DBG.println(AjxDebug.DBG1, "shell control event: dW = " + deltaWidth + ", dH = " + deltaHeight);
1316 		if (this._isNewWindow) {
1317 			var view = this._view[this._currentViewId] || this._emptyView
1318 			if (view.component) {
1319 				// reset width of top toolbar
1320 				var topToolbar = view.component[ZmAppViewMgr.C_TOOLBAR_TOP]; //todo - something similar for new button here?
1321 				if (topToolbar) {
1322 					topToolbar.setSize(ev.newWidth, Dwt.DEFAULT);
1323 				}
1324 				// make sure to remove height of top toolbar for height of app content
1325 				var appContent = this.getCurrentView();
1326 				if (appContent) {
1327 					appContent.setSize(ev.newWidth, ev.newHeight - topToolbar.getH());
1328 				}
1329 			}
1330 		} else {
1331 			this.fitAll();
1332 		}
1333 	}
1334 };
1335 
1336 /**
1337  * @private
1338  */
1339 ZmAppViewMgr.prototype._debugShowMetrics =
1340 function(components) {
1341 	for (var i = 0; i < components.length; i++) {
1342 		var cid = components[i];
1343 		var cont = this.getContainer(cid);
1344 		if (cont) {
1345 			var contBds = Dwt.getBounds(cont);
1346 			DBG.println("Container bounds for " + cid + ": " + contBds.x + ", " + contBds.y + " | " + contBds.width + " x " + contBds.height);
1347 		}
1348 	}
1349 };
1350 
1351 /**
1352  * Handles browser Back/Forward. We compare the new index to our current one
1353  * to see if the user has gone back or forward. If back, we pop the view,
1354  * otherwise we push the appropriate view.
1355  * 
1356  * @param {AjxEvent}	ev	the history event
1357  * 
1358  * @private
1359  */
1360 ZmAppViewMgr.prototype._historyChangeListener = function(ev) {
1361 
1362 	if (appCtxt.inStartup) {
1363         return;
1364     }
1365 	if (!(ev && ev.data)) {
1366         return;
1367     }
1368 	if (this._ignoreHistoryChange) {
1369 		this._ignoreHistoryChange = false;
1370 		return;
1371 	}
1372 
1373 	var dlg;
1374     while (dlg = DwtBaseDialog.getActiveDialog()) {
1375         if (dlg && dlg.isPoppedUp()) {
1376             dlg.popdown();
1377         }
1378     }
1379 
1380 	var hashIndex = parseInt(ev.data);
1381 	this._noHistory = true;
1382 	var viewId = this._hashViewId[hashIndex];
1383 	if (hashIndex == (this._curHashIndex - 1)) {
1384 		// Back button has been pressed
1385 		this._browserAction = ZmAppViewMgr.BROWSER_BACK;
1386 		this.popView();
1387 	} else if (hashIndex == (this._curHashIndex + 1)) {
1388 		// Forward button has been pressed
1389 		this._browserAction = ZmAppViewMgr.BROWSER_FORWARD;
1390 		this.pushView(viewId);
1391 	} else {
1392 		// Not sure where we are - just push the correct view
1393 		this._browserAction = null;
1394 		this.pushView(viewId);
1395 	}
1396 	this._curHashIndex = hashIndex;
1397 
1398 	DBG.println(AjxDebug.DBG2, "History change to " + hashIndex + ", new view: " + viewId);
1399 };
1400 
1401 /**
1402  * Handles app/tree movement. If you move the sash beyond the max or min width, pins to the respective width.
1403  * 
1404  * @private
1405  */
1406 ZmAppViewMgr.prototype._appTreeSashCallback =
1407 function(delta) {
1408 	if (!window.skin) { return; }
1409 
1410 	// ask skin for width of tree, rather than hard-coding name of tree div here
1411 	var currentWidth = skin.getTreeWidth();
1412 	if (currentWidth === null) { return 0; }
1413 
1414 	DBG.println(AjxDebug.DBG3, "************ sash callback **************");
1415 	DBG.println(AjxDebug.DBG3, "delta = " + delta);
1416 	DBG.println(AjxDebug.DBG3, "shell width = " + this._shellSz.x);
1417 	DBG.println(AjxDebug.DBG3, "current width = " + currentWidth);
1418 
1419 	// MOW: get the min/max sizes from the skin.hints
1420 	if (!this.treeMinSize) {
1421 		this.treeMinSize =
1422 			DwtCssStyle.asPixelCount(window.skin.hints.tree.minWidth || 150);
1423 		this.treeMaxSize =
1424 			DwtCssStyle.asPixelCount(window.skin.hints.tree.maxWidth || 1000);
1425 	}
1426 
1427 	// pin the resize to the minimum and maximum allowable
1428 	if (currentWidth + delta > this.treeMaxSize) {
1429 		delta = Math.max(0, this.treeMaxSize - currentWidth);
1430 	}
1431 	if (currentWidth + delta < this.treeMinSize) {
1432 		delta = Math.min(0, this.treeMinSize - currentWidth);
1433 	}
1434 
1435 	// tell skin to resize the tree to keep the separation of tree/skin clean
1436 	var newTreeWidth = currentWidth + delta;
1437 
1438 	skin.setTreeWidth(newTreeWidth);
1439 
1440 	// call fitAll() on timeout, so we dont get into a problem w/ sash movement code
1441 	var me = this;
1442 	setTimeout(function(){me.fitAll()},0);
1443 	return delta;
1444 };
1445