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