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