2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 WebInspector.DataGrid = function(columns, editCallback, deleteCallback)
28 this.element = document.createElement("div");
29 this.element.className = "data-grid";
30 this.element.tabIndex = 0;
31 this.element.addEventListener("keydown", this._keyDown.bind(this), false);
33 this._headerTable = document.createElement("table");
34 this._headerTable.className = "header";
35 this._headerTableHeaders = {};
37 this._dataTable = document.createElement("table");
38 this._dataTable.className = "data";
40 this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
41 this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
43 this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
45 // FIXME: Add a createCallback which is different from editCallback and has different
46 // behavior when creating a new node.
48 this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
49 this._editCallback = editCallback;
52 this._deleteCallback = deleteCallback;
56 this._scrollContainer = document.createElement("div");
57 this._scrollContainer.className = "data-container";
58 this._scrollContainer.appendChild(this._dataTable);
60 this.element.appendChild(this._headerTable);
61 this.element.appendChild(this._scrollContainer);
63 var headerRow = document.createElement("tr");
64 var columnGroup = document.createElement("colgroup");
65 this._columnCount = 0;
67 for (var columnIdentifier in columns) {
68 var column = columns[columnIdentifier];
69 if (column.disclosure)
70 this.disclosureColumnIdentifier = columnIdentifier;
72 var col = document.createElement("col");
74 col.style.width = column.width;
76 columnGroup.appendChild(col);
78 var cell = document.createElement("th");
79 cell.className = columnIdentifier + "-column";
80 cell.columnIdentifier = columnIdentifier;
81 this._headerTableHeaders[columnIdentifier] = cell;
83 var div = document.createElement("div");
84 if (column.titleDOMFragment)
85 div.appendChild(column.titleDOMFragment);
87 div.textContent = column.title;
88 cell.appendChild(div);
91 cell.addStyleClass("sort-" + column.sort);
92 this._sortColumnCell = cell;
95 if (column.sortable) {
96 cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
97 cell.addStyleClass("sortable");
101 this.aligned[columnIdentifier] = column.aligned;
103 headerRow.appendChild(cell);
108 columnGroup.span = this._columnCount;
110 var cell = document.createElement("th");
111 cell.className = "corner";
112 headerRow.appendChild(cell);
114 this._headerTableColumnGroup = columnGroup;
115 this._headerTable.appendChild(this._headerTableColumnGroup);
116 this.headerTableBody.appendChild(headerRow);
118 var fillerRow = document.createElement("tr");
119 fillerRow.className = "filler";
121 for (var columnIdentifier in columns) {
122 var column = columns[columnIdentifier];
123 var cell = document.createElement("td");
124 cell.className = columnIdentifier + "-column";
125 fillerRow.appendChild(cell);
128 this._dataTableColumnGroup = columnGroup.cloneNode(true);
129 this._dataTable.appendChild(this._dataTableColumnGroup);
130 this.dataTableBody.appendChild(fillerRow);
132 this.columns = columns || {};
133 this._columnsArray = [];
134 for (var columnIdentifier in columns) {
135 columns[columnIdentifier].ordinal = this._columnsArray.length;
136 columns[columnIdentifier].identifier = columnIdentifier;
137 this._columnsArray.push(columns[columnIdentifier]);
140 for (var i = 0; i < this._columnsArray.length; ++i)
141 this._columnsArray[i].bodyElement = this._dataTableColumnGroup.children[i];
144 this.selectedNode = null;
145 this.expandNodesWhenArrowing = false;
147 this.hasChildren = false;
148 this.expanded = true;
149 this.revealed = true;
150 this.selected = false;
151 this.dataGrid = this;
152 this.indentWidth = 15;
154 this._columnWidthsInitialized = false;
157 WebInspector.DataGrid.prototype = {
158 get refreshCallback()
160 return this._refreshCallback;
163 set refreshCallback(refreshCallback)
165 this._refreshCallback = refreshCallback;
168 _ondblclick: function(event)
170 if (this._editing || this._editingNode)
173 this._startEditing(event.target);
176 _startEditingColumnOfDataGridNode: function(node, column)
178 this._editing = true;
179 this._editingNode = node;
180 this._editingNode.select();
182 var element = this._editingNode._element.children[column];
183 WebInspector.startEditing(element, {
184 context: element.textContent,
185 commitHandler: this._editingCommitted.bind(this),
186 cancelHandler: this._editingCancelled.bind(this)
188 window.getSelection().setBaseAndExtent(element, 0, element, 1);
191 _startEditing: function(target)
193 var element = target.enclosingNodeOrSelfWithNodeName("td");
197 this._editingNode = this.dataGridNodeFromNode(target);
198 if (!this._editingNode) {
199 if (!this.creationNode)
201 this._editingNode = this.creationNode;
204 // Force editing the 1st column when editing the creation node
205 if (this._editingNode.isCreationNode)
206 return this._startEditingColumnOfDataGridNode(this._editingNode, 0);
208 this._editing = true;
209 WebInspector.startEditing(element, {
210 context: element.textContent,
211 commitHandler: this._editingCommitted.bind(this),
212 cancelHandler: this._editingCancelled.bind(this)
214 window.getSelection().setBaseAndExtent(element, 0, element, 1);
217 _editingCommitted: function(element, newText, oldText, context, moveDirection)
219 // FIXME: We need more column identifiers here throughout this function.
220 // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value.
222 // FIXME: Better way to do this than regular expressions?
223 var columnIdentifier = parseInt(element.className.match(/\b(\d+)-column\b/)[1]);
225 var textBeforeEditing = this._editingNode.data[columnIdentifier];
226 var currentEditingNode = this._editingNode;
228 function moveToNextIfNeeded(wasChange) {
232 if (moveDirection === "forward") {
233 if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange)
236 if (columnIdentifier === 0)
237 return this._startEditingColumnOfDataGridNode(currentEditingNode, 1);
239 var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
240 if (nextDataGridNode)
241 return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0);
242 if (currentEditingNode.isCreationNode && wasChange) {
243 addCreationNode(false);
244 return this._startEditingColumnOfDataGridNode(this.creationNode, 0);
249 if (moveDirection === "backward") {
250 if (columnIdentifier === 1)
251 return this._startEditingColumnOfDataGridNode(currentEditingNode, 0);
252 var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true);
254 if (nextDataGridNode)
255 return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1);
260 if (textBeforeEditing == newText) {
261 this._editingCancelled(element);
262 moveToNextIfNeeded.call(this, false);
266 // Update the text in the datagrid that we typed
267 this._editingNode.data[columnIdentifier] = newText;
269 // Make the callback - expects an editing node (table row), the column number that is being edited,
270 // the text that used to be there, and the new text.
271 this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText);
273 if (this._editingNode.isCreationNode)
274 this.addCreationNode(false);
276 this._editingCancelled(element);
277 moveToNextIfNeeded.call(this, true);
280 _editingCancelled: function(element, context)
282 delete this._editing;
283 this._editingNode = null;
286 get sortColumnIdentifier()
288 if (!this._sortColumnCell)
290 return this._sortColumnCell.columnIdentifier;
295 if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
297 if (this._sortColumnCell.hasStyleClass("sort-descending"))
302 get headerTableBody()
304 if ("_headerTableBody" in this)
305 return this._headerTableBody;
307 this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
308 if (!this._headerTableBody) {
309 this._headerTableBody = this.element.ownerDocument.createElement("tbody");
310 this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
313 return this._headerTableBody;
318 if ("_dataTableBody" in this)
319 return this._dataTableBody;
321 this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
322 if (!this._dataTableBody) {
323 this._dataTableBody = this.element.ownerDocument.createElement("tbody");
324 this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
327 return this._dataTableBody;
330 autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel)
333 minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount));
335 var columns = this.columns;
336 for (var columnIdentifier in columns)
337 widths[columnIdentifier] = (columns[columnIdentifier].title || "").length;
339 var children = maxDescentLevel ? this._enumerateChildren(this, [], maxDescentLevel + 1) : this.children;
340 for (var i = 0; i < children.length; ++i) {
341 var node = children[i];
342 for (var columnIdentifier in columns) {
343 var text = node.data[columnIdentifier] || "";
344 if (text.length > widths[columnIdentifier])
345 widths[columnIdentifier] = text.length;
349 var totalColumnWidths = 0;
350 for (var columnIdentifier in columns)
351 totalColumnWidths += widths[columnIdentifier];
353 var recoupPercent = 0;
354 for (var columnIdentifier in columns) {
355 var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths);
356 if (minPercent && width < minPercent) {
357 recoupPercent += (minPercent - width);
359 } else if (maxPercent && width > maxPercent) {
360 recoupPercent -= (width - maxPercent);
363 widths[columnIdentifier] = width;
366 while (minPercent && recoupPercent > 0) {
367 for (var columnIdentifier in columns) {
368 if (widths[columnIdentifier] > minPercent) {
369 --widths[columnIdentifier];
377 while (maxPercent && recoupPercent < 0) {
378 for (var columnIdentifier in columns) {
379 if (widths[columnIdentifier] < maxPercent) {
380 ++widths[columnIdentifier];
388 for (var columnIdentifier in columns)
389 columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%";
390 this._columnWidthsInitialized = false;
394 _enumerateChildren: function(rootNode, result, maxLevel)
397 result.push(rootNode);
400 for (var i = 0; i < rootNode.children.length; ++i)
401 this._enumerateChildren(rootNode.children[i], result, maxLevel - 1);
405 // Updates the widths of the table, including the positions of the column
408 // IMPORTANT: This function MUST be called once after the element of the
409 // DataGrid is attached to its parent element and every subsequent time the
410 // width of the parent element is changed in order to make it possible to
411 // resize the columns.
413 // If this function is not called after the DataGrid is attached to its
414 // parent element, then the DataGrid's columns will not be resizable.
415 updateWidths: function()
417 var headerTableColumns = this._headerTableColumnGroup.children;
419 var tableWidth = this._dataTable.offsetWidth;
420 var numColumns = headerTableColumns.length;
422 // Do not attempt to use offsetes if we're not attached to the document tree yet.
423 if (!this._columnWidthsInitialized && this.element.offsetWidth) {
424 // Give all the columns initial widths now so that during a resize,
425 // when the two columns that get resized get a percent value for
426 // their widths, all the other columns already have percent values
428 for (var i = 0; i < numColumns; i++) {
429 var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
430 var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
431 this._headerTableColumnGroup.children[i].style.width = percentWidth;
432 this._dataTableColumnGroup.children[i].style.width = percentWidth;
434 this._columnWidthsInitialized = true;
436 this._positionResizers();
437 this.dispatchEventToListeners("width changed");
440 columnWidthsMap: function()
443 for (var i = 0; i < this._columnsArray.length; ++i) {
444 var width = this._headerTableColumnGroup.children[i].style.width;
445 result[this._columnsArray[i].columnIdentifier] = parseFloat(width);
450 applyColumnWidthsMap: function(columnWidthsMap)
452 for (var columnIdentifier in this.columns) {
453 var column = this.columns[columnIdentifier];
454 var width = (columnWidthsMap[columnIdentifier] || 0) + "%";
455 this._headerTableColumnGroup.children[column.ordinal].style.width = width;
456 this._dataTableColumnGroup.children[column.ordinal].style.width = width;
460 delete this._columnWidthsInitialized;
464 isColumnVisible: function(columnIdentifier)
466 var column = this.columns[columnIdentifier];
467 var columnElement = column.element;
468 return !columnElement.hidden;
471 showColumn: function(columnIdentifier)
473 var column = this.columns[columnIdentifier];
474 var columnElement = column.element;
475 if (!columnElement.hidden)
478 columnElement.hidden = false;
479 columnElement.removeStyleClass("hidden");
481 var columnBodyElement = column.bodyElement;
482 columnBodyElement.hidden = false;
483 columnBodyElement.removeStyleClass("hidden");
486 hideColumn: function(columnIdentifier)
488 var column = this.columns[columnIdentifier];
489 var columnElement = column.element;
490 if (columnElement.hidden)
493 var oldWidth = parseFloat(columnElement.style.width);
495 columnElement.hidden = true;
496 columnElement.addStyleClass("hidden");
497 columnElement.style.width = 0;
499 var columnBodyElement = column.bodyElement;
500 columnBodyElement.hidden = true;
501 columnBodyElement.addStyleClass("hidden");
502 columnBodyElement.style.width = 0;
504 this._columnWidthsInitialized = false;
507 get scrollContainer()
509 return this._scrollContainer;
512 isScrolledToLastRow: function()
514 return this._scrollContainer.isScrolledToBottom();
517 scrollToLastRow: function()
519 this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight;
522 _positionResizers: function()
524 var headerTableColumns = this._headerTableColumnGroup.children;
525 var numColumns = headerTableColumns.length;
527 var previousResizer = null;
529 // Make n - 1 resizers for n columns.
530 for (var i = 0; i < numColumns - 1; i++) {
531 var resizer = this.resizers[i];
534 // This is the first call to updateWidth, so the resizers need
536 resizer = document.createElement("div");
537 resizer.addStyleClass("data-grid-resizer");
538 // This resizer is associated with the column to its right.
539 resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
540 this.element.appendChild(resizer);
541 this.resizers[i] = resizer;
544 // Get the width of the cell in the first (and only) row of the
545 // header table in order to determine the width of the column, since
546 // it is not possible to query a column for its width.
547 left += this.headerTableBody.rows[0].cells[i].offsetWidth;
549 var columnIsVisible = !this._headerTableColumnGroup.children[i].hidden;
550 if (columnIsVisible) {
551 resizer.style.removeProperty("display");
552 resizer.style.left = left + "px";
553 resizer.leftNeighboringColumnID = i;
555 previousResizer.rightNeighboringColumnID = i;
556 previousResizer = resizer;
558 resizer.style.setProperty("display", "none");
559 resizer.leftNeighboringColumnID = 0;
560 resizer.rightNeighboringColumnID = 0;
564 previousResizer.rightNeighboringColumnID = numColumns - 1;
567 addCreationNode: function(hasChildren)
569 if (this.creationNode)
570 this.creationNode.makeNormal();
573 for (var column in this.columns)
574 emptyData[column] = '';
575 this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
576 this.appendChild(this.creationNode);
579 appendChild: function(child)
581 this.insertChild(child, this.children.length);
584 insertChild: function(child, index)
587 throw("insertChild: Node can't be undefined or null.");
588 if (child.parent === this)
589 throw("insertChild: Node is already a child of this node.");
592 child.parent.removeChild(child);
594 this.children.splice(index, 0, child);
595 this.hasChildren = true;
598 child.dataGrid = this.dataGrid;
599 child._recalculateSiblings(index);
602 delete child._revealed;
603 delete child._attached;
604 child._shouldRefreshChildren = true;
606 var current = child.children[0];
608 current.dataGrid = this.dataGrid;
609 delete current._depth;
610 delete current._revealed;
611 delete current._attached;
612 current._shouldRefreshChildren = true;
613 current = current.traverseNextNode(false, child, true);
620 removeChild: function(child)
623 throw("removeChild: Node can't be undefined or null.");
624 if (child.parent !== this)
625 throw("removeChild: Node is not a child of this node.");
630 this.children.remove(child, true);
632 if (child.previousSibling)
633 child.previousSibling.nextSibling = child.nextSibling;
634 if (child.nextSibling)
635 child.nextSibling.previousSibling = child.previousSibling;
637 child.dataGrid = null;
639 child.nextSibling = null;
640 child.previousSibling = null;
642 if (this.children.length <= 0)
643 this.hasChildren = false;
646 removeChildren: function()
648 for (var i = 0; i < this.children.length; ++i) {
649 var child = this.children[i];
653 child.dataGrid = null;
655 child.nextSibling = null;
656 child.previousSibling = null;
660 this.hasChildren = false;
663 removeChildrenRecursive: function()
665 var childrenToRemove = this.children;
667 var child = this.children[0];
669 if (child.children.length)
670 childrenToRemove = childrenToRemove.concat(child.children);
671 child = child.traverseNextNode(false, this, true);
674 for (var i = 0; i < childrenToRemove.length; ++i) {
675 var child = childrenToRemove[i];
680 child.dataGrid = null;
682 child.nextSibling = null;
683 child.previousSibling = null;
689 sortNodes: function(comparator, reverseMode)
691 function comparatorWrapper(a, b)
693 if (a._dataGridNode._data.summaryRow)
695 if (b._dataGridNode._data.summaryRow)
698 var aDataGirdNode = a._dataGridNode;
699 var bDataGirdNode = b._dataGridNode;
700 return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode);
703 var tbody = this.dataTableBody;
704 var tbodyParent = tbody.parentElement;
705 tbodyParent.removeChild(tbody);
707 var childNodes = tbody.childNodes;
708 var fillerRow = childNodes[childNodes.length - 1];
710 var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1);
711 sortedRows.sort(comparatorWrapper);
712 var sortedRowsLength = sortedRows.length;
714 tbody.removeChildren();
715 var previousSiblingNode = null;
716 for (var i = 0; i < sortedRowsLength; ++i) {
717 var row = sortedRows[i];
718 var node = row._dataGridNode;
719 node.previousSibling = previousSiblingNode;
720 if (previousSiblingNode)
721 previousSiblingNode.nextSibling = node;
722 tbody.appendChild(row);
723 previousSiblingNode = node;
725 if (previousSiblingNode)
726 previousSiblingNode.nextSibling = null;
728 tbody.appendChild(fillerRow);
729 tbodyParent.appendChild(tbody);
732 _keyDown: function(event)
734 if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
738 var nextSelectedNode;
739 if (event.keyIdentifier === "Up" && !event.altKey) {
740 nextSelectedNode = this.selectedNode.traversePreviousNode(true);
741 while (nextSelectedNode && !nextSelectedNode.selectable)
742 nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing);
743 handled = nextSelectedNode ? true : false;
744 } else if (event.keyIdentifier === "Down" && !event.altKey) {
745 nextSelectedNode = this.selectedNode.traverseNextNode(true);
746 while (nextSelectedNode && !nextSelectedNode.selectable)
747 nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing);
748 handled = nextSelectedNode ? true : false;
749 } else if (event.keyIdentifier === "Left") {
750 if (this.selectedNode.expanded) {
752 this.selectedNode.collapseRecursively();
754 this.selectedNode.collapse();
756 } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
758 if (this.selectedNode.parent.selectable) {
759 nextSelectedNode = this.selectedNode.parent;
760 handled = nextSelectedNode ? true : false;
761 } else if (this.selectedNode.parent)
762 this.selectedNode.parent.collapse();
764 } else if (event.keyIdentifier === "Right") {
765 if (!this.selectedNode.revealed) {
766 this.selectedNode.reveal();
768 } else if (this.selectedNode.hasChildren) {
770 if (this.selectedNode.expanded) {
771 nextSelectedNode = this.selectedNode.children[0];
772 handled = nextSelectedNode ? true : false;
775 this.selectedNode.expandRecursively();
777 this.selectedNode.expand();
780 } else if (event.keyCode === 8 || event.keyCode === 46) {
781 if (this._deleteCallback) {
783 this._deleteCallback(this.selectedNode);
785 } else if (isEnterKey(event)) {
786 if (this._editCallback) {
788 // The first child of the selected element is the <td class="0-column">,
789 // and that's what we want to edit.
790 this._startEditing(this.selectedNode._element.children[0]);
794 if (nextSelectedNode) {
795 nextSelectedNode.reveal();
796 nextSelectedNode.select();
800 event.preventDefault();
801 event.stopPropagation();
807 // This is the root, do nothing.
812 // This is the root, do nothing.
817 // This is the root, do nothing.
820 revealAndSelect: function()
822 // This is the root, do nothing.
825 dataGridNodeFromNode: function(target)
827 var rowElement = target.enclosingNodeOrSelfWithNodeName("tr");
828 return rowElement && rowElement._dataGridNode;
831 dataGridNodeFromPoint: function(x, y)
833 var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
834 var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
835 return rowElement && rowElement._dataGridNode;
838 _clickInHeaderCell: function(event)
840 var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
841 if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
844 var sortOrder = this.sortOrder;
846 if (this._sortColumnCell)
847 this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
849 if (cell == this._sortColumnCell) {
850 if (sortOrder === "ascending")
851 sortOrder = "descending";
853 sortOrder = "ascending";
856 this._sortColumnCell = cell;
858 cell.addStyleClass("sort-" + sortOrder);
860 this.dispatchEventToListeners("sorting changed");
863 markColumnAsSortedBy: function(columnIdentifier, sortOrder)
865 if (this._sortColumnCell)
866 this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
867 this._sortColumnCell = this._headerTableHeaders[columnIdentifier];
868 this._sortColumnCell.addStyleClass("sort-" + sortOrder);
871 headerTableHeader: function(columnIdentifier)
873 return this._headerTableHeaders[columnIdentifier];
876 _mouseDownInDataTable: function(event)
878 var gridNode = this.dataGridNodeFromNode(event.target);
879 if (!gridNode || !gridNode.selectable)
882 if (gridNode.isEventWithinDisclosureTriangle(event))
886 if (gridNode.selected)
894 _contextMenuInDataTable: function(event)
896 var contextMenu = new WebInspector.ContextMenu();
898 var gridNode = this.dataGridNodeFromNode(event.target);
899 if (this.dataGrid._refreshCallback && (!gridNode || gridNode !== this.creationNode))
900 contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this));
902 if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) {
903 // FIXME: Use the column names for Editing, instead of just "Edit".
904 if (this.dataGrid._editCallback) {
905 if (gridNode === this.creationNode)
906 contextMenu.appendItem(WebInspector.UIString("Add New"), this._startEditing.bind(this, event.target));
908 contextMenu.appendItem(WebInspector.UIString("Edit"), this._startEditing.bind(this, event.target));
910 if (this.dataGrid._deleteCallback && gridNode !== this.creationNode)
911 contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
914 contextMenu.show(event);
917 _clickInDataTable: function(event)
919 var gridNode = this.dataGridNodeFromNode(event.target);
920 if (!gridNode || !gridNode.hasChildren)
923 if (!gridNode.isEventWithinDisclosureTriangle(event))
926 if (gridNode.expanded) {
928 gridNode.collapseRecursively();
933 gridNode.expandRecursively();
941 if (typeof this._resizeMethod === "undefined")
942 return WebInspector.DataGrid.ResizeMethod.Nearest;
943 return this._resizeMethod;
946 set resizeMethod(method)
948 this._resizeMethod = method;
951 _startResizerDragging: function(event)
953 this.currentResizer = event.target;
954 if (!this.currentResizer.rightNeighboringColumnID)
956 WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this),
957 this._endResizerDragging.bind(this), event, "col-resize");
960 _resizerDragging: function(event)
962 var resizer = this.currentResizer;
966 // Constrain the dragpoint to be within the containing div of the
968 var dragPoint = event.clientX - this.element.totalOffsetLeft();
969 // Constrain the dragpoint to be within the space made up by the
970 // column directly to the left and the column directly to the right.
971 var leftCellIndex = resizer.leftNeighboringColumnID;
972 var rightCellIndex = resizer.rightNeighboringColumnID;
973 var firstRowCells = this.headerTableBody.rows[0].cells;
974 var leftEdgeOfPreviousColumn = 0;
975 for (var i = 0; i < leftCellIndex; i++)
976 leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
978 // Differences for other resize methods
979 if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) {
980 rightCellIndex = this.resizers.length;
981 } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) {
982 leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth;
986 var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth;
988 // Give each column some padding so that they don't disappear.
989 var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
990 var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
992 dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
994 resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
996 var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
997 this._headerTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
998 this._dataTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
1000 var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
1001 this._headerTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn;
1002 this._dataTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn;
1004 this._positionResizers();
1005 event.preventDefault();
1006 this.dispatchEventToListeners("width changed");
1009 _endResizerDragging: function(event)
1011 WebInspector.elementDragEnd(event);
1012 this.currentResizer = null;
1013 this.dispatchEventToListeners("width changed");
1016 ColumnResizePadding: 10,
1018 CenterResizerOverBorderAdjustment: 3,
1021 WebInspector.DataGrid.ResizeMethod = {
1027 WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;
1029 WebInspector.DataGridNode = function(data, hasChildren)
1031 this._expanded = false;
1032 this._selected = false;
1033 this._shouldRefreshChildren = true;
1034 this._data = data || {};
1035 this.hasChildren = hasChildren || false;
1037 this.dataGrid = null;
1039 this.previousSibling = null;
1040 this.nextSibling = null;
1041 this.disclosureToggleWidth = 10;
1044 WebInspector.DataGridNode.prototype = {
1050 return this._element;
1055 this._element = document.createElement("tr");
1056 this._element._dataGridNode = this;
1058 if (this.hasChildren)
1059 this._element.addStyleClass("parent");
1061 this._element.addStyleClass("expanded");
1063 this._element.addStyleClass("selected");
1065 this._element.addStyleClass("revealed");
1068 return this._element;
1071 createCells: function()
1073 for (var columnIdentifier in this.dataGrid.columns) {
1074 var cell = this.createCell(columnIdentifier);
1075 this._element.appendChild(cell);
1086 this._data = x || {};
1092 if ("_revealed" in this)
1093 return this._revealed;
1095 var currentAncestor = this.parent;
1096 while (currentAncestor && !currentAncestor.root) {
1097 if (!currentAncestor.expanded) {
1098 this._revealed = false;
1102 currentAncestor = currentAncestor.parent;
1105 this._revealed = true;
1111 if (this._hasChildren === x)
1114 this._hasChildren = x;
1119 if (this._hasChildren)
1121 this._element.addStyleClass("parent");
1123 this._element.addStyleClass("expanded");
1127 this._element.removeStyleClass("parent");
1128 this._element.removeStyleClass("expanded");
1134 return this._hasChildren;
1139 if (this._revealed === x)
1144 if (this._element) {
1146 this._element.addStyleClass("revealed");
1148 this._element.removeStyleClass("revealed");
1151 for (var i = 0; i < this.children.length; ++i)
1152 this.children[i].revealed = x && this.expanded;
1157 if ("_depth" in this)
1159 if (this.parent && !this.parent.root)
1160 this._depth = this.parent.depth + 1;
1166 get shouldRefreshChildren()
1168 return this._shouldRefreshChildren;
1171 set shouldRefreshChildren(x)
1173 this._shouldRefreshChildren = x;
1174 if (x && this.expanded)
1180 return this._selected;
1193 return this._expanded;
1206 if (!this._element || !this.dataGrid)
1209 this._element.removeChildren();
1213 createCell: function(columnIdentifier)
1215 var cell = document.createElement("td");
1216 cell.className = columnIdentifier + "-column";
1218 var alignment = this.dataGrid.aligned[columnIdentifier];
1220 cell.addStyleClass(alignment);
1222 var div = document.createElement("div");
1223 div.textContent = this.data[columnIdentifier];
1224 cell.appendChild(div);
1226 if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
1227 cell.addStyleClass("disclosure");
1229 cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
1235 // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
1236 appendChild: WebInspector.DataGrid.prototype.appendChild,
1237 insertChild: WebInspector.DataGrid.prototype.insertChild,
1238 removeChild: WebInspector.DataGrid.prototype.removeChild,
1239 removeChildren: WebInspector.DataGrid.prototype.removeChildren,
1240 removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,
1242 _recalculateSiblings: function(myIndex)
1247 var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);
1249 if (previousChild) {
1250 previousChild.nextSibling = this;
1251 this.previousSibling = previousChild;
1253 this.previousSibling = null;
1255 var nextChild = this.parent.children[myIndex + 1];
1258 nextChild.previousSibling = this;
1259 this.nextSibling = nextChild;
1261 this.nextSibling = null;
1264 collapse: function()
1267 this._element.removeStyleClass("expanded");
1269 this._expanded = false;
1271 for (var i = 0; i < this.children.length; ++i)
1272 this.children[i].revealed = false;
1274 this.dispatchEventToListeners("collapsed");
1277 collapseRecursively: function()
1283 item = item.traverseNextNode(false, this, true);
1289 if (!this.hasChildren || this.expanded)
1292 if (this.revealed && !this._shouldRefreshChildren)
1293 for (var i = 0; i < this.children.length; ++i)
1294 this.children[i].revealed = true;
1296 if (this._shouldRefreshChildren) {
1297 for (var i = 0; i < this.children.length; ++i)
1298 this.children[i]._detach();
1300 this.dispatchEventToListeners("populate");
1302 if (this._attached) {
1303 for (var i = 0; i < this.children.length; ++i) {
1304 var child = this.children[i];
1306 child.revealed = true;
1311 delete this._shouldRefreshChildren;
1315 this._element.addStyleClass("expanded");
1317 this._expanded = true;
1319 this.dispatchEventToListeners("expanded");
1322 expandRecursively: function()
1327 item = item.traverseNextNode(false, this);
1333 var currentAncestor = this.parent;
1334 while (currentAncestor && !currentAncestor.root) {
1335 if (!currentAncestor.expanded)
1336 currentAncestor.expand();
1337 currentAncestor = currentAncestor.parent;
1340 this.element.scrollIntoViewIfNeeded(false);
1342 this.dispatchEventToListeners("revealed");
1345 select: function(supressSelectedEvent)
1347 if (!this.dataGrid || !this.selectable || this.selected)
1350 if (this.dataGrid.selectedNode)
1351 this.dataGrid.selectedNode.deselect();
1353 this._selected = true;
1354 this.dataGrid.selectedNode = this;
1357 this._element.addStyleClass("selected");
1359 if (!supressSelectedEvent)
1360 this.dispatchEventToListeners("selected");
1363 revealAndSelect: function()
1369 deselect: function(supressDeselectedEvent)
1371 if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
1374 this._selected = false;
1375 this.dataGrid.selectedNode = null;
1378 this._element.removeStyleClass("selected");
1380 if (!supressDeselectedEvent)
1381 this.dispatchEventToListeners("deselected");
1384 traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
1386 if (!dontPopulate && this.hasChildren)
1387 this.dispatchEventToListeners("populate");
1390 info.depthChange = 0;
1392 var node = (!skipHidden || this.revealed) ? this.children[0] : null;
1393 if (node && (!skipHidden || this.expanded)) {
1395 info.depthChange = 1;
1399 if (this === stayWithin)
1402 node = (!skipHidden || this.revealed) ? this.nextSibling : null;
1407 while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
1409 info.depthChange -= 1;
1416 return (!skipHidden || node.revealed) ? node.nextSibling : null;
1419 traversePreviousNode: function(skipHidden, dontPopulate)
1421 var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
1422 if (!dontPopulate && node && node.hasChildren)
1423 node.dispatchEventToListeners("populate");
1425 while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
1426 if (!dontPopulate && node.hasChildren)
1427 node.dispatchEventToListeners("populate");
1428 node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
1434 if (!this.parent || this.parent.root)
1440 isEventWithinDisclosureTriangle: function(event)
1442 if (!this.hasChildren)
1444 var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
1445 if (!cell.hasStyleClass("disclosure"))
1447 var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX);
1448 var left = cell.totalOffsetLeft() + computedLeftPadding;
1449 return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
1454 if (!this.dataGrid || this._attached)
1457 this._attached = true;
1459 var nextNode = null;
1460 var previousNode = this.traversePreviousNode(true, true);
1461 if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
1462 var nextNode = previousNode.element.nextSibling;
1464 nextNode = this.dataGrid.dataTableBody.lastChild;
1465 this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);
1468 for (var i = 0; i < this.children.length; ++i)
1469 this.children[i]._attach();
1474 if (!this._attached)
1477 this._attached = false;
1479 if (this._element && this._element.parentNode)
1480 this._element.parentNode.removeChild(this._element);
1482 for (var i = 0; i < this.children.length; ++i)
1483 this.children[i]._detach();
1486 savePosition: function()
1488 if (this._savedPosition)
1492 throw("savePosition: Node must have a parent.");
1493 this._savedPosition = {
1494 parent: this.parent,
1495 index: this.parent.children.indexOf(this)
1499 restorePosition: function()
1501 if (!this._savedPosition)
1504 if (this.parent !== this._savedPosition.parent)
1505 this._savedPosition.parent.insertChild(this, this._savedPosition.index);
1507 delete this._savedPosition;
1511 WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;
1513 WebInspector.CreationDataGridNode = function(data, hasChildren)
1515 WebInspector.DataGridNode.call(this, data, hasChildren);
1516 this.isCreationNode = true;
1519 WebInspector.CreationDataGridNode.prototype = {
1520 makeNormal: function()
1522 delete this.isCreationNode;
1523 delete this.makeNormal;
1527 WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;