treeoutline.js revision 643ca7872b450ea4efacab6188849e5aac2ba161
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.getCachedTreeElement = function(representedObject) 253{ 254 if (!representedObject) 255 return null; 256 257 if ("__treeElementIdentifier" in representedObject) { 258 // If this representedObject has a tree element identifier, and it is a known TreeElement 259 // in our tree we can just return that tree element. 260 var elements = this._knownTreeElements[representedObject.__treeElementIdentifier]; 261 if (elements) { 262 for (var i = 0; i < elements.length; ++i) 263 if (elements[i].representedObject === representedObject) 264 return elements[i]; 265 } 266 } 267 return null; 268} 269 270TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent) 271{ 272 if (!representedObject) 273 return null; 274 275 var cachedElement = this.getCachedTreeElement(representedObject); 276 if (cachedElement) 277 return cachedElement; 278 279 // The representedObject isn't know, so we start at the top of the tree and work down to find the first 280 // tree element that represents representedObject or one of its ancestors. 281 var item; 282 var found = false; 283 for (var i = 0; i < this.children.length; ++i) { 284 item = this.children[i]; 285 if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) { 286 found = true; 287 break; 288 } 289 } 290 291 if (!found) 292 return null; 293 294 // Make sure the item that we found is connected to the root of the tree. 295 // Build up a list of representedObject's ancestors that aren't already in our tree. 296 var ancestors = []; 297 var currentObject = representedObject; 298 while (currentObject) { 299 ancestors.unshift(currentObject); 300 if (currentObject === item.representedObject) 301 break; 302 currentObject = getParent(currentObject); 303 } 304 305 // For each of those ancestors we populate them to fill in the tree. 306 for (var i = 0; i < ancestors.length; ++i) { 307 // Make sure we don't call findTreeElement with the same representedObject 308 // again, to prevent infinite recursion. 309 if (ancestors[i] === representedObject) 310 continue; 311 // FIXME: we could do something faster than findTreeElement since we will know the next 312 // ancestor exists in the tree. 313 item = this.findTreeElement(ancestors[i], isAncestor, getParent); 314 if (item && item.onpopulate) 315 item.onpopulate(item); 316 } 317 318 return this.getCachedTreeElement(representedObject); 319} 320 321TreeOutline.prototype.treeElementFromPoint = function(x, y) 322{ 323 var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y); 324 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]); 325 if (listNode) 326 return listNode.parentTreeElement || listNode.treeElement; 327 return null; 328} 329 330TreeOutline.prototype.handleKeyEvent = function(event) 331{ 332 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) 333 return false; 334 335 var handled = false; 336 var nextSelectedElement; 337 if (event.keyIdentifier === "Up" && !event.altKey) { 338 nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); 339 while (nextSelectedElement && !nextSelectedElement.selectable) 340 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); 341 handled = nextSelectedElement ? true : false; 342 } else if (event.keyIdentifier === "Down" && !event.altKey) { 343 nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); 344 while (nextSelectedElement && !nextSelectedElement.selectable) 345 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); 346 handled = nextSelectedElement ? true : false; 347 } else if (event.keyIdentifier === "Left") { 348 if (this.selectedTreeElement.expanded) { 349 if (event.altKey) 350 this.selectedTreeElement.collapseRecursively(); 351 else 352 this.selectedTreeElement.collapse(); 353 handled = true; 354 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { 355 handled = true; 356 if (this.selectedTreeElement.parent.selectable) { 357 nextSelectedElement = this.selectedTreeElement.parent; 358 handled = nextSelectedElement ? true : false; 359 } else if (this.selectedTreeElement.parent) 360 this.selectedTreeElement.parent.collapse(); 361 } 362 } else if (event.keyIdentifier === "Right") { 363 if (!this.selectedTreeElement.revealed()) { 364 this.selectedTreeElement.reveal(); 365 handled = true; 366 } else if (this.selectedTreeElement.hasChildren) { 367 handled = true; 368 if (this.selectedTreeElement.expanded) { 369 nextSelectedElement = this.selectedTreeElement.children[0]; 370 handled = nextSelectedElement ? true : false; 371 } else { 372 if (event.altKey) 373 this.selectedTreeElement.expandRecursively(); 374 else 375 this.selectedTreeElement.expand(); 376 } 377 } 378 } 379 380 if (nextSelectedElement) { 381 nextSelectedElement.reveal(); 382 nextSelectedElement.select(); 383 } 384 385 if (handled) { 386 event.preventDefault(); 387 event.stopPropagation(); 388 } 389 390 return handled; 391} 392 393TreeOutline.prototype.expand = function() 394{ 395 // this is the root, do nothing 396} 397 398TreeOutline.prototype.collapse = function() 399{ 400 // this is the root, do nothing 401} 402 403TreeOutline.prototype.revealed = function() 404{ 405 return true; 406} 407 408TreeOutline.prototype.reveal = function() 409{ 410 // this is the root, do nothing 411} 412 413TreeOutline.prototype.appendChild = TreeOutline._appendChild; 414TreeOutline.prototype.insertChild = TreeOutline._insertChild; 415TreeOutline.prototype.removeChild = TreeOutline._removeChild; 416TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; 417TreeOutline.prototype.removeChildren = TreeOutline._removeChildren; 418TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; 419 420function TreeElement(title, representedObject, hasChildren) 421{ 422 this._title = title; 423 this.representedObject = (representedObject || {}); 424 425 if (this.representedObject.__treeElementIdentifier) 426 this.identifier = this.representedObject.__treeElementIdentifier; 427 else { 428 this.identifier = TreeOutline._knownTreeElementNextIdentifier++; 429 this.representedObject.__treeElementIdentifier = this.identifier; 430 } 431 432 this._hidden = false; 433 this.expanded = false; 434 this.selected = false; 435 this.hasChildren = hasChildren; 436 this.children = []; 437 this.treeOutline = null; 438 this.parent = null; 439 this.previousSibling = null; 440 this.nextSibling = null; 441 this._listItemNode = null; 442} 443 444TreeElement.prototype = { 445 selectable: true, 446 arrowToggleWidth: 10, 447 448 get listItemElement() { 449 return this._listItemNode; 450 }, 451 452 get childrenListElement() { 453 return this._childrenListNode; 454 }, 455 456 get title() { 457 return this._title; 458 }, 459 460 set title(x) { 461 this._title = x; 462 if (this._listItemNode) 463 this._listItemNode.innerHTML = x; 464 }, 465 466 get tooltip() { 467 return this._tooltip; 468 }, 469 470 set tooltip(x) { 471 this._tooltip = x; 472 if (this._listItemNode) 473 this._listItemNode.title = x ? x : ""; 474 }, 475 476 get hasChildren() { 477 return this._hasChildren; 478 }, 479 480 set hasChildren(x) { 481 if (this._hasChildren === x) 482 return; 483 484 this._hasChildren = x; 485 486 if (!this._listItemNode) 487 return; 488 489 if (x) 490 this._listItemNode.addStyleClass("parent"); 491 else { 492 this._listItemNode.removeStyleClass("parent"); 493 this.collapse(); 494 } 495 }, 496 497 get hidden() { 498 return this._hidden; 499 }, 500 501 set hidden(x) { 502 if (this._hidden === x) 503 return; 504 505 this._hidden = x; 506 507 if (x) { 508 if (this._listItemNode) 509 this._listItemNode.addStyleClass("hidden"); 510 if (this._childrenListNode) 511 this._childrenListNode.addStyleClass("hidden"); 512 } else { 513 if (this._listItemNode) 514 this._listItemNode.removeStyleClass("hidden"); 515 if (this._childrenListNode) 516 this._childrenListNode.removeStyleClass("hidden"); 517 } 518 }, 519 520 get shouldRefreshChildren() { 521 return this._shouldRefreshChildren; 522 }, 523 524 set shouldRefreshChildren(x) { 525 this._shouldRefreshChildren = x; 526 if (x && this.expanded) 527 this.expand(); 528 } 529} 530 531TreeElement.prototype.appendChild = TreeOutline._appendChild; 532TreeElement.prototype.insertChild = TreeOutline._insertChild; 533TreeElement.prototype.removeChild = TreeOutline._removeChild; 534TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; 535TreeElement.prototype.removeChildren = TreeOutline._removeChildren; 536TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; 537 538TreeElement.prototype._attach = function() 539{ 540 if (!this._listItemNode || this.parent._shouldRefreshChildren) { 541 if (this._listItemNode && this._listItemNode.parentNode) 542 this._listItemNode.parentNode.removeChild(this._listItemNode); 543 544 this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); 545 this._listItemNode.treeElement = this; 546 this._listItemNode.innerHTML = this._title; 547 this._listItemNode.title = this._tooltip ? this._tooltip : ""; 548 549 if (this.hidden) 550 this._listItemNode.addStyleClass("hidden"); 551 if (this.hasChildren) 552 this._listItemNode.addStyleClass("parent"); 553 if (this.expanded) 554 this._listItemNode.addStyleClass("expanded"); 555 if (this.selected) 556 this._listItemNode.addStyleClass("selected"); 557 558 this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false); 559 this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); 560 this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); 561 562 if (this.onattach) 563 this.onattach(this); 564 } 565 566 var nextSibling = null; 567 if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) 568 nextSibling = this.nextSibling._listItemNode; 569 this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); 570 if (this._childrenListNode) 571 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 572 if (this.selected) 573 this.select(); 574 if (this.expanded) 575 this.expand(); 576} 577 578TreeElement.prototype._detach = function() 579{ 580 if (this._listItemNode && this._listItemNode.parentNode) 581 this._listItemNode.parentNode.removeChild(this._listItemNode); 582 if (this._childrenListNode && this._childrenListNode.parentNode) 583 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 584} 585 586TreeElement.treeElementSelected = function(event) 587{ 588 var element = event.currentTarget; 589 if (!element || !element.treeElement || !element.treeElement.selectable) 590 return; 591 592 if (element.treeElement.isEventWithinDisclosureTriangle(event)) 593 return; 594 595 element.treeElement.select(); 596} 597 598TreeElement.treeElementToggled = function(event) 599{ 600 var element = event.currentTarget; 601 if (!element || !element.treeElement) 602 return; 603 604 if (!element.treeElement.isEventWithinDisclosureTriangle(event)) 605 return; 606 607 if (element.treeElement.expanded) { 608 if (event.altKey) 609 element.treeElement.collapseRecursively(); 610 else 611 element.treeElement.collapse(); 612 } else { 613 if (event.altKey) 614 element.treeElement.expandRecursively(); 615 else 616 element.treeElement.expand(); 617 } 618} 619 620TreeElement.treeElementDoubleClicked = function(event) 621{ 622 var element = event.currentTarget; 623 if (!element || !element.treeElement) 624 return; 625 626 if (element.treeElement.ondblclick) 627 element.treeElement.ondblclick.call(element.treeElement, event); 628 else if (element.treeElement.hasChildren && !element.treeElement.expanded) 629 element.treeElement.expand(); 630} 631 632TreeElement.prototype.collapse = function() 633{ 634 if (this._listItemNode) 635 this._listItemNode.removeStyleClass("expanded"); 636 if (this._childrenListNode) 637 this._childrenListNode.removeStyleClass("expanded"); 638 639 this.expanded = false; 640 if (this.treeOutline) 641 this.treeOutline._treeElementsExpandedState[this.identifier] = true; 642 643 if (this.oncollapse) 644 this.oncollapse(this); 645} 646 647TreeElement.prototype.collapseRecursively = function() 648{ 649 var item = this; 650 while (item) { 651 if (item.expanded) 652 item.collapse(); 653 item = item.traverseNextTreeElement(false, this, true); 654 } 655} 656 657TreeElement.prototype.expand = function() 658{ 659 if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode)) 660 return; 661 662 if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { 663 if (this._childrenListNode && this._childrenListNode.parentNode) 664 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 665 666 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 667 this._childrenListNode.parentTreeElement = this; 668 this._childrenListNode.addStyleClass("children"); 669 670 if (this.hidden) 671 this._childrenListNode.addStyleClass("hidden"); 672 673 if (this.onpopulate) 674 this.onpopulate(this); 675 676 for (var i = 0; i < this.children.length; ++i) 677 this.children[i]._attach(); 678 679 delete this._shouldRefreshChildren; 680 } 681 682 if (this._listItemNode) { 683 this._listItemNode.addStyleClass("expanded"); 684 if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode) 685 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 686 } 687 688 if (this._childrenListNode) 689 this._childrenListNode.addStyleClass("expanded"); 690 691 this.expanded = true; 692 if (this.treeOutline) 693 this.treeOutline._treeElementsExpandedState[this.identifier] = true; 694 695 if (this.onexpand) 696 this.onexpand(this); 697} 698 699TreeElement.prototype.expandRecursively = function(maxDepth) 700{ 701 var item = this; 702 var info = {}; 703 var depth = 0; 704 705 // The Inspector uses TreeOutlines to represents object properties, so recursive expansion 706 // in some case can be infinite, since JavaScript objects can hold circular references. 707 // So default to a recursion cap of 3 levels, since that gives fairly good results. 708 if (typeof maxDepth === "undefined" || typeof maxDepth === "null") 709 maxDepth = 3; 710 711 while (item) { 712 if (depth < maxDepth) 713 item.expand(); 714 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); 715 depth += info.depthChange; 716 } 717} 718 719TreeElement.prototype.hasAncestor = function(ancestor) { 720 if (!ancestor) 721 return false; 722 723 var currentNode = this.parent; 724 while (currentNode) { 725 if (ancestor === currentNode) 726 return true; 727 currentNode = currentNode.parent; 728 } 729 730 return false; 731} 732 733TreeElement.prototype.reveal = function() 734{ 735 var currentAncestor = this.parent; 736 while (currentAncestor && !currentAncestor.root) { 737 if (!currentAncestor.expanded) 738 currentAncestor.expand(); 739 currentAncestor = currentAncestor.parent; 740 } 741 742 if (this.onreveal) 743 this.onreveal(this); 744} 745 746TreeElement.prototype.revealed = function() 747{ 748 var currentAncestor = this.parent; 749 while (currentAncestor && !currentAncestor.root) { 750 if (!currentAncestor.expanded) 751 return false; 752 currentAncestor = currentAncestor.parent; 753 } 754 755 return true; 756} 757 758TreeElement.prototype.select = function(supressOnSelect) 759{ 760 if (!this.treeOutline || !this.selectable || this.selected) 761 return; 762 763 if (this.treeOutline.selectedTreeElement) 764 this.treeOutline.selectedTreeElement.deselect(); 765 766 this.selected = true; 767 this.treeOutline.selectedTreeElement = this; 768 if (this._listItemNode) 769 this._listItemNode.addStyleClass("selected"); 770 771 if (this.onselect && !supressOnSelect) 772 this.onselect(this); 773} 774 775TreeElement.prototype.deselect = function(supressOnDeselect) 776{ 777 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) 778 return; 779 780 this.selected = false; 781 this.treeOutline.selectedTreeElement = null; 782 if (this._listItemNode) 783 this._listItemNode.removeStyleClass("selected"); 784 785 if (this.ondeselect && !supressOnDeselect) 786 this.ondeselect(this); 787} 788 789TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info) 790{ 791 if (!dontPopulate && this.hasChildren && this.onpopulate) 792 this.onpopulate(this); 793 794 if (info) 795 info.depthChange = 0; 796 797 var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0]; 798 if (element && (!skipHidden || (skipHidden && this.expanded))) { 799 if (info) 800 info.depthChange = 1; 801 return element; 802 } 803 804 if (this === stayWithin) 805 return null; 806 807 element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; 808 if (element) 809 return element; 810 811 element = this; 812 while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) { 813 if (info) 814 info.depthChange -= 1; 815 element = element.parent; 816 } 817 818 if (!element) 819 return null; 820 821 return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); 822} 823 824TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate) 825{ 826 var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; 827 if (!dontPopulate && element && element.hasChildren && element.onpopulate) 828 element.onpopulate(element); 829 830 while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { 831 if (!dontPopulate && element.hasChildren && element.onpopulate) 832 element.onpopulate(element); 833 element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); 834 } 835 836 if (element) 837 return element; 838 839 if (!this.parent || this.parent.root) 840 return null; 841 842 return this.parent; 843} 844 845TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) 846{ 847 var left = this._listItemNode.totalOffsetLeft; 848 return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; 849} 850