Home | History | Annotate | Download | only in front_end
      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";
     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}} */
    143 WebInspector.DataGrid.ColumnDescriptor;
    144 
    145 WebInspector.DataGrid.Events = {
    146     SelectedNode: "SelectedNode",
    147     DeselectedNode: "DeselectedNode",
    148     SortingChanged: "SortingChanged",
    149     ColumnsResized: "ColumnsResized"
    150 }
    151 
    152 /** @enum {string} */
    153 WebInspector.DataGrid.Order = {
    154     Ascending: "ascending",
    155     Descending: "descending"
    156 }
    157 
    158 /** @enum {string} */
    159 WebInspector.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  */
    169 WebInspector.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 
    240 WebInspector.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 
   1122 WebInspector.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  */
   1134 WebInspector.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 
   1154 WebInspector.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  */
   1796 WebInspector.CreationDataGridNode = function(data, hasChildren)
   1797 {
   1798     WebInspector.DataGridNode.call(this, data, hasChildren);
   1799     this.isCreationNode = true;
   1800 }
   1801 
   1802 WebInspector.CreationDataGridNode.prototype = {
   1803     makeNormal: function()
   1804     {
   1805         delete this.isCreationNode;
   1806         delete this.makeNormal;
   1807     },
   1808 
   1809     __proto__: WebInspector.DataGridNode.prototype
   1810 }
   1811