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