2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
32 function TreeOutline(listNode)
35 * @type {Array.<TreeElement>}
38 this.selectedTreeElement = null;
39 this._childrenListNode = listNode;
40 this._childrenListNode.removeChildren();
41 this._knownTreeElements = [];
42 this._treeElementsExpandedState = [];
43 this.expandTreeElementsWhenArrowing = false;
45 this.hasChildren = false;
47 this.selected = false;
48 this.treeOutline = this;
50 this._childrenListNode.tabIndex = 0;
51 this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
54 TreeOutline._knownTreeElementNextIdentifier = 1;
56 TreeOutline.prototype.appendChild = function(child)
59 throw("child can't be undefined or null");
61 var lastChild = this.children[this.children.length - 1];
63 lastChild.nextSibling = child;
64 child.previousSibling = lastChild;
66 child.previousSibling = null;
67 child.nextSibling = null;
70 this.children.push(child);
71 this.hasChildren = true;
73 child.treeOutline = this.treeOutline;
74 child.treeOutline._rememberTreeElement(child);
76 var current = child.children[0];
78 current.treeOutline = this.treeOutline;
79 current.treeOutline._rememberTreeElement(current);
80 current = current.traverseNextTreeElement(false, child, true);
83 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
84 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
86 if (!this._childrenListNode) {
87 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
88 this._childrenListNode.parentTreeElement = this;
89 this._childrenListNode.addStyleClass("children");
91 this._childrenListNode.addStyleClass("hidden");
97 TreeOutline.prototype.insertChild = function(child, index)
100 throw("child can't be undefined or null");
102 var previousChild = (index > 0 ? this.children[index - 1] : null);
104 previousChild.nextSibling = child;
105 child.previousSibling = previousChild;
107 child.previousSibling = null;
110 var nextChild = this.children[index];
112 nextChild.previousSibling = child;
113 child.nextSibling = nextChild;
115 child.nextSibling = null;
118 this.children.splice(index, 0, child);
119 this.hasChildren = true;
121 child.treeOutline = this.treeOutline;
122 child.treeOutline._rememberTreeElement(child);
124 var current = child.children[0];
126 current.treeOutline = this.treeOutline;
127 current.treeOutline._rememberTreeElement(current);
128 current = current.traverseNextTreeElement(false, child, true);
131 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
132 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
134 if (!this._childrenListNode) {
135 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
136 this._childrenListNode.parentTreeElement = this;
137 this._childrenListNode.addStyleClass("children");
139 this._childrenListNode.addStyleClass("hidden");
145 TreeOutline.prototype.removeChildAtIndex = function(childIndex)
147 if (childIndex < 0 || childIndex >= this.children.length)
148 throw("childIndex out of range");
150 var child = this.children[childIndex];
151 this.children.splice(childIndex, 1);
153 var parent = child.parent;
154 if (child.deselect()) {
155 if (child.previousSibling)
156 child.previousSibling.select();
157 else if (child.nextSibling)
158 child.nextSibling.select();
163 if (child.previousSibling)
164 child.previousSibling.nextSibling = child.nextSibling;
165 if (child.nextSibling)
166 child.nextSibling.previousSibling = child.previousSibling;
168 if (child.treeOutline) {
169 child.treeOutline._forgetTreeElement(child);
170 child.treeOutline._forgetChildrenRecursive(child);
174 child.treeOutline = null;
176 child.nextSibling = null;
177 child.previousSibling = null;
180 TreeOutline.prototype.removeChild = function(child)
183 throw("child can't be undefined or null");
185 var childIndex = this.children.indexOf(child);
186 if (childIndex === -1)
187 throw("child not found in this node's children");
189 this.removeChildAtIndex.call(this, childIndex);
192 TreeOutline.prototype.removeChildren = function()
194 for (var i = 0; i < this.children.length; ++i) {
195 var child = this.children[i];
198 if (child.treeOutline) {
199 child.treeOutline._forgetTreeElement(child);
200 child.treeOutline._forgetChildrenRecursive(child);
204 child.treeOutline = null;
206 child.nextSibling = null;
207 child.previousSibling = null;
213 TreeOutline.prototype.removeChildrenRecursive = function()
215 var childrenToRemove = this.children;
217 var child = this.children[0];
219 if (child.children.length)
220 childrenToRemove = childrenToRemove.concat(child.children);
221 child = child.traverseNextTreeElement(false, this, true);
224 for (var i = 0; i < childrenToRemove.length; ++i) {
225 child = childrenToRemove[i];
227 if (child.treeOutline)
228 child.treeOutline._forgetTreeElement(child);
231 child.treeOutline = null;
233 child.nextSibling = null;
234 child.previousSibling = null;
240 TreeOutline.prototype._rememberTreeElement = function(element)
242 if (!this._knownTreeElements[element.identifier])
243 this._knownTreeElements[element.identifier] = [];
245 // check if the element is already known
246 var elements = this._knownTreeElements[element.identifier];
247 if (elements.indexOf(element) !== -1)
251 elements.push(element);
254 TreeOutline.prototype._forgetTreeElement = function(element)
256 if (this._knownTreeElements[element.identifier])
257 this._knownTreeElements[element.identifier].remove(element, true);
260 TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
262 var child = parentElement.children[0];
264 this._forgetTreeElement(child);
265 child = child.traverseNextTreeElement(false, this, true);
269 TreeOutline.prototype.getCachedTreeElement = function(representedObject)
271 if (!representedObject)
274 if ("__treeElementIdentifier" in representedObject) {
275 // If this representedObject has a tree element identifier, and it is a known TreeElement
276 // in our tree we can just return that tree element.
277 var elements = this._knownTreeElements[representedObject.__treeElementIdentifier];
279 for (var i = 0; i < elements.length; ++i)
280 if (elements[i].representedObject === representedObject)
287 TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
289 if (!representedObject)
292 var cachedElement = this.getCachedTreeElement(representedObject);
294 return cachedElement;
296 // The representedObject isn't known, so we start at the top of the tree and work down to find the first
297 // tree element that represents representedObject or one of its ancestors.
300 for (var i = 0; i < this.children.length; ++i) {
301 item = this.children[i];
302 if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) {
311 // Make sure the item that we found is connected to the root of the tree.
312 // Build up a list of representedObject's ancestors that aren't already in our tree.
314 var currentObject = representedObject;
315 while (currentObject) {
316 ancestors.unshift(currentObject);
317 if (currentObject === item.representedObject)
319 currentObject = getParent(currentObject);
322 // For each of those ancestors we populate them to fill in the tree.
323 for (var i = 0; i < ancestors.length; ++i) {
324 // Make sure we don't call findTreeElement with the same representedObject
325 // again, to prevent infinite recursion.
326 if (ancestors[i] === representedObject)
328 // FIXME: we could do something faster than findTreeElement since we will know the next
329 // ancestor exists in the tree.
330 item = this.findTreeElement(ancestors[i], isAncestor, getParent);
335 return this.getCachedTreeElement(representedObject);
338 TreeOutline.prototype.treeElementFromPoint = function(x, y)
340 var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
344 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
346 return listNode.parentTreeElement || listNode.treeElement;
350 TreeOutline.prototype._treeKeyDown = function(event)
352 if (event.target !== this._childrenListNode)
355 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
359 var nextSelectedElement;
360 if (event.keyIdentifier === "Up" && !event.altKey) {
361 nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
362 while (nextSelectedElement && !nextSelectedElement.selectable)
363 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
364 handled = nextSelectedElement ? true : false;
365 } else if (event.keyIdentifier === "Down" && !event.altKey) {
366 nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
367 while (nextSelectedElement && !nextSelectedElement.selectable)
368 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
369 handled = nextSelectedElement ? true : false;
370 } else if (event.keyIdentifier === "Left") {
371 if (this.selectedTreeElement.expanded) {
373 this.selectedTreeElement.collapseRecursively();
375 this.selectedTreeElement.collapse();
377 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
379 if (this.selectedTreeElement.parent.selectable) {
380 nextSelectedElement = this.selectedTreeElement.parent;
381 handled = nextSelectedElement ? true : false;
382 } else if (this.selectedTreeElement.parent)
383 this.selectedTreeElement.parent.collapse();
385 } else if (event.keyIdentifier === "Right") {
386 if (!this.selectedTreeElement.revealed()) {
387 this.selectedTreeElement.reveal();
389 } else if (this.selectedTreeElement.hasChildren) {
391 if (this.selectedTreeElement.expanded) {
392 nextSelectedElement = this.selectedTreeElement.children[0];
393 handled = nextSelectedElement ? true : false;
396 this.selectedTreeElement.expandRecursively();
398 this.selectedTreeElement.expand();
401 } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) {
402 if (this.selectedTreeElement.ondelete)
403 handled = this.selectedTreeElement.ondelete();
404 } else if (isEnterKey(event)) {
405 if (this.selectedTreeElement.onenter)
406 handled = this.selectedTreeElement.onenter();
409 if (nextSelectedElement) {
410 nextSelectedElement.reveal();
411 nextSelectedElement.select(false, true);
415 event.preventDefault();
416 event.stopPropagation();
420 TreeOutline.prototype.expand = function()
422 // this is the root, do nothing
425 TreeOutline.prototype.collapse = function()
427 // this is the root, do nothing
430 TreeOutline.prototype.revealed = function()
435 TreeOutline.prototype.reveal = function()
437 // this is the root, do nothing
440 TreeOutline.prototype.select = function()
442 // this is the root, do nothing
445 TreeOutline.prototype.revealAndSelect = function(omitFocus)
447 // this is the root, do nothing
452 * @param {Object=} representedObject
453 * @param {boolean=} hasChildren
455 function TreeElement(title, representedObject, hasChildren)
458 this.representedObject = (representedObject || {});
460 if (this.representedObject.__treeElementIdentifier)
461 this.identifier = this.representedObject.__treeElementIdentifier;
463 this.identifier = TreeOutline._knownTreeElementNextIdentifier++;
464 this.representedObject.__treeElementIdentifier = this.identifier;
467 this._hidden = false;
468 this.expanded = false;
469 this.selected = false;
470 this.hasChildren = hasChildren;
472 this.treeOutline = null;
474 this.previousSibling = null;
475 this.nextSibling = null;
476 this._listItemNode = null;
479 TreeElement.prototype = {
481 arrowToggleWidth: 10,
483 get listItemElement() {
484 return this._listItemNode;
487 get childrenListElement() {
488 return this._childrenListNode;
497 this._setListItemNodeContent();
501 return this._titleHTML;
506 this._setListItemNodeContent();
510 return this._tooltip;
515 if (this._listItemNode)
516 this._listItemNode.title = x ? x : "";
520 return this._hasChildren;
524 if (this._hasChildren === x)
527 this._hasChildren = x;
529 if (!this._listItemNode)
533 this._listItemNode.addStyleClass("parent");
535 this._listItemNode.removeStyleClass("parent");
545 if (this._hidden === x)
551 if (this._listItemNode)
552 this._listItemNode.addStyleClass("hidden");
553 if (this._childrenListNode)
554 this._childrenListNode.addStyleClass("hidden");
556 if (this._listItemNode)
557 this._listItemNode.removeStyleClass("hidden");
558 if (this._childrenListNode)
559 this._childrenListNode.removeStyleClass("hidden");
563 get shouldRefreshChildren() {
564 return this._shouldRefreshChildren;
567 set shouldRefreshChildren(x) {
568 this._shouldRefreshChildren = x;
569 if (x && this.expanded)
573 _setListItemNodeContent: function()
575 if (!this._listItemNode)
578 if (!this._titleHTML && !this._title)
579 this._listItemNode.removeChildren();
580 else if (typeof this._titleHTML === "string")
581 this._listItemNode.innerHTML = this._titleHTML;
582 else if (typeof this._title === "string")
583 this._listItemNode.textContent = this._title;
585 this._listItemNode.removeChildren();
586 if (this._title.parentNode)
587 this._title.parentNode.removeChild(this._title);
588 this._listItemNode.appendChild(this._title);
593 TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild;
594 TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild;
595 TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild;
596 TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex;
597 TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren;
598 TreeElement.prototype.removeChildrenRecursive = TreeOutline.prototype.removeChildrenRecursive;
600 TreeElement.prototype._attach = function()
602 if (!this._listItemNode || this.parent._shouldRefreshChildren) {
603 if (this._listItemNode && this._listItemNode.parentNode)
604 this._listItemNode.parentNode.removeChild(this._listItemNode);
606 this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
607 this._listItemNode.treeElement = this;
608 this._setListItemNodeContent();
609 this._listItemNode.title = this._tooltip ? this._tooltip : "";
612 this._listItemNode.addStyleClass("hidden");
613 if (this.hasChildren)
614 this._listItemNode.addStyleClass("parent");
616 this._listItemNode.addStyleClass("expanded");
618 this._listItemNode.addStyleClass("selected");
620 this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false);
621 this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
622 this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
628 var nextSibling = null;
629 if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
630 nextSibling = this.nextSibling._listItemNode;
631 this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
632 if (this._childrenListNode)
633 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
640 TreeElement.prototype._detach = function()
642 if (this._listItemNode && this._listItemNode.parentNode)
643 this._listItemNode.parentNode.removeChild(this._listItemNode);
644 if (this._childrenListNode && this._childrenListNode.parentNode)
645 this._childrenListNode.parentNode.removeChild(this._childrenListNode);
648 TreeElement.treeElementMouseDown = function(event)
650 var element = event.currentTarget;
651 if (!element || !element.treeElement || !element.treeElement.selectable)
654 if (element.treeElement.isEventWithinDisclosureTriangle(event))
657 element.treeElement.selectOnMouseDown(event);
660 TreeElement.treeElementToggled = function(event)
662 var element = event.currentTarget;
663 if (!element || !element.treeElement)
666 var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable;
667 var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event);
668 if (!toggleOnClick && !isInTriangle)
671 if (element.treeElement.expanded) {
673 element.treeElement.collapseRecursively();
675 element.treeElement.collapse();
678 element.treeElement.expandRecursively();
680 element.treeElement.expand();
682 event.stopPropagation();
685 TreeElement.treeElementDoubleClicked = function(event)
687 var element = event.currentTarget;
688 if (!element || !element.treeElement)
691 if (element.treeElement.ondblclick)
692 element.treeElement.ondblclick.call(element.treeElement, event);
693 else if (element.treeElement.hasChildren && !element.treeElement.expanded)
694 element.treeElement.expand();
697 TreeElement.prototype.collapse = function()
699 if (this._listItemNode)
700 this._listItemNode.removeStyleClass("expanded");
701 if (this._childrenListNode)
702 this._childrenListNode.removeStyleClass("expanded");
704 this.expanded = false;
705 if (this.treeOutline)
706 this.treeOutline._treeElementsExpandedState[this.identifier] = true;
709 this.oncollapse(this);
712 TreeElement.prototype.collapseRecursively = function()
718 item = item.traverseNextTreeElement(false, this, true);
722 TreeElement.prototype.expand = function()
724 if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
727 if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
728 if (this._childrenListNode && this._childrenListNode.parentNode)
729 this._childrenListNode.parentNode.removeChild(this._childrenListNode);
731 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
732 this._childrenListNode.parentTreeElement = this;
733 this._childrenListNode.addStyleClass("children");
736 this._childrenListNode.addStyleClass("hidden");
740 for (var i = 0; i < this.children.length; ++i)
741 this.children[i]._attach();
743 delete this._shouldRefreshChildren;
746 if (this._listItemNode) {
747 this._listItemNode.addStyleClass("expanded");
748 if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
749 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
752 if (this._childrenListNode)
753 this._childrenListNode.addStyleClass("expanded");
755 this.expanded = true;
756 if (this.treeOutline)
757 this.treeOutline._treeElementsExpandedState[this.identifier] = true;
763 TreeElement.prototype.expandRecursively = function(maxDepth)
769 // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
770 // in some case can be infinite, since JavaScript objects can hold circular references.
771 // So default to a recursion cap of 3 levels, since that gives fairly good results.
772 if (typeof maxDepth === "undefined" || typeof maxDepth === "null")
776 if (depth < maxDepth)
778 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
779 depth += info.depthChange;
783 TreeElement.prototype.hasAncestor = function(ancestor) {
787 var currentNode = this.parent;
788 while (currentNode) {
789 if (ancestor === currentNode)
791 currentNode = currentNode.parent;
797 TreeElement.prototype.reveal = function()
799 var currentAncestor = this.parent;
800 while (currentAncestor && !currentAncestor.root) {
801 if (!currentAncestor.expanded)
802 currentAncestor.expand();
803 currentAncestor = currentAncestor.parent;
810 TreeElement.prototype.revealed = function()
812 var currentAncestor = this.parent;
813 while (currentAncestor && !currentAncestor.root) {
814 if (!currentAncestor.expanded)
816 currentAncestor = currentAncestor.parent;
822 TreeElement.prototype.selectOnMouseDown = function(event)
824 this.select(false, true);
828 * @param {boolean=} omitFocus
829 * @param {boolean=} selectedByUser
831 TreeElement.prototype.select = function(omitFocus, selectedByUser)
833 if (!this.treeOutline || !this.selectable || this.selected)
836 if (this.treeOutline.selectedTreeElement)
837 this.treeOutline.selectedTreeElement.deselect();
839 this.selected = true;
842 this.treeOutline._childrenListNode.focus();
844 // Focusing on another node may detach "this" from tree.
845 if (!this.treeOutline)
847 this.treeOutline.selectedTreeElement = this;
848 if (this._listItemNode)
849 this._listItemNode.addStyleClass("selected");
852 this.onselect(this, selectedByUser);
855 TreeElement.prototype.revealAndSelect = function(omitFocus)
858 this.select(omitFocus);
862 * @param {boolean=} supressOnDeselect
864 TreeElement.prototype.deselect = function(supressOnDeselect)
866 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
869 this.selected = false;
870 this.treeOutline.selectedTreeElement = null;
871 if (this._listItemNode)
872 this._listItemNode.removeStyleClass("selected");
874 if (this.ondeselect && !supressOnDeselect)
875 this.ondeselect(this);
879 TreeElement.prototype.onpopulate = function()
881 // Overriden by subclasses.
884 TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info)
886 if (!dontPopulate && this.hasChildren)
890 info.depthChange = 0;
892 var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
893 if (element && (!skipHidden || (skipHidden && this.expanded))) {
895 info.depthChange = 1;
899 if (this === stayWithin)
902 element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
907 while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
909 info.depthChange -= 1;
910 element = element.parent;
916 return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
919 TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate)
921 var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
922 if (!dontPopulate && element && element.hasChildren)
923 element.onpopulate();
925 while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
926 if (!dontPopulate && element.hasChildren)
927 element.onpopulate();
928 element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
934 if (!this.parent || this.parent.root)
940 TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
942 var left = this._listItemNode.totalOffsetLeft();
943 return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;