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