• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2008 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 * 1. Redistributions of source code must retain the above copyright
8 *        notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *        notice, this list of conditions and the following disclaimer in the
11 *        documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.         IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.DataGrid = function(columns)
27{
28    this.element = document.createElement("div");
29    this.element.className = "data-grid";
30    this.element.tabIndex = 0;
31    this.element.addEventListener("keydown", this._keyDown.bind(this), false);
32
33    this._headerTable = document.createElement("table");
34    this._headerTable.className = "header";
35
36    this._dataTable = document.createElement("table");
37    this._dataTable.className = "data";
38
39    this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
40    this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
41
42    var scrollContainer = document.createElement("div");
43    scrollContainer.className = "data-container";
44    scrollContainer.appendChild(this._dataTable);
45
46    this.element.appendChild(this._headerTable);
47    this.element.appendChild(scrollContainer);
48
49    var headerRow = document.createElement("tr");
50    var columnGroup = document.createElement("colgroup");
51    var columnCount = 0;
52
53    for (var columnIdentifier in columns) {
54        var column = columns[columnIdentifier];
55        if (column.disclosure)
56            this.disclosureColumnIdentifier = columnIdentifier;
57
58        var col = document.createElement("col");
59        if (column.width)
60            col.style.width = column.width;
61        columnGroup.appendChild(col);
62
63        var cell = document.createElement("th");
64        cell.className = columnIdentifier + "-column";
65        cell.columnIdentifier = columnIdentifier;
66
67        var div = document.createElement("div");
68        div.textContent = column.title;
69        cell.appendChild(div);
70
71        if (column.sort) {
72            cell.addStyleClass("sort-" + column.sort);
73            this._sortColumnCell = cell;
74        }
75
76        if (column.sortable) {
77            cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
78            cell.addStyleClass("sortable");
79        }
80
81        headerRow.appendChild(cell);
82
83        ++columnCount;
84    }
85
86    columnGroup.span = columnCount;
87
88    var cell = document.createElement("th");
89    cell.className = "corner";
90    headerRow.appendChild(cell);
91
92    this._headerTableColumnGroup = columnGroup;
93    this._headerTable.appendChild(this._headerTableColumnGroup);
94    this.headerTableBody.appendChild(headerRow);
95
96    var fillerRow = document.createElement("tr");
97    fillerRow.className = "filler";
98
99    for (var i = 0; i < columnCount; ++i) {
100        var cell = document.createElement("td");
101        fillerRow.appendChild(cell);
102    }
103
104    this._dataTableColumnGroup = columnGroup.cloneNode(true);
105    this._dataTable.appendChild(this._dataTableColumnGroup);
106    this.dataTableBody.appendChild(fillerRow);
107
108    this.columns = columns || {};
109    this.children = [];
110    this.selectedNode = null;
111    this.expandNodesWhenArrowing = false;
112    this.root = true;
113    this.hasChildren = false;
114    this.expanded = true;
115    this.revealed = true;
116    this.selected = false;
117    this.dataGrid = this;
118    this.indentWidth = 15;
119    this.resizers = [];
120    this.columnWidthsInitialized = false;
121}
122
123WebInspector.DataGrid.prototype = {
124    get sortColumnIdentifier()
125    {
126        if (!this._sortColumnCell)
127            return null;
128        return this._sortColumnCell.columnIdentifier;
129    },
130
131    get sortOrder()
132    {
133        if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
134            return "ascending";
135        if (this._sortColumnCell.hasStyleClass("sort-descending"))
136            return "descending";
137        return null;
138    },
139
140    get headerTableBody()
141    {
142        if ("_headerTableBody" in this)
143            return this._headerTableBody;
144
145        this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
146        if (!this._headerTableBody) {
147            this._headerTableBody = this.element.ownerDocument.createElement("tbody");
148            this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
149        }
150
151        return this._headerTableBody;
152    },
153
154    get dataTableBody()
155    {
156        if ("_dataTableBody" in this)
157            return this._dataTableBody;
158
159        this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
160        if (!this._dataTableBody) {
161            this._dataTableBody = this.element.ownerDocument.createElement("tbody");
162            this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
163        }
164
165        return this._dataTableBody;
166    },
167
168    // Updates the widths of the table, including the positions of the column
169    // resizers.
170    //
171    // IMPORTANT: This function MUST be called once after the element of the
172    // DataGrid is attached to its parent element and every subsequent time the
173    // width of the parent element is changed in order to make it possible to
174    // resize the columns.
175    //
176    // If this function is not called after the DataGrid is attached to its
177    // parent element, then the DataGrid's columns will not be resizable.
178    updateWidths: function()
179    {
180        var headerTableColumns = this._headerTableColumnGroup.children;
181
182        var left = 0;
183        var tableWidth = this._dataTable.offsetWidth;
184        var numColumns = headerTableColumns.length;
185
186        if (!this.columnWidthsInitialized) {
187            // Give all the columns initial widths now so that during a resize,
188            // when the two columns that get resized get a percent value for
189            // their widths, all the other columns already have percent values
190            // for their widths.
191            for (var i = 0; i < numColumns; i++) {
192                var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
193                var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
194                this._headerTableColumnGroup.children[i].style.width = percentWidth;
195                this._dataTableColumnGroup.children[i].style.width = percentWidth;
196            }
197            this.columnWidthsInitialized = true;
198        }
199
200        // Make n - 1 resizers for n columns.
201        for (var i = 0; i < numColumns - 1; i++) {
202            var resizer = this.resizers[i];
203
204            if (!resizer) {
205                // This is the first call to updateWidth, so the resizers need
206                // to be created.
207                resizer = document.createElement("div");
208                resizer.addStyleClass("data-grid-resizer");
209                // This resizer is associated with the column to its right.
210                resizer.rightNeighboringColumnID = i + 1;
211                resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
212                this.element.appendChild(resizer);
213                this.resizers[i] = resizer;
214            }
215
216            // Get the width of the cell in the first (and only) row of the
217            // header table in order to determine the width of the column, since
218            // it is not possible to query a column for its width.
219            left += this.headerTableBody.rows[0].cells[i].offsetWidth;
220
221            resizer.style.left = left + "px";
222        }
223    },
224
225    addCreationNode: function(hasChildren)
226    {
227        if (this.creationNode)
228            this.creationNode.makeNormal();
229
230        var emptyData = {};
231        for (var column in this.columns)
232            emptyData[column] = '';
233        this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
234        this.appendChild(this.creationNode);
235    },
236
237    appendChild: function(child)
238    {
239        this.insertChild(child, this.children.length);
240    },
241
242    insertChild: function(child, index)
243    {
244        if (!child)
245            throw("insertChild: Node can't be undefined or null.");
246        if (child.parent === this)
247            throw("insertChild: Node is already a child of this node.");
248
249        if (child.parent)
250            child.parent.removeChild(child);
251
252        this.children.splice(index, 0, child);
253        this.hasChildren = true;
254
255        child.parent = this;
256        child.dataGrid = this.dataGrid;
257        child._recalculateSiblings(index);
258
259        delete child._depth;
260        delete child._revealed;
261        delete child._attached;
262
263        var current = child.children[0];
264        while (current) {
265            current.dataGrid = this.dataGrid;
266            delete current._depth;
267            delete current._revealed;
268            delete current._attached;
269            current = current.traverseNextNode(false, child, true);
270        }
271
272        if (this.expanded)
273            child._attach();
274    },
275
276    removeChild: function(child)
277    {
278        if (!child)
279            throw("removeChild: Node can't be undefined or null.");
280        if (child.parent !== this)
281            throw("removeChild: Node is not a child of this node.");
282
283        child.deselect();
284
285        this.children.remove(child, true);
286
287        if (child.previousSibling)
288            child.previousSibling.nextSibling = child.nextSibling;
289        if (child.nextSibling)
290            child.nextSibling.previousSibling = child.previousSibling;
291
292        child.dataGrid = null;
293        child.parent = null;
294        child.nextSibling = null;
295        child.previousSibling = null;
296
297        if (this.children.length <= 0)
298            this.hasChildren = false;
299    },
300
301    removeChildren: function()
302    {
303        for (var i = 0; i < this.children.length; ++i) {
304            var child = this.children[i];
305            child.deselect();
306            child._detach();
307
308            child.dataGrid = null;
309            child.parent = null;
310            child.nextSibling = null;
311            child.previousSibling = null;
312        }
313
314        this.children = [];
315        this.hasChildren = false;
316    },
317
318    removeChildrenRecursive: function()
319    {
320        var childrenToRemove = this.children;
321
322        var child = this.children[0];
323        while (child) {
324            if (child.children.length)
325                childrenToRemove = childrenToRemove.concat(child.children);
326            child = child.traverseNextNode(false, this, true);
327        }
328
329        for (var i = 0; i < childrenToRemove.length; ++i) {
330            var child = childrenToRemove[i];
331            child.deselect();
332            child._detach();
333
334            child.children = [];
335            child.dataGrid = null;
336            child.parent = null;
337            child.nextSibling = null;
338            child.previousSibling = null;
339        }
340
341        this.children = [];
342    },
343
344    handleKeyEvent: function(event)
345    {
346        if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey)
347            return false;
348
349        var handled = false;
350        var nextSelectedNode;
351        if (event.keyIdentifier === "Up" && !event.altKey) {
352            nextSelectedNode = this.selectedNode.traversePreviousNode(true);
353            while (nextSelectedNode && !nextSelectedNode.selectable)
354                nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing);
355            handled = nextSelectedNode ? true : false;
356        } else if (event.keyIdentifier === "Down" && !event.altKey) {
357            nextSelectedNode = this.selectedNode.traverseNextNode(true);
358            while (nextSelectedNode && !nextSelectedNode.selectable)
359                nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing);
360            handled = nextSelectedNode ? true : false;
361        } else if (event.keyIdentifier === "Left") {
362            if (this.selectedNode.expanded) {
363                if (event.altKey)
364                    this.selectedNode.collapseRecursively();
365                else
366                    this.selectedNode.collapse();
367                handled = true;
368            } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
369                handled = true;
370                if (this.selectedNode.parent.selectable) {
371                    nextSelectedNode = this.selectedNode.parent;
372                    handled = nextSelectedNode ? true : false;
373                } else if (this.selectedNode.parent)
374                    this.selectedNode.parent.collapse();
375            }
376        } else if (event.keyIdentifier === "Right") {
377            if (!this.selectedNode.revealed) {
378                this.selectedNode.reveal();
379                handled = true;
380            } else if (this.selectedNode.hasChildren) {
381                handled = true;
382                if (this.selectedNode.expanded) {
383                    nextSelectedNode = this.selectedNode.children[0];
384                    handled = nextSelectedNode ? true : false;
385                } else {
386                    if (event.altKey)
387                        this.selectedNode.expandRecursively();
388                    else
389                        this.selectedNode.expand();
390                }
391            }
392        }
393
394        if (nextSelectedNode) {
395            nextSelectedNode.reveal();
396            nextSelectedNode.select();
397        }
398
399        if (handled) {
400            event.preventDefault();
401            event.stopPropagation();
402        }
403
404        return handled;
405    },
406
407    expand: function()
408    {
409        // This is the root, do nothing.
410    },
411
412    collapse: function()
413    {
414        // This is the root, do nothing.
415    },
416
417    reveal: function()
418    {
419        // This is the root, do nothing.
420    },
421
422    dataGridNodeFromEvent: function(event)
423    {
424        var rowElement = event.target.enclosingNodeOrSelfWithNodeName("tr");
425        return rowElement._dataGridNode;
426    },
427
428    dataGridNodeFromPoint: function(x, y)
429    {
430        var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
431        var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
432        return rowElement._dataGridNode;
433    },
434
435    _keyDown: function(event)
436    {
437        this.handleKeyEvent(event);
438    },
439
440    _clickInHeaderCell: function(event)
441    {
442        var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
443        if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
444            return;
445
446        var sortOrder = this.sortOrder;
447
448        if (this._sortColumnCell) {
449            this._sortColumnCell.removeStyleClass("sort-ascending");
450            this._sortColumnCell.removeStyleClass("sort-descending");
451        }
452
453        if (cell == this._sortColumnCell) {
454            if (sortOrder == "ascending")
455                sortOrder = "descending";
456            else
457                sortOrder = "ascending";
458        }
459
460        this._sortColumnCell = cell;
461
462        cell.addStyleClass("sort-" + sortOrder);
463
464        this.dispatchEventToListeners("sorting changed");
465    },
466
467    _mouseDownInDataTable: function(event)
468    {
469        var gridNode = this.dataGridNodeFromEvent(event);
470        if (!gridNode || !gridNode.selectable)
471            return;
472
473        if (gridNode.isEventWithinDisclosureTriangle(event))
474            return;
475
476        if (event.metaKey) {
477            if (gridNode.selected)
478                gridNode.deselect();
479            else
480                gridNode.select();
481        } else
482            gridNode.select();
483    },
484
485    _clickInDataTable: function(event)
486    {
487        var gridNode = this.dataGridNodeFromEvent(event);
488        if (!gridNode || !gridNode.hasChildren)
489            return;
490
491        if (!gridNode.isEventWithinDisclosureTriangle(event))
492            return;
493
494        if (gridNode.expanded) {
495            if (event.altKey)
496                gridNode.collapseRecursively();
497            else
498                gridNode.collapse();
499        } else {
500            if (event.altKey)
501                gridNode.expandRecursively();
502            else
503                gridNode.expand();
504        }
505    },
506
507    _startResizerDragging: function(event)
508    {
509        this.currentResizer = event.target;
510        if (!this.currentResizer.rightNeighboringColumnID)
511            return;
512        WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this),
513            this._endResizerDragging.bind(this), event, "col-resize");
514    },
515
516    _resizerDragging: function(event)
517    {
518        var resizer = this.currentResizer;
519        if (!resizer)
520            return;
521
522        // Constrain the dragpoint to be within the containing div of the
523        // datagrid.
524        var dragPoint = event.clientX - this.element.totalOffsetLeft;
525        // Constrain the dragpoint to be within the space made up by the
526        // column directly to the left and the column directly to the right.
527        var leftEdgeOfPreviousColumn = 0;
528        var firstRowCells = this.headerTableBody.rows[0].cells;
529        for (var i = 0; i < resizer.rightNeighboringColumnID - 1; i++)
530            leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
531
532        var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.rightNeighboringColumnID - 1].offsetWidth + firstRowCells[resizer.rightNeighboringColumnID].offsetWidth;
533
534        // Give each column some padding so that they don't disappear.
535        var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
536        var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
537
538        dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
539
540        resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
541
542        var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
543        this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn;
544        this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn;
545
546        var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
547        this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width =  percentRightColumn;
548        this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn;
549
550        event.preventDefault();
551    },
552
553    _endResizerDragging: function(event)
554    {
555        WebInspector.elementDragEnd(event);
556        this.currentResizer = null;
557    },
558
559    ColumnResizePadding: 10,
560
561    CenterResizerOverBorderAdjustment: 3,
562}
563
564WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;
565
566WebInspector.DataGridNode = function(data, hasChildren)
567{
568    this._expanded = false;
569    this._selected = false;
570    this._shouldRefreshChildren = true;
571    this._data = data || {};
572    this.hasChildren = hasChildren || false;
573    this.children = [];
574    this.dataGrid = null;
575    this.parent = null;
576    this.previousSibling = null;
577    this.nextSibling = null;
578    this.disclosureToggleWidth = 10;
579}
580
581WebInspector.DataGridNode.prototype = {
582    selectable: true,
583
584    get element()
585    {
586        if (this._element)
587            return this._element;
588
589        if (!this.dataGrid)
590            return null;
591
592        this._element = document.createElement("tr");
593        this._element._dataGridNode = this;
594
595        if (this.hasChildren)
596            this._element.addStyleClass("parent");
597        if (this.expanded)
598            this._element.addStyleClass("expanded");
599        if (this.selected)
600            this._element.addStyleClass("selected");
601        if (this.revealed)
602            this._element.addStyleClass("revealed");
603
604        for (var columnIdentifier in this.dataGrid.columns) {
605            var cell = this.createCell(columnIdentifier);
606            this._element.appendChild(cell);
607        }
608
609        return this._element;
610    },
611
612    get data()
613    {
614        return this._data;
615    },
616
617    set data(x)
618    {
619        this._data = x || {};
620        this.refresh();
621    },
622
623    get revealed()
624    {
625        if ("_revealed" in this)
626            return this._revealed;
627
628        var currentAncestor = this.parent;
629        while (currentAncestor && !currentAncestor.root) {
630            if (!currentAncestor.expanded) {
631                this._revealed = false;
632                return false;
633            }
634
635            currentAncestor = currentAncestor.parent;
636        }
637
638        this._revealed = true;
639        return true;
640    },
641
642    set hasChildren(x)
643    {
644        if (this._hasChildren === x)
645            return;
646
647        this._hasChildren = x;
648
649        if (!this._element)
650            return;
651
652        if (this._hasChildren)
653        {
654            this._element.addStyleClass("parent");
655            if (this.expanded)
656                this._element.addStyleClass("expanded");
657        }
658        else
659        {
660            this._element.removeStyleClass("parent");
661            this._element.removeStyleClass("expanded");
662        }
663    },
664
665    get hasChildren()
666    {
667        return this._hasChildren;
668    },
669
670    set revealed(x)
671    {
672        if (this._revealed === x)
673            return;
674
675        this._revealed = x;
676
677        if (this._element) {
678            if (this._revealed)
679                this._element.addStyleClass("revealed");
680            else
681                this._element.removeStyleClass("revealed");
682        }
683
684        for (var i = 0; i < this.children.length; ++i)
685            this.children[i].revealed = x && this.expanded;
686    },
687
688    get depth()
689    {
690        if ("_depth" in this)
691            return this._depth;
692        if (this.parent && !this.parent.root)
693            this._depth = this.parent.depth + 1;
694        else
695            this._depth = 0;
696        return this._depth;
697    },
698
699    get shouldRefreshChildren()
700    {
701        return this._shouldRefreshChildren;
702    },
703
704    set shouldRefreshChildren(x)
705    {
706        this._shouldRefreshChildren = x;
707        if (x && this.expanded)
708            this.expand();
709    },
710
711    get selected()
712    {
713        return this._selected;
714    },
715
716    set selected(x)
717    {
718        if (x)
719            this.select();
720        else
721            this.deselect();
722    },
723
724    get expanded()
725    {
726        return this._expanded;
727    },
728
729    set expanded(x)
730    {
731        if (x)
732            this.expand();
733        else
734            this.collapse();
735    },
736
737    refresh: function()
738    {
739        if (!this._element || !this.dataGrid)
740            return;
741
742        this._element.removeChildren();
743
744        for (var columnIdentifier in this.dataGrid.columns) {
745            var cell = this.createCell(columnIdentifier);
746            this._element.appendChild(cell);
747        }
748    },
749
750    createCell: function(columnIdentifier)
751    {
752        var cell = document.createElement("td");
753        cell.className = columnIdentifier + "-column";
754
755        var div = document.createElement("div");
756        div.textContent = this.data[columnIdentifier];
757        cell.appendChild(div);
758
759        if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
760            cell.addStyleClass("disclosure");
761            if (this.depth)
762                cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
763        }
764
765        return cell;
766    },
767
768    // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
769    appendChild: WebInspector.DataGrid.prototype.appendChild,
770    insertChild: WebInspector.DataGrid.prototype.insertChild,
771    removeChild: WebInspector.DataGrid.prototype.removeChild,
772    removeChildren: WebInspector.DataGrid.prototype.removeChildren,
773    removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,
774
775    _recalculateSiblings: function(myIndex)
776    {
777        if (!this.parent)
778            return;
779
780        var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);
781
782        if (previousChild) {
783            previousChild.nextSibling = this;
784            this.previousSibling = previousChild;
785        } else
786            this.previousSibling = null;
787
788        var nextChild = this.parent.children[myIndex + 1];
789
790        if (nextChild) {
791            nextChild.previousSibling = this;
792            this.nextSibling = nextChild;
793        } else
794            this.nextSibling = null;
795    },
796
797    collapse: function()
798    {
799        if (this._element)
800            this._element.removeStyleClass("expanded");
801
802        this._expanded = false;
803
804        for (var i = 0; i < this.children.length; ++i)
805            this.children[i].revealed = false;
806
807        this.dispatchEventToListeners("collapsed");
808    },
809
810    collapseRecursively: function()
811    {
812        var item = this;
813        while (item) {
814            if (item.expanded)
815                item.collapse();
816            item = item.traverseNextNode(false, this, true);
817        }
818    },
819
820    expand: function()
821    {
822        if (!this.hasChildren || this.expanded)
823            return;
824
825        if (this.revealed && !this._shouldRefreshChildren)
826            for (var i = 0; i < this.children.length; ++i)
827                this.children[i].revealed = true;
828
829        if (this._shouldRefreshChildren) {
830            for (var i = 0; i < this.children.length; ++i)
831                this.children[i]._detach();
832
833            this.dispatchEventToListeners("populate");
834
835            if (this._attached) {
836                for (var i = 0; i < this.children.length; ++i) {
837                    var child = this.children[i];
838                    if (this.revealed)
839                        child.revealed = true;
840                    child._attach();
841                }
842            }
843
844            delete this._shouldRefreshChildren;
845        }
846
847        if (this._element)
848            this._element.addStyleClass("expanded");
849
850        this._expanded = true;
851
852        this.dispatchEventToListeners("expanded");
853    },
854
855    expandRecursively: function()
856    {
857        var item = this;
858        while (item) {
859            item.expand();
860            item = item.traverseNextNode(false, this);
861        }
862    },
863
864    reveal: function()
865    {
866        var currentAncestor = this.parent;
867        while (currentAncestor && !currentAncestor.root) {
868            if (!currentAncestor.expanded)
869                currentAncestor.expand();
870            currentAncestor = currentAncestor.parent;
871        }
872
873        this.element.scrollIntoViewIfNeeded(false);
874
875        this.dispatchEventToListeners("revealed");
876    },
877
878    select: function(supressSelectedEvent)
879    {
880        if (!this.dataGrid || !this.selectable || this.selected)
881            return;
882
883        if (this.dataGrid.selectedNode)
884            this.dataGrid.selectedNode.deselect();
885
886        this._selected = true;
887        this.dataGrid.selectedNode = this;
888
889        if (this._element)
890            this._element.addStyleClass("selected");
891
892        if (!supressSelectedEvent)
893            this.dispatchEventToListeners("selected");
894    },
895
896    deselect: function(supressDeselectedEvent)
897    {
898        if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
899            return;
900
901        this._selected = false;
902        this.dataGrid.selectedNode = null;
903
904        if (this._element)
905            this._element.removeStyleClass("selected");
906
907        if (!supressDeselectedEvent)
908            this.dispatchEventToListeners("deselected");
909    },
910
911    traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
912    {
913        if (!dontPopulate && this.hasChildren)
914            this.dispatchEventToListeners("populate");
915
916        if (info)
917            info.depthChange = 0;
918
919        var node = (!skipHidden || this.revealed) ? this.children[0] : null;
920        if (node && (!skipHidden || this.expanded)) {
921            if (info)
922                info.depthChange = 1;
923            return node;
924        }
925
926        if (this === stayWithin)
927            return null;
928
929        node = (!skipHidden || this.revealed) ? this.nextSibling : null;
930        if (node)
931            return node;
932
933        node = this;
934        while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
935            if (info)
936                info.depthChange -= 1;
937            node = node.parent;
938        }
939
940        if (!node)
941            return null;
942
943        return (!skipHidden || node.revealed) ? node.nextSibling : null;
944    },
945
946    traversePreviousNode: function(skipHidden, dontPopulate)
947    {
948        var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
949        if (!dontPopulate && node && node.hasChildren)
950            node.dispatchEventToListeners("populate");
951
952        while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
953            if (!dontPopulate && node.hasChildren)
954                node.dispatchEventToListeners("populate");
955            node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
956        }
957
958        if (node)
959            return node;
960
961        if (!this.parent || this.parent.root)
962            return null;
963
964        return this.parent;
965    },
966
967    isEventWithinDisclosureTriangle: function(event)
968    {
969        if (!this.hasChildren)
970            return false;
971        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
972        if (!cell.hasStyleClass("disclosure"))
973            return false;
974        var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX);
975        var left = cell.totalOffsetLeft + computedLeftPadding;
976        return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
977    },
978
979    _attach: function()
980    {
981        if (!this.dataGrid || this._attached)
982            return;
983
984        this._attached = true;
985
986        var nextNode = null;
987        var previousNode = this.traversePreviousNode(true, true);
988        if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
989            var nextNode = previousNode.element.nextSibling;
990        if (!nextNode)
991            nextNode = this.dataGrid.dataTableBody.lastChild;
992        this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);
993
994        if (this.expanded)
995            for (var i = 0; i < this.children.length; ++i)
996                this.children[i]._attach();
997    },
998
999    _detach: function()
1000    {
1001        if (!this._attached)
1002            return;
1003
1004        this._attached = false;
1005
1006        if (this._element && this._element.parentNode)
1007            this._element.parentNode.removeChild(this._element);
1008
1009        for (var i = 0; i < this.children.length; ++i)
1010            this.children[i]._detach();
1011    }
1012}
1013
1014WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;
1015
1016WebInspector.CreationDataGridNode = function(data, hasChildren)
1017{
1018    WebInspector.DataGridNode.call(this, data, hasChildren);
1019    this.isCreationNode = true;
1020}
1021
1022WebInspector.CreationDataGridNode.prototype = {
1023    makeNormal: function()
1024    {
1025        delete this.isCreationNode;
1026        delete this.makeNormal;
1027    }
1028}
1029
1030WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
1031