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.
31 * @param {!Element} listNode
32 * @param {boolean=} nonFocusable
34 function TreeOutline(listNode, nonFocusable)
36 /** @type {!Array.<!TreeElement>} */
38 this.selectedTreeElement = null;
39 this._childrenListNode = listNode;
40 this.childrenListElement = this._childrenListNode;
41 this._childrenListNode.removeChildren();
42 this.expandTreeElementsWhenArrowing = false;
44 this.hasChildren = false;
46 this.selected = false;
47 this.treeOutline = this;
48 /** @type {?function(!TreeElement, !TreeElement):number} */
49 this.comparator = null;
51 this.setFocusable(!nonFocusable);
52 this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
54 /** @type {!Map.<!Object, !Array.<!TreeElement>>} */
55 this._treeElementsMap = new Map();
56 /** @type {!Map.<!Object, boolean>} */
57 this._expandedStateMap = new Map();
58 this.element = listNode;
61 TreeOutline.prototype.setFocusable = function(focusable)
64 this._childrenListNode.setAttribute("tabIndex", 0);
66 this._childrenListNode.removeAttribute("tabIndex");
70 * @param {!TreeElement} child
72 TreeOutline.prototype.appendChild = function(child)
75 if (this.treeOutline.comparator)
76 insertionIndex = insertionIndexForObjectInListSortedByFunction(child, this.children, this.treeOutline.comparator);
78 insertionIndex = this.children.length;
79 this.insertChild(child, insertionIndex);
83 * @param {!TreeElement} child
84 * @param {!TreeElement} beforeChild
86 TreeOutline.prototype.insertBeforeChild = function(child, beforeChild)
89 throw("child can't be undefined or null");
92 throw("beforeChild can't be undefined or null");
94 var childIndex = this.children.indexOf(beforeChild);
95 if (childIndex === -1)
96 throw("beforeChild not found in this node's children");
98 this.insertChild(child, childIndex);
102 * @param {!TreeElement} child
103 * @param {number} index
105 TreeOutline.prototype.insertChild = function(child, index)
108 throw("child can't be undefined or null");
110 var previousChild = (index > 0 ? this.children[index - 1] : null);
112 previousChild.nextSibling = child;
113 child.previousSibling = previousChild;
115 child.previousSibling = null;
118 var nextChild = this.children[index];
120 nextChild.previousSibling = child;
121 child.nextSibling = nextChild;
123 child.nextSibling = null;
126 this.children.splice(index, 0, child);
127 this.hasChildren = true;
129 child.treeOutline = this.treeOutline;
130 child.treeOutline._rememberTreeElement(child);
132 var current = child.children[0];
134 current.treeOutline = this.treeOutline;
135 current.treeOutline._rememberTreeElement(current);
136 current = current.traverseNextTreeElement(false, child, true);
139 if (child.hasChildren && typeof(child.treeOutline._expandedStateMap.get(child.representedObject)) !== "undefined")
140 child.expanded = child.treeOutline._expandedStateMap.get(child.representedObject);
142 if (!this._childrenListNode) {
143 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
144 this._childrenListNode.parentTreeElement = this;
145 this._childrenListNode.classList.add("children");
147 this._childrenListNode.classList.add("hidden");
154 * @param {number} childIndex
156 TreeOutline.prototype.removeChildAtIndex = function(childIndex)
158 if (childIndex < 0 || childIndex >= this.children.length)
159 throw("childIndex out of range");
161 var child = this.children[childIndex];
162 this.children.splice(childIndex, 1);
164 var parent = child.parent;
165 if (child.deselect()) {
166 if (child.previousSibling)
167 child.previousSibling.select();
168 else if (child.nextSibling)
169 child.nextSibling.select();
174 if (child.previousSibling)
175 child.previousSibling.nextSibling = child.nextSibling;
176 if (child.nextSibling)
177 child.nextSibling.previousSibling = child.previousSibling;
179 if (child.treeOutline) {
180 child.treeOutline._forgetTreeElement(child);
181 child.treeOutline._forgetChildrenRecursive(child);
185 child.treeOutline = null;
187 child.nextSibling = null;
188 child.previousSibling = null;
192 * @param {!TreeElement} child
194 TreeOutline.prototype.removeChild = function(child)
197 throw("child can't be undefined or null");
199 var childIndex = this.children.indexOf(child);
200 if (childIndex === -1)
201 throw("child not found in this node's children");
203 this.removeChildAtIndex.call(this, childIndex);
206 TreeOutline.prototype.removeChildren = function()
208 for (var i = 0; i < this.children.length; ++i) {
209 var child = this.children[i];
212 if (child.treeOutline) {
213 child.treeOutline._forgetTreeElement(child);
214 child.treeOutline._forgetChildrenRecursive(child);
218 child.treeOutline = null;
220 child.nextSibling = null;
221 child.previousSibling = null;
228 * @param {!TreeElement} element
230 TreeOutline.prototype._rememberTreeElement = function(element)
232 if (!this._treeElementsMap.get(element.representedObject))
233 this._treeElementsMap.set(element.representedObject, []);
235 // check if the element is already known
236 var elements = this._treeElementsMap.get(element.representedObject);
237 if (elements.indexOf(element) !== -1)
241 elements.push(element);
245 * @param {!TreeElement} element
247 TreeOutline.prototype._forgetTreeElement = function(element)
249 if (this._treeElementsMap.get(element.representedObject)) {
250 var elements = this._treeElementsMap.get(element.representedObject);
251 elements.remove(element, true);
252 if (!elements.length)
253 this._treeElementsMap.remove(element.representedObject);
258 * @param {!TreeElement} parentElement
260 TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
262 var child = parentElement.children[0];
264 this._forgetTreeElement(child);
265 child = child.traverseNextTreeElement(false, parentElement, true);
270 * @param {?Object} representedObject
271 * @return {?TreeElement}
273 TreeOutline.prototype.getCachedTreeElement = function(representedObject)
275 if (!representedObject)
278 var elements = this._treeElementsMap.get(representedObject);
279 if (elements && elements.length)
285 * @param {?Object} representedObject
286 * @param {function(!Object):?Object} getParent
287 * @return {?TreeElement}
289 TreeOutline.prototype.findTreeElement = function(representedObject, getParent)
291 if (!representedObject)
294 var cachedElement = this.getCachedTreeElement(representedObject);
296 return cachedElement;
298 // Walk up the parent pointers from the desired representedObject
300 for (var currentObject = getParent(representedObject); currentObject; currentObject = getParent(currentObject)) {
301 ancestors.push(currentObject);
302 if (this.getCachedTreeElement(currentObject)) // stop climbing as soon as we hit
309 // Walk down to populate each ancestor's children, to fill in the tree and the cache.
310 for (var i = ancestors.length - 1; i >= 0; --i) {
311 var treeElement = this.getCachedTreeElement(ancestors[i]);
313 treeElement.onpopulate(); // fill the cache with the children of treeElement
316 return this.getCachedTreeElement(representedObject);
322 * @return {?TreeElement}
324 TreeOutline.prototype.treeElementFromPoint = function(x, y)
326 var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
330 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
332 return listNode.parentTreeElement || listNode.treeElement;
336 TreeOutline.prototype._treeKeyDown = function(event)
338 if (event.target !== this._childrenListNode)
341 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
345 var nextSelectedElement;
346 if (event.keyIdentifier === "Up" && !event.altKey) {
347 nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
348 while (nextSelectedElement && !nextSelectedElement.selectable)
349 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
350 handled = nextSelectedElement ? true : false;
351 } else if (event.keyIdentifier === "Down" && !event.altKey) {
352 nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
353 while (nextSelectedElement && !nextSelectedElement.selectable)
354 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
355 handled = nextSelectedElement ? true : false;
356 } else if (event.keyIdentifier === "Left") {
357 if (this.selectedTreeElement.expanded) {
359 this.selectedTreeElement.collapseRecursively();
361 this.selectedTreeElement.collapse();
363 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
365 if (this.selectedTreeElement.parent.selectable) {
366 nextSelectedElement = this.selectedTreeElement.parent;
367 while (nextSelectedElement && !nextSelectedElement.selectable)
368 nextSelectedElement = nextSelectedElement.parent;
369 handled = nextSelectedElement ? true : false;
370 } else if (this.selectedTreeElement.parent)
371 this.selectedTreeElement.parent.collapse();
373 } else if (event.keyIdentifier === "Right") {
374 if (!this.selectedTreeElement.revealed()) {
375 this.selectedTreeElement.reveal();
377 } else if (this.selectedTreeElement.hasChildren) {
379 if (this.selectedTreeElement.expanded) {
380 nextSelectedElement = this.selectedTreeElement.children[0];
381 while (nextSelectedElement && !nextSelectedElement.selectable)
382 nextSelectedElement = nextSelectedElement.nextSibling;
383 handled = nextSelectedElement ? true : false;
386 this.selectedTreeElement.expandRecursively();
388 this.selectedTreeElement.expand();
391 } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */)
392 handled = this.selectedTreeElement.ondelete();
393 else if (isEnterKey(event))
394 handled = this.selectedTreeElement.onenter();
395 else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code)
396 handled = this.selectedTreeElement.onspace();
398 if (nextSelectedElement) {
399 nextSelectedElement.reveal();
400 nextSelectedElement.select(false, true);
407 TreeOutline.prototype.expand = function()
409 // this is the root, do nothing
412 TreeOutline.prototype.collapse = function()
414 // this is the root, do nothing
420 TreeOutline.prototype.revealed = function()
425 TreeOutline.prototype.reveal = function()
427 // this is the root, do nothing
430 TreeOutline.prototype.select = function()
432 // this is the root, do nothing
436 * @param {boolean=} omitFocus
438 TreeOutline.prototype.revealAndSelect = function(omitFocus)
440 // this is the root, do nothing
445 * @param {string|!Node} title
446 * @param {?Object=} representedObject
447 * @param {boolean=} hasChildren
449 function TreeElement(title, representedObject, hasChildren)
452 this.representedObject = (representedObject || {});
455 this._hidden = false;
456 this._selectable = true;
457 this.expanded = false;
458 this.selected = false;
459 this.hasChildren = hasChildren;
461 this.treeOutline = null;
463 this.previousSibling = null;
464 this.nextSibling = null;
465 this._listItemNode = null;
468 TreeElement.prototype = {
469 arrowToggleWidth: 10,
474 return this._selectable;
478 this._selectable = x;
481 get listItemElement() {
482 return this._listItemNode;
485 get childrenListElement() {
486 return this._childrenListNode;
495 this._setListItemNodeContent();
499 return this._tooltip;
504 if (this._listItemNode)
505 this._listItemNode.title = x ? x : "";
509 return this._hasChildren;
513 if (this._hasChildren === x)
516 this._hasChildren = x;
518 if (!this._listItemNode)
522 this._listItemNode.classList.add("parent");
524 this._listItemNode.classList.remove("parent");
534 if (this._hidden === x)
540 if (this._listItemNode)
541 this._listItemNode.classList.add("hidden");
542 if (this._childrenListNode)
543 this._childrenListNode.classList.add("hidden");
545 if (this._listItemNode)
546 this._listItemNode.classList.remove("hidden");
547 if (this._childrenListNode)
548 this._childrenListNode.classList.remove("hidden");
552 get shouldRefreshChildren() {
553 return this._shouldRefreshChildren;
556 set shouldRefreshChildren(x) {
557 this._shouldRefreshChildren = x;
558 if (x && this.expanded)
562 _setListItemNodeContent: function()
564 if (!this._listItemNode)
567 if (typeof this._title === "string")
568 this._listItemNode.textContent = this._title;
570 this._listItemNode.removeChildren();
572 this._listItemNode.appendChild(this._title);
577 TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild;
578 TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild;
579 TreeElement.prototype.insertBeforeChild = TreeOutline.prototype.insertBeforeChild;
580 TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild;
581 TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex;
582 TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren;
584 TreeElement.prototype._attach = function()
586 if (!this._listItemNode || this.parent._shouldRefreshChildren) {
587 if (this._listItemNode && this._listItemNode.parentNode)
588 this._listItemNode.parentNode.removeChild(this._listItemNode);
590 this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
591 this._listItemNode.treeElement = this;
592 this._setListItemNodeContent();
593 this._listItemNode.title = this._tooltip ? this._tooltip : "";
596 this._listItemNode.classList.add("hidden");
597 if (this.hasChildren)
598 this._listItemNode.classList.add("parent");
600 this._listItemNode.classList.add("expanded");
602 this._listItemNode.classList.add("selected");
604 this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false);
605 this._listItemNode.addEventListener("selectstart", TreeElement.treeElementSelectStart, false);
606 this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
607 this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
612 var nextSibling = null;
613 if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
614 nextSibling = this.nextSibling._listItemNode;
615 this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
616 if (this._childrenListNode)
617 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
624 TreeElement.prototype._detach = function()
626 if (this._listItemNode && this._listItemNode.parentNode)
627 this._listItemNode.parentNode.removeChild(this._listItemNode);
628 if (this._childrenListNode && this._childrenListNode.parentNode)
629 this._childrenListNode.parentNode.removeChild(this._childrenListNode);
632 TreeElement.treeElementMouseDown = function(event)
634 var element = event.currentTarget;
637 delete element._selectionStarted;
639 if (!element.treeElement || !element.treeElement.selectable)
642 if (element.treeElement.isEventWithinDisclosureTriangle(event))
645 element.treeElement.selectOnMouseDown(event);
648 TreeElement.treeElementSelectStart = function(event)
650 var element = event.currentTarget;
653 element._selectionStarted = true;
656 TreeElement.treeElementToggled = function(event)
658 var element = event.currentTarget;
661 if (element._selectionStarted) {
662 delete element._selectionStarted
663 var selection = window.getSelection();
664 if (selection && !selection.isCollapsed && element.isSelfOrAncestor(selection.anchorNode) && element.isSelfOrAncestor(selection.focusNode))
668 if (!element.treeElement)
671 var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable;
672 var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event);
673 if (!toggleOnClick && !isInTriangle)
676 if (event.target && event.target.enclosingNodeOrSelfWithNodeName("a"))
679 if (element.treeElement.expanded) {
681 element.treeElement.collapseRecursively();
683 element.treeElement.collapse();
686 element.treeElement.expandRecursively();
688 element.treeElement.expand();
693 TreeElement.treeElementDoubleClicked = function(event)
695 var element = event.currentTarget;
696 if (!element || !element.treeElement)
699 var handled = element.treeElement.ondblclick.call(element.treeElement, event);
702 if (element.treeElement.hasChildren && !element.treeElement.expanded)
703 element.treeElement.expand();
706 TreeElement.prototype.collapse = function()
708 if (this._listItemNode)
709 this._listItemNode.classList.remove("expanded");
710 if (this._childrenListNode)
711 this._childrenListNode.classList.remove("expanded");
713 this.expanded = false;
715 if (this.treeOutline)
716 this.treeOutline._expandedStateMap.set(this.representedObject, false);
721 TreeElement.prototype.collapseRecursively = function()
727 item = item.traverseNextTreeElement(false, this, true);
731 TreeElement.prototype.expand = function()
733 if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
736 // Set this before onpopulate. Since onpopulate can add elements, this makes
737 // sure the expanded flag is true before calling those functions. This prevents the possibility
738 // of an infinite loop if onpopulate were to call expand.
740 this.expanded = true;
741 if (this.treeOutline)
742 this.treeOutline._expandedStateMap.set(this.representedObject, true);
744 if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
745 if (this._childrenListNode && this._childrenListNode.parentNode)
746 this._childrenListNode.parentNode.removeChild(this._childrenListNode);
748 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
749 this._childrenListNode.parentTreeElement = this;
750 this._childrenListNode.classList.add("children");
753 this._childrenListNode.classList.add("hidden");
757 for (var i = 0; i < this.children.length; ++i)
758 this.children[i]._attach();
760 delete this._shouldRefreshChildren;
763 if (this._listItemNode) {
764 this._listItemNode.classList.add("expanded");
765 if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
766 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
769 if (this._childrenListNode)
770 this._childrenListNode.classList.add("expanded");
775 TreeElement.prototype.expandRecursively = function(maxDepth)
781 // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
782 // in some case can be infinite, since JavaScript objects can hold circular references.
783 // So default to a recursion cap of 3 levels, since that gives fairly good results.
788 if (depth < maxDepth)
790 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
791 depth += info.depthChange;
796 * @param {?TreeElement} ancestor
799 TreeElement.prototype.hasAncestor = function(ancestor) {
803 var currentNode = this.parent;
804 while (currentNode) {
805 if (ancestor === currentNode)
807 currentNode = currentNode.parent;
813 TreeElement.prototype.reveal = function()
815 var currentAncestor = this.parent;
816 while (currentAncestor && !currentAncestor.root) {
817 if (!currentAncestor.expanded)
818 currentAncestor.expand();
819 currentAncestor = currentAncestor.parent;
828 TreeElement.prototype.revealed = function()
830 var currentAncestor = this.parent;
831 while (currentAncestor && !currentAncestor.root) {
832 if (!currentAncestor.expanded)
834 currentAncestor = currentAncestor.parent;
840 TreeElement.prototype.selectOnMouseDown = function(event)
842 if (this.select(false, true))
847 * @param {boolean=} omitFocus
848 * @param {boolean=} selectedByUser
851 TreeElement.prototype.select = function(omitFocus, selectedByUser)
853 if (!this.treeOutline || !this.selectable || this.selected)
856 if (this.treeOutline.selectedTreeElement)
857 this.treeOutline.selectedTreeElement.deselect();
859 this.selected = true;
862 this.treeOutline._childrenListNode.focus();
864 // Focusing on another node may detach "this" from tree.
865 if (!this.treeOutline)
867 this.treeOutline.selectedTreeElement = this;
868 if (this._listItemNode)
869 this._listItemNode.classList.add("selected");
871 return this.onselect(selectedByUser);
875 * @param {boolean=} omitFocus
877 TreeElement.prototype.revealAndSelect = function(omitFocus)
880 this.select(omitFocus);
884 * @param {boolean=} supressOnDeselect
887 TreeElement.prototype.deselect = function(supressOnDeselect)
889 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
892 this.selected = false;
893 this.treeOutline.selectedTreeElement = null;
894 if (this._listItemNode)
895 this._listItemNode.classList.remove("selected");
899 // Overridden by subclasses.
900 TreeElement.prototype.onpopulate = function() { }
905 TreeElement.prototype.onenter = function() { return false; }
910 TreeElement.prototype.ondelete = function() { return false; }
915 TreeElement.prototype.onspace = function() { return false; }
917 TreeElement.prototype.onattach = function() { }
919 TreeElement.prototype.onexpand = function() { }
921 TreeElement.prototype.oncollapse = function() { }
924 * @param {!MouseEvent} e
927 TreeElement.prototype.ondblclick = function(e) { return false; }
929 TreeElement.prototype.onreveal = function() { }
932 * @param {boolean=} selectedByUser
935 TreeElement.prototype.onselect = function(selectedByUser) { return false; }
938 * @param {boolean} skipUnrevealed
939 * @param {(!TreeOutline|!TreeElement|null)=} stayWithin
940 * @param {boolean=} dontPopulate
941 * @param {!Object=} info
942 * @return {?TreeElement}
944 TreeElement.prototype.traverseNextTreeElement = function(skipUnrevealed, stayWithin, dontPopulate, info)
946 if (!dontPopulate && this.hasChildren)
950 info.depthChange = 0;
952 var element = skipUnrevealed ? (this.revealed() ? this.children[0] : null) : this.children[0];
953 if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) {
955 info.depthChange = 1;
959 if (this === stayWithin)
962 element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
967 while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
969 info.depthChange -= 1;
970 element = element.parent;
976 return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
980 * @param {boolean} skipUnrevealed
981 * @param {boolean=} dontPopulate
982 * @return {?TreeElement}
984 TreeElement.prototype.traversePreviousTreeElement = function(skipUnrevealed, dontPopulate)
986 var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
987 if (!dontPopulate && element && element.hasChildren)
988 element.onpopulate();
990 while (element && (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
991 if (!dontPopulate && element.hasChildren)
992 element.onpopulate();
993 element = (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
999 if (!this.parent || this.parent.root)
1008 TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
1010 // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446)
1011 var paddingLeftValue = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left");
1012 var computedLeftPadding = paddingLeftValue ? paddingLeftValue.getFloatValue(CSSPrimitiveValue.CSS_PX) : 0;
1013 var left = this._listItemNode.totalOffsetLeft() + computedLeftPadding;
1014 return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;