1/* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29function TreeOutline(listNode) 30{ 31 this.children = []; 32 this.selectedTreeElement = null; 33 this._childrenListNode = listNode; 34 this._childrenListNode.removeChildren(); 35 this._knownTreeElements = []; 36 this._treeElementsExpandedState = []; 37 this.expandTreeElementsWhenArrowing = false; 38 this.root = true; 39 this.hasChildren = false; 40 this.expanded = true; 41 this.selected = false; 42 this.treeOutline = this; 43 44 this._childrenListNode.tabIndex = 0; 45 this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true); 46} 47 48TreeOutline._knownTreeElementNextIdentifier = 1; 49 50TreeOutline._appendChild = function(child) 51{ 52 if (!child) 53 throw("child can't be undefined or null"); 54 55 var lastChild = this.children[this.children.length - 1]; 56 if (lastChild) { 57 lastChild.nextSibling = child; 58 child.previousSibling = lastChild; 59 } else { 60 child.previousSibling = null; 61 child.nextSibling = null; 62 } 63 64 this.children.push(child); 65 this.hasChildren = true; 66 child.parent = this; 67 child.treeOutline = this.treeOutline; 68 child.treeOutline._rememberTreeElement(child); 69 70 var current = child.children[0]; 71 while (current) { 72 current.treeOutline = this.treeOutline; 73 current.treeOutline._rememberTreeElement(current); 74 current = current.traverseNextTreeElement(false, child, true); 75 } 76 77 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) 78 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; 79 80 if (!this._childrenListNode) { 81 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 82 this._childrenListNode.parentTreeElement = this; 83 this._childrenListNode.addStyleClass("children"); 84 if (this.hidden) 85 this._childrenListNode.addStyleClass("hidden"); 86 } 87 88 child._attach(); 89} 90 91TreeOutline._insertChild = function(child, index) 92{ 93 if (!child) 94 throw("child can't be undefined or null"); 95 96 var previousChild = (index > 0 ? this.children[index - 1] : null); 97 if (previousChild) { 98 previousChild.nextSibling = child; 99 child.previousSibling = previousChild; 100 } else { 101 child.previousSibling = null; 102 } 103 104 var nextChild = this.children[index]; 105 if (nextChild) { 106 nextChild.previousSibling = child; 107 child.nextSibling = nextChild; 108 } else { 109 child.nextSibling = null; 110 } 111 112 this.children.splice(index, 0, child); 113 this.hasChildren = true; 114 child.parent = this; 115 child.treeOutline = this.treeOutline; 116 child.treeOutline._rememberTreeElement(child); 117 118 var current = child.children[0]; 119 while (current) { 120 current.treeOutline = this.treeOutline; 121 current.treeOutline._rememberTreeElement(current); 122 current = current.traverseNextTreeElement(false, child, true); 123 } 124 125 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) 126 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; 127 128 if (!this._childrenListNode) { 129 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 130 this._childrenListNode.parentTreeElement = this; 131 this._childrenListNode.addStyleClass("children"); 132 if (this.hidden) 133 this._childrenListNode.addStyleClass("hidden"); 134 } 135 136 child._attach(); 137} 138 139TreeOutline._removeChildAtIndex = function(childIndex) 140{ 141 if (childIndex < 0 || childIndex >= this.children.length) 142 throw("childIndex out of range"); 143 144 var child = this.children[childIndex]; 145 this.children.splice(childIndex, 1); 146 147 var parent = child.parent; 148 if (child.deselect()) { 149 if (child.previousSibling) 150 child.previousSibling.select(); 151 else if (child.nextSibling) 152 child.nextSibling.select(); 153 else 154 parent.select(); 155 } 156 157 if (child.previousSibling) 158 child.previousSibling.nextSibling = child.nextSibling; 159 if (child.nextSibling) 160 child.nextSibling.previousSibling = child.previousSibling; 161 162 if (child.treeOutline) { 163 child.treeOutline._forgetTreeElement(child); 164 child.treeOutline._forgetChildrenRecursive(child); 165 } 166 167 child._detach(); 168 child.treeOutline = null; 169 child.parent = null; 170 child.nextSibling = null; 171 child.previousSibling = null; 172} 173 174TreeOutline._removeChild = function(child) 175{ 176 if (!child) 177 throw("child can't be undefined or null"); 178 179 var childIndex = this.children.indexOf(child); 180 if (childIndex === -1) 181 throw("child not found in this node's children"); 182 183 TreeOutline._removeChildAtIndex.call(this, childIndex); 184} 185 186TreeOutline._removeChildren = function() 187{ 188 for (var i = 0; i < this.children.length; ++i) { 189 var child = this.children[i]; 190 child.deselect(); 191 192 if (child.treeOutline) { 193 child.treeOutline._forgetTreeElement(child); 194 child.treeOutline._forgetChildrenRecursive(child); 195 } 196 197 child._detach(); 198 child.treeOutline = null; 199 child.parent = null; 200 child.nextSibling = null; 201 child.previousSibling = null; 202 } 203 204 this.children = []; 205} 206 207TreeOutline._removeChildrenRecursive = function() 208{ 209 var childrenToRemove = this.children; 210 211 var child = this.children[0]; 212 while (child) { 213 if (child.children.length) 214 childrenToRemove = childrenToRemove.concat(child.children); 215 child = child.traverseNextTreeElement(false, this, true); 216 } 217 218 for (var i = 0; i < childrenToRemove.length; ++i) { 219 var child = childrenToRemove[i]; 220 child.deselect(); 221 if (child.treeOutline) 222 child.treeOutline._forgetTreeElement(child); 223 child._detach(); 224 child.children = []; 225 child.treeOutline = null; 226 child.parent = null; 227 child.nextSibling = null; 228 child.previousSibling = null; 229 } 230 231 this.children = []; 232} 233 234TreeOutline.prototype._rememberTreeElement = function(element) 235{ 236 if (!this._knownTreeElements[element.identifier]) 237 this._knownTreeElements[element.identifier] = []; 238 239 // check if the element is already known 240 var elements = this._knownTreeElements[element.identifier]; 241 if (elements.indexOf(element) !== -1) 242 return; 243 244 // add the element 245 elements.push(element); 246} 247 248TreeOutline.prototype._forgetTreeElement = function(element) 249{ 250 if (this._knownTreeElements[element.identifier]) 251 this._knownTreeElements[element.identifier].remove(element, true); 252} 253 254TreeOutline.prototype._forgetChildrenRecursive = function(parentElement) 255{ 256 var child = parentElement.children[0]; 257 while (child) { 258 this._forgetTreeElement(child); 259 child = child.traverseNextTreeElement(false, this, true); 260 } 261} 262 263TreeOutline.prototype.getCachedTreeElement = function(representedObject) 264{ 265 if (!representedObject) 266 return null; 267 268 if ("__treeElementIdentifier" in representedObject) { 269 // If this representedObject has a tree element identifier, and it is a known TreeElement 270 // in our tree we can just return that tree element. 271 var elements = this._knownTreeElements[representedObject.__treeElementIdentifier]; 272 if (elements) { 273 for (var i = 0; i < elements.length; ++i) 274 if (elements[i].representedObject === representedObject) 275 return elements[i]; 276 } 277 } 278 return null; 279} 280 281TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent) 282{ 283 if (!representedObject) 284 return null; 285 286 var cachedElement = this.getCachedTreeElement(representedObject); 287 if (cachedElement) 288 return cachedElement; 289 290 // The representedObject isn't known, so we start at the top of the tree and work down to find the first 291 // tree element that represents representedObject or one of its ancestors. 292 var item; 293 var found = false; 294 for (var i = 0; i < this.children.length; ++i) { 295 item = this.children[i]; 296 if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) { 297 found = true; 298 break; 299 } 300 } 301 302 if (!found) 303 return null; 304 305 // Make sure the item that we found is connected to the root of the tree. 306 // Build up a list of representedObject's ancestors that aren't already in our tree. 307 var ancestors = []; 308 var currentObject = representedObject; 309 while (currentObject) { 310 ancestors.unshift(currentObject); 311 if (currentObject === item.representedObject) 312 break; 313 currentObject = getParent(currentObject); 314 } 315 316 // For each of those ancestors we populate them to fill in the tree. 317 for (var i = 0; i < ancestors.length; ++i) { 318 // Make sure we don't call findTreeElement with the same representedObject 319 // again, to prevent infinite recursion. 320 if (ancestors[i] === representedObject) 321 continue; 322 // FIXME: we could do something faster than findTreeElement since we will know the next 323 // ancestor exists in the tree. 324 item = this.findTreeElement(ancestors[i], isAncestor, getParent); 325 if (item && item.onpopulate) 326 item.onpopulate(item); 327 } 328 329 return this.getCachedTreeElement(representedObject); 330} 331 332TreeOutline.prototype.treeElementFromPoint = function(x, y) 333{ 334 var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y); 335 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]); 336 if (listNode) 337 return listNode.parentTreeElement || listNode.treeElement; 338 return null; 339} 340 341TreeOutline.prototype._treeKeyDown = function(event) 342{ 343 if (event.target !== this._childrenListNode) 344 return; 345 346 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) 347 return; 348 349 var handled = false; 350 var nextSelectedElement; 351 if (event.keyIdentifier === "Up" && !event.altKey) { 352 nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); 353 while (nextSelectedElement && !nextSelectedElement.selectable) 354 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); 355 handled = nextSelectedElement ? true : false; 356 } else if (event.keyIdentifier === "Down" && !event.altKey) { 357 nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); 358 while (nextSelectedElement && !nextSelectedElement.selectable) 359 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); 360 handled = nextSelectedElement ? true : false; 361 } else if (event.keyIdentifier === "Left") { 362 if (this.selectedTreeElement.expanded) { 363 if (event.altKey) 364 this.selectedTreeElement.collapseRecursively(); 365 else 366 this.selectedTreeElement.collapse(); 367 handled = true; 368 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { 369 handled = true; 370 if (this.selectedTreeElement.parent.selectable) { 371 nextSelectedElement = this.selectedTreeElement.parent; 372 handled = nextSelectedElement ? true : false; 373 } else if (this.selectedTreeElement.parent) 374 this.selectedTreeElement.parent.collapse(); 375 } 376 } else if (event.keyIdentifier === "Right") { 377 if (!this.selectedTreeElement.revealed()) { 378 this.selectedTreeElement.reveal(); 379 handled = true; 380 } else if (this.selectedTreeElement.hasChildren) { 381 handled = true; 382 if (this.selectedTreeElement.expanded) { 383 nextSelectedElement = this.selectedTreeElement.children[0]; 384 handled = nextSelectedElement ? true : false; 385 } else { 386 if (event.altKey) 387 this.selectedTreeElement.expandRecursively(); 388 else 389 this.selectedTreeElement.expand(); 390 } 391 } 392 } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code || event.keyCode === WebInspector.KeyboardShortcut.Keys.Delete.code) { 393 if (this.selectedTreeElement.ondelete) 394 handled = this.selectedTreeElement.ondelete(); 395 } else if (isEnterKey(event)) { 396 if (this.selectedTreeElement.onenter) 397 handled = this.selectedTreeElement.onenter(); 398 } 399 400 if (nextSelectedElement) { 401 nextSelectedElement.reveal(); 402 nextSelectedElement.select(false, true); 403 } 404 405 if (handled) { 406 event.preventDefault(); 407 event.stopPropagation(); 408 } 409} 410 411TreeOutline.prototype.expand = function() 412{ 413 // this is the root, do nothing 414} 415 416TreeOutline.prototype.collapse = function() 417{ 418 // this is the root, do nothing 419} 420 421TreeOutline.prototype.revealed = function() 422{ 423 return true; 424} 425 426TreeOutline.prototype.reveal = function() 427{ 428 // this is the root, do nothing 429} 430 431TreeOutline.prototype.select = function() 432{ 433 // this is the root, do nothing 434} 435 436TreeOutline.prototype.appendChild = TreeOutline._appendChild; 437TreeOutline.prototype.insertChild = TreeOutline._insertChild; 438TreeOutline.prototype.removeChild = TreeOutline._removeChild; 439TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; 440TreeOutline.prototype.removeChildren = TreeOutline._removeChildren; 441TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; 442 443function TreeElement(title, representedObject, hasChildren) 444{ 445 this._title = title; 446 this.representedObject = (representedObject || {}); 447 448 if (this.representedObject.__treeElementIdentifier) 449 this.identifier = this.representedObject.__treeElementIdentifier; 450 else { 451 this.identifier = TreeOutline._knownTreeElementNextIdentifier++; 452 this.representedObject.__treeElementIdentifier = this.identifier; 453 } 454 455 this._hidden = false; 456 this.expanded = false; 457 this.selected = false; 458 this.hasChildren = hasChildren; 459 this.children = []; 460 this.treeOutline = null; 461 this.parent = null; 462 this.previousSibling = null; 463 this.nextSibling = null; 464 this._listItemNode = null; 465} 466 467TreeElement.prototype = { 468 selectable: true, 469 arrowToggleWidth: 10, 470 471 get listItemElement() { 472 return this._listItemNode; 473 }, 474 475 get childrenListElement() { 476 return this._childrenListNode; 477 }, 478 479 get title() { 480 return this._title; 481 }, 482 483 set title(x) { 484 this._title = x; 485 this._setListItemNodeContent(); 486 }, 487 488 get titleHTML() { 489 return this._titleHTML; 490 }, 491 492 set titleHTML(x) { 493 this._titleHTML = x; 494 this._setListItemNodeContent(); 495 }, 496 497 get tooltip() { 498 return this._tooltip; 499 }, 500 501 set tooltip(x) { 502 this._tooltip = x; 503 if (this._listItemNode) 504 this._listItemNode.title = x ? x : ""; 505 }, 506 507 get hasChildren() { 508 return this._hasChildren; 509 }, 510 511 set hasChildren(x) { 512 if (this._hasChildren === x) 513 return; 514 515 this._hasChildren = x; 516 517 if (!this._listItemNode) 518 return; 519 520 if (x) 521 this._listItemNode.addStyleClass("parent"); 522 else { 523 this._listItemNode.removeStyleClass("parent"); 524 this.collapse(); 525 } 526 }, 527 528 get hidden() { 529 return this._hidden; 530 }, 531 532 set hidden(x) { 533 if (this._hidden === x) 534 return; 535 536 this._hidden = x; 537 538 if (x) { 539 if (this._listItemNode) 540 this._listItemNode.addStyleClass("hidden"); 541 if (this._childrenListNode) 542 this._childrenListNode.addStyleClass("hidden"); 543 } else { 544 if (this._listItemNode) 545 this._listItemNode.removeStyleClass("hidden"); 546 if (this._childrenListNode) 547 this._childrenListNode.removeStyleClass("hidden"); 548 } 549 }, 550 551 get shouldRefreshChildren() { 552 return this._shouldRefreshChildren; 553 }, 554 555 set shouldRefreshChildren(x) { 556 this._shouldRefreshChildren = x; 557 if (x && this.expanded) 558 this.expand(); 559 }, 560 561 _setListItemNodeContent: function() 562 { 563 if (!this._listItemNode) 564 return; 565 566 if (!this._titleHTML && !this._title) 567 this._listItemNode.removeChildren(); 568 else if (typeof this._titleHTML === "string") 569 this._listItemNode.innerHTML = this._titleHTML; 570 else if (typeof this._title === "string") 571 this._listItemNode.textContent = this._title; 572 else { 573 this._listItemNode.removeChildren(); 574 if (this._title.parentNode) 575 this._title.parentNode.removeChild(this._title); 576 this._listItemNode.appendChild(this._title); 577 } 578 } 579} 580 581TreeElement.prototype.appendChild = TreeOutline._appendChild; 582TreeElement.prototype.insertChild = TreeOutline._insertChild; 583TreeElement.prototype.removeChild = TreeOutline._removeChild; 584TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; 585TreeElement.prototype.removeChildren = TreeOutline._removeChildren; 586TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; 587 588TreeElement.prototype._attach = function() 589{ 590 if (!this._listItemNode || this.parent._shouldRefreshChildren) { 591 if (this._listItemNode && this._listItemNode.parentNode) 592 this._listItemNode.parentNode.removeChild(this._listItemNode); 593 594 this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); 595 this._listItemNode.treeElement = this; 596 this._setListItemNodeContent(); 597 this._listItemNode.title = this._tooltip ? this._tooltip : ""; 598 599 if (this.hidden) 600 this._listItemNode.addStyleClass("hidden"); 601 if (this.hasChildren) 602 this._listItemNode.addStyleClass("parent"); 603 if (this.expanded) 604 this._listItemNode.addStyleClass("expanded"); 605 if (this.selected) 606 this._listItemNode.addStyleClass("selected"); 607 608 this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false); 609 this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); 610 this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); 611 612 if (this.onattach) 613 this.onattach(this); 614 } 615 616 var nextSibling = null; 617 if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) 618 nextSibling = this.nextSibling._listItemNode; 619 this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); 620 if (this._childrenListNode) 621 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 622 if (this.selected) 623 this.select(); 624 if (this.expanded) 625 this.expand(); 626} 627 628TreeElement.prototype._detach = function() 629{ 630 if (this._listItemNode && this._listItemNode.parentNode) 631 this._listItemNode.parentNode.removeChild(this._listItemNode); 632 if (this._childrenListNode && this._childrenListNode.parentNode) 633 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 634} 635 636TreeElement.treeElementMouseDown = function(event) 637{ 638 var element = event.currentTarget; 639 if (!element || !element.treeElement || !element.treeElement.selectable) 640 return; 641 642 if (element.treeElement.isEventWithinDisclosureTriangle(event)) 643 return; 644 645 element.treeElement.selectOnMouseDown(event); 646} 647 648TreeElement.treeElementToggled = function(event) 649{ 650 var element = event.currentTarget; 651 if (!element || !element.treeElement) 652 return; 653 654 if (!element.treeElement.isEventWithinDisclosureTriangle(event)) 655 return; 656 657 if (element.treeElement.expanded) { 658 if (event.altKey) 659 element.treeElement.collapseRecursively(); 660 else 661 element.treeElement.collapse(); 662 } else { 663 if (event.altKey) 664 element.treeElement.expandRecursively(); 665 else 666 element.treeElement.expand(); 667 } 668 event.stopPropagation(); 669} 670 671TreeElement.treeElementDoubleClicked = function(event) 672{ 673 var element = event.currentTarget; 674 if (!element || !element.treeElement) 675 return; 676 677 if (element.treeElement.ondblclick) 678 element.treeElement.ondblclick.call(element.treeElement, event); 679 else if (element.treeElement.hasChildren && !element.treeElement.expanded) 680 element.treeElement.expand(); 681} 682 683TreeElement.prototype.collapse = function() 684{ 685 if (this._listItemNode) 686 this._listItemNode.removeStyleClass("expanded"); 687 if (this._childrenListNode) 688 this._childrenListNode.removeStyleClass("expanded"); 689 690 this.expanded = false; 691 if (this.treeOutline) 692 this.treeOutline._treeElementsExpandedState[this.identifier] = true; 693 694 if (this.oncollapse) 695 this.oncollapse(this); 696} 697 698TreeElement.prototype.collapseRecursively = function() 699{ 700 var item = this; 701 while (item) { 702 if (item.expanded) 703 item.collapse(); 704 item = item.traverseNextTreeElement(false, this, true); 705 } 706} 707 708TreeElement.prototype.expand = function() 709{ 710 if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode)) 711 return; 712 713 if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { 714 if (this._childrenListNode && this._childrenListNode.parentNode) 715 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 716 717 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 718 this._childrenListNode.parentTreeElement = this; 719 this._childrenListNode.addStyleClass("children"); 720 721 if (this.hidden) 722 this._childrenListNode.addStyleClass("hidden"); 723 724 if (this.onpopulate) 725 this.onpopulate(this); 726 727 for (var i = 0; i < this.children.length; ++i) 728 this.children[i]._attach(); 729 730 delete this._shouldRefreshChildren; 731 } 732 733 if (this._listItemNode) { 734 this._listItemNode.addStyleClass("expanded"); 735 if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode) 736 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 737 } 738 739 if (this._childrenListNode) 740 this._childrenListNode.addStyleClass("expanded"); 741 742 this.expanded = true; 743 if (this.treeOutline) 744 this.treeOutline._treeElementsExpandedState[this.identifier] = true; 745 746 if (this.onexpand) 747 this.onexpand(this); 748} 749 750TreeElement.prototype.expandRecursively = function(maxDepth) 751{ 752 var item = this; 753 var info = {}; 754 var depth = 0; 755 756 // The Inspector uses TreeOutlines to represents object properties, so recursive expansion 757 // in some case can be infinite, since JavaScript objects can hold circular references. 758 // So default to a recursion cap of 3 levels, since that gives fairly good results. 759 if (typeof maxDepth === "undefined" || typeof maxDepth === "null") 760 maxDepth = 3; 761 762 while (item) { 763 if (depth < maxDepth) 764 item.expand(); 765 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); 766 depth += info.depthChange; 767 } 768} 769 770TreeElement.prototype.hasAncestor = function(ancestor) { 771 if (!ancestor) 772 return false; 773 774 var currentNode = this.parent; 775 while (currentNode) { 776 if (ancestor === currentNode) 777 return true; 778 currentNode = currentNode.parent; 779 } 780 781 return false; 782} 783 784TreeElement.prototype.reveal = function() 785{ 786 var currentAncestor = this.parent; 787 while (currentAncestor && !currentAncestor.root) { 788 if (!currentAncestor.expanded) 789 currentAncestor.expand(); 790 currentAncestor = currentAncestor.parent; 791 } 792 793 if (this.onreveal) 794 this.onreveal(this); 795} 796 797TreeElement.prototype.revealed = function() 798{ 799 var currentAncestor = this.parent; 800 while (currentAncestor && !currentAncestor.root) { 801 if (!currentAncestor.expanded) 802 return false; 803 currentAncestor = currentAncestor.parent; 804 } 805 806 return true; 807} 808 809TreeElement.prototype.selectOnMouseDown = function(event) 810{ 811 this.select(false, true); 812} 813 814TreeElement.prototype.select = function(supressOnSelect, selectedByUser) 815{ 816 if (!this.treeOutline || !this.selectable || this.selected) 817 return; 818 819 if (this.treeOutline.selectedTreeElement) 820 this.treeOutline.selectedTreeElement.deselect(); 821 822 this.selected = true; 823 this.treeOutline._childrenListNode.focus(); 824 825 // Focusing on another node may detach "this" from tree. 826 if (!this.treeOutline) 827 return; 828 this.treeOutline.selectedTreeElement = this; 829 if (this._listItemNode) 830 this._listItemNode.addStyleClass("selected"); 831 832 if (this.onselect && !supressOnSelect) 833 this.onselect(this, selectedByUser); 834} 835 836TreeElement.prototype.deselect = function(supressOnDeselect) 837{ 838 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) 839 return false; 840 841 this.selected = false; 842 this.treeOutline.selectedTreeElement = null; 843 if (this._listItemNode) 844 this._listItemNode.removeStyleClass("selected"); 845 846 if (this.ondeselect && !supressOnDeselect) 847 this.ondeselect(this); 848 return true; 849} 850 851TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info) 852{ 853 if (!dontPopulate && this.hasChildren && this.onpopulate) 854 this.onpopulate(this); 855 856 if (info) 857 info.depthChange = 0; 858 859 var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0]; 860 if (element && (!skipHidden || (skipHidden && this.expanded))) { 861 if (info) 862 info.depthChange = 1; 863 return element; 864 } 865 866 if (this === stayWithin) 867 return null; 868 869 element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; 870 if (element) 871 return element; 872 873 element = this; 874 while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) { 875 if (info) 876 info.depthChange -= 1; 877 element = element.parent; 878 } 879 880 if (!element) 881 return null; 882 883 return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); 884} 885 886TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate) 887{ 888 var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; 889 if (!dontPopulate && element && element.hasChildren && element.onpopulate) 890 element.onpopulate(element); 891 892 while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { 893 if (!dontPopulate && element.hasChildren && element.onpopulate) 894 element.onpopulate(element); 895 element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); 896 } 897 898 if (element) 899 return element; 900 901 if (!this.parent || this.parent.root) 902 return null; 903 904 return this.parent; 905} 906 907TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) 908{ 909 var left = this._listItemNode.totalOffsetLeft; 910 return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; 911} 912