Home | History | Annotate | Download | only in ui
      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  */
     35 WebInspector.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.
    134 WebInspector.DataGrid.CornerWidth = 14;
    135 
    136 /** @typedef {!{id: ?string, editable: boolean, longText: ?boolean, sort: !WebInspector.DataGrid.Order, sortable: boolean, align: !WebInspector.DataGrid.Align}} */
    137 WebInspector.DataGrid.ColumnDescriptor;
    138 
    139 WebInspector.DataGrid.Events = {
    140     SelectedNode: "SelectedNode",
    141     DeselectedNode: "DeselectedNode",
    142     SortingChanged: "SortingChanged",
    143     ColumnsResized: "ColumnsResized"
    144 }
    145 
    146 /** @enum {string} */
    147 WebInspector.DataGrid.Order = {
    148     Ascending: "ascending",
    149     Descending: "descending"
    150 }
    151 
    152 /** @enum {string} */
    153 WebInspector.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  */
    163 WebInspector.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 
    234 WebInspector.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 
   1165 WebInspector.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  */
   1177 WebInspector.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 
   1197 WebInspector.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  */
   1823 WebInspector.CreationDataGridNode = function(data, hasChildren)
   1824 {
   1825     WebInspector.DataGridNode.call(this, data, hasChildren);
   1826     this.isCreationNode = true;
   1827 }
   1828 
   1829 WebInspector.CreationDataGridNode.prototype = {
   1830     makeNormal: function()
   1831     {
   1832         delete this.isCreationNode;
   1833         delete this.makeNormal;
   1834     },
   1835 
   1836     __proto__: WebInspector.DataGridNode.prototype
   1837 }
   1838