Home | History | Annotate | Download | only in front-end
      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     this._childrenListNode.tabIndex = 0;
     45     this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
     46 }
     47 
     48 TreeOutline._knownTreeElementNextIdentifier = 1;
     49 
     50 TreeOutline._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 
     91 TreeOutline._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 
    139 TreeOutline._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 
    174 TreeOutline._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 
    186 TreeOutline._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 
    207 TreeOutline._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 
    234 TreeOutline.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 
    248 TreeOutline.prototype._forgetTreeElement = function(element)
    249 {
    250     if (this._knownTreeElements[element.identifier])
    251         this._knownTreeElements[element.identifier].remove(element, true);
    252 }
    253 
    254 TreeOutline.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 
    263 TreeOutline.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 
    281 TreeOutline.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 
    332 TreeOutline.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 
    341 TreeOutline.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 
    405 TreeOutline.prototype.expand = function()
    406 {
    407     // this is the root, do nothing
    408 }
    409 
    410 TreeOutline.prototype.collapse = function()
    411 {
    412     // this is the root, do nothing
    413 }
    414 
    415 TreeOutline.prototype.revealed = function()
    416 {
    417     return true;
    418 }
    419 
    420 TreeOutline.prototype.reveal = function()
    421 {
    422     // this is the root, do nothing
    423 }
    424 
    425 TreeOutline.prototype.appendChild = TreeOutline._appendChild;
    426 TreeOutline.prototype.insertChild = TreeOutline._insertChild;
    427 TreeOutline.prototype.removeChild = TreeOutline._removeChild;
    428 TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
    429 TreeOutline.prototype.removeChildren = TreeOutline._removeChildren;
    430 TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
    431 
    432 function 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 
    456 TreeElement.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 
    543 TreeElement.prototype.appendChild = TreeOutline._appendChild;
    544 TreeElement.prototype.insertChild = TreeOutline._insertChild;
    545 TreeElement.prototype.removeChild = TreeOutline._removeChild;
    546 TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
    547 TreeElement.prototype.removeChildren = TreeOutline._removeChildren;
    548 TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
    549 
    550 TreeElement.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 
    590 TreeElement.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 
    598 TreeElement.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 
    610 TreeElement.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 
    632 TreeElement.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 
    644 TreeElement.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 
    659 TreeElement.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 
    669 TreeElement.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 
    711 TreeElement.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 
    731 TreeElement.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 
    745 TreeElement.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 
    758 TreeElement.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 
    770 TreeElement.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 
    788 TreeElement.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 
    803 TreeElement.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 
    838 TreeElement.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 
    859 TreeElement.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