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 45TreeOutline._knownTreeElementNextIdentifier = 1; 46 47TreeOutline._appendChild = function(child) 48{ 49 if (!child) 50 throw("child can't be undefined or null"); 51 52 var lastChild = this.children[this.children.length - 1]; 53 if (lastChild) { 54 lastChild.nextSibling = child; 55 child.previousSibling = lastChild; 56 } else { 57 child.previousSibling = null; 58 child.nextSibling = null; 59 } 60 61 this.children.push(child); 62 this.hasChildren = true; 63 child.parent = this; 64 child.treeOutline = this.treeOutline; 65 child.treeOutline._rememberTreeElement(child); 66 67 var current = child.children[0]; 68 while (current) { 69 current.treeOutline = this.treeOutline; 70 current.treeOutline._rememberTreeElement(current); 71 current = current.traverseNextTreeElement(false, child, true); 72 } 73 74 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) 75 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; 76 77 if (!this._childrenListNode) { 78 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 79 this._childrenListNode.parentTreeElement = this; 80 this._childrenListNode.addStyleClass("children"); 81 if (this.hidden) 82 this._childrenListNode.addStyleClass("hidden"); 83 } 84 85 child._attach(); 86} 87 88TreeOutline._insertChild = function(child, index) 89{ 90 if (!child) 91 throw("child can't be undefined or null"); 92 93 var previousChild = (index > 0 ? this.children[index - 1] : null); 94 if (previousChild) { 95 previousChild.nextSibling = child; 96 child.previousSibling = previousChild; 97 } else { 98 child.previousSibling = null; 99 } 100 101 var nextChild = this.children[index]; 102 if (nextChild) { 103 nextChild.previousSibling = child; 104 child.nextSibling = nextChild; 105 } else { 106 child.nextSibling = null; 107 } 108 109 this.children.splice(index, 0, child); 110 this.hasChildren = true; 111 child.parent = this; 112 child.treeOutline = this.treeOutline; 113 child.treeOutline._rememberTreeElement(child); 114 115 var current = child.children[0]; 116 while (current) { 117 current.treeOutline = this.treeOutline; 118 current.treeOutline._rememberTreeElement(current); 119 current = current.traverseNextTreeElement(false, child, true); 120 } 121 122 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) 123 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; 124 125 if (!this._childrenListNode) { 126 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 127 this._childrenListNode.parentTreeElement = this; 128 this._childrenListNode.addStyleClass("children"); 129 if (this.hidden) 130 this._childrenListNode.addStyleClass("hidden"); 131 } 132 133 child._attach(); 134} 135 136TreeOutline._removeChildAtIndex = function(childIndex) 137{ 138 if (childIndex < 0 || childIndex >= this.children.length) 139 throw("childIndex out of range"); 140 141 var child = this.children[childIndex]; 142 this.children.splice(childIndex, 1); 143 144 child.deselect(); 145 146 if (child.previousSibling) 147 child.previousSibling.nextSibling = child.nextSibling; 148 if (child.nextSibling) 149 child.nextSibling.previousSibling = child.previousSibling; 150 151 if (child.treeOutline) { 152 child.treeOutline._forgetTreeElement(child); 153 child.treeOutline._forgetChildrenRecursive(child); 154 } 155 156 child._detach(); 157 child.treeOutline = null; 158 child.parent = null; 159 child.nextSibling = null; 160 child.previousSibling = null; 161} 162 163TreeOutline._removeChild = function(child) 164{ 165 if (!child) 166 throw("child can't be undefined or null"); 167 168 var childIndex = this.children.indexOf(child); 169 if (childIndex === -1) 170 throw("child not found in this node's children"); 171 172 TreeOutline._removeChildAtIndex.call(this, childIndex); 173} 174 175TreeOutline._removeChildren = function() 176{ 177 for (var i = 0; i < this.children.length; ++i) { 178 var child = this.children[i]; 179 child.deselect(); 180 181 if (child.treeOutline) { 182 child.treeOutline._forgetTreeElement(child); 183 child.treeOutline._forgetChildrenRecursive(child); 184 } 185 186 child._detach(); 187 child.treeOutline = null; 188 child.parent = null; 189 child.nextSibling = null; 190 child.previousSibling = null; 191 } 192 193 this.children = []; 194} 195 196TreeOutline._removeChildrenRecursive = function() 197{ 198 var childrenToRemove = this.children; 199 200 var child = this.children[0]; 201 while (child) { 202 if (child.children.length) 203 childrenToRemove = childrenToRemove.concat(child.children); 204 child = child.traverseNextTreeElement(false, this, true); 205 } 206 207 for (var i = 0; i < childrenToRemove.length; ++i) { 208 var child = childrenToRemove[i]; 209 child.deselect(); 210 if (child.treeOutline) 211 child.treeOutline._forgetTreeElement(child); 212 child._detach(); 213 child.children = []; 214 child.treeOutline = null; 215 child.parent = null; 216 child.nextSibling = null; 217 child.previousSibling = null; 218 } 219 220 this.children = []; 221} 222 223TreeOutline.prototype._rememberTreeElement = function(element) 224{ 225 if (!this._knownTreeElements[element.identifier]) 226 this._knownTreeElements[element.identifier] = []; 227 228 // check if the element is already known 229 var elements = this._knownTreeElements[element.identifier]; 230 if (elements.indexOf(element) !== -1) 231 return; 232 233 // add the element 234 elements.push(element); 235} 236 237TreeOutline.prototype._forgetTreeElement = function(element) 238{ 239 if (this._knownTreeElements[element.identifier]) 240 this._knownTreeElements[element.identifier].remove(element, true); 241} 242 243TreeOutline.prototype._forgetChildrenRecursive = function(parentElement) 244{ 245 var child = parentElement.children[0]; 246 while (child) { 247 this._forgetTreeElement(child); 248 child = child.traverseNextTreeElement(false, this, true); 249 } 250} 251 252TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent, equal) 253{ 254 if (!representedObject) 255 return null; 256 257 if (!equal) 258 equal = function(a, b) { return a === b }; 259 260 if ("__treeElementIdentifier" in representedObject) { 261 // If this representedObject has a tree element identifier, and it is a known TreeElement 262 // in our tree we can just return that tree element. 263 var elements = this._knownTreeElements[representedObject.__treeElementIdentifier]; 264 if (elements) { 265 for (var i = 0; i < elements.length; ++i) 266 if (equal(elements[i].representedObject, representedObject)) 267 return elements[i]; 268 } 269 } 270 271 if (!isAncestor || !(isAncestor instanceof Function) || !getParent || !(getParent instanceof Function)) 272 return null; 273 274 // The representedObject isn't know, so we start at the top of the tree and work down to find the first 275 // tree element that represents representedObject or one of its ancestors. 276 var item; 277 var found = false; 278 for (var i = 0; i < this.children.length; ++i) { 279 item = this.children[i]; 280 if (equal(item.representedObject, representedObject) || isAncestor(item.representedObject, representedObject)) { 281 found = true; 282 break; 283 } 284 } 285 286 if (!found) 287 return null; 288 289 // Make sure the item that we found is connected to the root of the tree. 290 // Build up a list of representedObject's ancestors that aren't already in our tree. 291 var ancestors = []; 292 var currentObject = representedObject; 293 while (currentObject) { 294 ancestors.unshift(currentObject); 295 if (equal(currentObject, item.representedObject)) 296 break; 297 currentObject = getParent(currentObject); 298 } 299 300 // For each of those ancestors we populate them to fill in the tree. 301 for (var i = 0; i < ancestors.length; ++i) { 302 // Make sure we don't call findTreeElement with the same representedObject 303 // again, to prevent infinite recursion. 304 if (equal(ancestors[i], representedObject)) 305 continue; 306 // FIXME: we could do something faster than findTreeElement since we will know the next 307 // ancestor exists in the tree. 308 item = this.findTreeElement(ancestors[i], isAncestor, getParent, equal); 309 if (item && item.onpopulate) 310 item.onpopulate(item); 311 } 312 313 // Now that all the ancestors are populated, try to find the representedObject again. This time 314 // without the isAncestor and getParent functions to prevent an infinite recursion if it isn't found. 315 return this.findTreeElement(representedObject, null, null, equal); 316} 317 318TreeOutline.prototype.treeElementFromPoint = function(x, y) 319{ 320 var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y); 321 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]); 322 if (listNode) 323 return listNode.parentTreeElement || listNode.treeElement; 324 return null; 325} 326 327TreeOutline.prototype.handleKeyEvent = function(event) 328{ 329 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) 330 return false; 331 332 var handled = false; 333 var nextSelectedElement; 334 if (event.keyIdentifier === "Up" && !event.altKey) { 335 nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); 336 while (nextSelectedElement && !nextSelectedElement.selectable) 337 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); 338 handled = nextSelectedElement ? true : false; 339 } else if (event.keyIdentifier === "Down" && !event.altKey) { 340 nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); 341 while (nextSelectedElement && !nextSelectedElement.selectable) 342 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); 343 handled = nextSelectedElement ? true : false; 344 } else if (event.keyIdentifier === "Left") { 345 if (this.selectedTreeElement.expanded) { 346 if (event.altKey) 347 this.selectedTreeElement.collapseRecursively(); 348 else 349 this.selectedTreeElement.collapse(); 350 handled = true; 351 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { 352 handled = true; 353 if (this.selectedTreeElement.parent.selectable) { 354 nextSelectedElement = this.selectedTreeElement.parent; 355 handled = nextSelectedElement ? true : false; 356 } else if (this.selectedTreeElement.parent) 357 this.selectedTreeElement.parent.collapse(); 358 } 359 } else if (event.keyIdentifier === "Right") { 360 if (!this.selectedTreeElement.revealed()) { 361 this.selectedTreeElement.reveal(); 362 handled = true; 363 } else if (this.selectedTreeElement.hasChildren) { 364 handled = true; 365 if (this.selectedTreeElement.expanded) { 366 nextSelectedElement = this.selectedTreeElement.children[0]; 367 handled = nextSelectedElement ? true : false; 368 } else { 369 if (event.altKey) 370 this.selectedTreeElement.expandRecursively(); 371 else 372 this.selectedTreeElement.expand(); 373 } 374 } 375 } 376 377 if (nextSelectedElement) { 378 nextSelectedElement.reveal(); 379 nextSelectedElement.select(); 380 } 381 382 if (handled) { 383 event.preventDefault(); 384 event.stopPropagation(); 385 } 386 387 return handled; 388} 389 390TreeOutline.prototype.expand = function() 391{ 392 // this is the root, do nothing 393} 394 395TreeOutline.prototype.collapse = function() 396{ 397 // this is the root, do nothing 398} 399 400TreeOutline.prototype.revealed = function() 401{ 402 return true; 403} 404 405TreeOutline.prototype.reveal = function() 406{ 407 // this is the root, do nothing 408} 409 410TreeOutline.prototype.appendChild = TreeOutline._appendChild; 411TreeOutline.prototype.insertChild = TreeOutline._insertChild; 412TreeOutline.prototype.removeChild = TreeOutline._removeChild; 413TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; 414TreeOutline.prototype.removeChildren = TreeOutline._removeChildren; 415TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; 416 417function TreeElement(title, representedObject, hasChildren) 418{ 419 this._title = title; 420 this.representedObject = (representedObject || {}); 421 422 if (this.representedObject.__treeElementIdentifier) 423 this.identifier = this.representedObject.__treeElementIdentifier; 424 else { 425 this.identifier = TreeOutline._knownTreeElementNextIdentifier++; 426 this.representedObject.__treeElementIdentifier = this.identifier; 427 } 428 429 this._hidden = false; 430 this.expanded = false; 431 this.selected = false; 432 this.hasChildren = hasChildren; 433 this.children = []; 434 this.treeOutline = null; 435 this.parent = null; 436 this.previousSibling = null; 437 this.nextSibling = null; 438 this._listItemNode = null; 439} 440 441TreeElement.prototype = { 442 selectable: true, 443 arrowToggleWidth: 10, 444 445 get listItemElement() { 446 return this._listItemNode; 447 }, 448 449 get childrenListElement() { 450 return this._childrenListNode; 451 }, 452 453 get title() { 454 return this._title; 455 }, 456 457 set title(x) { 458 this._title = x; 459 if (this._listItemNode) 460 this._listItemNode.innerHTML = x; 461 }, 462 463 get tooltip() { 464 return this._tooltip; 465 }, 466 467 set tooltip(x) { 468 this._tooltip = x; 469 if (this._listItemNode) 470 this._listItemNode.title = x ? x : ""; 471 }, 472 473 get hasChildren() { 474 return this._hasChildren; 475 }, 476 477 set hasChildren(x) { 478 if (this._hasChildren === x) 479 return; 480 481 this._hasChildren = x; 482 483 if (!this._listItemNode) 484 return; 485 486 if (x) 487 this._listItemNode.addStyleClass("parent"); 488 else { 489 this._listItemNode.removeStyleClass("parent"); 490 this.collapse(); 491 } 492 }, 493 494 get hidden() { 495 return this._hidden; 496 }, 497 498 set hidden(x) { 499 if (this._hidden === x) 500 return; 501 502 this._hidden = x; 503 504 if (x) { 505 if (this._listItemNode) 506 this._listItemNode.addStyleClass("hidden"); 507 if (this._childrenListNode) 508 this._childrenListNode.addStyleClass("hidden"); 509 } else { 510 if (this._listItemNode) 511 this._listItemNode.removeStyleClass("hidden"); 512 if (this._childrenListNode) 513 this._childrenListNode.removeStyleClass("hidden"); 514 } 515 }, 516 517 get shouldRefreshChildren() { 518 return this._shouldRefreshChildren; 519 }, 520 521 set shouldRefreshChildren(x) { 522 this._shouldRefreshChildren = x; 523 if (x && this.expanded) 524 this.expand(); 525 } 526} 527 528TreeElement.prototype.appendChild = TreeOutline._appendChild; 529TreeElement.prototype.insertChild = TreeOutline._insertChild; 530TreeElement.prototype.removeChild = TreeOutline._removeChild; 531TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; 532TreeElement.prototype.removeChildren = TreeOutline._removeChildren; 533TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; 534 535TreeElement.prototype._attach = function() 536{ 537 if (!this._listItemNode || this.parent._shouldRefreshChildren) { 538 if (this._listItemNode && this._listItemNode.parentNode) 539 this._listItemNode.parentNode.removeChild(this._listItemNode); 540 541 this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); 542 this._listItemNode.treeElement = this; 543 this._listItemNode.innerHTML = this._title; 544 this._listItemNode.title = this._tooltip ? this._tooltip : ""; 545 546 if (this.hidden) 547 this._listItemNode.addStyleClass("hidden"); 548 if (this.hasChildren) 549 this._listItemNode.addStyleClass("parent"); 550 if (this.expanded) 551 this._listItemNode.addStyleClass("expanded"); 552 if (this.selected) 553 this._listItemNode.addStyleClass("selected"); 554 555 this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false); 556 this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); 557 this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); 558 559 if (this.onattach) 560 this.onattach(this); 561 } 562 563 var nextSibling = null; 564 if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) 565 nextSibling = this.nextSibling._listItemNode; 566 this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); 567 if (this._childrenListNode) 568 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 569 if (this.selected) 570 this.select(); 571 if (this.expanded) 572 this.expand(); 573} 574 575TreeElement.prototype._detach = function() 576{ 577 if (this._listItemNode && this._listItemNode.parentNode) 578 this._listItemNode.parentNode.removeChild(this._listItemNode); 579 if (this._childrenListNode && this._childrenListNode.parentNode) 580 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 581} 582 583TreeElement.treeElementSelected = function(event) 584{ 585 var element = event.currentTarget; 586 if (!element || !element.treeElement || !element.treeElement.selectable) 587 return; 588 589 if (element.treeElement.isEventWithinDisclosureTriangle(event)) 590 return; 591 592 element.treeElement.select(); 593} 594 595TreeElement.treeElementToggled = function(event) 596{ 597 var element = event.currentTarget; 598 if (!element || !element.treeElement) 599 return; 600 601 if (!element.treeElement.isEventWithinDisclosureTriangle(event)) 602 return; 603 604 if (element.treeElement.expanded) { 605 if (event.altKey) 606 element.treeElement.collapseRecursively(); 607 else 608 element.treeElement.collapse(); 609 } else { 610 if (event.altKey) 611 element.treeElement.expandRecursively(); 612 else 613 element.treeElement.expand(); 614 } 615} 616 617TreeElement.treeElementDoubleClicked = function(event) 618{ 619 var element = event.currentTarget; 620 if (!element || !element.treeElement) 621 return; 622 623 if (element.treeElement.ondblclick) 624 element.treeElement.ondblclick(element.treeElement, event); 625 else if (element.treeElement.hasChildren && !element.treeElement.expanded) 626 element.treeElement.expand(); 627} 628 629TreeElement.prototype.collapse = function() 630{ 631 if (this._listItemNode) 632 this._listItemNode.removeStyleClass("expanded"); 633 if (this._childrenListNode) 634 this._childrenListNode.removeStyleClass("expanded"); 635 636 this.expanded = false; 637 if (this.treeOutline) 638 this.treeOutline._treeElementsExpandedState[this.identifier] = true; 639 640 if (this.oncollapse) 641 this.oncollapse(this); 642} 643 644TreeElement.prototype.collapseRecursively = function() 645{ 646 var item = this; 647 while (item) { 648 if (item.expanded) 649 item.collapse(); 650 item = item.traverseNextTreeElement(false, this, true); 651 } 652} 653 654TreeElement.prototype.expand = function() 655{ 656 if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode)) 657 return; 658 659 if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { 660 if (this._childrenListNode && this._childrenListNode.parentNode) 661 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 662 663 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 664 this._childrenListNode.parentTreeElement = this; 665 this._childrenListNode.addStyleClass("children"); 666 667 if (this.hidden) 668 this._childrenListNode.addStyleClass("hidden"); 669 670 if (this.onpopulate) 671 this.onpopulate(this); 672 673 for (var i = 0; i < this.children.length; ++i) 674 this.children[i]._attach(); 675 676 delete this._shouldRefreshChildren; 677 } 678 679 if (this._listItemNode) { 680 this._listItemNode.addStyleClass("expanded"); 681 if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode) 682 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 683 } 684 685 if (this._childrenListNode) 686 this._childrenListNode.addStyleClass("expanded"); 687 688 this.expanded = true; 689 if (this.treeOutline) 690 this.treeOutline._treeElementsExpandedState[this.identifier] = true; 691 692 if (this.onexpand) 693 this.onexpand(this); 694} 695 696TreeElement.prototype.expandRecursively = function(maxDepth) 697{ 698 var item = this; 699 var info = {}; 700 var depth = 0; 701 702 // The Inspector uses TreeOutlines to represents object properties, so recursive expansion 703 // in some case can be infinite, since JavaScript objects can hold circular references. 704 // So default to a recursion cap of 3 levels, since that gives fairly good results. 705 if (typeof maxDepth === "undefined" || typeof maxDepth === "null") 706 maxDepth = 3; 707 708 while (item) { 709 if (depth < maxDepth) 710 item.expand(); 711 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); 712 depth += info.depthChange; 713 } 714} 715 716TreeElement.prototype.hasAncestor = function(ancestor) { 717 if (!ancestor) 718 return false; 719 720 var currentNode = this.parent; 721 while (currentNode) { 722 if (ancestor === currentNode) 723 return true; 724 currentNode = currentNode.parent; 725 } 726 727 return false; 728} 729 730TreeElement.prototype.reveal = function() 731{ 732 var currentAncestor = this.parent; 733 while (currentAncestor && !currentAncestor.root) { 734 if (!currentAncestor.expanded) 735 currentAncestor.expand(); 736 currentAncestor = currentAncestor.parent; 737 } 738 739 if (this.onreveal) 740 this.onreveal(this); 741} 742 743TreeElement.prototype.revealed = function() 744{ 745 var currentAncestor = this.parent; 746 while (currentAncestor && !currentAncestor.root) { 747 if (!currentAncestor.expanded) 748 return false; 749 currentAncestor = currentAncestor.parent; 750 } 751 752 return true; 753} 754 755TreeElement.prototype.select = function(supressOnSelect) 756{ 757 if (!this.treeOutline || !this.selectable || this.selected) 758 return; 759 760 if (this.treeOutline.selectedTreeElement) 761 this.treeOutline.selectedTreeElement.deselect(); 762 763 this.selected = true; 764 this.treeOutline.selectedTreeElement = this; 765 if (this._listItemNode) 766 this._listItemNode.addStyleClass("selected"); 767 768 if (this.onselect && !supressOnSelect) 769 this.onselect(this); 770} 771 772TreeElement.prototype.deselect = function(supressOnDeselect) 773{ 774 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) 775 return; 776 777 this.selected = false; 778 this.treeOutline.selectedTreeElement = null; 779 if (this._listItemNode) 780 this._listItemNode.removeStyleClass("selected"); 781 782 if (this.ondeselect && !supressOnDeselect) 783 this.ondeselect(this); 784} 785 786TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info) 787{ 788 if (!dontPopulate && this.hasChildren && this.onpopulate) 789 this.onpopulate(this); 790 791 if (info) 792 info.depthChange = 0; 793 794 var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0]; 795 if (element && (!skipHidden || (skipHidden && this.expanded))) { 796 if (info) 797 info.depthChange = 1; 798 return element; 799 } 800 801 if (this === stayWithin) 802 return null; 803 804 element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; 805 if (element) 806 return element; 807 808 element = this; 809 while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) { 810 if (info) 811 info.depthChange -= 1; 812 element = element.parent; 813 } 814 815 if (!element) 816 return null; 817 818 return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); 819} 820 821TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate) 822{ 823 var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; 824 if (!dontPopulate && element && element.hasChildren && element.onpopulate) 825 element.onpopulate(element); 826 827 while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { 828 if (!dontPopulate && element.hasChildren && element.onpopulate) 829 element.onpopulate(element); 830 element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); 831 } 832 833 if (element) 834 return element; 835 836 if (!this.parent || this.parent.root) 837 return null; 838 839 return this.parent; 840} 841 842TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) 843{ 844 var left = this._listItemNode.totalOffsetLeft; 845 return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; 846} 847