44dc4d34235e12a0b7124136d06c4a73980b9dd8
[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     if (listNode)
293         return listNode.parentTreeElement || listNode.treeElement;
294     return null;
295 }
296
297 TreeOutline.prototype.handleKeyEvent = function(event)
298 {
299     if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
300         return false;
301
302     var handled = false;
303     var nextSelectedElement;
304     if (event.keyIdentifier === "Up" && !event.altKey) {
305         nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
306         while (nextSelectedElement && !nextSelectedElement.selectable)
307             nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
308         handled = nextSelectedElement ? true : false;
309     } else if (event.keyIdentifier === "Down" && !event.altKey) {
310         nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
311         while (nextSelectedElement && !nextSelectedElement.selectable)
312             nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
313         handled = nextSelectedElement ? true : false;
314     } else if (event.keyIdentifier === "Left") {
315         if (this.selectedTreeElement.expanded) {
316             if (event.altKey)
317                 this.selectedTreeElement.collapseRecursively();
318             else
319                 this.selectedTreeElement.collapse();
320             handled = true;
321         } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
322             handled = true;
323             if (this.selectedTreeElement.parent.selectable) {
324                 nextSelectedElement = this.selectedTreeElement.parent;
325                 handled = nextSelectedElement ? true : false;
326             } else if (this.selectedTreeElement.parent)
327                 this.selectedTreeElement.parent.collapse();
328         }
329     } else if (event.keyIdentifier === "Right") {
330         if (!this.selectedTreeElement.revealed()) {
331             this.selectedTreeElement.reveal();
332             handled = true;
333         } else if (this.selectedTreeElement.hasChildren) {
334             handled = true;
335             if (this.selectedTreeElement.expanded) {
336                 nextSelectedElement = this.selectedTreeElement.children[0];
337                 handled = nextSelectedElement ? true : false;
338             } else {
339                 if (event.altKey)
340                     this.selectedTreeElement.expandRecursively();
341                 else
342                     this.selectedTreeElement.expand();
343             }
344         }
345     }
346
347     if (nextSelectedElement) {
348         nextSelectedElement.reveal();
349         nextSelectedElement.select();
350     }
351
352     if (handled) {
353         event.preventDefault();
354         event.stopPropagation();
355     }
356
357     return handled;
358 }
359
360 TreeOutline.prototype.expand = function()
361 {
362     // this is the root, do nothing
363 }
364
365 TreeOutline.prototype.collapse = function()
366 {
367     // this is the root, do nothing
368 }
369
370 TreeOutline.prototype.revealed = function()
371 {
372     return true;
373 }
374
375 TreeOutline.prototype.reveal = function()
376 {
377     // this is the root, do nothing
378 }
379
380 TreeOutline.prototype.appendChild = TreeOutline._appendChild;
381 TreeOutline.prototype.insertChild = TreeOutline._insertChild;
382 TreeOutline.prototype.removeChild = TreeOutline._removeChild;
383 TreeOutline.prototype.removeChildren = TreeOutline._removeChildren;
384 TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
385
386 function TreeElement(title, representedObject, hasChildren)
387 {
388     this._title = title;
389     this.representedObject = (representedObject || {});
390
391     if (this.representedObject.__treeElementIdentifier)
392         this.identifier = this.representedObject.__treeElementIdentifier;
393     else {
394         this.identifier = TreeOutline._knownTreeElementNextIdentifier++;
395         this.representedObject.__treeElementIdentifier = this.identifier;
396     }
397
398     this._hidden = false;
399     this.expanded = false;
400     this.selected = false;
401     this.hasChildren = hasChildren;
402     this.children = [];
403     this.treeOutline = null;
404     this.parent = null;
405     this.previousSibling = null;
406     this.nextSibling = null;
407     this._listItemNode = null;
408 }
409
410 TreeElement.prototype = {
411     selectable: true,
412     arrowToggleWidth: 10,
413
414     get listItemElement() {
415         return this._listItemNode;
416     },
417
418     get childrenListElement() {
419         return this._childrenListNode;
420     },
421
422     get title() {
423         return this._title;
424     },
425
426     set title(x) {
427         this._title = x;
428         if (this._listItemNode)
429             this._listItemNode.innerHTML = x;
430     },
431
432     get tooltip() {
433         return this._tooltip;
434     },
435
436     set tooltip(x) {
437         this._tooltip = x;
438         if (this._listItemNode)
439             this._listItemNode.title = x ? x : "";
440     },
441
442     get hidden() {
443         return this._hidden;
444     },
445
446     set hidden(x) {
447         if (this._hidden === x)
448             return;
449
450         this._hidden = x;
451
452         if (x) {
453             if (this._listItemNode)
454                 this._listItemNode.addStyleClass("hidden");
455             if (this._childrenListNode)
456                 this._childrenListNode.addStyleClass("hidden");
457         } else {
458             if (this._listItemNode)
459                 this._listItemNode.removeStyleClass("hidden");
460             if (this._childrenListNode)
461                 this._childrenListNode.removeStyleClass("hidden");
462         }
463     }
464 }
465
466 TreeElement.prototype.appendChild = TreeOutline._appendChild;
467 TreeElement.prototype.insertChild = TreeOutline._insertChild;
468 TreeElement.prototype.removeChild = TreeOutline._removeChild;
469 TreeElement.prototype.removeChildren = TreeOutline._removeChildren;
470 TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
471
472 TreeElement.prototype._attach = function()
473 {
474     if (!this._listItemNode || this.parent.refreshChildren) {
475         if (this._listItemNode && this._listItemNode.parentNode)
476             this._listItemNode.parentNode.removeChild(this._listItemNode);
477
478         this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
479         this._listItemNode.treeElement = this;
480         this._listItemNode.innerHTML = this._title;
481         this._listItemNode.title = this._tooltip ? this._tooltip : "";
482
483         if (this.hidden)
484             this._listItemNode.addStyleClass("hidden");
485         if (this.hasChildren)
486             this._listItemNode.addStyleClass("parent");
487         if (this.expanded)
488             this._listItemNode.addStyleClass("expanded");
489         if (this.selected)
490             this._listItemNode.addStyleClass("selected");
491
492         this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false);
493         this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
494         this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
495
496         if (this.onattach)
497             this.onattach(this);
498     }
499
500     this.parent._childrenListNode.insertBefore(this._listItemNode, (this.nextSibling ? this.nextSibling._listItemNode : null));
501     if (this._childrenListNode)
502         this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
503     if (this.selected)
504         this.select();
505     if (this.expanded)
506         this.expand();
507 }
508
509 TreeElement.prototype._detach = function()
510 {
511     if (this._listItemNode && this._listItemNode.parentNode)
512         this._listItemNode.parentNode.removeChild(this._listItemNode);
513     if (this._childrenListNode && this._childrenListNode.parentNode)
514         this._childrenListNode.parentNode.removeChild(this._childrenListNode);
515 }
516
517 TreeElement.treeElementSelected = function(event)
518 {
519     var element = event.currentTarget;
520     if (!element || !element.treeElement || !element.treeElement.selectable)
521         return;
522
523     if (element.treeElement.isEventWithinDisclosureTriangle(event))
524         return;
525
526     element.treeElement.select();
527 }
528
529 TreeElement.treeElementToggled = function(event)
530 {
531     var element = event.currentTarget;
532     if (!element || !element.treeElement)
533         return;
534
535     if (!element.treeElement.isEventWithinDisclosureTriangle(event))
536         return;
537
538     if (element.treeElement.expanded) {
539         if (event.altKey)
540             element.treeElement.collapseRecursively();
541         else
542             element.treeElement.collapse();
543     } else {
544         if (event.altKey)
545             element.treeElement.expandRecursively();
546         else
547             element.treeElement.expand();
548     }
549 }
550
551 TreeElement.treeElementDoubleClicked = function(event)
552 {
553     var element = event.currentTarget;
554     if (!element || !element.treeElement)
555         return;
556
557     if (element.treeElement.ondblclick)
558         element.treeElement.ondblclick(element.treeElement, event);
559     else if (element.treeElement.hasChildren && !element.treeElement.expanded)
560         element.treeElement.expand();
561 }
562
563 TreeElement.prototype.collapse = function()
564 {
565     if (this._listItemNode)
566         this._listItemNode.removeStyleClass("expanded");
567     if (this._childrenListNode)
568         this._childrenListNode.removeStyleClass("expanded");
569
570     this.expanded = false;
571     if (this.treeOutline)
572         this.treeOutline._treeElementsExpandedState[this.identifier] = true;
573
574     if (this.oncollapse)
575         this.oncollapse(this);
576 }
577
578 TreeElement.prototype.collapseRecursively = function()
579 {
580     var item = this;
581     while (item) {
582         if (item.expanded)
583             item.collapse();
584         item = item.traverseNextTreeElement(false, this, true);
585     }
586 }
587
588 TreeElement.prototype.expand = function()
589 {
590     if (!this.hasChildren || (this.expanded && !this.refreshChildren && this._childrenListNode))
591         return;
592
593     if (!this._childrenListNode || this.refreshChildren) {
594         if (this._childrenListNode && this._childrenListNode.parentNode)
595             this._childrenListNode.parentNode.removeChild(this._childrenListNode);
596
597         this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
598         this._childrenListNode.parentTreeElement = this;
599         this._childrenListNode.addStyleClass("children");
600
601         if (this.hidden)
602             this._childrenListNode.addStyleClass("hidden");
603
604         if (this.onpopulate)
605             this.onpopulate(this);
606
607         for (var i = 0; i < this.children.length; ++i)
608             this.children[i]._attach();
609
610         delete this.refreshChildren;
611     }
612
613     if (this._listItemNode) {
614         this._listItemNode.addStyleClass("expanded");
615         if (this._childrenListNode.parentNode != this._listItemNode.parentNode)
616             this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
617     }
618
619     if (this._childrenListNode)
620         this._childrenListNode.addStyleClass("expanded");
621
622     this.expanded = true;
623     if (this.treeOutline)
624         this.treeOutline._treeElementsExpandedState[this.identifier] = true;
625
626     if (this.onexpand)
627         this.onexpand(this);
628 }
629
630 TreeElement.prototype.expandRecursively = function()
631 {
632     var item = this;
633     while (item) {
634         item.expand();
635         item = item.traverseNextTreeElement(false, this);
636     }
637 }
638
639 TreeElement.prototype.reveal = function()
640 {
641     var currentAncestor = this.parent;
642     while (currentAncestor && !currentAncestor.root) {
643         if (!currentAncestor.expanded)
644             currentAncestor.expand();
645         currentAncestor = currentAncestor.parent;
646     }
647
648     if (this.onreveal)
649         this.onreveal(this);
650 }
651
652 TreeElement.prototype.revealed = function()
653 {
654     var currentAncestor = this.parent;
655     while (currentAncestor && !currentAncestor.root) {
656         if (!currentAncestor.expanded)
657             return false;
658         currentAncestor = currentAncestor.parent;
659     }
660
661     return true;
662 }
663
664 TreeElement.prototype.select = function(supressOnSelect)
665 {
666     if (!this.treeOutline || !this.selectable || this.selected)
667         return;
668
669     if (this.treeOutline.selectedTreeElement)
670         this.treeOutline.selectedTreeElement.deselect();
671
672     this.selected = true;
673     this.treeOutline.selectedTreeElement = this;
674     if (this._listItemNode)
675         this._listItemNode.addStyleClass("selected");
676
677     if (this.onselect && !supressOnSelect)
678         this.onselect(this);
679 }
680
681 TreeElement.prototype.deselect = function(supressOnDeselect)
682 {
683     if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
684         return;
685
686     this.selected = false;
687     this.treeOutline.selectedTreeElement = null;
688     if (this._listItemNode)
689         this._listItemNode.removeStyleClass("selected");
690
691     if (this.ondeselect && !supressOnDeselect)
692         this.ondeselect(this);
693 }
694
695 TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate)
696 {
697     if (!dontPopulate && this.hasChildren && this.onpopulate)
698         this.onpopulate(this);
699
700     var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
701     if (element && (!skipHidden || (skipHidden && this.expanded)))
702         return element;
703
704     if (this === stayWithin)
705         return null;
706
707     element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
708     if (element)
709         return element;
710
711     element = this;
712     while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin)
713         element = element.parent;
714
715     if (!element)
716         return null;
717
718     return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
719 }
720
721 TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate)
722 {
723     var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
724     if (!dontPopulate && element && element.hasChildren && element.onpopulate)
725         element.onpopulate(element);
726
727     while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
728         if (!dontPopulate && element.hasChildren && element.onpopulate)
729             element.onpopulate(element);
730         element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
731     }
732
733     if (element)
734         return element;
735
736     if (!this.parent || this.parent.root)
737         return null;
738
739     return this.parent;
740 }
741
742 TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
743 {
744     var left = this._listItemNode.totalOffsetLeft;
745     return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
746 }