| /* |
| * Copyright (C) 2008 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WebInspector.DataGrid = function(columns, editCallback, deleteCallback) |
| { |
| this.element = document.createElement("div"); |
| this.element.className = "data-grid"; |
| this.element.tabIndex = 0; |
| this.element.addEventListener("keydown", this._keyDown.bind(this), false); |
| |
| this._headerTable = document.createElement("table"); |
| this._headerTable.className = "header"; |
| |
| this._dataTable = document.createElement("table"); |
| this._dataTable.className = "data"; |
| |
| this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true); |
| this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true); |
| |
| this._dataTable.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._editCallback = editCallback; |
| } |
| if (deleteCallback) |
| this._deleteCallback = deleteCallback; |
| |
| this.aligned = {}; |
| |
| var scrollContainer = document.createElement("div"); |
| scrollContainer.className = "data-container"; |
| scrollContainer.appendChild(this._dataTable); |
| |
| this.element.appendChild(this._headerTable); |
| this.element.appendChild(scrollContainer); |
| |
| var headerRow = document.createElement("tr"); |
| var columnGroup = document.createElement("colgroup"); |
| this._columnCount = 0; |
| |
| for (var columnIdentifier in columns) { |
| var column = columns[columnIdentifier]; |
| if (column.disclosure) |
| this.disclosureColumnIdentifier = columnIdentifier; |
| |
| 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; |
| |
| var div = document.createElement("div"); |
| div.textContent = column.title; |
| cell.appendChild(div); |
| |
| if (column.sort) { |
| cell.addStyleClass("sort-" + column.sort); |
| this._sortColumnCell = cell; |
| } |
| |
| if (column.sortable) { |
| cell.addEventListener("click", this._clickInHeaderCell.bind(this), false); |
| cell.addStyleClass("sortable"); |
| } |
| |
| if (column.aligned) { |
| cell.addStyleClass(column.aligned); |
| this.aligned[columnIdentifier] = column.aligned; |
| } |
| |
| headerRow.appendChild(cell); |
| |
| ++this._columnCount; |
| } |
| |
| columnGroup.span = this._columnCount; |
| |
| var cell = document.createElement("th"); |
| cell.className = "corner"; |
| headerRow.appendChild(cell); |
| |
| this._headerTableColumnGroup = columnGroup; |
| this._headerTable.appendChild(this._headerTableColumnGroup); |
| this.headerTableBody.appendChild(headerRow); |
| |
| var fillerRow = document.createElement("tr"); |
| fillerRow.className = "filler"; |
| |
| for (var i = 0; i < this._columnCount; ++i) { |
| var cell = document.createElement("td"); |
| fillerRow.appendChild(cell); |
| } |
| |
| this._dataTableColumnGroup = columnGroup.cloneNode(true); |
| this._dataTable.appendChild(this._dataTableColumnGroup); |
| this.dataTableBody.appendChild(fillerRow); |
| |
| this.columns = columns || {}; |
| 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; |
| } |
| |
| WebInspector.DataGrid.prototype = { |
| _ondblclick: function(event) |
| { |
| if (this._editing || this._editingNode) |
| return; |
| |
| this._startEditing(event.target); |
| }, |
| |
| _startEditingColumnOfDataGridNode: function(node, column) |
| { |
| this._editing = true; |
| this._editingNode = node; |
| this._editingNode.select(); |
| |
| var element = this._editingNode._element.children[column]; |
| WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent); |
| window.getSelection().setBaseAndExtent(element, 0, element, 1); |
| }, |
| |
| _startEditing: function(target) |
| { |
| var element = target.enclosingNodeOrSelfWithNodeName("td"); |
| if (!element) |
| return; |
| |
| this._editingNode = this.dataGridNodeFromNode(target); |
| if (!this._editingNode) { |
| if (!this.creationNode) |
| return; |
| this._editingNode = this.creationNode; |
| } |
| |
| // Force editing the 1st column when editing the creation node |
| if (this._editingNode.isCreationNode) |
| return this._startEditingColumnOfDataGridNode(this._editingNode, 0); |
| |
| this._editing = true; |
| WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent); |
| window.getSelection().setBaseAndExtent(element, 0, element, 1); |
| }, |
| |
| _editingCommitted: function(element, newText, oldText, context, moveDirection) |
| { |
| // FIXME: We need more column identifiers here throughout this function. |
| // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value. |
| |
| // FIXME: Better way to do this than regular expressions? |
| var columnIdentifier = parseInt(element.className.match(/\b(\d+)-column\b/)[1]); |
| |
| var textBeforeEditing = this._editingNode.data[columnIdentifier]; |
| var currentEditingNode = this._editingNode; |
| |
| function moveToNextIfNeeded(wasChange) { |
| if (!moveDirection) |
| return; |
| |
| if (moveDirection === "forward") { |
| if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange) |
| return; |
| |
| if (columnIdentifier === 0) |
| return this._startEditingColumnOfDataGridNode(currentEditingNode, 1); |
| |
| var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true); |
| if (nextDataGridNode) |
| return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0); |
| if (currentEditingNode.isCreationNode && wasChange) { |
| addCreationNode(false); |
| return this._startEditingColumnOfDataGridNode(this.creationNode, 0); |
| } |
| return; |
| } |
| |
| if (moveDirection === "backward") { |
| if (columnIdentifier === 1) |
| return this._startEditingColumnOfDataGridNode(currentEditingNode, 0); |
| var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true); |
| |
| if (nextDataGridNode) |
| return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1); |
| return; |
| } |
| } |
| |
| if (textBeforeEditing == newText) { |
| this._editingCancelled(element); |
| moveToNextIfNeeded.call(this, false); |
| return; |
| } |
| |
| // Update the text in the datagrid that we typed |
| this._editingNode.data[columnIdentifier] = newText; |
| |
| // Make the callback - expects an editing node (table row), the column number that is being edited, |
| // the text that used to be there, and the new text. |
| this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText); |
| |
| if (this._editingNode.isCreationNode) |
| this.addCreationNode(false); |
| |
| this._editingCancelled(element); |
| moveToNextIfNeeded.call(this, true); |
| }, |
| |
| _editingCancelled: function(element, context) |
| { |
| delete this._editing; |
| this._editingNode = null; |
| }, |
| |
| get sortColumnIdentifier() |
| { |
| if (!this._sortColumnCell) |
| return null; |
| return this._sortColumnCell.columnIdentifier; |
| }, |
| |
| get sortOrder() |
| { |
| if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending")) |
| return "ascending"; |
| if (this._sortColumnCell.hasStyleClass("sort-descending")) |
| return "descending"; |
| 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; |
| }, |
| |
| autoSizeColumns: function(minPercent, maxPercent) |
| { |
| if (minPercent) |
| minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount)); |
| var widths = {}; |
| var columns = this.columns; |
| for (var columnIdentifier in columns) |
| widths[columnIdentifier] = (columns[columnIdentifier].title || "").length; |
| |
| for (var i = 0; i < this.children.length; ++i) { |
| var node = this.children[i]; |
| for (var columnIdentifier in columns) { |
| var text = node.data[columnIdentifier] || ""; |
| if (text.length > widths[columnIdentifier]) |
| widths[columnIdentifier] = text.length; |
| } |
| } |
| |
| var totalColumnWidths = 0; |
| for (var columnIdentifier in columns) |
| totalColumnWidths += widths[columnIdentifier]; |
| |
| var recoupPercent = 0; |
| for (var columnIdentifier in columns) { |
| var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths); |
| if (minPercent && width < minPercent) { |
| recoupPercent += (minPercent - width); |
| width = minPercent; |
| } else if (maxPercent && width > maxPercent) { |
| recoupPercent -= (width - maxPercent); |
| width = maxPercent; |
| } |
| widths[columnIdentifier] = width; |
| } |
| |
| while (minPercent && recoupPercent > 0) { |
| for (var columnIdentifier in columns) { |
| if (widths[columnIdentifier] > minPercent) { |
| --widths[columnIdentifier]; |
| --recoupPercent; |
| if (!recoupPercent) |
| break; |
| } |
| } |
| } |
| |
| while (maxPercent && recoupPercent < 0) { |
| for (var columnIdentifier in columns) { |
| if (widths[columnIdentifier] < maxPercent) { |
| ++widths[columnIdentifier]; |
| ++recoupPercent; |
| if (!recoupPercent) |
| break; |
| } |
| } |
| } |
| |
| for (var columnIdentifier in columns) |
| columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%"; |
| this.columnWidthsInitialized = false; |
| this.updateWidths(); |
| }, |
| |
| // Updates the widths of the table, including the positions of the column |
| // resizers. |
| // |
| // IMPORTANT: This function MUST be called once after the element of the |
| // DataGrid is attached to its parent element and every subsequent time the |
| // width of the parent element is changed in order to make it possible to |
| // resize the columns. |
| // |
| // If this function is not called after the DataGrid is attached to its |
| // parent element, then the DataGrid's columns will not be resizable. |
| updateWidths: function() |
| { |
| var headerTableColumns = this._headerTableColumnGroup.children; |
| |
| var left = 0; |
| var tableWidth = this._dataTable.offsetWidth; |
| var numColumns = headerTableColumns.length; |
| |
| if (!this.columnWidthsInitialized) { |
| // Give all the columns initial widths now so that during a resize, |
| // 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. |
| for (var i = 0; i < numColumns; i++) { |
| var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth; |
| var percentWidth = ((columnWidth / tableWidth) * 100) + "%"; |
| this._headerTableColumnGroup.children[i].style.width = percentWidth; |
| this._dataTableColumnGroup.children[i].style.width = percentWidth; |
| } |
| this.columnWidthsInitialized = true; |
| } |
| |
| // Make n - 1 resizers for n columns. |
| for (var i = 0; i < numColumns - 1; i++) { |
| var resizer = this.resizers[i]; |
| |
| if (!resizer) { |
| // This is the first call to updateWidth, so the resizers need |
| // to be created. |
| resizer = document.createElement("div"); |
| resizer.addStyleClass("data-grid-resizer"); |
| // This resizer is associated with the column to its right. |
| resizer.rightNeighboringColumnID = i + 1; |
| resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false); |
| this.element.appendChild(resizer); |
| this.resizers[i] = resizer; |
| } |
| |
| // 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; |
| |
| resizer.style.left = left + "px"; |
| } |
| }, |
| |
| addCreationNode: function(hasChildren) |
| { |
| if (this.creationNode) |
| this.creationNode.makeNormal(); |
| |
| var emptyData = {}; |
| for (var column in this.columns) |
| emptyData[column] = ''; |
| this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren); |
| this.appendChild(this.creationNode); |
| }, |
| |
| appendChild: function(child) |
| { |
| this.insertChild(child, this.children.length); |
| }, |
| |
| insertChild: function(child, index) |
| { |
| if (!child) |
| throw("insertChild: Node can't be undefined or null."); |
| if (child.parent === this) |
| throw("insertChild: Node is already a child of this node."); |
| |
| if (child.parent) |
| child.parent.removeChild(child); |
| |
| this.children.splice(index, 0, child); |
| this.hasChildren = true; |
| |
| child.parent = this; |
| child.dataGrid = this.dataGrid; |
| child._recalculateSiblings(index); |
| |
| delete child._depth; |
| delete child._revealed; |
| delete child._attached; |
| child._shouldRefreshChildren = true; |
| |
| var current = child.children[0]; |
| while (current) { |
| current.dataGrid = this.dataGrid; |
| delete current._depth; |
| delete current._revealed; |
| delete current._attached; |
| current._shouldRefreshChildren = true; |
| current = current.traverseNextNode(false, child, true); |
| } |
| |
| if (this.expanded) |
| child._attach(); |
| }, |
| |
| removeChild: function(child) |
| { |
| if (!child) |
| throw("removeChild: Node can't be undefined or null."); |
| if (child.parent !== this) |
| throw("removeChild: Node is not a child of this node."); |
| |
| child.deselect(); |
| |
| this.children.remove(child, true); |
| |
| if (child.previousSibling) |
| child.previousSibling.nextSibling = child.nextSibling; |
| if (child.nextSibling) |
| child.nextSibling.previousSibling = child.previousSibling; |
| |
| child.dataGrid = null; |
| child.parent = null; |
| child.nextSibling = null; |
| child.previousSibling = null; |
| |
| if (this.children.length <= 0) |
| this.hasChildren = false; |
| }, |
| |
| removeChildren: function() |
| { |
| for (var i = 0; i < this.children.length; ++i) { |
| var child = this.children[i]; |
| child.deselect(); |
| child._detach(); |
| |
| child.dataGrid = null; |
| child.parent = null; |
| child.nextSibling = null; |
| child.previousSibling = null; |
| } |
| |
| this.children = []; |
| this.hasChildren = false; |
| }, |
| |
| removeChildrenRecursive: function() |
| { |
| var childrenToRemove = this.children; |
| |
| var child = this.children[0]; |
| while (child) { |
| if (child.children.length) |
| childrenToRemove = childrenToRemove.concat(child.children); |
| child = child.traverseNextNode(false, this, true); |
| } |
| |
| for (var i = 0; i < childrenToRemove.length; ++i) { |
| var child = childrenToRemove[i]; |
| child.deselect(); |
| child._detach(); |
| |
| child.children = []; |
| child.dataGrid = null; |
| child.parent = null; |
| child.nextSibling = null; |
| child.previousSibling = null; |
| } |
| |
| this.children = []; |
| }, |
| |
| |
| _keyDown: function(event) |
| { |
| if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing) |
| return; |
| |
| var handled = false; |
| var nextSelectedNode; |
| if (event.keyIdentifier === "Up" && !event.altKey) { |
| nextSelectedNode = this.selectedNode.traversePreviousNode(true); |
| while (nextSelectedNode && !nextSelectedNode.selectable) |
| nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing); |
| handled = nextSelectedNode ? true : false; |
| } else if (event.keyIdentifier === "Down" && !event.altKey) { |
| nextSelectedNode = this.selectedNode.traverseNextNode(true); |
| while (nextSelectedNode && !nextSelectedNode.selectable) |
| nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing); |
| handled = nextSelectedNode ? true : false; |
| } else if (event.keyIdentifier === "Left") { |
| if (this.selectedNode.expanded) { |
| if (event.altKey) |
| this.selectedNode.collapseRecursively(); |
| else |
| this.selectedNode.collapse(); |
| handled = true; |
| } else if (this.selectedNode.parent && !this.selectedNode.parent.root) { |
| handled = true; |
| if (this.selectedNode.parent.selectable) { |
| nextSelectedNode = this.selectedNode.parent; |
| handled = nextSelectedNode ? true : false; |
| } else if (this.selectedNode.parent) |
| this.selectedNode.parent.collapse(); |
| } |
| } else if (event.keyIdentifier === "Right") { |
| if (!this.selectedNode.revealed) { |
| this.selectedNode.reveal(); |
| handled = true; |
| } else if (this.selectedNode.hasChildren) { |
| handled = true; |
| if (this.selectedNode.expanded) { |
| nextSelectedNode = this.selectedNode.children[0]; |
| handled = nextSelectedNode ? true : false; |
| } else { |
| if (event.altKey) |
| this.selectedNode.expandRecursively(); |
| else |
| this.selectedNode.expand(); |
| } |
| } |
| } else if (event.keyCode === 8 || event.keyCode === 46) { |
| if (this._deleteCallback) { |
| handled = true; |
| this._deleteCallback(this.selectedNode); |
| } |
| } else if (isEnterKey(event)) { |
| if (this._editCallback) { |
| handled = true; |
| // The first child of the selected element is the <td class="0-column">, |
| // and that's what we want to edit. |
| this._startEditing(this.selectedNode._element.children[0]); |
| } |
| } |
| |
| if (nextSelectedNode) { |
| nextSelectedNode.reveal(); |
| nextSelectedNode.select(); |
| } |
| |
| if (handled) { |
| event.preventDefault(); |
| event.stopPropagation(); |
| } |
| }, |
| |
| expand: function() |
| { |
| // This is the root, do nothing. |
| }, |
| |
| collapse: function() |
| { |
| // This is the root, do nothing. |
| }, |
| |
| reveal: function() |
| { |
| // This is the root, do nothing. |
| }, |
| |
| dataGridNodeFromNode: function(target) |
| { |
| var rowElement = target.enclosingNodeOrSelfWithNodeName("tr"); |
| return rowElement._dataGridNode; |
| }, |
| |
| dataGridNodeFromPoint: function(x, y) |
| { |
| var node = this._dataTable.ownerDocument.elementFromPoint(x, y); |
| var rowElement = node.enclosingNodeOrSelfWithNodeName("tr"); |
| return rowElement._dataGridNode; |
| }, |
| |
| _clickInHeaderCell: function(event) |
| { |
| var cell = event.target.enclosingNodeOrSelfWithNodeName("th"); |
| if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable")) |
| return; |
| |
| var sortOrder = this.sortOrder; |
| |
| if (this._sortColumnCell) { |
| this._sortColumnCell.removeStyleClass("sort-ascending"); |
| this._sortColumnCell.removeStyleClass("sort-descending"); |
| } |
| |
| if (cell == this._sortColumnCell) { |
| if (sortOrder == "ascending") |
| sortOrder = "descending"; |
| else |
| sortOrder = "ascending"; |
| } |
| |
| this._sortColumnCell = cell; |
| |
| cell.addStyleClass("sort-" + sortOrder); |
| |
| this.dispatchEventToListeners("sorting changed"); |
| }, |
| |
| _mouseDownInDataTable: function(event) |
| { |
| var gridNode = this.dataGridNodeFromNode(event.target); |
| if (!gridNode || !gridNode.selectable) |
| return; |
| |
| if (gridNode.isEventWithinDisclosureTriangle(event)) |
| return; |
| |
| if (event.metaKey) { |
| if (gridNode.selected) |
| gridNode.deselect(); |
| else |
| gridNode.select(); |
| } else |
| gridNode.select(); |
| }, |
| |
| _contextMenuInDataTable: function(event) |
| { |
| var gridNode = this.dataGridNodeFromNode(event.target); |
| if (!gridNode || !gridNode.selectable) |
| return; |
| |
| if (gridNode.isEventWithinDisclosureTriangle(event)) |
| return; |
| |
| var contextMenu = new WebInspector.ContextMenu(); |
| |
| // FIXME: Use the column names for Editing, instead of just "Edit". |
| if (this.dataGrid._editCallback) { |
| if (gridNode === this.creationNode) |
| contextMenu.appendItem(WebInspector.UIString("Add New"), this._startEditing.bind(this, event.target)); |
| else |
| contextMenu.appendItem(WebInspector.UIString("Edit"), this._startEditing.bind(this, event.target)); |
| } |
| if (this.dataGrid._deleteCallback && gridNode !== this.creationNode) |
| contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode)); |
| |
| contextMenu.show(event); |
| }, |
| |
| _clickInDataTable: function(event) |
| { |
| var gridNode = this.dataGridNodeFromNode(event.target); |
| if (!gridNode || !gridNode.hasChildren) |
| return; |
| |
| if (!gridNode.isEventWithinDisclosureTriangle(event)) |
| return; |
| |
| if (gridNode.expanded) { |
| if (event.altKey) |
| gridNode.collapseRecursively(); |
| else |
| gridNode.collapse(); |
| } else { |
| if (event.altKey) |
| gridNode.expandRecursively(); |
| else |
| gridNode.expand(); |
| } |
| }, |
| |
| _startResizerDragging: function(event) |
| { |
| this.currentResizer = event.target; |
| if (!this.currentResizer.rightNeighboringColumnID) |
| return; |
| WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this), |
| this._endResizerDragging.bind(this), event, "col-resize"); |
| }, |
| |
| _resizerDragging: function(event) |
| { |
| var resizer = this.currentResizer; |
| if (!resizer) |
| return; |
| |
| // Constrain the dragpoint to be within the containing div of the |
| // datagrid. |
| var dragPoint = event.clientX - this.element.totalOffsetLeft; |
| // Constrain the dragpoint to be within the space made up by the |
| // column directly to the left and the column directly to the right. |
| var leftEdgeOfPreviousColumn = 0; |
| var firstRowCells = this.headerTableBody.rows[0].cells; |
| for (var i = 0; i < resizer.rightNeighboringColumnID - 1; i++) |
| leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth; |
| |
| var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.rightNeighboringColumnID - 1].offsetWidth + firstRowCells[resizer.rightNeighboringColumnID].offsetWidth; |
| |
| // Give each column some padding so that they don't disappear. |
| var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding; |
| var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding; |
| |
| dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum); |
| |
| resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px"; |
| |
| var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%"; |
| this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn; |
| this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn; |
| |
| var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%"; |
| this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn; |
| this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn; |
| |
| event.preventDefault(); |
| }, |
| |
| _endResizerDragging: function(event) |
| { |
| WebInspector.elementDragEnd(event); |
| this.currentResizer = null; |
| }, |
| |
| ColumnResizePadding: 10, |
| |
| CenterResizerOverBorderAdjustment: 3, |
| } |
| |
| WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype; |
| |
| WebInspector.DataGridNode = function(data, hasChildren) |
| { |
| this._expanded = false; |
| this._selected = false; |
| this._shouldRefreshChildren = true; |
| this._data = data || {}; |
| this.hasChildren = hasChildren || false; |
| this.children = []; |
| this.dataGrid = null; |
| this.parent = null; |
| this.previousSibling = null; |
| this.nextSibling = null; |
| this.disclosureToggleWidth = 10; |
| } |
| |
| WebInspector.DataGridNode.prototype = { |
| selectable: true, |
| |
| get element() |
| { |
| if (this._element) |
| return this._element; |
| |
| if (!this.dataGrid) |
| return null; |
| |
| this._element = document.createElement("tr"); |
| this._element._dataGridNode = this; |
| |
| if (this.hasChildren) |
| this._element.addStyleClass("parent"); |
| if (this.expanded) |
| this._element.addStyleClass("expanded"); |
| if (this.selected) |
| this._element.addStyleClass("selected"); |
| if (this.revealed) |
| this._element.addStyleClass("revealed"); |
| |
| for (var columnIdentifier in this.dataGrid.columns) { |
| var cell = this.createCell(columnIdentifier); |
| this._element.appendChild(cell); |
| } |
| |
| return this._element; |
| }, |
| |
| get data() |
| { |
| return this._data; |
| }, |
| |
| set data(x) |
| { |
| this._data = x || {}; |
| this.refresh(); |
| }, |
| |
| get revealed() |
| { |
| if ("_revealed" in this) |
| return this._revealed; |
| |
| var currentAncestor = this.parent; |
| while (currentAncestor && !currentAncestor.root) { |
| if (!currentAncestor.expanded) { |
| this._revealed = false; |
| return false; |
| } |
| |
| currentAncestor = currentAncestor.parent; |
| } |
| |
| this._revealed = true; |
| return true; |
| }, |
| |
| set hasChildren(x) |
| { |
| if (this._hasChildren === x) |
| return; |
| |
| this._hasChildren = x; |
| |
| if (!this._element) |
| return; |
| |
| if (this._hasChildren) |
| { |
| this._element.addStyleClass("parent"); |
| if (this.expanded) |
| this._element.addStyleClass("expanded"); |
| } |
| else |
| { |
| this._element.removeStyleClass("parent"); |
| this._element.removeStyleClass("expanded"); |
| } |
| }, |
| |
| get hasChildren() |
| { |
| return this._hasChildren; |
| }, |
| |
| set revealed(x) |
| { |
| if (this._revealed === x) |
| return; |
| |
| this._revealed = x; |
| |
| if (this._element) { |
| if (this._revealed) |
| this._element.addStyleClass("revealed"); |
| else |
| this._element.removeStyleClass("revealed"); |
| } |
| |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i].revealed = x && this.expanded; |
| }, |
| |
| get depth() |
| { |
| if ("_depth" in this) |
| return this._depth; |
| if (this.parent && !this.parent.root) |
| this._depth = this.parent.depth + 1; |
| else |
| this._depth = 0; |
| return this._depth; |
| }, |
| |
| get shouldRefreshChildren() |
| { |
| return this._shouldRefreshChildren; |
| }, |
| |
| set shouldRefreshChildren(x) |
| { |
| this._shouldRefreshChildren = x; |
| if (x && this.expanded) |
| this.expand(); |
| }, |
| |
| get selected() |
| { |
| return this._selected; |
| }, |
| |
| set selected(x) |
| { |
| if (x) |
| this.select(); |
| else |
| this.deselect(); |
| }, |
| |
| get expanded() |
| { |
| return this._expanded; |
| }, |
| |
| set expanded(x) |
| { |
| if (x) |
| this.expand(); |
| else |
| this.collapse(); |
| }, |
| |
| refresh: function() |
| { |
| if (!this._element || !this.dataGrid) |
| return; |
| |
| this._element.removeChildren(); |
| |
| for (var columnIdentifier in this.dataGrid.columns) { |
| var cell = this.createCell(columnIdentifier); |
| this._element.appendChild(cell); |
| } |
| }, |
| |
| createCell: function(columnIdentifier) |
| { |
| var cell = document.createElement("td"); |
| cell.className = columnIdentifier + "-column"; |
| |
| var alignment = this.dataGrid.aligned[columnIdentifier]; |
| if (alignment) |
| cell.addStyleClass(alignment); |
| |
| var div = document.createElement("div"); |
| div.textContent = this.data[columnIdentifier]; |
| cell.appendChild(div); |
| |
| if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) { |
| cell.addStyleClass("disclosure"); |
| if (this.depth) |
| cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); |
| } |
| |
| return cell; |
| }, |
| |
| // Share these functions with DataGrid. They are written to work with a DataGridNode this object. |
| appendChild: WebInspector.DataGrid.prototype.appendChild, |
| insertChild: WebInspector.DataGrid.prototype.insertChild, |
| removeChild: WebInspector.DataGrid.prototype.removeChild, |
| removeChildren: WebInspector.DataGrid.prototype.removeChildren, |
| removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive, |
| |
| _recalculateSiblings: function(myIndex) |
| { |
| if (!this.parent) |
| return; |
| |
| var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null); |
| |
| if (previousChild) { |
| previousChild.nextSibling = this; |
| this.previousSibling = previousChild; |
| } else |
| this.previousSibling = null; |
| |
| var nextChild = this.parent.children[myIndex + 1]; |
| |
| if (nextChild) { |
| nextChild.previousSibling = this; |
| this.nextSibling = nextChild; |
| } else |
| this.nextSibling = null; |
| }, |
| |
| collapse: function() |
| { |
| if (this._element) |
| this._element.removeStyleClass("expanded"); |
| |
| this._expanded = false; |
| |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i].revealed = false; |
| |
| this.dispatchEventToListeners("collapsed"); |
| }, |
| |
| collapseRecursively: function() |
| { |
| var item = this; |
| while (item) { |
| if (item.expanded) |
| item.collapse(); |
| item = item.traverseNextNode(false, this, true); |
| } |
| }, |
| |
| expand: function() |
| { |
| if (!this.hasChildren || this.expanded) |
| return; |
| |
| if (this.revealed && !this._shouldRefreshChildren) |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i].revealed = true; |
| |
| if (this._shouldRefreshChildren) { |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i]._detach(); |
| |
| this.dispatchEventToListeners("populate"); |
| |
| if (this._attached) { |
| for (var i = 0; i < this.children.length; ++i) { |
| var child = this.children[i]; |
| if (this.revealed) |
| child.revealed = true; |
| child._attach(); |
| } |
| } |
| |
| delete this._shouldRefreshChildren; |
| } |
| |
| if (this._element) |
| this._element.addStyleClass("expanded"); |
| |
| this._expanded = true; |
| |
| this.dispatchEventToListeners("expanded"); |
| }, |
| |
| expandRecursively: function() |
| { |
| var item = this; |
| while (item) { |
| item.expand(); |
| item = item.traverseNextNode(false, this); |
| } |
| }, |
| |
| reveal: function() |
| { |
| var currentAncestor = this.parent; |
| while (currentAncestor && !currentAncestor.root) { |
| if (!currentAncestor.expanded) |
| currentAncestor.expand(); |
| currentAncestor = currentAncestor.parent; |
| } |
| |
| this.element.scrollIntoViewIfNeeded(false); |
| |
| this.dispatchEventToListeners("revealed"); |
| }, |
| |
| select: function(supressSelectedEvent) |
| { |
| if (!this.dataGrid || !this.selectable || this.selected) |
| return; |
| |
| if (this.dataGrid.selectedNode) |
| this.dataGrid.selectedNode.deselect(); |
| |
| this._selected = true; |
| this.dataGrid.selectedNode = this; |
| |
| if (this._element) |
| this._element.addStyleClass("selected"); |
| |
| if (!supressSelectedEvent) |
| this.dispatchEventToListeners("selected"); |
| }, |
| |
| deselect: function(supressDeselectedEvent) |
| { |
| if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected) |
| return; |
| |
| this._selected = false; |
| this.dataGrid.selectedNode = null; |
| |
| if (this._element) |
| this._element.removeStyleClass("selected"); |
| |
| if (!supressDeselectedEvent) |
| this.dispatchEventToListeners("deselected"); |
| }, |
| |
| traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info) |
| { |
| if (!dontPopulate && this.hasChildren) |
| this.dispatchEventToListeners("populate"); |
| |
| if (info) |
| info.depthChange = 0; |
| |
| var node = (!skipHidden || this.revealed) ? this.children[0] : null; |
| if (node && (!skipHidden || this.expanded)) { |
| if (info) |
| info.depthChange = 1; |
| return node; |
| } |
| |
| if (this === stayWithin) |
| return null; |
| |
| node = (!skipHidden || this.revealed) ? this.nextSibling : null; |
| if (node) |
| return node; |
| |
| node = this; |
| while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) { |
| if (info) |
| info.depthChange -= 1; |
| node = node.parent; |
| } |
| |
| if (!node) |
| return null; |
| |
| return (!skipHidden || node.revealed) ? node.nextSibling : null; |
| }, |
| |
| traversePreviousNode: function(skipHidden, dontPopulate) |
| { |
| var node = (!skipHidden || this.revealed) ? this.previousSibling : null; |
| if (!dontPopulate && node && node.hasChildren) |
| node.dispatchEventToListeners("populate"); |
| |
| while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) { |
| if (!dontPopulate && node.hasChildren) |
| node.dispatchEventToListeners("populate"); |
| node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null); |
| } |
| |
| if (node) |
| return node; |
| |
| if (!this.parent || this.parent.root) |
| return null; |
| |
| return this.parent; |
| }, |
| |
| isEventWithinDisclosureTriangle: function(event) |
| { |
| if (!this.hasChildren) |
| return false; |
| var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); |
| if (!cell.hasStyleClass("disclosure")) |
| return false; |
| var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX); |
| var left = cell.totalOffsetLeft + computedLeftPadding; |
| return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth; |
| }, |
| |
| _attach: function() |
| { |
| if (!this.dataGrid || this._attached) |
| return; |
| |
| this._attached = true; |
| |
| var nextNode = null; |
| var previousNode = this.traversePreviousNode(true, true); |
| if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling) |
| var nextNode = previousNode.element.nextSibling; |
| if (!nextNode) |
| nextNode = this.dataGrid.dataTableBody.lastChild; |
| this.dataGrid.dataTableBody.insertBefore(this.element, nextNode); |
| |
| if (this.expanded) |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i]._attach(); |
| }, |
| |
| _detach: function() |
| { |
| if (!this._attached) |
| return; |
| |
| this._attached = false; |
| |
| if (this._element && this._element.parentNode) |
| this._element.parentNode.removeChild(this._element); |
| |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i]._detach(); |
| }, |
| |
| savePosition: function() |
| { |
| if (this._savedPosition) |
| return; |
| |
| if (!this.parent) |
| throw("savePosition: Node must have a parent."); |
| this._savedPosition = { |
| parent: this.parent, |
| index: this.parent.children.indexOf(this) |
| }; |
| }, |
| |
| restorePosition: function() |
| { |
| if (!this._savedPosition) |
| return; |
| |
| if (this.parent !== this._savedPosition.parent) |
| this._savedPosition.parent.insertChild(this, this._savedPosition.index); |
| |
| delete this._savedPosition; |
| } |
| } |
| |
| WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype; |
| |
| WebInspector.CreationDataGridNode = function(data, hasChildren) |
| { |
| WebInspector.DataGridNode.call(this, data, hasChildren); |
| this.isCreationNode = true; |
| } |
| |
| WebInspector.CreationDataGridNode.prototype = { |
| makeNormal: function() |
| { |
| delete this.isCreationNode; |
| delete this.makeNormal; |
| } |
| } |
| |
| WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; |