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