Web Inspector: Clean up DataGrid and add table columns incrementally
authorbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Feb 2014 19:49:41 +0000 (19:49 +0000)
committerbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Feb 2014 19:49:41 +0000 (19:49 +0000)
https://bugs.webkit.org/show_bug.cgi?id=128332

Reviewed by Timothy Hatcher.

Miscellaneous improvements to DataGrid to make it more maintainable.
In particular, it now uses a `Map` to store column settings, and the
construction sequence has been simplified so that it is safe to add
columns at the end of the constructor (or any later time).

DataGrid stores a bunch of settings per-column, but before this was done
by both storing properties on the provided `column` objects as well as
keeping several `columnIdentifier`-keyed maps for properties like `group`
and `hidden`.

Improve code readability by using for..of loops, destructuring assignment,
`Node.createElement`, and updating variable names. All variables that refer
to DOM nodes now have a `Element` suffix to distinguish them from model objects.

* UserInterface/DataGrid.js:
(.sortDataGrid):
(WebInpector.DataGrid): Create all DOM elements before populating columns.
Remove redundant objects for storing per-column values. Copy column settings
into a map rather than mutating the passed-in JSON-like settings data structure.

(WebInspector.DataGrid.createSortableDataGrid):
(WebInspector.DataGrid.prototype.get length):
(WebInspector.DataGrid.prototype.updateLayout):
(WebInspector.DataGrid.prototype.get scrollContainer):
(WebInspector.DataGrid.prototype.isScrolledToLastRow):
(WebInspector.DataGrid.prototype.scrollToLastRow):
(WebInspector.DataGrid.prototype._positionResizerElements):
(WebInspector.DataGrid.prototype.addCreationNode):
(WebInspector.DataGrid.prototype.):
(WebInspector.DataGrid.prototype.sortNodes):
(WebInspector.DataGrid.prototype.dataGridNodeFromPoint):
(WebInspector.DataGrid.prototype._clickInHeaderCell):
(WebInspector.DataGrid.prototype.isColumnSortColumn):
(WebInspector.DataGrid.prototype.headerTableHeader):
(WebInspector.DataGrid.prototype._copyTextForDataGridNode):
(WebInspector.DataGrid.prototype._resizerDragging):
(WebInspector.DataGridNode.prototype.createCells):
(WebInspector.DataGridNode.prototype.createCell.get var):
(WebInspector.DataGridNode.prototype.elementWithColumnIdentifier):
(WebInspector.DataGridNode.prototype._attach):
* UserInterface/TimelineDataGrid.js:
(WebInspector.TimelineDataGrid.prototype.treeElementMatchesActiveScopeFilters.scopeBar.this.columns.get scopeBar):
(WebInspector.TimelineDataGrid.prototype.treeElementMatchesActiveScopeFilters):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@163637 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/DataGrid.js
Source/WebInspectorUI/UserInterface/TimelineDataGrid.js

index d9c5785..921b062 100644 (file)
@@ -1,3 +1,54 @@
+2014-02-07  Brian Burg  <bburg@apple.com>
+
+        Web Inspector: Clean up DataGrid and add table columns incrementally
+        https://bugs.webkit.org/show_bug.cgi?id=128332
+
+        Reviewed by Timothy Hatcher.
+
+        Miscellaneous improvements to DataGrid to make it more maintainable.
+        In particular, it now uses a `Map` to store column settings, and the
+        construction sequence has been simplified so that it is safe to add
+        columns at the end of the constructor (or any later time).
+
+        DataGrid stores a bunch of settings per-column, but before this was done
+        by both storing properties on the provided `column` objects as well as
+        keeping several `columnIdentifier`-keyed maps for properties like `group`
+        and `hidden`.
+
+        Improve code readability by using for..of loops, destructuring assignment,
+        `Node.createElement`, and updating variable names. All variables that refer
+        to DOM nodes now have a `Element` suffix to distinguish them from model objects.
+
+        * UserInterface/DataGrid.js:
+        (.sortDataGrid):
+        (WebInpector.DataGrid): Create all DOM elements before populating columns.
+        Remove redundant objects for storing per-column values. Copy column settings
+        into a map rather than mutating the passed-in JSON-like settings data structure.
+
+        (WebInspector.DataGrid.createSortableDataGrid):
+        (WebInspector.DataGrid.prototype.get length):
+        (WebInspector.DataGrid.prototype.updateLayout):
+        (WebInspector.DataGrid.prototype.get scrollContainer):
+        (WebInspector.DataGrid.prototype.isScrolledToLastRow):
+        (WebInspector.DataGrid.prototype.scrollToLastRow):
+        (WebInspector.DataGrid.prototype._positionResizerElements):
+        (WebInspector.DataGrid.prototype.addCreationNode):
+        (WebInspector.DataGrid.prototype.):
+        (WebInspector.DataGrid.prototype.sortNodes):
+        (WebInspector.DataGrid.prototype.dataGridNodeFromPoint):
+        (WebInspector.DataGrid.prototype._clickInHeaderCell):
+        (WebInspector.DataGrid.prototype.isColumnSortColumn):
+        (WebInspector.DataGrid.prototype.headerTableHeader):
+        (WebInspector.DataGrid.prototype._copyTextForDataGridNode):
+        (WebInspector.DataGrid.prototype._resizerDragging):
+        (WebInspector.DataGridNode.prototype.createCells):
+        (WebInspector.DataGridNode.prototype.createCell.get var):
+        (WebInspector.DataGridNode.prototype.elementWithColumnIdentifier):
+        (WebInspector.DataGridNode.prototype._attach):
+        * UserInterface/TimelineDataGrid.js:
+        (WebInspector.TimelineDataGrid.prototype.treeElementMatchesActiveScopeFilters.scopeBar.this.columns.get scopeBar):
+        (WebInspector.TimelineDataGrid.prototype.treeElementMatchesActiveScopeFilters):
+
 2014-02-07  Mihai Tica  <mitica@adobe.com>
 
         [CSS Background Blending] Unprefix the -webkit-background-blend-mode property
index ac52175..d04951f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2013 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2008, 2013, 2014 Apple Inc. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-/**
- * @constructor
- * @extends {WebInspector.Object}
- * @param {function(WebInspector.DataGridNode, number, string, string)=} editCallback
- * @param {function(WebInspector.DataGridNode)=} deleteCallback
- */
-WebInspector.DataGrid = function(columns, editCallback, deleteCallback)
+WebInspector.DataGrid = function(columnsData, editCallback, deleteCallback)
 {
+    this.columns = new Map;
+    this.orderedColumns = [];
+
+    for (var columnIdentifier in columnsData) {
+        columnData = new Map;
+        for (var propertyName in columnsData[columnIdentifier])
+            columnData.set(propertyName, columnsData[columnIdentifier][propertyName]);
+
+        columnData.set("ordinal", this.orderedColumns.length);
+        columnData.set("columnIdentifier", columnIdentifier);
+        this.orderedColumns.push(columnIdentifier);
+        this.columns.set(columnIdentifier, columnData);
+    }
+
+    this.children = [];
+    this.selectedNode = null;
+    this.expandNodesWhenArrowing = false;
+    this.root = true;
+    this.hasChildren = false;
+    this.expanded = true;
+    this.revealed = true;
+    this.selected = false;
+    this.dataGrid = this;
+    this.indentWidth = 15;
+    this.resizerElements = [];
+    this._columnWidthsInitialized = false;
+
     this.element = document.createElement("div");
     this.element.className = "data-grid";
     this.element.tabIndex = 0;
     this.element.addEventListener("keydown", this._keyDown.bind(this), false);
     this.element.copyHandler = this;
 
-    this._headerTable = document.createElement("table");
-    this._headerTable.className = "header";
-    this._headerTableHeaders = {};
+    this._headerTableElement = document.createElement("table");
+    this._headerTableElement.className = "header";
+    this._headerTableColumnGroupElement = this._headerTableElement.createChild("colgroup");
+    this._headerTableBodyElement = this._headerTableElement.createChild("tbody");
+    this._headerTableRowElement = this._headerTableBodyElement.createChild("tr");
+
+    this._headerTableCellElements = new Map;
 
-    this._dataTable = document.createElement("table");
-    this._dataTable.className = "data";
+    this._scrollContainerElement = document.createElement("div");
+    this._scrollContainerElement.className = "data-container";
 
-    this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this));
-    this._dataTable.addEventListener("click", this._clickInDataTable.bind(this));
+    this._dataTableElement = this._scrollContainerElement.createChild("table");
+    this._dataTableElement.className = "data";
 
-    this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
+    this._dataTableElement.addEventListener("mousedown", this._mouseDownInDataTable.bind(this));
+    this._dataTableElement.addEventListener("click", this._clickInDataTable.bind(this));
+    this._dataTableElement.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
 
     // FIXME: Add a createCallback which is different from editCallback and has different
     // behavior when creating a new node.
     if (editCallback) {
-        this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
+        this._dataTableElement.addEventListener("dblclick", this._ondblclick.bind(this), false);
         this._editCallback = editCallback;
     }
     if (deleteCallback)
         this._deleteCallback = deleteCallback;
 
-    this.aligned = {};
-    this.groups = {};
-    this._hiddenColumns = {};
-
-    this._scrollContainer = document.createElement("div");
-    this._scrollContainer.className = "data-container";
-    this._scrollContainer.appendChild(this._dataTable);
-
-    this.element.appendChild(this._headerTable);
-    this.element.appendChild(this._scrollContainer);
-
-    var headerRow = document.createElement("tr");
-    var columnGroup = document.createElement("colgroup");
-    this._columnCount = 0;
+    this._dataTableColumnGroupElement = this._headerTableColumnGroupElement.cloneNode(true);
+    this._dataTableElement.appendChild(this._dataTableColumnGroupElement);
 
-    for (var columnIdentifier in columns) {
-        var column = columns[columnIdentifier];
-        if (column.disclosure)
-            this.disclosureColumnIdentifier = columnIdentifier;
+    // This element is used by DataGridNodes to manipulate table rows and cells.
+    this.dataTableBodyElement = this._dataTableElement.createChild("tbody");
+    this._fillerRowElement = this.dataTableBodyElement.createChild("tr");
+    this._fillerRowElement.className = "filler";
 
-        var col = document.createElement("col");
-        if (column.width)
-            col.style.width = column.width;
-        column.element = col;
-        columnGroup.appendChild(col);
-
-        var cell = document.createElement("th");
-        cell.className = columnIdentifier + "-column";
-        cell.columnIdentifier = columnIdentifier;
-        if (column.aligned)
-            cell.classList.add(column.aligned);
-        this._headerTableHeaders[columnIdentifier] = cell;
-
-        var div = document.createElement("div");
-        if (column.titleDOMFragment)
-            div.appendChild(column.titleDOMFragment);
-        else
-            div.textContent = column.title || "";
-        cell.appendChild(div);
+    this.element.appendChild(this._headerTableElement);
+    this.element.appendChild(this._scrollContainerElement);
 
-        if (column.sort) {
-            cell.classList.add("sort-" + column.sort);
-            this._sortColumnCell = cell;
-        }
-
-        if (column.sortable) {
-            cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
-            cell.classList.add("sortable");
-        }
-
-        if (column.aligned)
-            this.aligned[columnIdentifier] = column.aligned;
-
-        if (column.group) {
-            this.groups[columnIdentifier] = column.group;
-            cell.classList.add("column-group-" + column.group);
-        }
-
-        if (column.collapsesGroup) {
-            console.assert(column.group !== column.collapsesGroup);
-
-            var divider = document.createElement("div");
-            divider.className = "divider";
-            cell.appendChild(divider);
-
-            var collapseDiv = document.createElement("div");
-            collapseDiv.className = "collapser-button";
-            collapseDiv.title = this._collapserButtonCollapseColumnsToolTip();
-            collapseDiv.addEventListener("mouseover", this._mouseoverColumnCollapser.bind(this));
-            collapseDiv.addEventListener("mouseout", this._mouseoutColumnCollapser.bind(this));
-            collapseDiv.addEventListener("click", this._clickInColumnCollapser.bind(this));
-            cell.appendChild(collapseDiv);
-
-            cell.collapsesGroup = column.collapsesGroup;
-            cell.classList.add("collapser");
-        }
-
-        headerRow.appendChild(cell);
-
-        ++this._columnCount;
-    }
-
-    columnGroup.span = this._columnCount;
-
-    this._headerTableColumnGroup = columnGroup;
-    this._headerTable.appendChild(this._headerTableColumnGroup);
-    this.headerTableBody.appendChild(headerRow);
-
-    var fillerRow = document.createElement("tr");
-    fillerRow.className = "filler";
-
-    for (var columnIdentifier in columns) {
-        var column = columns[columnIdentifier];
-        var td = document.createElement("td");
-        td.className = columnIdentifier + "-column";
-        td.__columnIdentifier = columnIdentifier;
-        var group = this.groups[columnIdentifier];
-        if (group)
-            td.classList.add("column-group-" + group);
-        fillerRow.appendChild(td);
-    }
-
-    this._dataTableColumnGroup = columnGroup.cloneNode(true);
-    this._dataTable.appendChild(this._dataTableColumnGroup);
-    this.dataTableBody.appendChild(fillerRow);
-
-    this.columns = columns || {};
-    this._columnsArray = [];
-
-    for (var columnIdentifier in columns) {
-        columns[columnIdentifier].ordinal = this._columnsArray.length;
-        columns[columnIdentifier].identifier = columnIdentifier;
-        this._columnsArray.push(columns[columnIdentifier]);
-    }
-
-    for (var i = 0; i < this._columnsArray.length; ++i)
-        this._columnsArray[i].bodyElement = this._dataTableColumnGroup.children[i];
-
-    this.children = [];
-    this.selectedNode = null;
-    this.expandNodesWhenArrowing = false;
-    this.root = true;
-    this.hasChildren = false;
-    this.expanded = true;
-    this.revealed = true;
-    this.selected = false;
-    this.dataGrid = this;
-    this.indentWidth = 15;
-    this.resizers = [];
-    this._columnWidthsInitialized = false;
-
-    for (var columnIdentifier in columns) {
-        if (columns[columnIdentifier].hidden)
-            this._hideColumn(columnIdentifier);
-    }
+    for (var columnIdentifier of this.orderedColumns)
+        this.addColumn(columnIdentifier);
 
     this._generateSortIndicatorImagesIfNeeded();
 }
@@ -215,15 +120,15 @@ WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
     if (!numColumns)
         return null;
 
-    var columns = {};
+    var columnsData = {};
 
-    for (var i = 0; i < columnNames.length; ++i) {
+    for (var columnName of columnNames) {
         var column = {};
-        column.width = columnNames[i].length;
-        column.title = columnNames[i];
+        column.width = columnName.length;
+        column.title = columnName;
         column.sortable = true;
 
-        columns[columnNames[i]] = column;
+        columnsData[columnName] = column;
     }
 
     var nodes = [];
@@ -237,10 +142,9 @@ WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
         nodes.push(node);
     }
 
-    var dataGrid = new WebInspector.DataGrid(columns);
-    var length = nodes.length;
-    for (var i = 0; i < length; ++i)
-        dataGrid.appendChild(nodes[i]);
+    var dataGrid = new WebInspector.DataGrid(columnsData);
+    for (var node of nodes)
+        dataGrid.appendChild(node);
 
     dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, sortDataGrid, this);
 
@@ -251,8 +155,8 @@ WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
         var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1;
         var columnIsNumeric = true;
 
-        for (var i = 0; i < nodes.length; i++) {
-            if (isNaN(Number(nodes[i].data[sortColumnIdentifier] || "")))
+        for (var node of nodes) {
+            if (isNaN(Number(node.data[sortColumnIdentifier] || "")))
                 columnIsNumeric = false;
         }
 
@@ -275,8 +179,8 @@ WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
 
         nodes.sort(comparator);
         dataGrid.removeChildren();
-        for (var i = 0; i < nodes.length; i++)
-            dataGrid.appendChild(nodes[i]);
+        for (var node of nodes)
+            dataGrid.appendChild(node);
     }
     return dataGrid;
 }
@@ -423,63 +327,33 @@ WebInspector.DataGrid.prototype = {
         return null;
     },
 
-    get headerTableBody()
-    {
-        if ("_headerTableBody" in this)
-            return this._headerTableBody;
-
-        this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
-        if (!this._headerTableBody) {
-            this._headerTableBody = this.element.ownerDocument.createElement("tbody");
-            this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
-        }
-
-        return this._headerTableBody;
-    },
-
-    get dataTableBody()
-    {
-        if ("_dataTableBody" in this)
-            return this._dataTableBody;
-
-        this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
-        if (!this._dataTableBody) {
-            this._dataTableBody = this.element.ownerDocument.createElement("tbody");
-            this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
-        }
-
-        return this._dataTableBody;
-    },
-
-    /**
-     * @param {number=} maxDescentLevel
-     */
     autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel)
     {
         if (minPercent)
-            minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount));
+            minPercent = Math.min(minPercent, Math.floor(100 / this.orderedColumns.length));
         var widths = {};
-        var columns = this.columns;
-        for (var columnIdentifier in columns)
-            widths[columnIdentifier] = (columns[columnIdentifier].title || "").length;
+        // For the first width approximation, use the character length of column titles.
+        for (var [identifier, column] of this.columns)
+            widths[identifier] = column.get("title", "").length;
 
+        // Now approximate the width of each column as max(title, cells).
         var children = maxDescentLevel ? this._enumerateChildren(this, [], maxDescentLevel + 1) : this.children;
-        for (var i = 0; i < children.length; ++i) {
-            var node = children[i];
-            for (var columnIdentifier in columns) {
-                var text = node.data[columnIdentifier] || "";
-                if (text.length > widths[columnIdentifier])
-                    widths[columnIdentifier] = text.length;
+        for (var node of children) {
+            for (var identifier of this.columns.keys()) {
+                var text = node.data[identifier] || "";
+                if (text.length > widths[identifier])
+                    widths[identifier] = text.length;
             }
         }
 
         var totalColumnWidths = 0;
-        for (var columnIdentifier in columns)
-            totalColumnWidths += widths[columnIdentifier];
+        for (var identifier of this.columns.keys())
+            totalColumnWidths += widths[identifier];
 
+        // Compute percentages and clamp desired widths to min and max widths.
         var recoupPercent = 0;
-        for (var columnIdentifier in columns) {
-            var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths);
+        for (var identifier of this.columns.keys()) {
+            var width = Math.round(100 * widths[identifier] / totalColumnWidths);
             if (minPercent && width < minPercent) {
                 recoupPercent += (minPercent - width);
                 width = minPercent;
@@ -487,13 +361,14 @@ WebInspector.DataGrid.prototype = {
                 recoupPercent -= (width - maxPercent);
                 width = maxPercent;
             }
-            widths[columnIdentifier] = width;
+            widths[identifier] = width;
         }
 
+        // If we assigned too much width due to the above, reduce column widths.
         while (minPercent && recoupPercent > 0) {
-            for (var columnIdentifier in columns) {
-                if (widths[columnIdentifier] > minPercent) {
-                    --widths[columnIdentifier];
+            for (var identifier of this.columns.keys()) {
+                if (widths[identifier] > minPercent) {
+                    --widths[identifier];
                     --recoupPercent;
                     if (!recoupPercent)
                         break;
@@ -501,10 +376,11 @@ WebInspector.DataGrid.prototype = {
             }
         }
 
+        // If extra width remains after clamping widths, expand column widths.
         while (maxPercent && recoupPercent < 0) {
-            for (var columnIdentifier in columns) {
-                if (widths[columnIdentifier] < maxPercent) {
-                    ++widths[columnIdentifier];
+            for (var identifier of this.columns.keys()) {
+                if (widths[identifier] < maxPercent) {
+                    ++widths[identifier];
                     ++recoupPercent;
                     if (!recoupPercent)
                         break;
@@ -512,12 +388,84 @@ WebInspector.DataGrid.prototype = {
             }
         }
 
-        for (var columnIdentifier in columns)
-            columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%";
+        for (var [identifier, column] of this.columns)
+            column.get("element").style.width = widths[identifier] + "%";
         this._columnWidthsInitialized = false;
         this.updateLayout();
     },
 
+    addColumn: function(columnIdentifier)
+    {
+        var column = this.columns.get(columnIdentifier);
+        console.assert(column);
+
+        if (column.has("disclosure"))
+            this.disclosureColumnIdentifier = columnIdentifier;
+
+        var headerColumnElement = this._headerTableColumnGroupElement.createChild("col");
+        if (column.has("width"))
+            headerColumnElement.style.width = column.get("width");
+        column.set("element", headerColumnElement);
+
+        var headerCellElement = this._headerTableRowElement.createChild("th");
+        headerCellElement.className = columnIdentifier + "-column";
+        headerCellElement.columnIdentifier = columnIdentifier;
+        if (column.has("aligned"))
+            headerCellElement.classList.add(column.get("aligned"));
+        this._headerTableCellElements.set(columnIdentifier, headerCellElement);
+
+        var div = headerCellElement.createChild("div");
+        if (column.has("titleDOMFragment"))
+            div.appendChild(column.get("titleDOMFragment"));
+        else
+            div.textContent = column.get("title", "");
+
+        if (column.has("sort")) {
+            headerCellElement.classList.add("sort-" + column.get("sort"));
+            this._sortColumnCell = headerCellElement;
+        }
+
+        if (column.has("sortable")) {
+            headerCellElement.addEventListener("click", this._clickInHeaderCell.bind(this), false);
+            headerCellElement.classList.add("sortable");
+        }
+
+        if (column.has("group"))
+            headerCellElement.classList.add("column-group-" + column.get("group"));
+
+        if (column.has("collapsesGroup")) {
+            console.assert(column.get("group") !== column.get("collapsesGroup"));
+
+            var dividerElement = headerCellElement.createChild("div");
+            dividerElement.className = "divider";
+
+            var collapseDiv = headerCellElement.createChild("div");
+            collapseDiv.className = "collapser-button";
+            collapseDiv.title = this._collapserButtonCollapseColumnsToolTip();
+            collapseDiv.addEventListener("mouseover", this._mouseoverColumnCollapser.bind(this));
+            collapseDiv.addEventListener("mouseout", this._mouseoutColumnCollapser.bind(this));
+            collapseDiv.addEventListener("click", this._clickInColumnCollapser.bind(this));
+
+            headerCellElement.collapsesGroup = column.get("collapsesGroup");
+            headerCellElement.classList.add("collapser");
+        }
+
+        this._headerTableColumnGroupElement.span = this.orderedColumns.length;
+
+        var dataColumnElement = headerColumnElement.cloneNode();
+        this._dataTableColumnGroupElement.appendChild(dataColumnElement);
+        column.set("bodyElement", dataColumnElement);
+
+        var fillerCellElement = this._fillerRowElement.createChild("td");
+        fillerCellElement.className = columnIdentifier + "-column";
+        fillerCellElement.__columnIdentifier = columnIdentifier;
+        if (column.has("group"))
+            fillerCellElement.classList.add("column-group-" + column.get("group"));
+
+        if (column.has("hidden"))
+            this._hideColumn(columnIdentifier);
+    },
+
     _enumerateChildren: function(rootNode, result, maxLevel)
     {
         if (!rootNode.root)
@@ -547,35 +495,34 @@ WebInspector.DataGrid.prototype = {
             // when the two columns that get resized get a percent value for
             // their widths, all the other columns already have percent values
             // for their widths.
-            var headerTableColumns = this._headerTableColumnGroup.children;
-            var tableWidth = this._dataTable.offsetWidth;
-            var numColumns = headerTableColumns.length;
+            var headerTableColumnElements = this._headerTableColumnGroupElement.children;
+            var tableWidth = this._dataTableElement.offsetWidth;
+            var numColumns = headerTableColumnElements.length;
             for (var i = 0; i < numColumns; i++) {
-                var headerCell = this.headerTableBody.rows[0].cells[i]
-                if (this._isColumnVisible(headerCell.columnIdentifier)) {
-                    var columnWidth = headerCell.offsetWidth;
+                var headerCellElement = this._headerTableBodyElement.rows[0].cells[i]
+                if (this._isColumnVisible(headerCellElement.columnIdentifier)) {
+                    var columnWidth = headerCellElement.offsetWidth;
                     var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
-                    this._headerTableColumnGroup.children[i].style.width = percentWidth;
-                    this._dataTableColumnGroup.children[i].style.width = percentWidth;
+                    this._headerTableColumnGroupElement.children[i].style.width = percentWidth;
+                    this._dataTableColumnGroupElement.children[i].style.width = percentWidth;
                 } else {
-                    this._headerTableColumnGroup.children[i].style.width = 0;
-                    this._dataTableColumnGroup.children[i].style.width = 0;
+                    this._headerTableColumnGroupElement.children[i].style.width = 0;
+                    this._dataTableColumnGroupElement.children[i].style.width = 0;
                 }
             }
 
             this._columnWidthsInitialized = true;
         }
 
-        this._positionResizers();
+        this._positionResizerElements();
         this.dispatchEventToListeners(WebInspector.DataGrid.Event.DidLayout);
     },
 
     columnWidthsMap: function()
     {
         var result = {};
-        for (var columnIdentifier in this.columns) {
-            var column = this.columns[columnIdentifier];
-            var width = this._headerTableColumnGroup.children[column.ordinal].style.width;
+        for (var [identifier, column] of this.columns) {
+            var width = this._headerTableColumnGroupElement.children[column.get("ordinal")].style.width;
             result[columnIdentifier] = parseFloat(width);
         }
         return result;
@@ -583,11 +530,11 @@ WebInspector.DataGrid.prototype = {
 
     applyColumnWidthsMap: function(columnWidthsMap)
     {
-        for (var columnIdentifier in this.columns) {
-            var column = this.columns[columnIdentifier];
-            var width = (columnWidthsMap[columnIdentifier] || 0) + "%";
-            this._headerTableColumnGroup.children[column.ordinal].style.width = width;
-            this._dataTableColumnGroup.children[column.ordinal].style.width = width;
+        for (var [identifier, column] of this.columns) {
+            var width = (columnWidthsMap[identifier] || 0) + "%";
+            var ordinal = column.get("ordinal");
+            this._headerTableColumnGroupElement.children[ordinal].style.width = width;
+            this._dataTableColumnGroupElement.children[ordinal].style.width = width;
         }
 
         this.updateLayout();
@@ -595,86 +542,80 @@ WebInspector.DataGrid.prototype = {
 
     _isColumnVisible: function(columnIdentifier)
     {
-        return !(columnIdentifier in this._hiddenColumns);
+        return !this.columns.get(columnIdentifier).has("hidden");
     },
 
     _showColumn: function(columnIdentifier)
     {
-        delete this._hiddenColumns[columnIdentifier];
+        this.columns.get(columnIdentifier).delete("hidden");
     },
 
     _hideColumn: function(columnIdentifier)
     {
-        this._hiddenColumns[columnIdentifier] = true;
+        var column = this.columns.get(columnIdentifier);
+        column.set("hidden", true);
 
-        var column = this.columns[columnIdentifier];
-        var columnElement = column.element;
+        var columnElement = column.get("element");
         columnElement.style.width = 0;
 
-        var columnBodyElement = column.bodyElement;
-        columnBodyElement.style.width = 0;
-
         this._columnWidthsInitialized = false;
     },
 
     get scrollContainer()
     {
-        return this._scrollContainer;
+        return this._scrollContainerElement;
     },
 
     isScrolledToLastRow: function()
     {
-        return this._scrollContainer.isScrolledToBottom();
+        return this._scrollContainerElement.isScrolledToBottom();
     },
 
     scrollToLastRow: function()
     {
-        this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight;
+        this._scrollContainerElement.scrollTop = this._scrollContainerElement.scrollHeight - this._scrollContainerElement.offsetHeight;
     },
 
-    _positionResizers: function()
+    _positionResizerElements: function()
     {
-        var headerTableColumns = this._headerTableColumnGroup.children;
-        var numColumns = headerTableColumns.length;
         var left = 0;
-        var previousResizer = null;
+        var previousResizerElement = null;
 
         // Make n - 1 resizers for n columns.
-        for (var i = 0; i < numColumns - 1; i++) {
-            var resizer = this.resizers[i];
+        for (var i = 0; i < this.orderedColumns.length - 1; ++i) {
+            var resizerElement = this.resizerElements[i];
 
-            if (!resizer) {
+            if (!resizerElement) {
                 // This is the first call to updateWidth, so the resizers need
                 // to be created.
-                resizer = document.createElement("div");
-                resizer.classList.add("data-grid-resizer");
+                resizerElement = document.createElement("div");
+                resizerElement.classList.add("data-grid-resizer");
                 // This resizer is associated with the column to its right.
-                resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
-                this.element.appendChild(resizer);
-                this.resizers[i] = resizer;
+                resizerElement.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
+                this.element.appendChild(resizerElement);
+                this.resizerElements[i] = resizerElement;
             }
 
             // Get the width of the cell in the first (and only) row of the
             // header table in order to determine the width of the column, since
             // it is not possible to query a column for its width.
-            left += this.headerTableBody.rows[0].cells[i].offsetWidth;
-
-            var columnIsVisible = this._isColumnVisible(this._columnsArray[i].identifier);
-            if (columnIsVisible) {
-                resizer.style.removeProperty("display");
-                resizer.style.left = left + "px";
-                resizer.leftNeighboringColumnID = i;
-                if (previousResizer)
-                    previousResizer.rightNeighboringColumnID = i;
-                previousResizer = resizer;
+            left += this._headerTableBodyElement.rows[0].cells[i].offsetWidth;
+
+            if (this._isColumnVisible(this.orderedColumns[i])) {
+                resizerElement.style.removeProperty("display");
+                resizerElement.style.left = left + "px";
+                resizerElement.leftNeighboringColumnID = i;
+                if (previousResizerElement)
+                    previousResizerElement.rightNeighboringColumnID = i;
+                previousResizerElement = resizerElement;
             } else {
-                resizer.style.setProperty("display", "none");
-                resizer.leftNeighboringColumnID = 0;
-                resizer.rightNeighboringColumnID = 0;
+                resizerElement.style.setProperty("display", "none");
+                resizerElement.leftNeighboringColumnID = 0;
+                resizerElement.rightNeighboringColumnID = 0;
             }
         }
-        if (previousResizer)
-            previousResizer.rightNeighboringColumnID = numColumns - 1;
+        if (previousResizerElement)
+            previousResizerElement.rightNeighboringColumnID = this.orderedColumns.length - 1;
     },
 
     addCreationNode: function(hasChildren)
@@ -683,8 +624,8 @@ WebInspector.DataGrid.prototype = {
             this.creationNode.makeNormal();
 
         var emptyData = {};
-        for (var column in this.columns)
-            emptyData[column] = '';
+        for (var identifier of this.columns.keys())
+            emptyData[identifier] = '';
         this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
         this.appendChild(this.creationNode);
     },
@@ -811,12 +752,12 @@ WebInspector.DataGrid.prototype = {
             if (b._dataGridNode._data.summaryRow)
                 return -1;
 
-            var aDataGirdNode = a._dataGridNode;
-            var bDataGirdNode = b._dataGridNode;
-            return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode);
+            var aDataGridNode = a._dataGridNode;
+            var bDataGridNode = b._dataGridNode;
+            return reverseMode ? comparator(bDataGridNode, aDataGridNode) : comparator(aDataGridNode, bDataGridNode);
         }
 
-        var tbody = this.dataTableBody;
+        var tbody = this.dataTableBodyElement;
         var tbodyParent = tbody.parentElement;
         tbodyParent.removeChild(tbody);
 
@@ -944,7 +885,7 @@ WebInspector.DataGrid.prototype = {
 
     dataGridNodeFromPoint: function(x, y)
     {
-        var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
+        var node = this._dataTableElement.ownerDocument.elementFromPoint(x, y);
         var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
         return rowElement && rowElement._dataGridNode;
     },
@@ -970,7 +911,7 @@ WebInspector.DataGrid.prototype = {
         this._sortColumnCell = cell;
 
         cell.classList.add("sort-" + sortOrder);
-    
+
         this.dispatchEventToListeners(WebInspector.DataGrid.Event.SortChanged);
     },
 
@@ -1006,20 +947,19 @@ WebInspector.DataGrid.prototype = {
 
     collapseColumnGroup: function(columnGroup)
     {
-        var collapserColumn = null;
-        for (var columnIdentifier in this.columns) {
-            var column = this.columns[columnIdentifier];
-            if (column.collapsesGroup == columnGroup) {
-                collapserColumn = column;
+        var collapserColumnIdentifier = null;
+        for (var [identifier, column] of this.columns) {
+            if (column.get("collapsesGroup") == columnGroup) {
+                collapserColumnIdentifier = identifier;
                 break;
             }
         }
 
-        console.assert(collapserColumn);
-        if (!collapserColumn)
+        console.assert(collapserColumnIdentifier);
+        if (!collapserColumnIdentifier)
             return;
 
-        var cell = this._headerTableHeaders[collapserColumn.identifier];
+        var cell = this._headerTableCellElements.get(collapserColumnIdentifier);
         this._collapseColumnGroupWithCell(cell);
     },
 
@@ -1030,10 +970,9 @@ WebInspector.DataGrid.prototype = {
         this.willToggleColumnGroup(cell.collapsesGroup, columnsWillCollapse);
 
         var showOrHide = columnsWillCollapse ? this._hideColumn : this._showColumn;
-        for (var columnIdentifier in this.columns) {
-            var column = this.columns[columnIdentifier];
-            if (column.group === cell.collapsesGroup)
-                showOrHide.call(this, columnIdentifier);
+        for (var [identifier, column] of this.columns) {
+            if (column.get("group") === cell.collapsesGroup)
+                showOrHide.call(this, identifier);
         }
 
         var collapserButton = cell.querySelector(".collapser-button");
@@ -1065,20 +1004,20 @@ WebInspector.DataGrid.prototype = {
 
     isColumnSortColumn: function(columnIdentifier)
     {
-        return this._sortColumnCell === this._headerTableHeaders[columnIdentifier];
+        return this._sortColumnCell === this._headerTableCellElements.get(columnIdentifier);
     },
 
     markColumnAsSortedBy: function(columnIdentifier, sortOrder)
     {
         if (this._sortColumnCell)
             this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
-        this._sortColumnCell = this._headerTableHeaders[columnIdentifier];
+        this._sortColumnCell = this._headerTableCellElements.get(columnIdentifier);
         this._sortColumnCell.classList.add("sort-" + sortOrder);
     },
 
     headerTableHeader: function(columnIdentifier)
     {
-        return this._headerTableHeaders[columnIdentifier];
+        return this._headerTableCellElements.get(columnIdentifier);
     },
 
     _generateSortIndicatorImagesIfNeeded: function()
@@ -1172,8 +1111,8 @@ WebInspector.DataGrid.prototype = {
     _copyTextForDataGridNode: function(node)
     {
         var fields = [];
-        for (var columnIdentifier in node.dataGrid.columns)
-            fields.push(node.data[columnIdentifier] || "");
+        for (var identifier of node.dataGrid.orderedColumns)
+            fields.push(node.data[identifier] || "");
 
         var tabSeparatedValues = fields.join("\t");
         return tabSeparatedValues;
@@ -1247,14 +1186,14 @@ WebInspector.DataGrid.prototype = {
         // column directly to the left and the column directly to the right.
         var leftCellIndex = resizer.leftNeighboringColumnID;
         var rightCellIndex = resizer.rightNeighboringColumnID;
-        var firstRowCells = this.headerTableBody.rows[0].cells;
+        var firstRowCells = this._headerTableBodyElement.rows[0].cells;
         var leftEdgeOfPreviousColumn = 0;
         for (var i = 0; i < leftCellIndex; i++)
             leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
 
         // Differences for other resize methods
         if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) {
-            rightCellIndex = this.resizers.length;
+            rightCellIndex = this.resizerElements.length;
         } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) {
             leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth;
             leftCellIndex = 0;
@@ -1270,15 +1209,15 @@ WebInspector.DataGrid.prototype = {
 
         resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
 
-        var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
-        this._headerTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
-        this._dataTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
+        var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTableElement.offsetWidth) * 100) + "%";
+        this._headerTableColumnGroupElement.children[leftCellIndex].style.width = percentLeftColumn;
+        this._dataTableColumnGroupElement.children[leftCellIndex].style.width = percentLeftColumn;
 
-        var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
-        this._headerTableColumnGroup.children[rightCellIndex].style.width =  percentRightColumn;
-        this._dataTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn;
+        var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTableElement.offsetWidth) * 100) + "%";
+        this._headerTableColumnGroupElement.children[rightCellIndex].style.width =  percentRightColumn;
+        this._dataTableColumnGroupElement.children[rightCellIndex].style.width = percentRightColumn;
 
-        this._positionResizers();
+        this._positionResizerElements();
         event.preventDefault();
         this.dispatchEventToListeners(WebInspector.DataGrid.Event.DidLayout);
     },
@@ -1358,10 +1297,8 @@ WebInspector.DataGridNode.prototype = {
 
     createCells: function()
     {
-        for (var columnIdentifier in this.dataGrid.columns) {
-            var cell = this.createCell(columnIdentifier);
-            this._element.appendChild(cell);
-        }
+        for (var columnIdentifier of this.dataGrid.orderedColumns)
+            this._element.appendChild(this.createCell(columnIdentifier));
     },
 
     refreshIfNeeded: function()
@@ -1546,30 +1483,29 @@ WebInspector.DataGridNode.prototype = {
 
     createCell: function(columnIdentifier)
     {
-        var cell = document.createElement("td");
-        cell.className = columnIdentifier + "-column";
-        cell.__columnIdentifier = columnIdentifier;
+        var cellElement = document.createElement("td");
+        cellElement.className = columnIdentifier + "-column";
+        cellElement.__columnIdentifier = columnIdentifier;
+
+        var column = this.dataGrid.columns.get(columnIdentifier);
 
-        var alignment = this.dataGrid.aligned[columnIdentifier];
-        if (alignment)
-            cell.classList.add(alignment);
+        if (column.has("aligned"))
+            cellElement.classList.add(column.get("aligned"));
 
-        var group = this.dataGrid.groups[columnIdentifier];
-        if (group)
-            cell.classList.add("column-group-" + group);
+        if (column.has("group"))
+            cellElement.classList.add("column-group-" + column.get("group"));
 
-        var div = document.createElement("div");
-        var content = this.createCellContent(columnIdentifier, cell);
+        var div = cellElement.createChild("div");
+        var content = this.createCellContent(columnIdentifier, cellElement);
         div.appendChild(content instanceof Node ? content : document.createTextNode(content));
-        cell.appendChild(div);
 
         if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
-            cell.classList.add("disclosure");
+            cellElement.classList.add("disclosure");
             if (this.leftPadding)
-                cell.style.setProperty("padding-left", this.leftPadding + "px");
+                cellElement.style.setProperty("padding-left", this.leftPadding + "px");
         }
 
-        return cell;
+        return cellElement;
     },
 
     createCellContent: function(columnIdentifier)
@@ -1579,7 +1515,7 @@ WebInspector.DataGridNode.prototype = {
 
     elementWithColumnIdentifier: function(columnIdentifier)
     {
-        var index = Object.keys(this.dataGrid.columns).indexOf(columnIdentifier);
+        var index = this.dataGrid.orderedColumns.indexOf(columnIdentifier);
         if (index === -1)
             return null;
 
@@ -1828,14 +1764,14 @@ WebInspector.DataGridNode.prototype = {
         if (previousGridNode && previousGridNode.element.parentNode)
             nextElement = previousGridNode.element.nextSibling;
         else if (!previousGridNode)
-            nextElement = this.dataGrid.dataTableBody.firstChild;
+            nextElement = this.dataGrid.dataTableBodyElement.firstChild;
 
         // If there is no next grid node, then append before the last child since the last child is the filler row.
-        console.assert(this.dataGrid.dataTableBody.lastChild.classList.contains("filler"));
+        console.assert(this.dataGrid.dataTableBodyElement.lastChild.classList.contains("filler"));
         if (!nextElement)
-            nextElement = this.dataGrid.dataTableBody.lastChild;
+            nextElement = this.dataGrid.dataTableBodyElement.lastChild;
 
-        this.dataGrid.dataTableBody.insertBefore(this.element, nextElement);
+        this.dataGrid.dataTableBodyElement.insertBefore(this.element, nextElement);
 
         if (this.expanded)
             for (var i = 0; i < this.children.length; ++i)
index 72df04d..319767b 100644 (file)
@@ -49,7 +49,7 @@ WebInspector.TimelineDataGrid = function(treeOutline, columns, delegate, editCal
     }
 
     if (this._filterableColumns.length) {
-        var items = [new WebInspector.FlexibleSpaceNavigationItem, this.columns[this._filterableColumns[0]].scopeBar, new WebInspector.FlexibleSpaceNavigationItem];
+        var items = [new WebInspector.FlexibleSpaceNavigationItem, this.columns.get(this._filterableColumns[0]).get("scopeBar"), new WebInspector.FlexibleSpaceNavigationItem];
         this._navigationBar = new WebInspector.NavigationBar(null, items);
         var container = this.element.appendChild(document.createElement("div"));
         container.className = "navigation-bar-container";
@@ -149,7 +149,7 @@ WebInspector.TimelineDataGrid.prototype = {
         console.assert(dataGridNode);
 
         for (var identifier of this._filterableColumns) {
-            var scopeBar = this.columns[identifier].scopeBar;
+            var scopeBar = this.columns.get(identifier).scopeBar;
             if (!scopeBar || scopeBar.defaultItem.selected)
                 continue;