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