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 */ 27 28 /** 29 * Creates an empty tree view. 30 * @class 31 * This class displays data in a tree structure. 32 * 33 * @author Conrad Damon 34 * 35 * @param {Hash} params the hash of parameters 36 * @param {DwtControl} params.parent the tree's parent widget 37 * @param {constant} params.type the organizer type 38 * @param {String} params.className the CSS class 39 * @param {constant} params.posStyle the positioning style 40 * @param {constant} params.overviewId theoverview ID 41 * @param {String} params.headerClass the CSS class for header item 42 * @param {DwtDragSource} params.dragSrc the drag source 43 * @param {DwtDropTarget} params.dropTgt the drop target 44 * @param {constant} params.treeStyle tree style (see {@link DwtTree}) 45 * @param {Boolean} params.isCheckedByDefault sets the default state of "checked" tree style 46 * @param {Hash} params.allowedTypes a hash of org types this tree may display 47 * @param {Hash} params.allowedSubTypes a hash of org types this tree may display below top level 48 * @param {boolean} params.actionSupported (default to value from Overview if not passed) 49 * 50 * @extends DwtTree 51 */ 52 ZmTreeView = function(params) { 53 54 if (arguments.length == 0) { return; } 55 56 DwtTree.call(this, { 57 parent: params.parent, 58 parentElement: params.parentElement, 59 style: params.treeStyle, 60 isCheckedByDefault: params.isCheckedByDefault, 61 className: (params.className || "OverviewTree"), 62 posStyle: params.posStyle, 63 id: params.id 64 }); 65 66 this._headerClass = params.headerClass || "overviewHeader"; 67 this.overviewId = params.overviewId; 68 this.type = params.type; 69 this.allowedTypes = params.allowedTypes; 70 this.allowedSubTypes = params.allowedSubTypes; 71 72 this._overview = appCtxt.getOverviewController().getOverview(this.overviewId); 73 74 this._dragSrc = params.dragSrc; 75 this._dropTgt = params.dropTgt; 76 77 this.actionSupported = params.actionSupported !== undefined 78 ? params.actionSupported 79 : this._overview.actionSupported; 80 81 this.dynamicWidth = this._overview.dynamicWidth; 82 83 this._dataTree = null; 84 this._treeItemHash = {}; // map organizer to its corresponding tree item by ID 85 this._idToOrganizer = {}; // map DwtControl htmlElId to the organizer for external Drag and Drop 86 87 }; 88 89 ZmTreeView.KEY_TYPE = "_type_"; 90 ZmTreeView.KEY_ID = "_treeId_"; 91 92 // compare functions for each type 93 ZmTreeView.COMPARE_FUNC = {}; 94 95 // add space after the following items 96 ZmTreeView.ADD_SEP = {}; 97 ZmTreeView.ADD_SEP[ZmFolder.ID_TRASH] = true; 98 99 ZmTreeView.MAX_ITEMS = 50; 100 101 // Static methods 102 103 /** 104 * Finds the correct position for an organizer within a node, given 105 * a sort function. 106 * 107 * @param {DwtTreeItem} node the node under which organizer is to be added 108 * @param {ZmOrganizer} organizer the organizer 109 * @param {function} sortFunction the function for comparing two organizers 110 * @return {int} the index 111 */ 112 ZmTreeView.getSortIndex = 113 function(node, organizer, sortFunction) { 114 if (!sortFunction) return null; 115 var cnt = node.getItemCount(); 116 var children = node.getItems(); 117 for (var i = 0; i < children.length; i++) { 118 if (children[i]._isSeparator) continue; 119 var child = children[i].getData(Dwt.KEY_OBJECT); 120 if (!child) continue; 121 var test = sortFunction(organizer, child); 122 if (test == -1) { 123 return i; 124 } 125 } 126 return i; 127 }; 128 129 ZmTreeView.prototype = new DwtTree; 130 ZmTreeView.prototype.constructor = ZmTreeView; 131 132 // Public methods 133 134 /** 135 * Returns a string representation of the object. 136 * 137 * @return {String} a string representation of the object 138 */ 139 ZmTreeView.prototype.toString = 140 function() { 141 return "ZmTreeView"; 142 }; 143 144 145 /** 146 * Populates the tree view with the given data and displays it. 147 * 148 * @param {Hash} params a hash of parameters 149 * @param {ZmTree} params.dataTree data in tree form 150 * @param {Boolean} params.showUnread if <code>true</code>, show unread counts 151 * @param {Hash} params.omit a hash of organizer IDs to ignore 152 * @param {Hash} params.include a hash of organizer IDs to include 153 * @param {Boolean} params.omitParents if <code>true</code>, do NOT insert parent nodes as needed 154 * @param {Hash} params.searchTypes the types of saved searches to show 155 * @param {Boolean} params.noTooltips if <code>true</code>, don't show tooltips for tree items 156 * @param {Boolean} params.collapsed if <code>true</code>, initially leave the root collapsed 157 * @param {Hash} params.optButton a hash of data for showing a options button in the item: image, tooltip, callback 158 */ 159 ZmTreeView.prototype.set = 160 function(params) { 161 this._showUnread = params.showUnread; 162 this._dataTree = params.dataTree; 163 this._optButton = params.optButton; 164 165 this.clearItems(); 166 167 // create header item 168 var root = this._dataTree.root; 169 var isMultiAcctSubHeader = (appCtxt.multiAccounts && (this.type == ZmOrganizer.SEARCH || this.type == ZmOrganizer.TAG)); 170 var imageInfo = this._getHeaderTreeItemImage(); 171 var ti = this._headerItem = new DwtHeaderTreeItem({ 172 parent: this, 173 className: isMultiAcctSubHeader ? "DwtTreeItem" : this._headerClass, 174 imageInfo: imageInfo, 175 id: ZmId.getTreeItemId(this.overviewId, null, this.type), 176 optButton: params.optButton, 177 dndScrollCallback: this._overview && this._overview._dndScrollCallback, 178 dndScrollId: this._overview && this._overview._scrollableContainerId 179 }); 180 ti._isHeader = true; 181 var name = ZmMsg[ZmOrganizer.LABEL[this.type]]; 182 if (name) { 183 ti.setText(name); 184 } 185 ti.setData(Dwt.KEY_ID, root.id); 186 ti.setData(Dwt.KEY_OBJECT, root); 187 ti.setData(ZmTreeView.KEY_ID, this.overviewId); 188 ti.setData(ZmTreeView.KEY_TYPE, this.type); 189 if (this._dropTgt) { 190 ti.setDropTarget(this._dropTgt); 191 } 192 this._treeItemHash[root.id] = ti; 193 ti.getHtmlElement().style.overflow = "hidden"; 194 // render the root item's children (ie everything else) 195 params.treeNode = ti; 196 params.organizer = root; 197 this._render(params); 198 ti.setExpanded(!params.collapsed, null, true); 199 200 if (!appCtxt.multiAccounts) { 201 this.addSeparator(); 202 } 203 204 205 if (appCtxt.getSkinHint("noOverviewHeaders") || 206 this._hideHeaderTreeItem()) 207 { 208 ti.setVisible(false, true); 209 } 210 }; 211 212 /** 213 * Gets the tree item that represents the organizer with the given ID. 214 * 215 * @param {int} id an organizer ID 216 * @return {DwtTreeItem} the item 217 */ 218 ZmTreeView.prototype.getTreeItemById = 219 function(id) { 220 return this._treeItemHash[id]; 221 }; 222 223 /** 224 * Gets the tree view's header node. 225 * 226 * @return {DwtHeaderTreeItem} the item 227 */ 228 ZmTreeView.prototype.getHeaderItem = 229 function() { 230 return this._headerItem; 231 }; 232 233 /** 234 * Gets the currently selected organizer(s). If tree view is checkbox style, 235 * return value is an {Array} otherwise, a single {DwtTreeItem} object is returned. 236 * 237 * @return {Array|DwtTreeItem} the selected item(s) 238 */ 239 ZmTreeView.prototype.getSelected = 240 function() { 241 if (this.isCheckedStyle) { 242 var selected = []; 243 // bug #44805 - iterate thru the entire tree item hash in case there are 244 // more than one header items in the tree view (e.g. Imap accounts) 245 for (var i in this._treeItemHash) { 246 var ti = this._treeItemHash[i]; 247 if (ti && ti.getChecked()) { 248 selected.push(ti.getData(Dwt.KEY_OBJECT)); 249 } 250 } 251 return selected; 252 } else { 253 return (this.getSelectionCount() != 1) 254 ? null : this.getSelection()[0].getData(Dwt.KEY_OBJECT); 255 } 256 }; 257 258 /** 259 * Selects the tree item for the given organizer. 260 * 261 * @param {ZmOrganizer} organizer the organizer to select, or its ID 262 * @param {Boolean} skipNotify if <code>true</code>, skip notifications 263 * @param {Boolean} noFocus if <code>true</code>, select item but don't set focus to it 264 */ 265 ZmTreeView.prototype.setSelected = 266 function(organizer, skipNotify, noFocus) { 267 var id = ZmOrganizer.getSystemId((organizer instanceof ZmOrganizer) ? organizer.id : organizer); 268 if (!id || !this._treeItemHash[id]) { return; } 269 this.setSelection(this._treeItemHash[id], skipNotify, false, noFocus); 270 }; 271 272 273 // Private and protected methods 274 275 /** 276 * Draws the children of the given node. 277 * 278 * @param params [hash] hash of params: 279 * treeNode [DwtTreeItem] current node 280 * organizer [ZmOrganizer] its organizer 281 * omit [Object]* hash of system folder IDs to ignore 282 * include [object]* hash of system folder IDs to include 283 * showOrphans [boolean]* if true, show parent chain of any 284 * folder of this type, as well as the folder 285 * searchTypes [hash]* types of saved searches to show 286 * noTooltips [boolean]* if true, don't show tooltips for tree items 287 * startPos [int]* start rendering this far into list of children 288 * 289 * TODO: Add logic to support display of folders that are not normally allowed in 290 * this tree, but that have children (orphans) of an allowed type 291 * TODO: Only sort folders we're showing (requires two passes). 292 * 293 * @private 294 */ 295 ZmTreeView.prototype._render = 296 function(params) { 297 298 params.omit = params.omit || {}; 299 this._setOmit(params.omit, params.dataTree); 300 301 var org = params.organizer; 302 var children = org.children.getArray(); 303 if (org.isDataSource(ZmAccount.TYPE_IMAP)) { 304 children.sort(ZmImapAccount.sortCompare); 305 } else if (ZmTreeView.COMPARE_FUNC[this.type]) { 306 if (appCtxt.isOffline && this.type == ZmOrganizer.SEARCH) { 307 var local = []; 308 for (var j = 0; j < children.length; j++) { 309 var child = children[j]; 310 if (child && child.type == ZmOrganizer.SEARCH && !child.isOfflineGlobalSearch) { 311 local.push(child); 312 } 313 } 314 children = local; 315 } 316 // IE loses type info on the children array - the props are there and it can be iterated, 317 // but a function call like sort() blows up. So create an array local to child win. 318 if (appCtxt.isChildWindow && AjxEnv.isIE) { 319 var children1 = []; 320 for (var i = 0, len = children.length; i < len; i++) { 321 children1.push(children[i]); 322 } 323 children = children1; 324 } 325 children.sort(eval(ZmTreeView.COMPARE_FUNC[this.type])); 326 } 327 DBG.println(AjxDebug.DBG3, "Render: " + org.name + ": " + children.length); 328 var addSep = true; 329 var numItems = 0; 330 var len = children.length; 331 if (params.startPos === undefined && params.lastRenderedFolder ){ 332 for (var i = 0, len = children.length; i < len; i++) { 333 if (params.lastRenderedFolder == children[i] ){ 334 params.startPos = i + 1; // Next to lastRenderedFolder 335 break; 336 } 337 } 338 DBG.println(AjxDebug.DBG1, "load remaining folders: " + params.startPos); 339 } 340 for (var i = params.startPos || 0; i < len; i++) { 341 var child = children[i]; 342 if (!child || (params.omit && params.omit[child.nId])) { continue; } 343 if (!(params.include && params.include[child.nId])) { 344 if (!this._isAllowed(org, child)) { 345 if (params.omitParents) continue; 346 var proxy = AjxUtil.createProxy(params); 347 proxy.treeNode = null; 348 proxy.organizer = child; 349 this._render(proxy); 350 continue; 351 } 352 } 353 354 if (child.numTotal == 0 && (child.nId == ZmFolder.ID_SYNC_FAILURES)) { 355 continue; 356 } 357 358 var parentNode = params.treeNode; 359 var account = appCtxt.multiAccounts && child.getAccount(); 360 361 // bug: 43067 - reparent calendars for caldav-based accounts 362 if (account && account.isCalDavBased() && 363 child.parent.nId == ZmOrganizer.ID_CALENDAR) 364 { 365 parentNode = parentNode.parent; 366 } 367 368 // if there's a large number of folders to display, make user click on special placeholder 369 // to display remainder; we then display them MAX_ITEMS at a time 370 if (numItems >= ZmTreeView.MAX_ITEMS) { 371 if (params.startPos) { 372 // render next chunk 373 params.startPos = i; 374 params.len = (params.startPos + ZmTreeView.MAX_ITEMS >= len) ? len : 0; // hint that we're done 375 this._showRemainingFolders(params); 376 return; 377 } else if (numItems >= ZmTreeView.MAX_ITEMS * 2) { 378 // add placeholder tree item "Show remaining folders" 379 var orgs = ZmMsg[ZmOrganizer.LABEL[this.type]].toLowerCase(); 380 var name = AjxMessageFormat.format(ZmMsg.showRemainingFolders, orgs); 381 child = new ZmFolder({id:ZmFolder.ID_LOAD_FOLDERS, name:name, parent:org}); 382 child._tooltip = AjxMessageFormat.format(ZmMsg.showRemainingFoldersTooltip, [(children.length - i), orgs]); 383 var ti = this._addNew(parentNode, child); 384 ti.enableSelection(true); 385 if (this.isCheckedStyle) { 386 ti.showCheckBox(false); 387 } 388 params.lastRenderedFolder = children[i - 1]; 389 params.showRemainingFoldersNode = ti; 390 child._showFoldersCallback = new AjxCallback(this, this._showRemainingFolders, [params]); 391 if (this._dragSrc) { 392 // Bug 55763 - expand placeholder on hover; replacing the _dragHover function is the easiest way, if a bit hacky 393 ti._dragHover = this._showRemainingFolders.bind(this, params); 394 } 395 396 return; 397 } 398 } 399 400 // NOTE: Separates public and shared folders 401 if ((org.nId == ZmOrganizer.ID_ROOT) && child.link && addSep) { 402 params.treeNode.addSeparator(); 403 addSep = false; 404 } 405 this._addNew(parentNode, child, null, params.noTooltips, params.omit); 406 numItems++; 407 } 408 }; 409 410 ZmTreeView.prototype._setOmit = 411 function(omit, dataTree) { 412 for (var id in ZmFolder.HIDE_ID) { 413 omit[id] = true; 414 } 415 //note - the dataTree thing was in the previous code so I keep it, but seems all the ZmFolder.HIDE_NAME code is commented out, so 416 //not sure it's still needed. 417 dataTree = this.type !== ZmOrganizer.VOICE && dataTree; 418 if (!dataTree) { 419 return; 420 } 421 for (var name in ZmFolder.HIDE_NAME) { 422 var folder = dataTree.getByName(name); 423 if (folder) { 424 omit[folder.id] = true; 425 } 426 } 427 }; 428 429 /** 430 * a bit complicated and hard to explain - We should only allow (render on this view) 431 * a child of an "allowedSubTypes", if all its ancestors are allowed all the way to the root ("Folders"), meaning 432 * it has an ancestor that is of the allowedTypes (but is not the root) 433 * e.g. 434 * allowed: 435 * Folders-->folder1--->searchFolder1 436 * Folders--->folder1--->folder2--->folder3--->searchFolder1 437 * 438 * not allowed: 439 * Folders-->searchFolder1 440 * Folders-->searchFolder1--->searchFolder2 441 * 442 * @param org 443 * @param child 444 * @returns {*} 445 * @private 446 */ 447 ZmTreeView.prototype._isAllowed = 448 function(org, child) { 449 450 if (!org) { //could happen, for example the Zimlets root doesn't have a parent. 451 return true; //seems returning true in this case works... what a mess. 452 } 453 454 // Within the Searches tree, only show saved searches that return a type that belongs to this app 455 if (this.type === ZmOrganizer.SEARCH && child.type === ZmOrganizer.SEARCH && this._overview.appName) { 456 var searchTypes = child.search.types && child.search.types.getArray(); 457 if (!searchTypes || searchTypes.length === 0) { 458 searchTypes = [ ZmItem.MSG ]; // search with no types defaults to "message" 459 } 460 var common = AjxUtil.intersection(searchTypes, 461 ZmApp.SEARCH_TYPES[this._overview.appName] || ZmApp.SEARCH_TYPES[appCtxt.getCurrentAppName()]); 462 if (common.length === 0) { 463 return false; 464 } 465 } 466 467 if (org.nId == ZmOrganizer.ID_ROOT) { 468 return this.allowedTypes[child.type]; 469 } 470 471 //org is not root 472 if (this.allowedTypes[child.type]) { 473 return true; //optimization, end the recursion if we find a non root allowed ancestor. 474 } 475 476 if (this.allowedSubTypes[child.type]) { 477 return this._isAllowed(org.parent, org); //go up parent to see if eventually it's allowed. 478 } 479 480 return false; 481 }; 482 483 /** 484 * Adds a tree item node for the given organizer to the tree, and then adds its children. 485 * 486 * @param parentNode [DwtTreeItem] node under which to add the new one 487 * @param organizer [ZmOrganizer] organizer for the new node 488 * @param index [int]* position at which to add the new node 489 * @param noTooltips [boolean]* if true, don't show tooltips for tree items 490 * @param omit [Object]* hash of system folder IDs to ignore 491 * 492 * @private 493 */ 494 ZmTreeView.prototype._addNew = 495 function(parentNode, organizer, index, noTooltips, omit) { 496 var ti; 497 var parentControlId; 498 // check if we're adding a datasource folder 499 var dsColl = (organizer.type == ZmOrganizer.FOLDER) && appCtxt.getDataSourceCollection(); 500 var dss = dsColl && dsColl.getByFolderId(organizer.nId); 501 var ds = (dss && dss.length > 0) ? dss[0] : null; 502 503 if (ds && ds.type == ZmAccount.TYPE_IMAP) { 504 var cname = appCtxt.isFamilyMbox ? null : this._headerClass; 505 ti = new DwtHeaderTreeItem({ 506 parent:this, 507 text:organizer.getName(), 508 className:cname 509 }); 510 } else { 511 // create parent chain 512 if (!parentNode) { 513 var stack = []; 514 var parentOrganizer = organizer.parent; 515 if (parentOrganizer) { 516 while ((parentNode = this.getTreeItemById(parentOrganizer.id)) == null) { 517 stack.push(parentOrganizer); 518 parentOrganizer = parentOrganizer.parent; 519 } 520 } 521 while (parentOrganizer = stack.pop()) { 522 parentNode = this.getTreeItemById(parentOrganizer.parent.id); 523 parentControlId = ZmId.getTreeItemId(this.overviewId, parentOrganizer.id); 524 parentNode = new DwtTreeItem({ 525 parent: parentNode, 526 text: parentOrganizer.getName(), 527 imageInfo: parentOrganizer.getIconWithColor(), 528 forceNotifySelection: true, 529 arrowDisabled: !this.actionSupported, 530 dynamicWidth: this.dynamicWidth, 531 dndScrollCallback: this._overview && this._overview._dndScrollCallback, 532 dndScrollId: this._overview && this._overview._scrollableContainerId, 533 id: parentControlId 534 }); 535 parentNode.setData(Dwt.KEY_ID, parentOrganizer.id); 536 parentNode.setData(Dwt.KEY_OBJECT, parentOrganizer); 537 parentNode.setData(ZmTreeView.KEY_ID, this.overviewId); 538 parentNode.setData(ZmTreeView.KEY_TYPE, parentOrganizer.type); 539 this._treeItemHash[parentOrganizer.id] = parentNode; 540 this._idToOrganizer[parentControlId] = parentOrganizer.id; 541 } 542 } 543 var params = { 544 parent: parentNode, 545 index: index, 546 text: organizer.getName(this._showUnread), 547 arrowDisabled: !this.actionSupported, 548 dynamicWidth: this.dynamicWidth, 549 dndScrollCallback: this._overview && this._overview._dndScrollCallback, 550 dndScrollId: this._overview && this._overview._scrollableContainerId, 551 imageInfo: organizer.getIconWithColor(), 552 id: ZmId.getTreeItemId(this.overviewId, organizer.id) 553 }; 554 // now add item 555 ti = new DwtTreeItem(params); 556 this._idToOrganizer[params.id] = organizer.id; 557 } 558 559 if (appCtxt.multiAccounts && 560 (organizer.type == ZmOrganizer.SEARCH || 561 organizer.type == ZmOrganizer.TAG)) 562 { 563 ti.addClassName("DwtTreeItemChildDiv"); 564 } 565 566 ti.setDndText(organizer.getName()); 567 ti.setData(Dwt.KEY_ID, organizer.id); 568 ti.setData(Dwt.KEY_OBJECT, organizer); 569 ti.setData(ZmTreeView.KEY_ID, this.overviewId); 570 ti.setData(ZmTreeView.KEY_TYPE, organizer.type); 571 if (!noTooltips) { 572 var tooltip = organizer.getToolTip(); 573 if (tooltip) { 574 ti.setToolTipContent(tooltip); 575 } 576 } 577 if (this._dragSrc) { 578 ti.setDragSource(this._dragSrc); 579 } 580 if (this._dropTgt) { 581 ti.setDropTarget(this._dropTgt); 582 } 583 this._treeItemHash[organizer.id] = ti; 584 585 if (ZmTreeView.ADD_SEP[organizer.nId]) { 586 parentNode.addSeparator(); 587 } 588 589 // recursively add children 590 if (organizer.children && organizer.children.size()) { 591 this._render({treeNode:ti, organizer:organizer, omit:omit}); 592 } 593 594 if (ds && ds.type == ZmAccount.TYPE_IMAP) { 595 ti.setExpanded(!appCtxt.get(ZmSetting.COLLAPSE_IMAP_TREES)); 596 } 597 598 return ti; 599 }; 600 601 602 /** 603 * Gets the data (an organizer) from the tree item nearest the one 604 * associated with the given ID. 605 * 606 * @param {int} id an organizer ID 607 * @return {Object} the data or <code>null</code> for none 608 */ 609 ZmTreeView.prototype.getNextData = 610 function(id) { 611 var treeItem = this.getTreeItemById(id); 612 if(!treeItem || !treeItem.parent) { return null; } 613 614 while (treeItem && treeItem.parent) { 615 var parentN = treeItem.parent; 616 if (!(parentN instanceof DwtTreeItem)) { 617 return null; 618 } 619 var treeItems = parentN.getItems(); 620 var result = null; 621 if (treeItems && treeItems.length > 1) { 622 for(var i = 0; i < treeItems.length; i++) { 623 var tmp = treeItems[i]; 624 if (tmp == treeItem) { 625 var nextData = this.findNext(treeItem, treeItems, i); 626 if (nextData) { return nextData; } 627 var prevData = this.findPrev(treeItem, treeItems, i); 628 if (prevData) { return prevData; } 629 } 630 } 631 } 632 treeItem = treeItem.parent; 633 } 634 return null; 635 }; 636 637 ZmTreeView.prototype.findNext = 638 function(treeItem, treeItems, i) { 639 for (var j = i + 1; j < treeItems.length; j++) { 640 var next = treeItems[j]; 641 if (next && next.getData) { 642 return next.getData(Dwt.KEY_OBJECT); 643 } 644 } 645 return null; 646 }; 647 648 ZmTreeView.prototype.findPrev = 649 function(treeItem, treeItems, i) { 650 for (var j = i - 1; j >= 0; j--) { 651 var prev = treeItems[j]; 652 if (prev && prev.getData) { 653 return prev.getData(Dwt.KEY_OBJECT); 654 } 655 } 656 return null; 657 }; 658 659 /** 660 * Renders a chunk of tree items, using a timer so that the browser doesn't get overloaded. 661 * 662 * @param params [hash] hash of params (see _render) 663 * 664 * @private 665 */ 666 ZmTreeView.prototype._showRemainingFolders = 667 function(params) { 668 669 if (params.showRemainingFoldersNode){ 670 params.showRemainingFoldersNode.dispose(); 671 } 672 673 AjxTimedAction.scheduleAction(new AjxTimedAction(this, 674 function() { 675 this._render(params); 676 if (params.len) { 677 var orgs = ZmMsg[ZmOrganizer.LABEL[this.type]].toLowerCase(); 678 appCtxt.setStatusMsg(AjxMessageFormat.format(ZmMsg.foldersShown, [params.len, orgs])); 679 params.len = 0; 680 } 681 }), 100); 682 }; 683 684 ZmTreeView.prototype._getNextTreeItem = 685 function(next) { 686 var nextItem = DwtTree.prototype._getNextTreeItem.apply(this, arguments); 687 return nextItem || (this._overview && this._overview._getNextTreeItem(next, this)); 688 }; 689 690 ZmTreeView.prototype._getFirstTreeItem = 691 function() { 692 if (!this._overview) { 693 return DwtTree.prototype._getFirstTreeItem.call(tree); 694 } 695 696 var treeids = this._overview.getTreeViews(); 697 var tree = this._overview.getTreeView(treeids[0]); 698 return tree && DwtTree.prototype._getFirstTreeItem.call(tree); 699 }; 700 701 ZmTreeView.prototype._getLastTreeItem = 702 function() { 703 if (!this._overview) { 704 return DwtTree.prototype._getLastTreeItem.call(tree); 705 } 706 707 var treeids = this._overview.getTreeViews(); 708 var tree = this._overview.getTreeView(treeids[treeids.length - 1]); 709 return tree && DwtTree.prototype._getLastTreeItem.call(tree); 710 }; 711 712 ZmTreeView.prototype._hideHeaderTreeItem = 713 function() { 714 return (appCtxt.multiAccounts && appCtxt.accountList.size() > 1 && 715 (this.type == ZmOrganizer.FOLDER || 716 this.type == ZmOrganizer.ADDRBOOK || 717 this.type == ZmOrganizer.CALENDAR || 718 this.type == ZmOrganizer.TASKS || 719 this.type == ZmOrganizer.BRIEFCASE || 720 this.type == ZmOrganizer.PREF_PAGE || 721 this.type == ZmOrganizer.ZIMLET)); 722 }; 723 724 ZmTreeView.prototype._getHeaderTreeItemImage = 725 function() { 726 if (appCtxt.multiAccounts) { 727 if (this.type == ZmOrganizer.SEARCH) { return "SearchFolder"; } 728 if (this.type == ZmOrganizer.TAG) { return "TagStack"; } 729 } 730 return null; 731 }; 732