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