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