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