• 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
26/**
27 * @constructor
28 * @extends {WebInspector.View}
29 * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>} columnsArray
30 * @param {function(!WebInspector.DataGridNode, string, string, string)=} editCallback
31 * @param {function(!WebInspector.DataGridNode)=} deleteCallback
32 * @param {function()=} refreshCallback
33 * @param {function(!WebInspector.ContextMenu, !WebInspector.DataGridNode)=} contextMenuCallback
34 */
35WebInspector.DataGrid = function(columnsArray, editCallback, deleteCallback, refreshCallback, contextMenuCallback)
36{
37    WebInspector.View.call(this);
38    this.registerRequiredCSS("dataGrid.css");
39
40    this.element.className = "data-grid"; // Override
41    this.element.tabIndex = 0;
42    this.element.addEventListener("keydown", this._keyDown.bind(this), false);
43
44    this._headerTable = document.createElement("table");
45    this._headerTable.className = "header";
46    /**
47     * @type {!Object.<string, !Element>}
48     */
49    this._headerTableHeaders = {};
50
51    this._dataTable = document.createElement("table");
52    this._dataTable.className = "data";
53
54    this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
55    this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
56
57    this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
58
59    // FIXME: Add a createCallback which is different from editCallback and has different
60    // behavior when creating a new node.
61    if (editCallback)
62        this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
63    this._editCallback = editCallback;
64    this._deleteCallback = deleteCallback;
65    this._refreshCallback = refreshCallback;
66    this._contextMenuCallback = contextMenuCallback;
67
68    this._scrollContainer = document.createElement("div");
69    this._scrollContainer.className = "data-container";
70    this._scrollContainer.appendChild(this._dataTable);
71
72    this.element.appendChild(this._headerTable);
73    this.element.appendChild(this._scrollContainer);
74
75    this._headerRow = document.createElement("tr");
76    this._headerTableColumnGroup = document.createElement("colgroup");
77    this._dataTableColumnGroup = document.createElement("colgroup");
78
79    this._fillerRow = document.createElement("tr");
80    this._fillerRow.className = "filler";
81
82    this._columnsArray = columnsArray;
83    this._visibleColumnsArray = columnsArray;
84    this._columns = {};
85
86    for (var i = 0; i < columnsArray.length; ++i) {
87        var column = columnsArray[i];
88        var columnIdentifier = column.identifier = column.id || i;
89        this._columns[columnIdentifier] = column;
90        if (column.disclosure)
91            this.disclosureColumnIdentifier = columnIdentifier;
92
93        var cell = document.createElement("th");
94        cell.className = columnIdentifier + "-column";
95        cell.columnIdentifier = columnIdentifier;
96        this._headerTableHeaders[columnIdentifier] = cell;
97
98        var div = document.createElement("div");
99        if (column.titleDOMFragment)
100            div.appendChild(column.titleDOMFragment);
101        else
102            div.textContent = column.title;
103        cell.appendChild(div);
104
105        if (column.sort) {
106            cell.classList.add("sort-" + column.sort);
107            this._sortColumnCell = cell;
108        }
109
110        if (column.sortable) {
111            cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
112            cell.classList.add("sortable");
113        }
114    }
115
116    this._headerTable.appendChild(this._headerTableColumnGroup);
117    this.headerTableBody.appendChild(this._headerRow);
118
119    this._dataTable.appendChild(this._dataTableColumnGroup);
120    this.dataTableBody.appendChild(this._fillerRow);
121
122    this._refreshHeader();
123
124    this.selectedNode = null;
125    this.expandNodesWhenArrowing = false;
126    this.setRootNode(new WebInspector.DataGridNode());
127    this.indentWidth = 15;
128    this._resizers = [];
129    this._columnWidthsInitialized = false;
130    this._cornerWidth = WebInspector.DataGrid.CornerWidth;
131}
132
133// Keep in sync with .data-grid col.corner style rule.
134WebInspector.DataGrid.CornerWidth = 14;
135
136/** @typedef {!{id: ?string, editable: boolean, longText: ?boolean, sort: !WebInspector.DataGrid.Order, sortable: boolean, align: !WebInspector.DataGrid.Align}} */
137WebInspector.DataGrid.ColumnDescriptor;
138
139WebInspector.DataGrid.Events = {
140    SelectedNode: "SelectedNode",
141    DeselectedNode: "DeselectedNode",
142    SortingChanged: "SortingChanged",
143    ColumnsResized: "ColumnsResized"
144}
145
146/** @enum {string} */
147WebInspector.DataGrid.Order = {
148    Ascending: "ascending",
149    Descending: "descending"
150}
151
152/** @enum {string} */
153WebInspector.DataGrid.Align = {
154    Center: "center",
155    Right: "right"
156}
157
158/**
159 * @param {!Array.<string>} columnNames
160 * @param {!Array.<string>} values
161 * @return {?WebInspector.DataGrid}
162 */
163WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
164{
165    var numColumns = columnNames.length;
166    if (!numColumns)
167        return null;
168
169    var columns = [];
170    for (var i = 0; i < columnNames.length; ++i)
171        columns.push({title: columnNames[i], width: columnNames[i].length, sortable: true});
172
173    var nodes = [];
174    for (var i = 0; i < values.length / numColumns; ++i) {
175        var data = {};
176        for (var j = 0; j < columnNames.length; ++j)
177            data[j] = values[numColumns * i + j];
178
179        var node = new WebInspector.DataGridNode(data, false);
180        node.selectable = false;
181        nodes.push(node);
182    }
183
184    var dataGrid = new WebInspector.DataGrid(columns);
185    var length = nodes.length;
186    for (var i = 0; i < length; ++i)
187        dataGrid.rootNode().appendChild(nodes[i]);
188
189    dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, sortDataGrid);
190
191    function sortDataGrid()
192    {
193        var nodes = dataGrid._rootNode.children.slice();
194        var sortColumnIdentifier = dataGrid.sortColumnIdentifier();
195        var sortDirection = dataGrid.isSortOrderAscending() ? 1 : -1;
196        var columnIsNumeric = true;
197
198        for (var i = 0; i < nodes.length; i++) {
199            var value = nodes[i].data[sortColumnIdentifier];
200            value = value instanceof Node ? Number(value.textContent) : Number(value);
201            if (isNaN(value)) {
202                columnIsNumeric = false;
203                break;
204            }
205        }
206
207        function comparator(dataGridNode1, dataGridNode2)
208        {
209            var item1 = dataGridNode1.data[sortColumnIdentifier];
210            var item2 = dataGridNode2.data[sortColumnIdentifier];
211            item1 = item1 instanceof Node ? item1.textContent : String(item1);
212            item2 = item2 instanceof Node ? item2.textContent : String(item2);
213
214            var comparison;
215            if (columnIsNumeric) {
216                // Sort numbers based on comparing their values rather than a lexicographical comparison.
217                var number1 = parseFloat(item1);
218                var number2 = parseFloat(item2);
219                comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0);
220            } else
221                comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0);
222
223            return sortDirection * comparison;
224        }
225
226        nodes.sort(comparator);
227        dataGrid.rootNode().removeChildren();
228        for (var i = 0; i < nodes.length; i++)
229            dataGrid._rootNode.appendChild(nodes[i]);
230    }
231    return dataGrid;
232}
233
234WebInspector.DataGrid.prototype = {
235    _refreshHeader: function()
236    {
237        this._headerTableColumnGroup.removeChildren();
238        this._dataTableColumnGroup.removeChildren();
239        this._headerRow.removeChildren();
240        this._fillerRow.removeChildren();
241
242        for (var i = 0; i < this._visibleColumnsArray.length; ++i) {
243            var column = this._visibleColumnsArray[i];
244            var columnIdentifier = column.identifier;
245            var headerColumn = this._headerTableColumnGroup.createChild("col");
246            var dataColumn = this._dataTableColumnGroup.createChild("col");
247            if (column.width) {
248                headerColumn.style.width = column.width;
249                dataColumn.style.width = column.width;
250            }
251            this._headerRow.appendChild(this._headerTableHeaders[columnIdentifier]);
252            this._fillerRow.createChild("td", columnIdentifier + "-column");
253        }
254
255        this._headerRow.createChild("th", "corner");
256        this._fillerRow.createChild("td", "corner");
257        this._headerTableColumnGroup.createChild("col", "corner");
258        this._dataTableColumnGroup.createChild("col", "corner");
259    },
260
261    /**
262     * @param {!WebInspector.DataGridNode} rootNode
263     */
264    setRootNode: function(rootNode)
265    {
266        if (this._rootNode) {
267            this._rootNode.removeChildren();
268            this._rootNode.dataGrid = null;
269            this._rootNode._isRoot = false;
270        }
271        /** @type {!WebInspector.DataGridNode} */
272        this._rootNode = rootNode;
273        rootNode._isRoot = true;
274        rootNode.hasChildren = false;
275        rootNode._expanded = true;
276        rootNode._revealed = true;
277        rootNode.dataGrid = this;
278    },
279
280    /**
281     * @return {!WebInspector.DataGridNode}
282     */
283    rootNode: function()
284    {
285        return this._rootNode;
286    },
287
288    _ondblclick: function(event)
289    {
290        if (this._editing || this._editingNode)
291            return;
292
293        var columnIdentifier = this.columnIdentifierFromNode(event.target);
294        if (!columnIdentifier || !this._columns[columnIdentifier].editable)
295            return;
296        this._startEditing(event.target);
297    },
298
299    /**
300     * @param {!WebInspector.DataGridNode} node
301     * @param {number} cellIndex
302     */
303    _startEditingColumnOfDataGridNode: function(node, cellIndex)
304    {
305        this._editing = true;
306        /** @type {!WebInspector.DataGridNode} */
307        this._editingNode = node;
308        this._editingNode.select();
309
310        var element = this._editingNode._element.children[cellIndex];
311        WebInspector.InplaceEditor.startEditing(element, this._startEditingConfig(element));
312        window.getSelection().setBaseAndExtent(element, 0, element, 1);
313    },
314
315    _startEditing: function(target)
316    {
317        var element = target.enclosingNodeOrSelfWithNodeName("td");
318        if (!element)
319            return;
320
321        this._editingNode = this.dataGridNodeFromNode(target);
322        if (!this._editingNode) {
323            if (!this.creationNode)
324                return;
325            this._editingNode = this.creationNode;
326        }
327
328        // Force editing the 1st column when editing the creation node
329        if (this._editingNode.isCreationNode)
330            return this._startEditingColumnOfDataGridNode(this._editingNode, this._nextEditableColumn(-1));
331
332        this._editing = true;
333        WebInspector.InplaceEditor.startEditing(element, this._startEditingConfig(element));
334
335        window.getSelection().setBaseAndExtent(element, 0, element, 1);
336    },
337
338    renderInline: function()
339    {
340        this.element.classList.add("inline");
341        this._cornerWidth = 0;
342        this.updateWidths();
343    },
344
345    _startEditingConfig: function(element)
346    {
347        return new WebInspector.InplaceEditor.Config(this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
348    },
349
350    _editingCommitted: function(element, newText, oldText, context, moveDirection)
351    {
352        var columnIdentifier = this.columnIdentifierFromNode(element);
353        if (!columnIdentifier) {
354            this._editingCancelled(element);
355            return;
356        }
357        var column = this._columns[columnIdentifier];
358        var cellIndex = this._visibleColumnsArray.indexOf(column);
359        var textBeforeEditing = this._editingNode.data[columnIdentifier];
360        var currentEditingNode = this._editingNode;
361
362        /**
363         * @param {boolean} wasChange
364         * @this {WebInspector.DataGrid}
365         */
366        function moveToNextIfNeeded(wasChange) {
367            if (!moveDirection)
368                return;
369
370            if (moveDirection === "forward") {
371                var firstEditableColumn = this._nextEditableColumn(-1);
372                if (currentEditingNode.isCreationNode && cellIndex === firstEditableColumn && !wasChange)
373                    return;
374
375                var nextEditableColumn = this._nextEditableColumn(cellIndex);
376                if (nextEditableColumn !== -1)
377                    return this._startEditingColumnOfDataGridNode(currentEditingNode, nextEditableColumn);
378
379                var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
380                if (nextDataGridNode)
381                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, firstEditableColumn);
382                if (currentEditingNode.isCreationNode && wasChange) {
383                    this.addCreationNode(false);
384                    return this._startEditingColumnOfDataGridNode(this.creationNode, firstEditableColumn);
385                }
386                return;
387            }
388
389            if (moveDirection === "backward") {
390                var prevEditableColumn = this._nextEditableColumn(cellIndex, true);
391                if (prevEditableColumn !== -1)
392                    return this._startEditingColumnOfDataGridNode(currentEditingNode, prevEditableColumn);
393
394                var lastEditableColumn = this._nextEditableColumn(this._visibleColumnsArray.length, true);
395                var nextDataGridNode = currentEditingNode.traversePreviousNode(true, true);
396                if (nextDataGridNode)
397                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, lastEditableColumn);
398                return;
399            }
400        }
401
402        if (textBeforeEditing == newText) {
403            this._editingCancelled(element);
404            moveToNextIfNeeded.call(this, false);
405            return;
406        }
407
408        // Update the text in the datagrid that we typed
409        this._editingNode.data[columnIdentifier] = newText;
410
411        // Make the callback - expects an editing node (table row), the column number that is being edited,
412        // the text that used to be there, and the new text.
413        this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText);
414
415        if (this._editingNode.isCreationNode)
416            this.addCreationNode(false);
417
418        this._editingCancelled(element);
419        moveToNextIfNeeded.call(this, true);
420    },
421
422    _editingCancelled: function(element)
423    {
424        delete this._editing;
425        this._editingNode = null;
426    },
427
428    /**
429     * @param {number} cellIndex
430     * @param {boolean=} moveBackward
431     * @return {number}
432     */
433    _nextEditableColumn: function(cellIndex, moveBackward)
434    {
435        var increment = moveBackward ? -1 : 1;
436        var columns = this._visibleColumnsArray;
437        for (var i = cellIndex + increment; (i >= 0) && (i < columns.length); i += increment) {
438            if (columns[i].editable)
439                return i;
440        }
441        return -1;
442    },
443
444    /**
445     * @return {?string}
446     */
447    sortColumnIdentifier: function()
448    {
449        if (!this._sortColumnCell)
450            return null;
451        return this._sortColumnCell.columnIdentifier;
452    },
453
454    /**
455     * @return {?string}
456     */
457    sortOrder: function()
458    {
459        if (!this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending"))
460            return WebInspector.DataGrid.Order.Ascending;
461        if (this._sortColumnCell.classList.contains("sort-descending"))
462            return WebInspector.DataGrid.Order.Descending;
463        return null;
464    },
465
466    /**
467     * @return {boolean}
468     */
469    isSortOrderAscending: function()
470    {
471        return !this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending");
472    },
473
474    get headerTableBody()
475    {
476        if ("_headerTableBody" in this)
477            return this._headerTableBody;
478
479        this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
480        if (!this._headerTableBody) {
481            this._headerTableBody = this.element.ownerDocument.createElement("tbody");
482            this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
483        }
484
485        return this._headerTableBody;
486    },
487
488    get dataTableBody()
489    {
490        if ("_dataTableBody" in this)
491            return this._dataTableBody;
492
493        this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
494        if (!this._dataTableBody) {
495            this._dataTableBody = this.element.ownerDocument.createElement("tbody");
496            this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
497        }
498
499        return this._dataTableBody;
500    },
501
502    /**
503     * @param {!Array.<number>} widths
504     * @param {number} minPercent
505     * @param {number=} maxPercent
506     * @return {!Array.<number>}
507     */
508    _autoSizeWidths: function(widths, minPercent, maxPercent)
509    {
510        if (minPercent)
511            minPercent = Math.min(minPercent, Math.floor(100 / widths.length));
512        var totalWidth = 0;
513        for (var i = 0; i < widths.length; ++i)
514            totalWidth += widths[i];
515        var totalPercentWidth = 0;
516        for (var i = 0; i < widths.length; ++i) {
517            var width = Math.round(100 * widths[i] / totalWidth);
518            if (minPercent && width < minPercent)
519                width = minPercent;
520            else if (maxPercent && width > maxPercent)
521                width = maxPercent;
522            totalPercentWidth += width;
523            widths[i] = width;
524        }
525        var recoupPercent = totalPercentWidth - 100;
526
527        while (minPercent && recoupPercent > 0) {
528            for (var i = 0; i < widths.length; ++i) {
529                if (widths[i] > minPercent) {
530                    --widths[i];
531                    --recoupPercent;
532                    if (!recoupPercent)
533                        break;
534                }
535            }
536        }
537
538        while (maxPercent && recoupPercent < 0) {
539            for (var i = 0; i < widths.length; ++i) {
540                if (widths[i] < maxPercent) {
541                    ++widths[i];
542                    ++recoupPercent;
543                    if (!recoupPercent)
544                        break;
545                }
546            }
547        }
548
549        return widths;
550    },
551
552    /**
553     * @param {number} minPercent
554     * @param {number=} maxPercent
555     * @param {number=} maxDescentLevel
556     */
557    autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel)
558    {
559        var widths = [];
560        for (var i = 0; i < this._columnsArray.length; ++i)
561            widths.push((this._columnsArray[i].title || "").length);
562
563        maxDescentLevel = maxDescentLevel || 0;
564        var children = this._enumerateChildren(this._rootNode, [], maxDescentLevel + 1);
565        for (var i = 0; i < children.length; ++i) {
566            var node = children[i];
567            for (var j = 0; j < this._columnsArray.length; ++j) {
568                var text = node.data[this._columnsArray[j].identifier] || "";
569                if (text.length > widths[j])
570                    widths[j] = text.length;
571            }
572        }
573
574        widths = this._autoSizeWidths(widths, minPercent, maxPercent);
575
576        for (var i = 0; i < this._columnsArray.length; ++i)
577            this._columnsArray[i].weight = widths[i];
578        this._columnWidthsInitialized = false;
579        this.updateWidths();
580    },
581
582    _enumerateChildren: function(rootNode, result, maxLevel)
583    {
584        if (!rootNode._isRoot)
585            result.push(rootNode);
586        if (!maxLevel)
587            return;
588        for (var i = 0; i < rootNode.children.length; ++i)
589            this._enumerateChildren(rootNode.children[i], result, maxLevel - 1);
590        return result;
591    },
592
593    onResize: function()
594    {
595        this.updateWidths();
596    },
597
598    // Updates the widths of the table, including the positions of the column
599    // resizers.
600    //
601    // IMPORTANT: This function MUST be called once after the element of the
602    // DataGrid is attached to its parent element and every subsequent time the
603    // width of the parent element is changed in order to make it possible to
604    // resize the columns.
605    //
606    // If this function is not called after the DataGrid is attached to its
607    // parent element, then the DataGrid's columns will not be resizable.
608    updateWidths: function()
609    {
610        var headerTableColumns = this._headerTableColumnGroup.children;
611
612        // Use container size to avoid changes of table width caused by change of column widths.
613        var tableWidth = this.element.offsetWidth - this._cornerWidth;
614        var numColumns = headerTableColumns.length - 1; // Do not process corner column.
615
616        // Do not attempt to use offsetes if we're not attached to the document tree yet.
617        if (!this._columnWidthsInitialized && this.element.offsetWidth) {
618            // Give all the columns initial widths now so that during a resize,
619            // when the two columns that get resized get a percent value for
620            // their widths, all the other columns already have percent values
621            // for their widths.
622            for (var i = 0; i < numColumns; i++) {
623                var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
624                var column = this._visibleColumnsArray[i];
625                if (!column.weight)
626                    column.weight = 100 * columnWidth / tableWidth;
627            }
628            this._columnWidthsInitialized = true;
629        }
630        this._applyColumnWeights();
631    },
632
633    /**
634     * @param {string} name
635     */
636    setName: function(name)
637    {
638        this._columnWeightsSetting = WebInspector.settings.createSetting("dataGrid-" + name + "-columnWeights", {});
639        this._loadColumnWeights();
640    },
641
642    _loadColumnWeights: function()
643    {
644        if (!this._columnWeightsSetting)
645            return;
646        var weights = this._columnWeightsSetting.get();
647        for (var i = 0; i < this._columnsArray.length; ++i) {
648            var column = this._columnsArray[i];
649            var weight = weights[column.identifier];
650            if (weight)
651                column.weight = weight;
652        }
653        this._applyColumnWeights();
654    },
655
656    _saveColumnWeights: function()
657    {
658        if (!this._columnWeightsSetting)
659            return;
660        var weights = {};
661        for (var i = 0; i < this._columnsArray.length; ++i) {
662            var column = this._columnsArray[i];
663            weights[column.identifier] = column.weight;
664        }
665        this._columnWeightsSetting.set(weights);
666    },
667
668    wasShown: function()
669    {
670       this._loadColumnWeights();
671    },
672
673    _applyColumnWeights: function()
674    {
675        var tableWidth = this.element.offsetWidth - this._cornerWidth;
676        if (tableWidth <= 0)
677            return;
678
679        var sumOfWeights = 0.0;
680        for (var i = 0; i < this._visibleColumnsArray.length; ++i)
681            sumOfWeights += this._visibleColumnsArray[i].weight;
682
683        var sum = 0;
684        var lastOffset = 0;
685
686        for (var i = 0; i < this._visibleColumnsArray.length; ++i) {
687            sum += this._visibleColumnsArray[i].weight;
688            var offset = (sum * tableWidth / sumOfWeights) | 0;
689            var width = (offset - lastOffset) + "px";
690            this._headerTableColumnGroup.children[i].style.width = width;
691            this._dataTableColumnGroup.children[i].style.width = width;
692            lastOffset = offset;
693        }
694
695        this._positionResizers();
696        this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized);
697    },
698
699    /**
700     * @param {!Object.<string, boolean>} columnsVisibility
701     */
702    setColumnsVisiblity: function(columnsVisibility)
703    {
704        this._visibleColumnsArray = [];
705        for (var i = 0; i < this._columnsArray.length; ++i) {
706            var column = this._columnsArray[i];
707            if (columnsVisibility[column.identifier])
708                this._visibleColumnsArray.push(column);
709        }
710        this._refreshHeader();
711        this._applyColumnWeights();
712        var nodes = this._enumerateChildren(this.rootNode(), [], -1);
713        for (var i = 0; i < nodes.length; ++i)
714            nodes[i].refresh();
715    },
716
717    get scrollContainer()
718    {
719        return this._scrollContainer;
720    },
721
722    /**
723     * @return {boolean}
724     */
725    isScrolledToLastRow: function()
726    {
727        return this._scrollContainer.isScrolledToBottom();
728    },
729
730    scrollToLastRow: function()
731    {
732        this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight;
733    },
734
735    _positionResizers: function()
736    {
737        var headerTableColumns = this._headerTableColumnGroup.children;
738        var numColumns = headerTableColumns.length - 1; // Do not process corner column.
739        var left = [];
740        var resizers = this._resizers;
741
742        while (resizers.length > numColumns - 1)
743            resizers.pop().remove();
744
745        for (var i = 0; i < numColumns - 1; i++) {
746            // Get the width of the cell in the first (and only) row of the
747            // header table in order to determine the width of the column, since
748            // it is not possible to query a column for its width.
749            left[i] = (left[i-1] || 0) + this.headerTableBody.rows[0].cells[i].offsetWidth;
750        }
751
752        // Make n - 1 resizers for n columns.
753        for (var i = 0; i < numColumns - 1; i++) {
754            var resizer = this._resizers[i];
755            if (!resizer) {
756                // This is the first call to updateWidth, so the resizers need
757                // to be created.
758                resizer = document.createElement("div");
759                resizer.__index = i;
760                resizer.classList.add("data-grid-resizer");
761                // This resizer is associated with the column to its right.
762                WebInspector.installDragHandle(resizer, this._startResizerDragging.bind(this), this._resizerDragging.bind(this), this._endResizerDragging.bind(this), "col-resize");
763                this.element.appendChild(resizer);
764                resizers.push(resizer);
765            }
766            if (resizer.__position !== left[i]) {
767                resizer.__position = left[i];
768                resizer.style.left = left[i] + "px";
769            }
770        }
771    },
772
773    addCreationNode: function(hasChildren)
774    {
775        if (this.creationNode)
776            this.creationNode.makeNormal();
777
778        var emptyData = {};
779        for (var column in this._columns)
780            emptyData[column] = null;
781        this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
782        this.rootNode().appendChild(this.creationNode);
783    },
784
785    sortNodes: function(comparator, reverseMode)
786    {
787        function comparatorWrapper(a, b)
788        {
789            if (a._dataGridNode._data.summaryRow)
790                return 1;
791            if (b._dataGridNode._data.summaryRow)
792                return -1;
793
794            var aDataGirdNode = a._dataGridNode;
795            var bDataGirdNode = b._dataGridNode;
796            return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode);
797        }
798
799        var tbody = this.dataTableBody;
800        var tbodyParent = tbody.parentElement;
801        tbodyParent.removeChild(tbody);
802
803        var childNodes = tbody.childNodes;
804        var fillerRow = childNodes[childNodes.length - 1];
805
806        var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1);
807        sortedRows.sort(comparatorWrapper);
808        var sortedRowsLength = sortedRows.length;
809
810        tbody.removeChildren();
811        var previousSiblingNode = null;
812        for (var i = 0; i < sortedRowsLength; ++i) {
813            var row = sortedRows[i];
814            var node = row._dataGridNode;
815            node.previousSibling = previousSiblingNode;
816            if (previousSiblingNode)
817                previousSiblingNode.nextSibling = node;
818            tbody.appendChild(row);
819            previousSiblingNode = node;
820        }
821        if (previousSiblingNode)
822            previousSiblingNode.nextSibling = null;
823
824        tbody.appendChild(fillerRow);
825        tbodyParent.appendChild(tbody);
826    },
827
828    _keyDown: function(event)
829    {
830        if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
831            return;
832
833        var handled = false;
834        var nextSelectedNode;
835        if (event.keyIdentifier === "Up" && !event.altKey) {
836            nextSelectedNode = this.selectedNode.traversePreviousNode(true);
837            while (nextSelectedNode && !nextSelectedNode.selectable)
838                nextSelectedNode = nextSelectedNode.traversePreviousNode(true);
839            handled = nextSelectedNode ? true : false;
840        } else if (event.keyIdentifier === "Down" && !event.altKey) {
841            nextSelectedNode = this.selectedNode.traverseNextNode(true);
842            while (nextSelectedNode && !nextSelectedNode.selectable)
843                nextSelectedNode = nextSelectedNode.traverseNextNode(true);
844            handled = nextSelectedNode ? true : false;
845        } else if (event.keyIdentifier === "Left") {
846            if (this.selectedNode.expanded) {
847                if (event.altKey)
848                    this.selectedNode.collapseRecursively();
849                else
850                    this.selectedNode.collapse();
851                handled = true;
852            } else if (this.selectedNode.parent && !this.selectedNode.parent._isRoot) {
853                handled = true;
854                if (this.selectedNode.parent.selectable) {
855                    nextSelectedNode = this.selectedNode.parent;
856                    handled = nextSelectedNode ? true : false;
857                } else if (this.selectedNode.parent)
858                    this.selectedNode.parent.collapse();
859            }
860        } else if (event.keyIdentifier === "Right") {
861            if (!this.selectedNode.revealed) {
862                this.selectedNode.reveal();
863                handled = true;
864            } else if (this.selectedNode.hasChildren) {
865                handled = true;
866                if (this.selectedNode.expanded) {
867                    nextSelectedNode = this.selectedNode.children[0];
868                    handled = nextSelectedNode ? true : false;
869                } else {
870                    if (event.altKey)
871                        this.selectedNode.expandRecursively();
872                    else
873                        this.selectedNode.expand();
874                }
875            }
876        } else if (event.keyCode === 8 || event.keyCode === 46) {
877            if (this._deleteCallback) {
878                handled = true;
879                this._deleteCallback(this.selectedNode);
880                this.changeNodeAfterDeletion();
881            }
882        } else if (isEnterKey(event)) {
883            if (this._editCallback) {
884                handled = true;
885                this._startEditing(this.selectedNode._element.children[this._nextEditableColumn(-1)]);
886            }
887        }
888
889        if (nextSelectedNode) {
890            nextSelectedNode.reveal();
891            nextSelectedNode.select();
892        }
893
894        if (handled)
895            event.consume(true);
896    },
897
898    changeNodeAfterDeletion: function()
899    {
900        var nextSelectedNode = this.selectedNode.traverseNextNode(true);
901        while (nextSelectedNode && !nextSelectedNode.selectable)
902            nextSelectedNode = nextSelectedNode.traverseNextNode(true);
903
904        if (!nextSelectedNode || nextSelectedNode.isCreationNode) {
905            nextSelectedNode = this.selectedNode.traversePreviousNode(true);
906            while (nextSelectedNode && !nextSelectedNode.selectable)
907                nextSelectedNode = nextSelectedNode.traversePreviousNode(true);
908        }
909
910        if (nextSelectedNode) {
911            nextSelectedNode.reveal();
912            nextSelectedNode.select();
913        }
914    },
915
916    /**
917     * @param {!Node} target
918     * @return {?WebInspector.DataGridNode}
919     */
920    dataGridNodeFromNode: function(target)
921    {
922        var rowElement = target.enclosingNodeOrSelfWithNodeName("tr");
923        return rowElement && rowElement._dataGridNode;
924    },
925
926    /**
927     * @param {!Node} target
928     * @return {?string}
929     */
930    columnIdentifierFromNode: function(target)
931    {
932        var cellElement = target.enclosingNodeOrSelfWithNodeName("td");
933        return cellElement && cellElement.columnIdentifier_;
934    },
935
936    _clickInHeaderCell: function(event)
937    {
938        var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
939        if (!cell || (typeof cell.columnIdentifier === "undefined") || !cell.classList.contains("sortable"))
940            return;
941
942        var sortOrder = WebInspector.DataGrid.Order.Ascending;
943        if ((cell === this._sortColumnCell) && this.isSortOrderAscending())
944            sortOrder = WebInspector.DataGrid.Order.Descending;
945
946        if (this._sortColumnCell)
947            this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
948        this._sortColumnCell = cell;
949
950        cell.classList.add("sort-" + sortOrder);
951
952        this.dispatchEventToListeners(WebInspector.DataGrid.Events.SortingChanged);
953    },
954
955    /**
956     * @param {string} columnIdentifier
957     * @param {!WebInspector.DataGrid.Order} sortOrder
958     */
959    markColumnAsSortedBy: function(columnIdentifier, sortOrder)
960    {
961        if (this._sortColumnCell)
962            this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
963        this._sortColumnCell = this._headerTableHeaders[columnIdentifier];
964        this._sortColumnCell.classList.add("sort-" + sortOrder);
965    },
966
967    /**
968     * @param {string} columnIdentifier
969     * @return {!Element}
970     */
971    headerTableHeader: function(columnIdentifier)
972    {
973        return this._headerTableHeaders[columnIdentifier];
974    },
975
976    _mouseDownInDataTable: function(event)
977    {
978        var gridNode = this.dataGridNodeFromNode(event.target);
979        if (!gridNode || !gridNode.selectable)
980            return;
981
982        if (gridNode.isEventWithinDisclosureTriangle(event))
983            return;
984
985        if (event.metaKey) {
986            if (gridNode.selected)
987                gridNode.deselect();
988            else
989                gridNode.select();
990        } else
991            gridNode.select();
992    },
993
994    _contextMenuInDataTable: function(event)
995    {
996        var contextMenu = new WebInspector.ContextMenu(event);
997
998        var gridNode = this.dataGridNodeFromNode(event.target);
999        if (this._refreshCallback && (!gridNode || gridNode !== this.creationNode))
1000            contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this));
1001
1002        if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) {
1003            if (this._editCallback) {
1004                if (gridNode === this.creationNode)
1005                    contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add new" : "Add New"), this._startEditing.bind(this, event.target));
1006                else {
1007                    var columnIdentifier = this.columnIdentifierFromNode(event.target);
1008                    if (columnIdentifier && this._columns[columnIdentifier].editable)
1009                        contextMenu.appendItem(WebInspector.UIString("Edit \"%s\"", this._columns[columnIdentifier].title), this._startEditing.bind(this, event.target));
1010                }
1011            }
1012            if (this._deleteCallback && gridNode !== this.creationNode)
1013                contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
1014            if (this._contextMenuCallback)
1015                this._contextMenuCallback(contextMenu, gridNode);
1016        }
1017
1018        contextMenu.show();
1019    },
1020
1021    _clickInDataTable: function(event)
1022    {
1023        var gridNode = this.dataGridNodeFromNode(event.target);
1024        if (!gridNode || !gridNode.hasChildren)
1025            return;
1026
1027        if (!gridNode.isEventWithinDisclosureTriangle(event))
1028            return;
1029
1030        if (gridNode.expanded) {
1031            if (event.altKey)
1032                gridNode.collapseRecursively();
1033            else
1034                gridNode.collapse();
1035        } else {
1036            if (event.altKey)
1037                gridNode.expandRecursively();
1038            else
1039                gridNode.expand();
1040        }
1041    },
1042
1043    get resizeMethod()
1044    {
1045        if (typeof this._resizeMethod === "undefined")
1046            return WebInspector.DataGrid.ResizeMethod.Nearest;
1047        return this._resizeMethod;
1048    },
1049
1050    set resizeMethod(method)
1051    {
1052        this._resizeMethod = method;
1053    },
1054
1055    /**
1056     * @return {boolean}
1057     */
1058    _startResizerDragging: function(event)
1059    {
1060        this._currentResizer = event.target;
1061        return true;
1062    },
1063
1064    _resizerDragging: function(event)
1065    {
1066        var resizer = this._currentResizer;
1067        if (!resizer)
1068            return;
1069
1070        var tableWidth = this.element.offsetWidth; // Cache it early, before we invalidate layout.
1071
1072        // Constrain the dragpoint to be within the containing div of the
1073        // datagrid.
1074        var dragPoint = event.clientX - this.element.totalOffsetLeft();
1075        var firstRowCells = this.headerTableBody.rows[0].cells;
1076        var leftEdgeOfPreviousColumn = 0;
1077        // Constrain the dragpoint to be within the space made up by the
1078        // column directly to the left and the column directly to the right.
1079        var leftCellIndex = resizer.__index;
1080        var rightCellIndex = leftCellIndex + 1;
1081        for (var i = 0; i < leftCellIndex; i++)
1082            leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
1083
1084        // Differences for other resize methods
1085        if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) {
1086            rightCellIndex = this._resizers.length;
1087        } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) {
1088            leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth;
1089            leftCellIndex = 0;
1090        }
1091
1092        var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth;
1093
1094        // Give each column some padding so that they don't disappear.
1095        var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
1096        var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
1097        if (leftMinimum > rightMaximum)
1098            return;
1099
1100        dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
1101
1102        var position = (dragPoint - this.CenterResizerOverBorderAdjustment);
1103        resizer.__position = position;
1104        resizer.style.left = position + "px";
1105
1106        var pxLeftColumn = (dragPoint - leftEdgeOfPreviousColumn) + "px";
1107        this._headerTableColumnGroup.children[leftCellIndex].style.width = pxLeftColumn;
1108        this._dataTableColumnGroup.children[leftCellIndex].style.width = pxLeftColumn;
1109
1110        var pxRightColumn = (rightEdgeOfNextColumn - dragPoint) + "px";
1111        this._headerTableColumnGroup.children[rightCellIndex].style.width = pxRightColumn;
1112        this._dataTableColumnGroup.children[rightCellIndex].style.width = pxRightColumn;
1113
1114        var leftColumn = this._visibleColumnsArray[leftCellIndex];
1115        var rightColumn = this._visibleColumnsArray[rightCellIndex];
1116        if (leftColumn.weight || rightColumn.weight) {
1117            var sumOfWeights = leftColumn.weight + rightColumn.weight;
1118            var delta = rightEdgeOfNextColumn - leftEdgeOfPreviousColumn;
1119            leftColumn.weight = (dragPoint - leftEdgeOfPreviousColumn) * sumOfWeights / delta;
1120            rightColumn.weight = (rightEdgeOfNextColumn - dragPoint) * sumOfWeights / delta;
1121        }
1122
1123        this._positionResizers();
1124        event.preventDefault();
1125        this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized);
1126    },
1127
1128    /**
1129     * @param {string} columnId
1130     * @return {number}
1131     */
1132    columnOffset: function(columnId)
1133    {
1134        if (!this.element.offsetWidth)
1135            return 0;
1136        for (var i = 1; i < this._visibleColumnsArray.length; ++i) {
1137            if (columnId === this._visibleColumnsArray[i].identifier)
1138                return this._resizers[i - 1].__position;
1139        }
1140        return 0;
1141    },
1142
1143    _endResizerDragging: function(event)
1144    {
1145        this._currentResizer = null;
1146        this._saveColumnWeights();
1147        this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized);
1148    },
1149
1150    /**
1151     * @return {?Element}
1152     */
1153    defaultAttachLocation: function()
1154    {
1155        return this.dataTableBody.firstChild;
1156    },
1157
1158    ColumnResizePadding: 24,
1159
1160    CenterResizerOverBorderAdjustment: 3,
1161
1162    __proto__: WebInspector.View.prototype
1163}
1164
1165WebInspector.DataGrid.ResizeMethod = {
1166    Nearest: "nearest",
1167    First: "first",
1168    Last: "last"
1169}
1170
1171/**
1172 * @constructor
1173 * @extends {WebInspector.Object}
1174 * @param {?Object.<string, *>=} data
1175 * @param {boolean=} hasChildren
1176 */
1177WebInspector.DataGridNode = function(data, hasChildren)
1178{
1179    this._expanded = false;
1180    this._selected = false;
1181    this._shouldRefreshChildren = true;
1182    /** @type {!Object.<string, *>} */
1183    this._data = data || {};
1184    /** @type {boolean} */
1185    this.hasChildren = hasChildren || false;
1186    /** @type {!Array.<!WebInspector.DataGridNode>} */
1187    this.children = [];
1188    this.dataGrid = null;
1189    this.parent = null;
1190    /** @type {?WebInspector.DataGridNode} */
1191    this.previousSibling = null;
1192    /** @type {?WebInspector.DataGridNode} */
1193    this.nextSibling = null;
1194    this.disclosureToggleWidth = 10;
1195}
1196
1197WebInspector.DataGridNode.prototype = {
1198    /** @type {boolean} */
1199    selectable: true,
1200
1201    /** @type {boolean} */
1202    _isRoot: false,
1203
1204    get element()
1205    {
1206        if (this._element)
1207            return this._element;
1208
1209        if (!this.dataGrid)
1210            return null;
1211
1212        this._element = document.createElement("tr");
1213        this._element._dataGridNode = this;
1214
1215        if (this.hasChildren)
1216            this._element.classList.add("parent");
1217        if (this.expanded)
1218            this._element.classList.add("expanded");
1219        if (this.selected)
1220            this._element.classList.add("selected");
1221        if (this.revealed)
1222            this._element.classList.add("revealed");
1223
1224        this.createCells();
1225        this._element.createChild("td", "corner");
1226
1227        return this._element;
1228    },
1229
1230    createCells: function()
1231    {
1232        var columnsArray = this.dataGrid._visibleColumnsArray;
1233        for (var i = 0; i < columnsArray.length; ++i) {
1234            var cell = this.createCell(columnsArray[i].identifier);
1235            this._element.appendChild(cell);
1236        }
1237    },
1238
1239    get data()
1240    {
1241        return this._data;
1242    },
1243
1244    set data(x)
1245    {
1246        this._data = x || {};
1247        this.refresh();
1248    },
1249
1250    get revealed()
1251    {
1252        if ("_revealed" in this)
1253            return this._revealed;
1254
1255        var currentAncestor = this.parent;
1256        while (currentAncestor && !currentAncestor._isRoot) {
1257            if (!currentAncestor.expanded) {
1258                this._revealed = false;
1259                return false;
1260            }
1261
1262            currentAncestor = currentAncestor.parent;
1263        }
1264
1265        this._revealed = true;
1266        return true;
1267    },
1268
1269    set hasChildren(x)
1270    {
1271        if (this._hasChildren === x)
1272            return;
1273
1274        this._hasChildren = x;
1275
1276        if (!this._element)
1277            return;
1278
1279        this._element.classList.toggle("parent", this._hasChildren);
1280        this._element.classList.toggle("expanded", this._hasChildren && this.expanded);
1281    },
1282
1283    get hasChildren()
1284    {
1285        return this._hasChildren;
1286    },
1287
1288    set revealed(x)
1289    {
1290        if (this._revealed === x)
1291            return;
1292
1293        this._revealed = x;
1294
1295        if (this._element)
1296            this._element.classList.toggle("revealed", this._revealed);
1297
1298        for (var i = 0; i < this.children.length; ++i)
1299            this.children[i].revealed = x && this.expanded;
1300    },
1301
1302    get depth()
1303    {
1304        if ("_depth" in this)
1305            return this._depth;
1306        if (this.parent && !this.parent._isRoot)
1307            this._depth = this.parent.depth + 1;
1308        else
1309            this._depth = 0;
1310        return this._depth;
1311    },
1312
1313    get leftPadding()
1314    {
1315        if (typeof this._leftPadding === "number")
1316            return this._leftPadding;
1317
1318        this._leftPadding = this.depth * this.dataGrid.indentWidth;
1319        return this._leftPadding;
1320    },
1321
1322    get shouldRefreshChildren()
1323    {
1324        return this._shouldRefreshChildren;
1325    },
1326
1327    set shouldRefreshChildren(x)
1328    {
1329        this._shouldRefreshChildren = x;
1330        if (x && this.expanded)
1331            this.expand();
1332    },
1333
1334    get selected()
1335    {
1336        return this._selected;
1337    },
1338
1339    set selected(x)
1340    {
1341        if (x)
1342            this.select();
1343        else
1344            this.deselect();
1345    },
1346
1347    get expanded()
1348    {
1349        return this._expanded;
1350    },
1351
1352    /**
1353     * @param {boolean} x
1354     */
1355    set expanded(x)
1356    {
1357        if (x)
1358            this.expand();
1359        else
1360            this.collapse();
1361    },
1362
1363    refresh: function()
1364    {
1365        if (!this._element || !this.dataGrid)
1366            return;
1367
1368        this._element.removeChildren();
1369        this.createCells();
1370        this._element.createChild("td", "corner");
1371    },
1372
1373    /**
1374     * @param {string} columnIdentifier
1375     * @return {!Element}
1376     */
1377    createTD: function(columnIdentifier)
1378    {
1379        var cell = document.createElement("td");
1380        cell.className = columnIdentifier + "-column";
1381        cell.columnIdentifier_ = columnIdentifier;
1382
1383        var alignment = this.dataGrid._columns[columnIdentifier].align;
1384        if (alignment)
1385            cell.classList.add(alignment);
1386
1387        return cell;
1388    },
1389
1390    /**
1391     * @param {string} columnIdentifier
1392     * @return {!Element}
1393     */
1394    createCell: function(columnIdentifier)
1395    {
1396        var cell = this.createTD(columnIdentifier);
1397
1398        var data = this.data[columnIdentifier];
1399        if (data instanceof Node) {
1400            cell.appendChild(data);
1401        } else {
1402            cell.textContent = data;
1403            if (this.dataGrid._columns[columnIdentifier].longText)
1404                cell.title = data;
1405        }
1406
1407        if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
1408            cell.classList.add("disclosure");
1409            if (this.leftPadding)
1410                cell.style.setProperty("padding-left", this.leftPadding + "px");
1411        }
1412
1413        return cell;
1414    },
1415
1416    /**
1417     * @return {number}
1418     */
1419    nodeSelfHeight: function()
1420    {
1421        return 16;
1422    },
1423
1424    /**
1425     * @param {!WebInspector.DataGridNode} child
1426     */
1427    appendChild: function(child)
1428    {
1429        this.insertChild(child, this.children.length);
1430    },
1431
1432    /**
1433     * @param {!WebInspector.DataGridNode} child
1434     * @param {number} index
1435     */
1436    insertChild: function(child, index)
1437    {
1438        if (!child)
1439            throw("insertChild: Node can't be undefined or null.");
1440        if (child.parent === this)
1441            throw("insertChild: Node is already a child of this node.");
1442
1443        if (child.parent)
1444            child.parent.removeChild(child);
1445
1446        this.children.splice(index, 0, child);
1447        this.hasChildren = true;
1448
1449        child.parent = this;
1450        child.dataGrid = this.dataGrid;
1451        child._recalculateSiblings(index);
1452
1453        delete child._depth;
1454        delete child._revealed;
1455        delete child._attached;
1456        child._shouldRefreshChildren = true;
1457
1458        var current = child.children[0];
1459        while (current) {
1460            current.dataGrid = this.dataGrid;
1461            delete current._depth;
1462            delete current._revealed;
1463            delete current._attached;
1464            current._shouldRefreshChildren = true;
1465            current = current.traverseNextNode(false, child, true);
1466        }
1467
1468        if (this.expanded)
1469            child._attach();
1470        if (!this.revealed)
1471            child.revealed = false;
1472    },
1473
1474    /**
1475     * @param {!WebInspector.DataGridNode} child
1476     */
1477    removeChild: function(child)
1478    {
1479        if (!child)
1480            throw("removeChild: Node can't be undefined or null.");
1481        if (child.parent !== this)
1482            throw("removeChild: Node is not a child of this node.");
1483
1484        child.deselect();
1485        child._detach();
1486
1487        this.children.remove(child, true);
1488
1489        if (child.previousSibling)
1490            child.previousSibling.nextSibling = child.nextSibling;
1491        if (child.nextSibling)
1492            child.nextSibling.previousSibling = child.previousSibling;
1493
1494        child.dataGrid = null;
1495        child.parent = null;
1496        child.nextSibling = null;
1497        child.previousSibling = null;
1498
1499        if (this.children.length <= 0)
1500            this.hasChildren = false;
1501    },
1502
1503    removeChildren: function()
1504    {
1505        for (var i = 0; i < this.children.length; ++i) {
1506            var child = this.children[i];
1507            child.deselect();
1508            child._detach();
1509
1510            child.dataGrid = null;
1511            child.parent = null;
1512            child.nextSibling = null;
1513            child.previousSibling = null;
1514        }
1515
1516        this.children = [];
1517        this.hasChildren = false;
1518    },
1519
1520    /**
1521     * @param {number} myIndex
1522     */
1523    _recalculateSiblings: function(myIndex)
1524    {
1525        if (!this.parent)
1526            return;
1527
1528        var previousChild = this.parent.children[myIndex - 1] || null;
1529        if (previousChild)
1530            previousChild.nextSibling = this;
1531        this.previousSibling = previousChild;
1532
1533        var nextChild = this.parent.children[myIndex + 1] || null;
1534        if (nextChild)
1535            nextChild.previousSibling = this;
1536        this.nextSibling = nextChild;
1537    },
1538
1539    collapse: function()
1540    {
1541        if (this._isRoot)
1542            return;
1543        if (this._element)
1544            this._element.classList.remove("expanded");
1545
1546        this._expanded = false;
1547
1548        for (var i = 0; i < this.children.length; ++i)
1549            this.children[i].revealed = false;
1550    },
1551
1552    collapseRecursively: function()
1553    {
1554        var item = this;
1555        while (item) {
1556            if (item.expanded)
1557                item.collapse();
1558            item = item.traverseNextNode(false, this, true);
1559        }
1560    },
1561
1562    populate: function() { },
1563
1564    expand: function()
1565    {
1566        if (!this.hasChildren || this.expanded)
1567            return;
1568        if (this._isRoot)
1569            return;
1570
1571        if (this.revealed && !this._shouldRefreshChildren)
1572            for (var i = 0; i < this.children.length; ++i)
1573                this.children[i].revealed = true;
1574
1575        if (this._shouldRefreshChildren) {
1576            for (var i = 0; i < this.children.length; ++i)
1577                this.children[i]._detach();
1578
1579            this.populate();
1580
1581            if (this._attached) {
1582                for (var i = 0; i < this.children.length; ++i) {
1583                    var child = this.children[i];
1584                    if (this.revealed)
1585                        child.revealed = true;
1586                    child._attach();
1587                }
1588            }
1589
1590            delete this._shouldRefreshChildren;
1591        }
1592
1593        if (this._element)
1594            this._element.classList.add("expanded");
1595
1596        this._expanded = true;
1597    },
1598
1599    expandRecursively: function()
1600    {
1601        var item = this;
1602        while (item) {
1603            item.expand();
1604            item = item.traverseNextNode(false, this);
1605        }
1606    },
1607
1608    reveal: function()
1609    {
1610        if (this._isRoot)
1611            return;
1612        var currentAncestor = this.parent;
1613        while (currentAncestor && !currentAncestor._isRoot) {
1614            if (!currentAncestor.expanded)
1615                currentAncestor.expand();
1616            currentAncestor = currentAncestor.parent;
1617        }
1618
1619        this.element.scrollIntoViewIfNeeded(false);
1620    },
1621
1622    /**
1623     * @param {boolean=} supressSelectedEvent
1624     */
1625    select: function(supressSelectedEvent)
1626    {
1627        if (!this.dataGrid || !this.selectable || this.selected)
1628            return;
1629
1630        if (this.dataGrid.selectedNode)
1631            this.dataGrid.selectedNode.deselect();
1632
1633        this._selected = true;
1634        this.dataGrid.selectedNode = this;
1635
1636        if (this._element)
1637            this._element.classList.add("selected");
1638
1639        if (!supressSelectedEvent)
1640            this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Events.SelectedNode);
1641    },
1642
1643    revealAndSelect: function()
1644    {
1645        if (this._isRoot)
1646            return;
1647        this.reveal();
1648        this.select();
1649    },
1650
1651    /**
1652     * @param {boolean=} supressDeselectedEvent
1653     */
1654    deselect: function(supressDeselectedEvent)
1655    {
1656        if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
1657            return;
1658
1659        this._selected = false;
1660        this.dataGrid.selectedNode = null;
1661
1662        if (this._element)
1663            this._element.classList.remove("selected");
1664
1665        if (!supressDeselectedEvent)
1666            this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Events.DeselectedNode);
1667    },
1668
1669    /**
1670     * @param {boolean} skipHidden
1671     * @param {?WebInspector.DataGridNode=} stayWithin
1672     * @param {boolean=} dontPopulate
1673     * @param {!Object=} info
1674     * @return {?WebInspector.DataGridNode}
1675     */
1676    traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
1677    {
1678        if (!dontPopulate && this.hasChildren)
1679            this.populate();
1680
1681        if (info)
1682            info.depthChange = 0;
1683
1684        var node = (!skipHidden || this.revealed) ? this.children[0] : null;
1685        if (node && (!skipHidden || this.expanded)) {
1686            if (info)
1687                info.depthChange = 1;
1688            return node;
1689        }
1690
1691        if (this === stayWithin)
1692            return null;
1693
1694        node = (!skipHidden || this.revealed) ? this.nextSibling : null;
1695        if (node)
1696            return node;
1697
1698        node = this;
1699        while (node && !node._isRoot && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
1700            if (info)
1701                info.depthChange -= 1;
1702            node = node.parent;
1703        }
1704
1705        if (!node)
1706            return null;
1707
1708        return (!skipHidden || node.revealed) ? node.nextSibling : null;
1709    },
1710
1711    /**
1712     * @param {boolean} skipHidden
1713     * @param {boolean=} dontPopulate
1714     * @return {?WebInspector.DataGridNode}
1715     */
1716    traversePreviousNode: function(skipHidden, dontPopulate)
1717    {
1718        var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
1719        if (!dontPopulate && node && node.hasChildren)
1720            node.populate();
1721
1722        while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
1723            if (!dontPopulate && node.hasChildren)
1724                node.populate();
1725            node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
1726        }
1727
1728        if (node)
1729            return node;
1730
1731        if (!this.parent || this.parent._isRoot)
1732            return null;
1733
1734        return this.parent;
1735    },
1736
1737    /**
1738     * @return {boolean}
1739     */
1740    isEventWithinDisclosureTriangle: function(event)
1741    {
1742        if (!this.hasChildren)
1743            return false;
1744        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
1745        if (!cell.classList.contains("disclosure"))
1746            return false;
1747
1748        var left = cell.totalOffsetLeft() + this.leftPadding;
1749        return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
1750    },
1751
1752    _attach: function()
1753    {
1754        if (!this.dataGrid || this._attached)
1755            return;
1756
1757        this._attached = true;
1758
1759        var nextNode = null;
1760        var previousNode = this.traversePreviousNode(true, true);
1761        if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
1762            nextNode = previousNode.element.nextSibling;
1763        if (!nextNode)
1764            nextNode = this.dataGrid.defaultAttachLocation();
1765        this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);
1766
1767        if (this.expanded)
1768            for (var i = 0; i < this.children.length; ++i)
1769                this.children[i]._attach();
1770    },
1771
1772    _detach: function()
1773    {
1774        if (!this._attached)
1775            return;
1776
1777        this._attached = false;
1778
1779        if (this._element)
1780            this._element.remove();
1781
1782        for (var i = 0; i < this.children.length; ++i)
1783            this.children[i]._detach();
1784
1785        this.wasDetached();
1786    },
1787
1788    wasDetached: function()
1789    {
1790    },
1791
1792    savePosition: function()
1793    {
1794        if (this._savedPosition)
1795            return;
1796
1797        if (!this.parent)
1798            throw("savePosition: Node must have a parent.");
1799        this._savedPosition = {
1800            parent: this.parent,
1801            index: this.parent.children.indexOf(this)
1802        };
1803    },
1804
1805    restorePosition: function()
1806    {
1807        if (!this._savedPosition)
1808            return;
1809
1810        if (this.parent !== this._savedPosition.parent)
1811            this._savedPosition.parent.insertChild(this, this._savedPosition.index);
1812
1813        delete this._savedPosition;
1814    },
1815
1816    __proto__: WebInspector.Object.prototype
1817}
1818
1819/**
1820 * @constructor
1821 * @extends {WebInspector.DataGridNode}
1822 */
1823WebInspector.CreationDataGridNode = function(data, hasChildren)
1824{
1825    WebInspector.DataGridNode.call(this, data, hasChildren);
1826    this.isCreationNode = true;
1827}
1828
1829WebInspector.CreationDataGridNode.prototype = {
1830    makeNormal: function()
1831    {
1832        delete this.isCreationNode;
1833        delete this.makeNormal;
1834    },
1835
1836    __proto__: WebInspector.DataGridNode.prototype
1837}
1838