1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 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 /** 26 * Creates a Tree Item. 27 * @constructor 28 * @class 29 * This class implements a tree item widget. 30 * 31 * @author Ross Dargahi 32 * 33 * @param {hash} params a hash of parameters 34 * @param {DwtComposite} params.parent the parent widget 35 * @param {number} params.index the index at which to add this control among parent's children 36 * @param {string} params.text the label text for the tree item 37 * @param {string} params.imageInfo the icon for the left end of the tree item 38 * @param {string} params.extraInfo the icon for the right end of the tree item 39 * @param {string} params.expandNodeImage the icon to use for expanding tree item (instead of default) 40 * @param {string} params.collapseNodeImage the icon to use for collapsing tree item (instead of default) 41 * @param {string} params.className the CSS class 42 * @param {constant} params.posStyle the positioning style (see {@link DwtControl}) 43 * @param {boolean} params.deferred if <code>true</code>, postpone initialization until needed. 44 * @param {boolean} params.selectable if <code>true</code>, this item is selectable 45 * @param {boolean} params.forceNotifySelection force notify selection even if checked style 46 * @param {boolean} params.forceNotifyAction force notify action even if checked style 47 * @param {boolean} params.singleClickAction if <code>true</code>, an action is performed in single click 48 * @param {AjxCallback} params.dndScrollCallback the callback triggered when scrolling of a drop area for an object being dragged 49 * @param {string} params.dndScrollId the id 50 * @param {boolean} params.arrowDisabled 51 * @param {boolean} params.dynamicWidth if <code>true</code>, the table should be width auto instead of the default fixed 52 * 53 * @extends DwtComposite 54 */ 55 DwtTreeItem = function(params) { 56 57 if (arguments.length == 0) { return; } 58 59 params = Dwt.getParams(arguments, DwtTreeItem.PARAMS); 60 var parent = params.parent; 61 if (parent instanceof DwtTree) { 62 this._tree = parent; 63 } else if (parent instanceof DwtTreeItem) { 64 this._tree = parent._tree; 65 } else { 66 throw new DwtException("DwtTreeItem parent must be a DwtTree or DwtTreeItem", DwtException.INVALIDPARENT, "DwtTreeItem"); 67 } 68 69 this._origClassName = params.className || "DwtTreeItem"; 70 this._textClassName = [this._origClassName, "Text"].join("-"); 71 this._selectedClassName = this._origClassName + ' ' + [this._origClassName, DwtCssStyle.SELECTED].join("-"); 72 this._selectedFocusedClassName = this._selectedClassName + ' ' + [this._origClassName, DwtCssStyle.SELECTED, DwtCssStyle.FOCUSED].join("-"); 73 this._actionedClassName = this._origClassName + ' ' + [this._origClassName, DwtCssStyle.ACTIONED].join("-"); 74 this._dragOverClassName = this._origClassName + ' ' + [this._origClassName, DwtCssStyle.DRAG_OVER].join("-"); 75 this._treeItemTextClass = "DwtTreeItem-Text"; 76 this._treeItemExtraImgClass = "DwtTreeItem-ExtraImg"; 77 78 this._dynamicWidth = params.dynamicWidth; 79 80 params.deferred = (params.deferred !== false); 81 params.className = 'DwtTreeItem-Control'; 82 DwtComposite.call(this, params); 83 84 this._imageInfoParam = params.imageInfo; 85 this._extraInfo = params.extraInfo; 86 this._textParam = params.text; 87 this._deferred = params.deferred; 88 this._expandNodeImage = params.expandNodeImage || "NodeExpanded"; 89 this._collapseNodeImage = params.collapseNodeImage || "NodeCollapsed"; 90 this._itemChecked = false; 91 this._initialized = false; 92 this._selectionEnabled = Boolean(params.selectable !== false); 93 this._forceNotifySelection = Boolean(params.forceNotifySelection); 94 this._actionEnabled = true; 95 this._forceNotifyAction = Boolean(params.forceNotifyAction); 96 this._dndScrollCallback = params.dndScrollCallback; 97 this._dndScrollId = params.dndScrollId; 98 this._arrowDisabled = params.arrowDisabled; 99 100 if (params.singleClickAction) { 101 this._singleClickAction = true; 102 this._selectedFocusedClassName = this._selectedClassName = this._textClassName; 103 this._hoverClassName = [this._origClassName, DwtCssStyle.HOVER].join("-"); 104 } else { 105 this._hoverClassName = this._textClassName; 106 } 107 108 // if our parent is DwtTree or our parent is initialized and is not deferred 109 // type or is expanded, then initialize ourself, else wait 110 if (parent instanceof DwtTree || (parent._initialized && (!parent._deferred || parent._expanded)) || !params.deferred) { 111 this._initialize(params.index); 112 } else { 113 parent._addDeferredChild(this, params.index); 114 this._index = params.index; 115 } 116 }; 117 118 DwtTreeItem.PARAMS = ["parent", "index", "text", "imageInfo", "deferred", "className", "posStyle", 119 "forceNotifySelection", "forceNotifyAction"]; 120 121 DwtTreeItem.prototype = new DwtComposite; 122 DwtTreeItem.prototype.constructor = DwtTreeItem; 123 124 DwtTreeItem.prototype.isDwtTreeItem = true; 125 DwtTreeItem.prototype.toString = function() { return "DwtTreeItem"; }; 126 127 DwtTreeItem.prototype.TEMPLATE = "dwt.Widgets#ZTreeItem"; 128 129 DwtTreeItem.prototype.role = "treeitem"; 130 DwtTreeItem.prototype.isFocusable = true; 131 132 DwtTreeItem.prototype._checkBoxVisible = true; // Assume it's shown, if check style 133 134 // Consts 135 136 DwtTreeItem._NODECELL_DIM = "16px"; 137 DwtTreeItem._processedMouseDown = false; 138 139 // Public Methods 140 141 DwtTreeItem.prototype.dispose = 142 function() { 143 DwtComposite.prototype.dispose.call(this); 144 this._itemDiv = null; 145 this._nodeCell = null; 146 this._checkBoxCell = null; 147 this._checkedImg = null; 148 this._checkBox = null; 149 this._imageCell = null; 150 this._textCell = null; 151 this._childDiv = null; 152 this._initialized = false; 153 }; 154 155 /** 156 * override DwtControl.prototype.getData to take care of special case of KEY_OBJECT of type ZmOrganizer. See bug 82027 157 * @param key 158 * @return {*} 159 */ 160 DwtTreeItem.prototype.getData = 161 function(key) { 162 var obj = this._data[key]; 163 if (key !== Dwt.KEY_OBJECT || !obj || !obj.isZmOrganizer) { 164 return obj; 165 } 166 //special case for ZmOrganizer instance of the Dwt.KEY_OBJECT attribute. 167 //bug 82027 - the folder attributes such as name could be wrong after refresh block+ rename when new instance was created but not set to the item Dwt.KEY_OBJECT attribute. 168 var cachedOrganizer = obj && appCtxt.cacheGet(obj.id); 169 return cachedOrganizer || obj; //just in case somehow it's no longer cached. No idea if could happen. 170 }; 171 172 /** 173 * Checks if the item is checked. 174 * 175 * @return {boolean} <code>true</code> if the item is checked 176 */ 177 DwtTreeItem.prototype.getChecked = 178 function() { 179 return this._itemChecked; 180 }; 181 182 /** 183 * Sets the checked flag. 184 * 185 * @param {boolean} checked if <code>true</code>, check the item 186 * @param {boolean} force if <code>true</code>, force the setting 187 */ 188 DwtTreeItem.prototype.setChecked = 189 function(checked, force) { 190 if ((this._itemChecked != checked) || force) { 191 this._itemChecked = checked; 192 if (this._checkBox != null && 193 (this._checkBoxCell && Dwt.getVisible(this._checkBoxCell))) 194 { 195 Dwt.setVisible(this._checkedImg, checked); 196 } 197 } 198 }; 199 200 DwtTreeItem.prototype._handleCheckboxOnclick = 201 function(ev) { 202 this.setChecked(!Dwt.getVisible(this._checkedImg)); 203 204 ev = ev || window.event; 205 ev.item = this; 206 this._tree._itemChecked(this, ev); 207 }; 208 209 DwtTreeItem.prototype.getExpanded = 210 function() { 211 return this._expanded; 212 }; 213 214 /** 215 * Expands or collapses this tree item. 216 * 217 * @param {boolean} expanded if <code>true</code>, expands this node; otherwise collapses it 218 * @param {boolean} recurse if <code>true</code>, expand children recursively (does not apply to collapsing) 219 * @param {boolean} skipNotify if <code>true</code>, do not notify the listeners 220 */ 221 DwtTreeItem.prototype.setExpanded = 222 function(expanded, recurse, skipNotify) { 223 // Go up the chain, ensuring that parents are expanded/initialized 224 if (expanded) { 225 var p = this.parent; 226 while (p instanceof DwtTreeItem && !p._expanded) { 227 p.setExpanded(true); 228 p = p.parent; 229 } 230 // Realize any deferred children 231 this._realizeDeferredChildren(); 232 } 233 234 // If we have children, then allow for expanding/collapsing 235 if (this.getNumChildren()) { 236 if (expanded && recurse) { 237 if (!this._expanded) { 238 this._expand(expanded, null, skipNotify); 239 } 240 var a = this.getChildren(); 241 for (var i = 0; i < a.length; i++) { 242 if (a[i] instanceof DwtTreeItem) { 243 a[i].setExpanded(expanded, recurse, skipNotify); 244 } 245 } 246 } else if (this._expanded != expanded) { 247 this._expand(expanded, null, skipNotify); 248 } 249 } 250 }; 251 252 /** 253 * Gets the child item count. 254 * 255 * @return {number} the child item count 256 */ 257 DwtTreeItem.prototype.getItemCount = 258 function() { 259 return this._children.size(); 260 }; 261 262 /** 263 * Gets the items. 264 * 265 * @return {array} an array of child {@link DwtTreeItem} objects 266 */ 267 DwtTreeItem.prototype.getItems = 268 function() { 269 return this._children.getArray(); 270 }; 271 272 DwtTreeItem.prototype.getChildIndex = 273 function(item) { 274 return this._children.indexOf(item); 275 }; 276 277 /** 278 * Get the nesting level; the toplevel tree is zero, and each lower layer 279 * increases by one. 280 * 281 * @return {number} the child item count 282 */ 283 DwtTreeItem.prototype.getNestingLevel = 284 function() { 285 return this.parent.getNestingLevel() + 1; 286 }; 287 288 /** 289 * Gets the image. 290 * 291 * @return {string} the image 292 */ 293 DwtTreeItem.prototype.getImage = 294 function() { 295 return this._imageInfo; 296 }; 297 298 /** 299 * Sets the image. 300 * 301 * @param {string} imageInfo the image 302 */ 303 DwtTreeItem.prototype.setImage = 304 function(imageInfo) { 305 if (this._initialized) { 306 if (this._imageCell) { 307 AjxImg.setImage(this._imageCell, imageInfo); 308 } 309 this._imageInfo = imageInfo; 310 } else { 311 this._imageInfoParam = imageInfo; 312 } 313 }; 314 315 DwtTreeItem.prototype.setDndImage = 316 function(imageInfo) { 317 this._dndImageInfo = imageInfo; 318 }; 319 320 DwtTreeItem.prototype.getSelected = 321 function() { 322 return this._selected; 323 }; 324 325 DwtTreeItem.prototype.getActioned = 326 function() { 327 return this._actioned; 328 }; 329 330 /** 331 * Gets the text. 332 * 333 * @return {string} the text 334 */ 335 DwtTreeItem.prototype.getText = 336 function() { 337 return this._text; 338 }; 339 340 /** 341 * Sets the text. 342 * 343 * @param {string} text the text 344 */ 345 DwtTreeItem.prototype.setText = 346 function(text) { 347 if (this._initialized && this._textCell) { 348 if (!text) text = ""; 349 this._text = this._textCell.innerHTML = text; 350 } else { 351 this._textParam = text; 352 } 353 }; 354 355 /** 356 * Sets the drag-and-drop text. 357 * 358 * @param {string} text the text 359 */ 360 DwtTreeItem.prototype.setDndText = 361 function(text) { 362 this._dndText = text; 363 }; 364 365 /** 366 * Shows (or hides) the check box. 367 * 368 * @param {boolean} show if <code>true</code>, show the check box 369 */ 370 DwtTreeItem.prototype.showCheckBox = 371 function(show) { 372 this._checkBoxVisible = show; 373 if (this._checkBoxCell) { 374 Dwt.setVisible(this._checkBoxCell, show); 375 } 376 }; 377 378 /** 379 * Shows (or hides) the expansion icon. 380 * 381 * @param {boolean} show if <code>true</code>, show the expansion icon 382 */ 383 DwtTreeItem.prototype.showExpansionIcon = 384 function(show) { 385 if (this._nodeCell) { 386 Dwt.setVisible(this._nodeCell, show); 387 } 388 }; 389 390 /** 391 * Enables (or disables) the selection. 392 * 393 * @param {boolean} enable if <code>true</code>, enable selection 394 */ 395 DwtTreeItem.prototype.enableSelection = 396 function(enable) { 397 this._selectionEnabled = enable; 398 this._selectedClassName = enable 399 ? this._origClassName + "-" + DwtCssStyle.SELECTED 400 : this._origClassName; 401 402 }; 403 404 DwtTreeItem.prototype.isSelectionEnabled = 405 function() { 406 return this._selectionEnabled; 407 }; 408 409 410 DwtTreeItem.prototype.enableAction = 411 function(enable) { 412 this._actionEnabled = enable; 413 }; 414 415 /** 416 * Adds a separator at the given index. If no index is provided, adds it at the 417 * end. A separator cannot currently be added as the first item (the child DIV will 418 * not have been created). 419 * 420 * @param {number} index the position at which to add the separator 421 */ 422 DwtTreeItem.prototype.addSeparator = 423 function(index) { 424 this._children.add((new DwtTreeItemSeparator(this)), index); 425 }; 426 427 /** 428 * Makes this tree item, or just part of it, visible or hidden. 429 * 430 * @param {boolean} visible if <code>true</code>, item (or part of it) becomes visible 431 * @param {boolean} itemOnly if <code>true</code>, apply to this item's DIV only; child items are unaffected 432 * @param {boolean} childOnly if <code>true</code>, apply to this item's child items only 433 */ 434 DwtTreeItem.prototype.setVisible = 435 function(visible, itemOnly, childOnly) { 436 if (itemOnly && !childOnly) { 437 Dwt.setVisible(this._itemDiv, visible); 438 } else if (childOnly && !itemOnly) { 439 Dwt.setVisible(this._childDiv, visible); 440 } else { 441 DwtComposite.prototype.setVisible.call(this, visible); 442 } 443 }; 444 445 DwtTreeItem.prototype.removeChild = 446 function(child) { 447 if (child._initialized) { 448 this._tree._deselect(child); 449 if (this._childDiv) { 450 this._childDiv.removeChild(child.getHtmlElement()); 451 } 452 } 453 this._children.remove(child); 454 455 // if we have no children and we are expanded, then mark us a collapsed. 456 // Also if there are no deferred children, then make sure we remove the 457 // expand/collapse icon and replace it with a blank16Icon. 458 if (this._children.size() == 0) { 459 if (this._expanded) 460 this._expanded = false; 461 462 this._expandable = false; 463 this.removeAttribute('aria-expanded') 464 465 if (this._initialized && this._nodeCell) { 466 AjxImg.setImage(this._nodeCell, "Blank_16"); 467 var imgEl = AjxImg.getImageElement(this._nodeCell); 468 if (imgEl) 469 Dwt.clearHandler(imgEl, DwtEvent.ONMOUSEDOWN); 470 } 471 } 472 }; 473 474 DwtTreeItem.prototype.getKeyMapName = 475 function() { 476 return DwtKeyMap.MAP_TREE; 477 }; 478 479 DwtTreeItem.prototype.handleKeyAction = 480 function(actionCode, ev) { 481 482 switch (actionCode) { 483 484 case DwtKeyMap.ENTER: 485 this._tree.setEnterSelection(this, true); 486 break; 487 488 489 case DwtKeyMap.NEXT: { 490 var ti = this._tree._getNextTreeItem(true); 491 if (ti) { 492 ti._tree.setSelection(ti, false, true); 493 } 494 break; 495 } 496 497 case DwtKeyMap.PREV: { 498 var ti = this._tree._getNextTreeItem(false); 499 if (ti) { 500 ti._tree.setSelection(ti, false, true); 501 } 502 break; 503 } 504 505 case DwtKeyMap.SELECT_FIRST: 506 case DwtKeyMap.SELECT_LAST: { 507 var ti = (actionCode === DwtKeyMap.SELECT_FIRST) ? 508 this._tree._getFirstTreeItem() : this._tree._getLastTreeItem(); 509 if (ti) { 510 ti._tree.setSelection(ti, false, true); 511 } 512 break; 513 } 514 515 case DwtKeyMap.EXPAND: { 516 if (!this._expanded) { 517 this.setExpanded(true, false, true); 518 } else if (this._children.size() > 0) { 519 // Select first child node 520 var firstChild = this._children.get(0); 521 this._tree.setSelection(firstChild, false, true); 522 } 523 break; 524 } 525 526 case DwtKeyMap.COLLAPSE: { 527 if (this._expanded) { 528 this.setExpanded(false, false, true); 529 } else if (this.parent.isDwtTreeItem) { 530 // select parent 531 this._tree.setSelection(this.parent, false, true); 532 } 533 break; 534 } 535 536 case DwtKeyMap.SUBMENU: { 537 var target = this.getHtmlElement(); 538 var p = Dwt.toWindow(target, 0, 0); 539 var s = this.getSize(); 540 var docX = p.x + s.x / 4; 541 var docY = p.y + s.y / 2; 542 this._gotMouseDownRight = true; 543 this._emulateSingleClick({dwtObj:this, target:target, button:DwtMouseEvent.RIGHT, 544 docX:docX, docY:docY, kbNavEvent:true}); 545 break; 546 } 547 548 default: 549 return false; 550 551 } 552 553 return true; 554 }; 555 556 DwtTreeItem.prototype.addNodeIconListeners = 557 function() { 558 var imgEl = AjxImg.getImageElement(this._nodeCell); 559 if (imgEl) { 560 Dwt.setHandler(imgEl, DwtEvent.ONMOUSEDOWN, DwtTreeItem._nodeIconMouseDownHdlr); 561 Dwt.setHandler(imgEl, DwtEvent.ONMOUSEUP, DwtTreeItem._nodeIconMouseUpHdlr); 562 } 563 }; 564 565 DwtTreeItem.prototype._initialize = 566 function(index, realizeDeferred, forceNode) { 567 this._checkState(); 568 if (AjxEnv.isIE) { 569 this._setEventHdlrs([DwtEvent.ONMOUSEENTER, DwtEvent.ONMOUSELEAVE]); 570 } 571 if (AjxEnv.isSafari) { // bug fix #25016 572 this._setEventHdlrs([DwtEvent.ONCONTEXTMENU]); 573 } 574 var data = { 575 id: this._htmlElId, 576 divClassName: this._origClassName, 577 isCheckedStyle: this._tree.isCheckedStyle, 578 textClassName: this._textClassName 579 }; 580 581 this._createHtmlFromTemplate(this.TEMPLATE, data); 582 583 // add this object's HTML element to the DOM 584 this.parent._addItem(this, index, realizeDeferred); 585 586 // cache DOM objects here 587 this._itemDiv = document.getElementById(data.id + "_div"); 588 this._nodeCell = document.getElementById(data.id + "_nodeCell"); 589 this._checkBoxCell = document.getElementById(data.id + "_checkboxCell"); 590 this._checkBox = document.getElementById(data.id + "_checkbox"); 591 this._checkedImg = document.getElementById(data.id + "_checkboxImg"); 592 this._imageCell = document.getElementById(data.id + "_imageCell"); 593 this._textCell = document.getElementById(data.id + "_textCell"); 594 this._extraCell = document.getElementById(data.id + "_extraCell"); 595 596 /* assign the ARIA level */ 597 this.setAttribute("aria-level", this.getNestingLevel()); 598 599 /* add a label for screenreaders, so that they don't read the entire 600 element */ 601 if (this._textCell) { 602 this.setAttribute("aria-labelledby", this._textCell.id); 603 } 604 605 if (this._dynamicWidth){ 606 var tableNode = document.getElementById(data.id + "_table"); 607 if (tableNode) { 608 tableNode.style.tableLayout = "auto"; 609 } 610 } 611 612 this._expandable = false; 613 this.removeAttribute('aria-expanded'); 614 615 // If we have deferred children, then make sure we set up accordingly 616 if (this._nodeCell) { 617 this._nodeCell.style.minWidth = this._nodeCell.style.width = this._nodeCell.style.height = DwtTreeItem._NODECELL_DIM; 618 if (this._children.size() > 0 || forceNode) { 619 this._expandable = true; 620 AjxImg.setImage(this._nodeCell, this._collapseNodeImage); 621 this.addNodeIconListeners(); 622 } 623 } 624 625 if (this._extraCell) { 626 AjxImg.setImage(this._extraCell, (this._extraInfo || "Blank_16")); 627 this._extraCell.className = this._treeItemExtraImgClass; 628 } 629 630 // initialize checkbox 631 if (this._tree.isCheckedStyle && this._checkBox) { 632 this._checkBox.onclick = AjxCallback.simpleClosure(this._handleCheckboxOnclick, this); 633 this.showCheckBox(this._checkBoxVisible); 634 this.setChecked(this._tree.isCheckedByDefault, true); 635 } 636 637 // initialize icon 638 if (this._imageCell && this._imageInfoParam) { 639 AjxImg.setImage(this._imageCell, this._imageInfoParam); 640 this._imageInfo = this._imageInfoParam; 641 } 642 643 // initialize text 644 if (this._textCell && this._textParam) { 645 this._textCell.innerHTML = this._text = this._textParam; 646 } 647 this._expanded = this._selected = this._actioned = false; 648 this._gotMouseDownLeft = this._gotMouseDownRight = false; 649 this._addMouseListeners(); 650 651 this._initialized = true; 652 }; 653 654 /** 655 * Sets the tree item color. 656 * 657 * @param {string} className the class name 658 */ 659 DwtTreeItem.prototype.setTreeItemColor = 660 function(className) { 661 var id = this._htmlElId +"_table"; 662 var treeItemTableEl = document.getElementById(id); 663 var treeItemDivEl = document.getElementById(this._htmlElId + "_div"); 664 var treeItemEl = this.getHtmlElement(); 665 666 var newClassName = this._origClassName + " " + className; 667 if (treeItemDivEl) { 668 treeItemDivEl.className = newClassName; 669 } else if (treeItemEl) { 670 treeItemEl.className = className; 671 } 672 }; 673 674 DwtTreeItem.prototype._addMouseListeners = 675 function() { 676 var events = [DwtEvent.ONMOUSEDOWN, DwtEvent.ONMOUSEUP, DwtEvent.ONDBLCLICK]; 677 if (AjxEnv.isIE) { 678 events.push(DwtEvent.ONMOUSEENTER, DwtEvent.ONMOUSELEAVE); 679 } else { 680 events.push(DwtEvent.ONMOUSEOVER, DwtEvent.ONMOUSEOUT); 681 } 682 if (AjxEnv.isSafari) { 683 events.push(DwtEvent.ONCONTEXTMENU); 684 } 685 for (var i = 0; i < events.length; i++) { 686 this.addListener(events[i], DwtTreeItem._listeners[events[i]]); 687 } 688 }; 689 690 DwtTreeItem.prototype._addDeferredChild = 691 function(child, index) { 692 // If we are initialized, then we need to add a expansion node 693 if (this._initialized && this._children.size() == 0) { 694 if (this._nodeCell) { 695 AjxImg.setImage(this._nodeCell, this._collapseNodeImage); 696 var imgEl = AjxImg.getImageElement(this._nodeCell); 697 if (imgEl) { 698 this._expandable = true; 699 this.setAttribute('aria-expanded', this._expanded); 700 Dwt.setHandler(imgEl, DwtEvent.ONMOUSEDOWN, DwtTreeItem._nodeIconMouseDownHdlr); 701 Dwt.setHandler(imgEl, DwtEvent.ONMOUSEUP, DwtTreeItem._nodeIconMouseUpHdlr); 702 } 703 } 704 } 705 this._children.add(child, index); 706 }; 707 708 DwtTreeItem.prototype.addChild = 709 function(child) { /* do nothing since we add to the DOM our own way */ }; 710 711 DwtTreeItem.prototype._addItem = 712 function(item, index, realizeDeferred) { 713 if (!this._children.contains(item)) { 714 this._children.add(item, index); 715 } 716 this._expandable = true; 717 718 if (this._childDiv == null) { 719 this._childDiv = document.createElement("div"); 720 this._childDiv.className = (this.parent != this._tree) 721 ? "DwtTreeItemChildDiv" : "DwtTreeItemLevel1ChildDiv"; 722 this._childDiv.setAttribute('role', 'group'); 723 this._childDiv.setAttribute('aria-labelledby', this._itemDiv.id); 724 this._childDiv.setAttribute('aria-expanded', this._expanded); 725 this.getHtmlElement().appendChild(this._childDiv); 726 if (!this._expanded) { 727 this._childDiv.style.display = "none"; 728 } 729 } 730 731 if (realizeDeferred && this._nodeCell) { 732 if (AjxImg.getImageClass(this._nodeCell) == AjxImg.getClassForImage("Blank_16")) { 733 AjxImg.setImage(this._nodeCell, this._expanded ? this._expandNodeImage : this._collapseNodeImage); 734 var imgEl = AjxImg.getImageElement(this._nodeCell); 735 if (imgEl) { 736 Dwt.setHandler(imgEl, DwtEvent.ONMOUSEDOWN, DwtTreeItem._nodeIconMouseDownHdlr); 737 } 738 } 739 } 740 741 var childDiv = this._childDiv; 742 var numChildren = childDiv.childNodes.length; 743 if (index == null || index >= numChildren || numChildren == 0) { 744 childDiv.appendChild(item.getHtmlElement()); 745 } else { 746 childDiv.insertBefore(item.getHtmlElement(), childDiv.childNodes[index]); 747 } 748 }; 749 750 DwtTreeItem.prototype.sort = 751 function(cmp) { 752 this._children.sort(cmp); 753 if (this._childDiv) { 754 this._setChildElOrder(); 755 } else { 756 this._needsSort = true; 757 } 758 }; 759 760 DwtTreeItem.prototype._setChildElOrder = 761 function(cmp) { 762 var df = document.createDocumentFragment(); 763 this._children.foreach(function(item, i) { 764 df.appendChild(item.getHtmlElement()); 765 item._index = i; 766 }); 767 this._childDiv.appendChild(df); 768 }; 769 770 DwtTreeItem.prototype._getDragProxy = 771 function() { 772 var icon = document.createElement("div"); 773 Dwt.setPosition(icon, Dwt.ABSOLUTE_STYLE); 774 var table = document.createElement("table"); 775 icon.appendChild(table); 776 table.cellSpacing = table.cellPadding = 0; 777 778 var row = table.insertRow(0); 779 var i = 0; 780 781 var c = row.insertCell(i++); 782 c.noWrap = true; 783 if (this._dndImageInfo) { 784 AjxImg.setImage(c, this._dndImageInfo); 785 } else if (this._imageInfo) { 786 AjxImg.setImage(c, this._imageInfo); 787 } 788 789 c = row.insertCell(i); 790 c.noWrap = true; 791 c.className = this._origClassName; 792 if (this._dndText) { 793 c.innerHTML = this._dndText; 794 } else if (this._text) { 795 c.innerHTML = this._text; 796 } 797 798 this.shell.getHtmlElement().appendChild(icon); 799 Dwt.setZIndex(icon, Dwt.Z_DND); 800 return icon; 801 }; 802 803 DwtTreeItem.prototype._dragEnter = 804 function() { 805 this._preDragClassName = this._textCell.className; 806 this._textCell.className = this._dragOverClassName; 807 this._draghovering = true; 808 }; 809 810 DwtTreeItem.prototype._dragHover = 811 function() { 812 if (this.getNumChildren() > 0 && !this.getExpanded()) { 813 this.setExpanded(true); 814 } 815 }; 816 817 DwtTreeItem.prototype._dragLeave = 818 function(ev) { 819 if (this._preDragClassName) { 820 this._textCell.className = this._preDragClassName; 821 } 822 this._draghovering = false; 823 }; 824 825 DwtTreeItem.prototype._drop = 826 function() { 827 if (this._preDragClassName) { 828 this._textCell.className = this._preDragClassName; 829 } 830 this._draghovering = false; 831 }; 832 833 /** 834 * This is for bug 45129. 835 * In the DwControl's focusByMouseDownEvent, it focuses the TreeItem 836 * And change TreeItem's color. But sometimes when mousedown and mouseup 837 * haven't been matched on the one element. It will cause multiple selection. 838 * For in the mouseup handle function, we has done focus if we find both mouse 839 * down and up happened on the same element. So when the mouse is down, we just 840 * do nothing. 841 */ 842 DwtTreeItem.prototype._focusByMouseDownEvent = 843 function(ev) { 844 845 } 846 847 DwtTreeItem._nodeIconMouseDownHdlr = 848 function(ev) { 849 var obj = DwtControl.getTargetControl(ev); 850 var mouseEv = DwtShell.mouseEvent; 851 mouseEv.setFromDhtmlEvent(ev, obj); 852 if (mouseEv.button == DwtMouseEvent.LEFT) { 853 obj._expand(!obj._expanded, mouseEv); 854 } else if (mouseEv.button == DwtMouseEvent.RIGHT) { 855 mouseEv.dwtObj._tree._itemActioned(mouseEv.dwtObj, mouseEv); 856 } 857 858 mouseEv._stopPropagation = true; 859 mouseEv._returnValue = false; 860 mouseEv.setToDhtmlEvent(ev); 861 return false; 862 }; 863 864 DwtTreeItem._nodeIconMouseUpHdlr = 865 function(ev) { 866 var obj = DwtControl.getTargetControl(ev); 867 var mouseEv = DwtShell.mouseEvent; 868 mouseEv._stopPropagation = true; 869 mouseEv._returnValue = false; 870 mouseEv.setToDhtmlEvent(ev); 871 return false; 872 }; 873 874 DwtTreeItem.prototype._expand = 875 function(expand, ev, skipNotify) { 876 if (expand !== this._expanded) { 877 if (!expand) { 878 this._expanded = false; 879 this._childDiv.style.display = "none"; 880 if (this._nodeCell) { 881 AjxImg.setImage(this._nodeCell, this._collapseNodeImage); 882 } 883 this._tree._itemCollapsed(this, ev, skipNotify); 884 } else { 885 // The first thing we need to do is initialize any deferred children so that they 886 // actually have content 887 this._realizeDeferredChildren(); 888 this._expanded = true; 889 if(this._childDiv && this._childDiv.style) 890 this._childDiv.style.display = "block"; 891 if (this._nodeCell) { 892 AjxImg.setImage(this._nodeCell, this._expandNodeImage); 893 } 894 this._tree._itemExpanded(this, ev, skipNotify); 895 } 896 897 this.setAttribute('aria-expanded', expand); 898 this._childDiv.setAttribute('aria-expanded', expand); 899 this._childDiv.setAttribute('aria-hidden', !expand); 900 } 901 }; 902 903 DwtTreeItem.prototype._realizeDeferredChildren = 904 function() { 905 var a = this._children.getArray(); 906 for (var i = 0; i < a.length; i++) { 907 var treeItem = a[i]; 908 if (!treeItem._initialized) { 909 treeItem._initialize(treeItem._index, true); 910 } else if (treeItem._isSeparator && !treeItem.div && this._childDiv) { 911 // Note: separators marked as initialized on construction 912 var div = treeItem.div = document.createElement("div"); 913 div.className = "vSpace"; 914 this._childDiv.appendChild(div); 915 treeItem._initialized = true; 916 } 917 } 918 if (this._needsSort) { 919 if (a.length) { 920 this._setChildElOrder(); 921 } 922 delete this.__needsSort; 923 } 924 }; 925 926 DwtTreeItem.prototype._isChildOf = 927 function(item) { 928 var test = this.parent; 929 while (test && test != this._tree) { 930 if (test == item) 931 return true; 932 test = test.parent; 933 } 934 return false; 935 }; 936 937 DwtTreeItem.prototype._setTreeElementStyles = 938 function(img, focused) { 939 if (this._arrowDisabled || this._draghovering) { 940 return; 941 } 942 var selected = focused ? "-focused" : ""; 943 if (this._extraCell) { 944 AjxImg.setImage(this._extraCell, img); 945 this._extraCell.className = this._treeItemExtraImgClass + selected; 946 } 947 if (this._textCell) 948 this._textCell.className = this._treeItemTextClass + selected; 949 } 950 951 DwtTreeItem.prototype._setSelected = 952 function(selected, noFocus) { 953 if (this._selected != selected && !this._disposed) { 954 this._selected = selected; 955 if (!this._initialized) { 956 this._initialize(); 957 } 958 if (!this._itemDiv) { return; } 959 960 var didSelect; 961 962 if (selected && (this._selectionEnabled || this._forceNotifySelection) /*&& this._origClassName == "DwtTreeItem"*/) { 963 this._itemDiv.className = this._selectedClassName; 964 this._setTreeElementStyles("DownArrowSmall", true); 965 this._tree.setAttribute('aria-activedescendant', this.getHTMLElId()); 966 if (!noFocus) { 967 this.focus(); 968 } 969 didSelect = true; 970 } else { 971 this.blur(); 972 this._setTreeElementStyles("Blank_16", false); 973 this._itemDiv.className = this._origClassName;; 974 this._tree.removeAttribute('aria-activedescendant'); 975 didSelect = false; 976 } 977 978 this.getHtmlElement().setAttribute('aria-selected', selected); 979 /* TODO: disable on IE? screenreaders in IE may announce items twice if 980 * we do the below, which is not strictly necessary */ 981 var treeEl = this._tree.getHtmlElement(); 982 983 if (selected) { 984 treeEl.setAttribute('aria-activedescendant', this.getHTMLElId()); 985 } else { 986 treeEl.removeAttribute('aria-activedescendant'); 987 } 988 989 return didSelect; 990 } 991 }; 992 993 DwtTreeItem.prototype._setActioned = 994 function(actioned) { 995 if (this._actioned != actioned) { 996 this._actioned = actioned; 997 if (!this._initialized) { 998 this._initialize(); 999 } 1000 1001 if (!this._itemDiv) { return; } 1002 1003 if (actioned && (this._actionEnabled || this._forceNotifyAction) && !this._selected) { 1004 this._itemDiv.className = this._actionedClassName; 1005 return true; 1006 } 1007 1008 if (!actioned) { 1009 if (!this._selected) { 1010 this._itemDiv.className = this._origClassName; 1011 } 1012 return false; 1013 } 1014 } 1015 }; 1016 1017 DwtTreeItem.prototype._focus = 1018 function() { 1019 if (!this._itemDiv) { return; } 1020 // focused tree item should always be selected as well 1021 this._itemDiv.className = this._selectedFocusedClassName; 1022 this._setTreeElementStyles("DownArrowSmall", true); 1023 }; 1024 1025 DwtTreeItem.prototype._blur = 1026 function() { 1027 if (!this._itemDiv) { return; } 1028 this._itemDiv.className = this._selected 1029 ? this._selectedClassName : this._origClassName; 1030 this._setTreeElementStyles(this._selected ? "DownArrowSmall" : "Blank_16", this._selected); 1031 }; 1032 1033 DwtTreeItem._mouseDownListener = 1034 function(ev) { 1035 var treeItem = ev.dwtObj; 1036 if (!treeItem) { return false; } 1037 if (ev.target == treeItem._childDiv) { return; } 1038 1039 if (ev.button == DwtMouseEvent.LEFT && (treeItem._selectionEnabled || treeItem._forceNotifySelection)) { 1040 treeItem._gotMouseDownLeft = true; 1041 } else if (ev.button == DwtMouseEvent.RIGHT && (treeItem._actionEnabled || treeItem._forceNotifyAction)) { 1042 treeItem._gotMouseDownRight = true; 1043 } 1044 1045 }; 1046 1047 DwtTreeItem._mouseOutListener = 1048 function(ev) { 1049 var treeItem = ev.dwtObj; 1050 if (!treeItem) { return false; } 1051 if (ev.target == treeItem._childDiv) { return; } 1052 1053 treeItem._gotMouseDownLeft = false; 1054 treeItem._gotMouseDownRight = false; 1055 if (treeItem._singleClickAction && treeItem._textCell) { 1056 treeItem._textCell.className = treeItem._textClassName; 1057 } 1058 if(!treeItem._selected){ 1059 treeItem._setTreeElementStyles("Blank_16", false); 1060 } 1061 1062 }; 1063 1064 DwtTreeItem._mouseOverListener = 1065 function(ev) { 1066 var treeItem = ev.dwtObj; 1067 if (!treeItem) { return false; } 1068 if (ev.target == treeItem._childDiv) { return; } 1069 1070 if (treeItem._singleClickAction && treeItem._textCell) { 1071 treeItem._textCell.className = treeItem._hoverClassName; 1072 } 1073 if(!treeItem._selected){ 1074 treeItem._setTreeElementStyles("ColumnDownArrow", true); 1075 } 1076 }; 1077 1078 DwtTreeItem._mouseUpListener = function(ev) { 1079 1080 var treeItem = ev.dwtObj; 1081 if (!treeItem) { 1082 return false; 1083 } 1084 1085 // Ignore any mouse events in the child div i.e. the div which 1086 // holds all the items children. In the case of IE, no clicks are 1087 // reported when clicking in the padding area (note all children 1088 // are indented using padding-left style); however, mozilla 1089 // reports mouse events that happen in the padding area 1090 if (ev.target === treeItem._childDiv) { 1091 return; 1092 } 1093 1094 //ignore the collapse/expand arrow. This is handled in DwtTreeItem._nodeIconMouseDownHdlr. It should only collapse/expand and never select this item, so no point in going on. 1095 if (treeItem._expandable && ev.target === AjxImg.getImageElement(treeItem._nodeCell)) { 1096 return; 1097 } 1098 1099 var targetElement = DwtUiEvent.getTargetWithProp(ev, "id"), 1100 isContextCmd = (treeItem._extraCell && targetElement && treeItem._extraCell.id === targetElement.id); 1101 1102 if ((ev.button === DwtMouseEvent.RIGHT && treeItem._gotMouseDownRight) || isContextCmd) { 1103 treeItem._tree._itemActioned(treeItem, ev); 1104 } 1105 else if (ev.button === DwtMouseEvent.LEFT && treeItem._gotMouseDownLeft) { 1106 treeItem._tree._itemClicked(treeItem, ev); 1107 } 1108 }; 1109 1110 DwtTreeItem._doubleClickListener = 1111 function(ev) { 1112 var treeItem = ev.dwtObj; 1113 if (!treeItem) { return false; } 1114 // See comment in DwtTreeItem.prototype._mouseDownListener 1115 if (ev.target == treeItem._childDiv) { return; } 1116 1117 var obj = DwtControl.getTargetControl(ev); 1118 var mouseEv = DwtShell.mouseEvent; 1119 mouseEv.setFromDhtmlEvent(ev, obj); 1120 if (mouseEv.button == DwtMouseEvent.LEFT || mouseEv.button == DwtMouseEvent.NONE) { // NONE for IE bug 1121 mouseEv.dwtObj._tree._itemDblClicked(mouseEv.dwtObj, mouseEv); 1122 } 1123 }; 1124 1125 DwtTreeItem._contextListener = 1126 function(ev) { 1127 // for Safari, we have to fake a right click 1128 if (AjxEnv.isSafari) { 1129 var obj = DwtControl.getTargetControl(ev); 1130 var prevent = obj ? obj.preventContextMenu() : true; 1131 if (prevent) { 1132 obj.notifyListeners(DwtEvent.ONMOUSEDOWN, ev); 1133 return obj.notifyListeners(DwtEvent.ONMOUSEUP, ev); 1134 } 1135 } 1136 }; 1137 1138 DwtTreeItem.prototype._emulateSingleClick = 1139 function(params) { 1140 var mev = new DwtMouseEvent(); 1141 this._setMouseEvent(mev, params); 1142 mev.kbNavEvent = params.kbNavEvent; 1143 this.notifyListeners(DwtEvent.ONMOUSEUP, mev); 1144 }; 1145 1146 DwtTreeItem.prototype.getTooltipBase = 1147 function(hoverEv) { 1148 return this._itemDiv; 1149 }; 1150 1151 DwtTreeItem._listeners = {}; 1152 DwtTreeItem._listeners[DwtEvent.ONMOUSEDOWN] = new AjxListener(null, DwtTreeItem._mouseDownListener); 1153 DwtTreeItem._listeners[DwtEvent.ONMOUSEOUT] = new AjxListener(null, DwtTreeItem._mouseOutListener); 1154 DwtTreeItem._listeners[DwtEvent.ONMOUSELEAVE] = new AjxListener(null, DwtTreeItem._mouseOutListener); 1155 DwtTreeItem._listeners[DwtEvent.ONMOUSEENTER] = new AjxListener(null, DwtTreeItem._mouseOverListener); 1156 DwtTreeItem._listeners[DwtEvent.ONMOUSEOVER] = new AjxListener(null, DwtTreeItem._mouseOverListener); 1157 DwtTreeItem._listeners[DwtEvent.ONMOUSEUP] = new AjxListener(null, DwtTreeItem._mouseUpListener); 1158 DwtTreeItem._listeners[DwtEvent.ONDBLCLICK] = new AjxListener(null, DwtTreeItem._doubleClickListener); 1159 DwtTreeItem._listeners[DwtEvent.ONCONTEXTMENU] = new AjxListener(null, DwtTreeItem._contextListener); 1160 1161 1162 /** 1163 * Minimal class for a separator (some vertical space) between other tree items. 1164 * The functions it has are to handle a dispose() call when the containing tree 1165 * is disposed. 1166 * 1167 * TODO: At some point we should just make this a DwtControl, or find some other 1168 * way of keeping it minimal. 1169 * 1170 * 1171 * @private 1172 */ 1173 DwtTreeItemSeparator = function(parent) { 1174 this.parent = parent; 1175 this._isSeparator = true; 1176 this._initialized = true; 1177 }; 1178 1179 DwtTreeItemSeparator.prototype.dispose = 1180 function() { 1181 DwtComposite.prototype.removeChild.call(this.parent, this); 1182 }; 1183 1184 DwtTreeItemSeparator.prototype.isInitialized = 1185 function() { 1186 return this._initialized; 1187 }; 1188 1189 DwtTreeItemSeparator.prototype.getHtmlElement = 1190 function() { 1191 return this.div; 1192 }; 1193