1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 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 * 27 * This file defines an overview, which holds tree views. 28 * 29 */ 30 31 /** 32 * @class 33 * Creates an overview. An overview is a {@link DwtComposite} that holds tree views. 34 * 35 * @author Conrad Damon 36 * 37 * @param {Hash} params a hash of parameters 38 * @param {String} params.id the id for the HTML element 39 * @param {String} params.overviewId the overview id 40 * @param {String} params.containerId the overview container id (multi-account) 41 * @param {Array} params.treeIds an array of organizer types that may be displayed in this overview 42 * @param {ZmZimbraAccount} params.account the account this overview belongs to 43 * @param {DwtControl} params.parent the containing widget 44 * @param {String} params.overviewClass the class name for overview DIV 45 * @param {constant} params.posStyle the positioning style for overview DIV 46 * @param {constant} params.scroll the scrolling style for overview DIV 47 * @param {Boolean} params.selectionSupported <code>true</code> left-click selection is supported 48 * @param {Boolean} params.actionSupported <code>true</code> if right-click action menus are supported 49 * @param {Boolean} params.dndSupported <code>true</code> if drag-and-drop is supported 50 * @param {String} params.headerClass the class name for header item 51 * @param {Boolean} params.showUnread if <code>true</code>, unread counts will be shown 52 * @param {Boolean} params.showNewButtons if <code>true</code>, tree headers may have buttons for creating new organizers 53 * @param {constant} params.treeStyle the default display style for tree views 54 * @param {Boolean} params.isCheckedByDefault the default state for "checked" display style 55 * @param {Boolean} params.noTooltips if <code>true</code>, do not show toolt ips for tree items 56 * @param {Boolean} params.skipImplicit if <code>true</code>, do not save implicit prefs of expanded/collapsed node status for this overview (see ZmDialog.prototype._setOverview) 57 * @param {Boolean} params.dynamicWidth if <code>true</code>, the width is dynamic, i.e. the width is auto instead of fixed. Used for ZmDolderChooser so far. 58 * @param {ZmOverviewController} controller the overview controller 59 * 60 * @extends DwtComposite 61 */ 62 ZmOverview = function(params, controller) { 63 64 var overviewClass = params.overviewClass ? params.overviewClass : "ZmOverview"; 65 params.id = params.id || ZmId.getOverviewId(params.overviewId); 66 DwtComposite.call(this, {parent:params.parent, className:overviewClass, posStyle:params.posStyle, id:params.id}); 67 68 this._controller = controller; 69 70 this.setScrollStyle(params.scroll || Dwt.SCROLL_Y); 71 72 this.overviewId = params.overviewId; 73 this.containerId = params.containerId; 74 this.account = params.account; 75 this.selectionSupported = params.selectionSupported; 76 this.actionSupported = params.actionSupported; 77 this.dynamicWidth = params.dynamicWidth; 78 this.dndSupported = params.dndSupported; 79 this.headerClass = params.headerClass; 80 this.showUnread = params.showUnread; 81 this.showNewButtons = params.showNewButtons; 82 this.treeStyle = params.treeStyle; 83 this.isCheckedByDefault = params.isCheckedByDefault; 84 this.noTooltips = params.noTooltips; 85 this.isAppOverview = params.isAppOverview; 86 this.skipImplicit = params.skipImplicit; 87 this.appName = params.appName; 88 89 this._treeIds = []; 90 this._treeHash = {}; 91 this._treeParents = {}; 92 93 // Create a parent div for each overview tree. 94 var doc = document; 95 var element = this.getHtmlElement(); 96 if (params.treeIds) { 97 for (var i = 0, count = params.treeIds.length; i < count; i++) { 98 var div = doc.createElement("DIV"); 99 var treeId = params.treeIds[i]; 100 this._treeParents[treeId] = div.id = [this.overviewId, treeId].join("-parent-"); 101 element.appendChild(div); 102 } 103 } 104 105 if (this.dndSupported) { 106 this._scrollableContainerId = this.containerId || this.overviewId; 107 var container = this.containerId ? document.getElementById(this.containerId) : this.getHtmlElement(); 108 var params = {container:container, threshold:15, amount:5, interval:10, id:this._scrollableContainerId}; 109 this._dndScrollCallback = new AjxCallback(null, DwtControl._dndScrollCallback, [params]); 110 } 111 112 this.setAttribute('aria-label', ZmMsg.overviewLabel); 113 114 // Let overview be a single tab stop, then manage focus among items using arrow keys 115 this.tabGroupMember = this; 116 }; 117 118 ZmOverview.prototype = new DwtComposite; 119 ZmOverview.prototype.constructor = ZmOverview; 120 121 ZmOverview.prototype.isZmOverview = true; 122 ZmOverview.prototype.toString = function() { return "ZmOverview"; }; 123 124 ZmOverview.prototype.role = "navigation"; 125 126 127 /** 128 * Gets the parent element for the given tree id. 129 * 130 * @param {String} treeId the tree id 131 * @return {Object} the tree parent element 132 */ 133 ZmOverview.prototype.getTreeParent = 134 function(treeId) { 135 return this._treeParents[treeId]; 136 }; 137 138 /** 139 * Displays the given list of tree views in this overview. 140 * 141 * @param {Array} treeIds an array of organizer ids 142 * @param {Hash} omit the hash of organizer ids to ignore 143 */ 144 ZmOverview.prototype.set = 145 function(treeIds, omit) { 146 if (treeIds && treeIds.length) { 147 for (var i = 0; i < treeIds.length; i++) { 148 this.setTreeView(treeIds[i], omit); 149 } 150 } 151 }; 152 153 /** 154 * Sets the given tree view. Its tree controller is responsible for using the appropriate 155 * data tree to populate the tree view. The tree controller will be lazily created if 156 * necessary. The tree view is cleared before it is set. The tree view inherits options 157 * from this overview. 158 * 159 * @param {String} treeId the organizer ID 160 * @param {Hash} omit a hash of organizer ids to ignore 161 */ 162 ZmOverview.prototype.setTreeView = function(treeId, omit) { 163 164 if (!appCtxt.checkPrecondition(ZmOrganizer.PRECONDITION[treeId])) { 165 return; 166 } 167 168 AjxDispatcher.require(ZmOrganizer.ORG_PACKAGE[treeId]); 169 var treeController = this._controller.getTreeController(treeId); 170 if (!treeController) { return; } 171 if (this._treeHash[treeId]) { 172 treeController.clearTreeView(this.overviewId); 173 } else { 174 this._treeIds.push(treeId); 175 } 176 var params = { 177 overviewId: this.overviewId, 178 omit: omit, 179 showUnread: this.showUnread, 180 account: this.account 181 }; 182 this._treeHash[treeId] = treeController.show(params); // render tree view 183 }; 184 185 ZmOverview.prototype.clearChangeListener = function(treeIds) { 186 // Added for the attachMail zimlet, operating in a child window. This clears the listeners added to 187 // the parent window trees (which causes problems in IE when the child window closes). See Bugs 188 // 99453 and 99913 189 for (var i = 0; i < treeIds.length; i++) { 190 var treeController = this._controller.getTreeController(treeIds[i]); 191 var changeListener = treeController._getTreeChangeListener(); 192 if (changeListener) { 193 var folderTree = appCtxt.getFolderTree(); 194 if (folderTree) { 195 folderTree.removeChangeListener(changeListener); 196 } 197 } 198 } 199 } 200 201 /** 202 * Gets the tree view. 203 * 204 * @param {String} treeId the tree id 205 * @return {Object} the tree view 206 */ 207 ZmOverview.prototype.getTreeView = 208 function(treeId) { 209 return this._treeHash[treeId]; 210 }; 211 212 /** 213 * Gets the tree views. 214 * 215 * @return {Array} an array of tree ids 216 */ 217 ZmOverview.prototype.getTreeViews = 218 function() { 219 return this._treeIds; 220 }; 221 222 /** 223 * Searches the tree views for the tree item whose data object has the given ID and type. 224 * 225 * @param {int} id the id to look for 226 * @param {constant} type the item must also have this type 227 * @return {Object} the item or <code>null</code> if not found 228 */ 229 ZmOverview.prototype.getTreeItemById = 230 function(id, type) { 231 if (!id) { return null; } 232 for (var i = 0; i < this._treeIds.length; i++) { 233 var treeView = this._treeHash[this._treeIds[i]]; 234 if (treeView) { 235 var item = treeView.getTreeItemById && treeView.getTreeItemById(id); 236 if (item && (!type || (this._treeIds[i] == type))) { 237 return item; 238 } 239 } 240 } 241 return null; 242 }; 243 244 /** 245 * Returns the first selected item within this overview. 246 * 247 * @param {Boolean} typeOnly if <code>true</code>, return the type only 248 * @return {Object} the item (or type if <code>typeOnly</code>) or <code>null</code> if not found 249 */ 250 ZmOverview.prototype.getSelected = 251 function(typeOnly) { 252 for (var i = 0; i < this._treeIds.length; i++) { 253 var treeView = this._treeHash[this._treeIds[i]]; 254 if (treeView) { 255 var item = treeView.getSelected(); 256 if (item) { 257 return typeOnly ? treeView.type : item; 258 } // otherwise continue with other treeviews to look for selected item 259 } 260 } 261 return null; 262 }; 263 264 ZmOverview.prototype.deselectAllTreeViews = 265 function() { 266 for (var i = 0; i < this._treeIds.length; i++) { 267 var treeView = this._treeHash[this._treeIds[i]]; 268 if (treeView) { 269 treeView.deselectAll(); 270 } 271 } 272 }; 273 274 275 /** 276 * Selects the item with the given ID within the given tree in this overview. 277 * 278 * @param {String} id the item id 279 * @param {constant} type the tree type 280 */ 281 ZmOverview.prototype.setSelected = 282 function(id, type) { 283 var ti, treeView; 284 if (type) { 285 treeView = this._treeHash[type]; 286 ti = treeView && treeView.getTreeItemById(id); 287 } else { 288 for (var type in this._treeHash) { 289 treeView = this._treeHash[type]; 290 ti = treeView && treeView.getTreeItemById(id); 291 if (ti) { break; } 292 } 293 } 294 295 if (ti && (this._selectedTreeItem != ti)) { 296 treeView.setSelected(id, true, true); 297 } 298 this.itemSelected(ti); 299 }; 300 301 /** 302 * Given a tree item, de-selects all items in the overview's 303 * other tree views, enforcing single selection within the overview. 304 * Passing a null argument will clear selection in all tree views. 305 * 306 * @param {DwtTreeItem} treeItem the tree item 307 */ 308 ZmOverview.prototype.itemSelected = 309 function(treeItem) { 310 if (appCtxt.multiAccounts && treeItem) { 311 var name = this.overviewId.substring(0, this.overviewId.indexOf(":")); 312 var container = this._controller.getOverviewContainer(name); 313 if (container) { 314 container.deselectAll(this); 315 } 316 } 317 318 if (this._selectedTreeItem && (this._selectedTreeItem._tree != (treeItem && treeItem._tree))) { 319 this._selectedTreeItem._tree.deselectAll(); 320 } 321 322 this._selectedTreeItem = treeItem; 323 }; 324 325 /** 326 * Clears the tree views. 327 */ 328 ZmOverview.prototype.clear = 329 function() { 330 for (var i = 0; i < this._treeIds.length; i++) { 331 var treeId = this._treeIds[i]; 332 if (this._treeHash[treeId]) { 333 var treeController = this._controller.getTreeController(treeId); 334 treeController.clearTreeView(this.overviewId); 335 delete this._treeHash[treeId]; 336 } 337 } 338 }; 339 340 ZmOverview.prototype.clearSelection = 341 function() { 342 if (this._selectedTreeItem) { 343 this._selectedTreeItem._tree.deselectAll(); 344 } 345 }; 346 347 /** 348 * @private 349 */ 350 ZmOverview.prototype._initialize = 351 function() { 352 // do nothing. 353 // - called by DwtTreeItem b/c it thinks its adding another tree item 354 }; 355 356 /** 357 * @private 358 */ 359 ZmOverview.prototype.focus = function() { 360 361 var item = this._selectedTreeItem; 362 if (!item) { 363 var tree = this._treeHash[this._treeIds[0]]; 364 if (tree) { 365 item = tree._getNextTreeItem(true); 366 } 367 } 368 369 if (item) { 370 item.focus(); 371 item._tree.setSelection(item, false, true); 372 return item; 373 } 374 }; 375 376 /** 377 * @private 378 */ 379 ZmOverview.prototype.blur = function() { 380 381 var item = this._selectedTreeItem; 382 if (item) { 383 item._blur(); 384 } 385 }; 386 387 /** 388 * Returns the next/previous selectable tree item within this overview, starting with the 389 * tree immediately after/before the given one. Used to handle tree item selection that 390 * spans trees. 391 * 392 * @param {Boolean} next if <code>true</code>, look for next item as opposed to previous item 393 * @param {ZmTreeView} tree the tree that we are just leaving 394 * 395 * @private 396 */ 397 ZmOverview.prototype._getNextTreeItem = 398 function(next, tree) { 399 400 for (var i = 0; i < this._treeIds.length; i++) { 401 if (this._treeHash[this._treeIds[i]] == tree) { 402 break; 403 } 404 } 405 406 var nextItem = null; 407 var idx = next ? i + 1 : i - 1; 408 tree = this._treeHash[this._treeIds[idx]]; 409 while (tree) { 410 nextItem = DwtTree.prototype._getNextTreeItem.call(tree, next); 411 if (nextItem) { 412 break; 413 } 414 idx = next ? idx + 1 : idx - 1; 415 tree = this._treeHash[this._treeIds[idx]]; 416 } 417 418 return nextItem; 419 }; 420