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.classList.add("sort-" + column.sort); 110 this._sortColumnCell = cell; 111 } 112 113 if (column.sortable) { 114 cell.addEventListener("click", this._clickInHeaderCell.bind(this), false); 115 cell.classList.add("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); 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.classList.add("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 /** 340 * @param {boolean} wasChange 341 * @this {WebInspector.DataGrid} 342 */ 343 function moveToNextIfNeeded(wasChange) { 344 if (!moveDirection) 345 return; 346 347 if (moveDirection === "forward") { 348 var firstEditableColumn = this._nextEditableColumn(-1); 349 if (currentEditingNode.isCreationNode && columnOrdinal === firstEditableColumn && !wasChange) 350 return; 351 352 var nextEditableColumn = this._nextEditableColumn(columnOrdinal); 353 if (nextEditableColumn !== -1) 354 return this._startEditingColumnOfDataGridNode(currentEditingNode, nextEditableColumn); 355 356 var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true); 357 if (nextDataGridNode) 358 return this._startEditingColumnOfDataGridNode(nextDataGridNode, firstEditableColumn); 359 if (currentEditingNode.isCreationNode && wasChange) { 360 this.addCreationNode(false); 361 return this._startEditingColumnOfDataGridNode(this.creationNode, firstEditableColumn); 362 } 363 return; 364 } 365 366 if (moveDirection === "backward") { 367 var prevEditableColumn = this._nextEditableColumn(columnOrdinal, true); 368 if (prevEditableColumn !== -1) 369 return this._startEditingColumnOfDataGridNode(currentEditingNode, prevEditableColumn); 370 371 var lastEditableColumn = this._nextEditableColumn(this._columnsArray.length, true); 372 var nextDataGridNode = currentEditingNode.traversePreviousNode(true, true); 373 if (nextDataGridNode) 374 return this._startEditingColumnOfDataGridNode(nextDataGridNode, lastEditableColumn); 375 return; 376 } 377 } 378 379 if (textBeforeEditing == newText) { 380 this._editingCancelled(element); 381 moveToNextIfNeeded.call(this, false); 382 return; 383 } 384 385 // Update the text in the datagrid that we typed 386 this._editingNode.data[columnIdentifier] = newText; 387 388 // Make the callback - expects an editing node (table row), the column number that is being edited, 389 // the text that used to be there, and the new text. 390 this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText); 391 392 if (this._editingNode.isCreationNode) 393 this.addCreationNode(false); 394 395 this._editingCancelled(element); 396 moveToNextIfNeeded.call(this, true); 397 }, 398 399 _editingCancelled: function(element) 400 { 401 delete this._editing; 402 this._editingNode = null; 403 }, 404 405 /** 406 * @param {number} columnOrdinal 407 * @param {boolean=} moveBackward 408 * @return {number} 409 */ 410 _nextEditableColumn: function(columnOrdinal, moveBackward) 411 { 412 var increment = moveBackward ? -1 : 1; 413 var columns = this._columnsArray; 414 for (var i = columnOrdinal + increment; (i >= 0) && (i < columns.length); i += increment) { 415 if (columns[i].editable) 416 return i; 417 } 418 return -1; 419 }, 420 421 /** 422 * @return {?string} 423 */ 424 sortColumnIdentifier: function() 425 { 426 if (!this._sortColumnCell) 427 return null; 428 return this._sortColumnCell.columnIdentifier; 429 }, 430 431 /** 432 * @return {?string} 433 */ 434 sortOrder: function() 435 { 436 if (!this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending")) 437 return WebInspector.DataGrid.Order.Ascending; 438 if (this._sortColumnCell.classList.contains("sort-descending")) 439 return WebInspector.DataGrid.Order.Descending; 440 return null; 441 }, 442 443 /** 444 * @return {boolean} 445 */ 446 isSortOrderAscending: function() 447 { 448 return !this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending"); 449 }, 450 451 get headerTableBody() 452 { 453 if ("_headerTableBody" in this) 454 return this._headerTableBody; 455 456 this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0]; 457 if (!this._headerTableBody) { 458 this._headerTableBody = this.element.ownerDocument.createElement("tbody"); 459 this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot); 460 } 461 462 return this._headerTableBody; 463 }, 464 465 get dataTableBody() 466 { 467 if ("_dataTableBody" in this) 468 return this._dataTableBody; 469 470 this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0]; 471 if (!this._dataTableBody) { 472 this._dataTableBody = this.element.ownerDocument.createElement("tbody"); 473 this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot); 474 } 475 476 return this._dataTableBody; 477 }, 478 479 /** 480 * @param {!Array.<number>} widths 481 * @param {number} minPercent 482 * @param {number=} maxPercent 483 * @return {!Array.<number>} 484 */ 485 _autoSizeWidths: function(widths, minPercent, maxPercent) 486 { 487 if (minPercent) 488 minPercent = Math.min(minPercent, Math.floor(100 / widths.length)); 489 var totalWidth = 0; 490 for (var i = 0; i < widths.length; ++i) 491 totalWidth += widths[i]; 492 var totalPercentWidth = 0; 493 for (var i = 0; i < widths.length; ++i) { 494 var width = Math.round(100 * widths[i] / totalWidth); 495 if (minPercent && width < minPercent) 496 width = minPercent; 497 else if (maxPercent && width > maxPercent) 498 width = maxPercent; 499 totalPercentWidth += width; 500 widths[i] = width; 501 } 502 var recoupPercent = totalPercentWidth - 100; 503 504 while (minPercent && recoupPercent > 0) { 505 for (var i = 0; i < widths.length; ++i) { 506 if (widths[i] > minPercent) { 507 --widths[i]; 508 --recoupPercent; 509 if (!recoupPercent) 510 break; 511 } 512 } 513 } 514 515 while (maxPercent && recoupPercent < 0) { 516 for (var i = 0; i < widths.length; ++i) { 517 if (widths[i] < maxPercent) { 518 ++widths[i]; 519 ++recoupPercent; 520 if (!recoupPercent) 521 break; 522 } 523 } 524 } 525 526 return widths; 527 }, 528 529 /** 530 * @param {number} minPercent 531 * @param {number=} maxPercent 532 * @param {number=} maxDescentLevel 533 */ 534 autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel) 535 { 536 var widths = []; 537 for (var i = 0; i < this._columnsArray.length; ++i) 538 widths.push((this._columnsArray[i].title || "").length); 539 540 maxDescentLevel = maxDescentLevel || 0; 541 var children = this._enumerateChildren(this._rootNode, [], maxDescentLevel + 1); 542 for (var i = 0; i < children.length; ++i) { 543 var node = children[i]; 544 for (var j = 0; j < this._columnsArray.length; ++j) { 545 var text = node.data[this._columnsArray[j].identifier] || ""; 546 if (text.length > widths[j]) 547 widths[j] = text.length; 548 } 549 } 550 551 widths = this._autoSizeWidths(widths, minPercent, maxPercent); 552 553 for (var i = 0; i < this._columnsArray.length; ++i) 554 this._columnsArray[i].element.style.width = widths[i] + "%"; 555 this._columnWidthsInitialized = false; 556 this.updateWidths(); 557 }, 558 559 _enumerateChildren: function(rootNode, result, maxLevel) 560 { 561 if (!rootNode._isRoot) 562 result.push(rootNode); 563 if (!maxLevel) 564 return; 565 for (var i = 0; i < rootNode.children.length; ++i) 566 this._enumerateChildren(rootNode.children[i], result, maxLevel - 1); 567 return result; 568 }, 569 570 onResize: function() 571 { 572 this.updateWidths(); 573 }, 574 575 // Updates the widths of the table, including the positions of the column 576 // resizers. 577 // 578 // IMPORTANT: This function MUST be called once after the element of the 579 // DataGrid is attached to its parent element and every subsequent time the 580 // width of the parent element is changed in order to make it possible to 581 // resize the columns. 582 // 583 // If this function is not called after the DataGrid is attached to its 584 // parent element, then the DataGrid's columns will not be resizable. 585 updateWidths: function() 586 { 587 var headerTableColumns = this._headerTableColumnGroup.children; 588 589 var tableWidth = this._dataTable.offsetWidth; 590 var numColumns = headerTableColumns.length - 1; // Do not process corner column. 591 592 // Do not attempt to use offsetes if we're not attached to the document tree yet. 593 if (!this._columnWidthsInitialized && this.element.offsetWidth) { 594 // Give all the columns initial widths now so that during a resize, 595 // when the two columns that get resized get a percent value for 596 // their widths, all the other columns already have percent values 597 // for their widths. 598 for (var i = 0; i < numColumns; i++) { 599 var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth; 600 var percentWidth = (100 * columnWidth / tableWidth) + "%"; 601 this._headerTableColumnGroup.children[i].style.width = percentWidth; 602 this._dataTableColumnGroup.children[i].style.width = percentWidth; 603 } 604 this._columnWidthsInitialized = true; 605 } 606 this._positionResizers(); 607 this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized); 608 }, 609 610 /** 611 * @param {string} name 612 */ 613 setName: function(name) 614 { 615 this._columnWeightsSetting = WebInspector.settings.createSetting("dataGrid-" + name + "-columnWeights", {}); 616 this._loadColumnWeights(); 617 }, 618 619 _loadColumnWeights: function() 620 { 621 if (!this._columnWeightsSetting) 622 return; 623 var weights = this._columnWeightsSetting.get(); 624 for (var i = 0; i < this._columnsArray.length; ++i) { 625 var column = this._columnsArray[i]; 626 var weight = weights[column.identifier]; 627 if (weight) 628 column.weight = weight; 629 } 630 this.applyColumnWeights(); 631 }, 632 633 _saveColumnWeights: function() 634 { 635 if (!this._columnWeightsSetting) 636 return; 637 var weights = {}; 638 for (var i = 0; i < this._columnsArray.length; ++i) { 639 var column = this._columnsArray[i]; 640 weights[column.identifier] = column.weight; 641 } 642 this._columnWeightsSetting.set(weights); 643 }, 644 645 wasShown: function() 646 { 647 this._loadColumnWeights(); 648 }, 649 650 applyColumnWeights: function() 651 { 652 var sumOfWeights = 0.0; 653 for (var i = 0; i < this._columnsArray.length; ++i) { 654 var column = this._columnsArray[i]; 655 if (this.isColumnVisible(column)) 656 sumOfWeights += column.weight; 657 } 658 659 for (var i = 0; i < this._columnsArray.length; ++i) { 660 var column = this._columnsArray[i]; 661 var width = this.isColumnVisible(column) ? (100 * column.weight / sumOfWeights) + "%" : "0%"; 662 this._headerTableColumnGroup.children[i].style.width = width; 663 this._dataTableColumnGroup.children[i].style.width = width; 664 } 665 666 this._positionResizers(); 667 this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized); 668 }, 669 670 /** 671 * @param {!WebInspector.DataGrid.ColumnDescriptor} column 672 * @return {boolean} 673 */ 674 isColumnVisible: function(column) 675 { 676 return !column.hidden; 677 }, 678 679 /** 680 * @param {string} columnIdentifier 681 * @param {boolean} visible 682 */ 683 setColumnVisible: function(columnIdentifier, visible) 684 { 685 if (visible === !this.columns[columnIdentifier].hidden) 686 return; 687 688 this.columns[columnIdentifier].hidden = !visible; 689 this.element.enableStyleClass("hide-" + columnIdentifier + "-column", !visible); 690 }, 691 692 get scrollContainer() 693 { 694 return this._scrollContainer; 695 }, 696 697 isScrolledToLastRow: function() 698 { 699 return this._scrollContainer.isScrolledToBottom(); 700 }, 701 702 scrollToLastRow: function() 703 { 704 this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight; 705 }, 706 707 _positionResizers: function() 708 { 709 var headerTableColumns = this._headerTableColumnGroup.children; 710 var numColumns = headerTableColumns.length - 1; // Do not process corner column. 711 var left = 0; 712 var previousResizer = null; 713 714 // Make n - 1 resizers for n columns. 715 for (var i = 0; i < numColumns - 1; i++) { 716 var resizer = this.resizers[i]; 717 718 if (!resizer) { 719 // This is the first call to updateWidth, so the resizers need 720 // to be created. 721 resizer = document.createElement("div"); 722 resizer.classList.add("data-grid-resizer"); 723 // This resizer is associated with the column to its right. 724 WebInspector.installDragHandle(resizer, this._startResizerDragging.bind(this), this._resizerDragging.bind(this), this._endResizerDragging.bind(this), "col-resize"); 725 this.element.appendChild(resizer); 726 this.resizers[i] = resizer; 727 } 728 729 // Get the width of the cell in the first (and only) row of the 730 // header table in order to determine the width of the column, since 731 // it is not possible to query a column for its width. 732 left += this.headerTableBody.rows[0].cells[i].offsetWidth; 733 734 if (!this._columnsArray[i].hidden) { 735 resizer.style.removeProperty("display"); 736 if (resizer._position !== left) { 737 resizer._position = left; 738 resizer.style.left = left + "px"; 739 } 740 resizer.leftNeighboringColumnIndex = i; 741 if (previousResizer) 742 previousResizer.rightNeighboringColumnIndex = i; 743 previousResizer = resizer; 744 } else { 745 if (previousResizer && previousResizer._position !== left) { 746 previousResizer._position = left; 747 previousResizer.style.left = left + "px"; 748 } 749 resizer.style.setProperty("display", "none"); 750 resizer.leftNeighboringColumnIndex = 0; 751 resizer.rightNeighboringColumnIndex = 0; 752 } 753 } 754 if (previousResizer) 755 previousResizer.rightNeighboringColumnIndex = numColumns - 1; 756 }, 757 758 addCreationNode: function(hasChildren) 759 { 760 if (this.creationNode) 761 this.creationNode.makeNormal(); 762 763 var emptyData = {}; 764 for (var column in this.columns) 765 emptyData[column] = null; 766 this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren); 767 this.rootNode().appendChild(this.creationNode); 768 }, 769 770 sortNodes: function(comparator, reverseMode) 771 { 772 function comparatorWrapper(a, b) 773 { 774 if (a._dataGridNode._data.summaryRow) 775 return 1; 776 if (b._dataGridNode._data.summaryRow) 777 return -1; 778 779 var aDataGirdNode = a._dataGridNode; 780 var bDataGirdNode = b._dataGridNode; 781 return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode); 782 } 783 784 var tbody = this.dataTableBody; 785 var tbodyParent = tbody.parentElement; 786 tbodyParent.removeChild(tbody); 787 788 var childNodes = tbody.childNodes; 789 var fillerRow = childNodes[childNodes.length - 1]; 790 791 var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); 792 sortedRows.sort(comparatorWrapper); 793 var sortedRowsLength = sortedRows.length; 794 795 tbody.removeChildren(); 796 var previousSiblingNode = null; 797 for (var i = 0; i < sortedRowsLength; ++i) { 798 var row = sortedRows[i]; 799 var node = row._dataGridNode; 800 node.previousSibling = previousSiblingNode; 801 if (previousSiblingNode) 802 previousSiblingNode.nextSibling = node; 803 tbody.appendChild(row); 804 previousSiblingNode = node; 805 } 806 if (previousSiblingNode) 807 previousSiblingNode.nextSibling = null; 808 809 tbody.appendChild(fillerRow); 810 tbodyParent.appendChild(tbody); 811 }, 812 813 _keyDown: function(event) 814 { 815 if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing) 816 return; 817 818 var handled = false; 819 var nextSelectedNode; 820 if (event.keyIdentifier === "Up" && !event.altKey) { 821 nextSelectedNode = this.selectedNode.traversePreviousNode(true); 822 while (nextSelectedNode && !nextSelectedNode.selectable) 823 nextSelectedNode = nextSelectedNode.traversePreviousNode(true); 824 handled = nextSelectedNode ? true : false; 825 } else if (event.keyIdentifier === "Down" && !event.altKey) { 826 nextSelectedNode = this.selectedNode.traverseNextNode(true); 827 while (nextSelectedNode && !nextSelectedNode.selectable) 828 nextSelectedNode = nextSelectedNode.traverseNextNode(true); 829 handled = nextSelectedNode ? true : false; 830 } else if (event.keyIdentifier === "Left") { 831 if (this.selectedNode.expanded) { 832 if (event.altKey) 833 this.selectedNode.collapseRecursively(); 834 else 835 this.selectedNode.collapse(); 836 handled = true; 837 } else if (this.selectedNode.parent && !this.selectedNode.parent._isRoot) { 838 handled = true; 839 if (this.selectedNode.parent.selectable) { 840 nextSelectedNode = this.selectedNode.parent; 841 handled = nextSelectedNode ? true : false; 842 } else if (this.selectedNode.parent) 843 this.selectedNode.parent.collapse(); 844 } 845 } else if (event.keyIdentifier === "Right") { 846 if (!this.selectedNode.revealed) { 847 this.selectedNode.reveal(); 848 handled = true; 849 } else if (this.selectedNode.hasChildren) { 850 handled = true; 851 if (this.selectedNode.expanded) { 852 nextSelectedNode = this.selectedNode.children[0]; 853 handled = nextSelectedNode ? true : false; 854 } else { 855 if (event.altKey) 856 this.selectedNode.expandRecursively(); 857 else 858 this.selectedNode.expand(); 859 } 860 } 861 } else if (event.keyCode === 8 || event.keyCode === 46) { 862 if (this._deleteCallback) { 863 handled = true; 864 this._deleteCallback(this.selectedNode); 865 this.changeNodeAfterDeletion(); 866 } 867 } else if (isEnterKey(event)) { 868 if (this._editCallback) { 869 handled = true; 870 this._startEditing(this.selectedNode._element.children[this._nextEditableColumn(-1)]); 871 } 872 } 873 874 if (nextSelectedNode) { 875 nextSelectedNode.reveal(); 876 nextSelectedNode.select(); 877 } 878 879 if (handled) 880 event.consume(true); 881 }, 882 883 changeNodeAfterDeletion: function() 884 { 885 var nextSelectedNode = this.selectedNode.traverseNextNode(true); 886 while (nextSelectedNode && !nextSelectedNode.selectable) 887 nextSelectedNode = nextSelectedNode.traverseNextNode(true); 888 889 if (!nextSelectedNode || nextSelectedNode.isCreationNode) { 890 nextSelectedNode = this.selectedNode.traversePreviousNode(true); 891 while (nextSelectedNode && !nextSelectedNode.selectable) 892 nextSelectedNode = nextSelectedNode.traversePreviousNode(true); 893 } 894 895 if (nextSelectedNode) { 896 nextSelectedNode.reveal(); 897 nextSelectedNode.select(); 898 } 899 }, 900 901 /** 902 * @param {!Node} target 903 * @return {?WebInspector.DataGridNode} 904 */ 905 dataGridNodeFromNode: function(target) 906 { 907 var rowElement = target.enclosingNodeOrSelfWithNodeName("tr"); 908 return rowElement && rowElement._dataGridNode; 909 }, 910 911 /** 912 * @param {!Node} target 913 * @return {?string} 914 */ 915 columnIdentifierFromNode: function(target) 916 { 917 var cellElement = target.enclosingNodeOrSelfWithNodeName("td"); 918 return cellElement && cellElement.columnIdentifier_; 919 }, 920 921 _clickInHeaderCell: function(event) 922 { 923 var cell = event.target.enclosingNodeOrSelfWithNodeName("th"); 924 if (!cell || (typeof cell.columnIdentifier === "undefined") || !cell.classList.contains("sortable")) 925 return; 926 927 var sortOrder = WebInspector.DataGrid.Order.Ascending; 928 if ((cell === this._sortColumnCell) && this.isSortOrderAscending()) 929 sortOrder = WebInspector.DataGrid.Order.Descending; 930 931 if (this._sortColumnCell) 932 this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+"); 933 this._sortColumnCell = cell; 934 935 cell.classList.add("sort-" + sortOrder); 936 937 this.dispatchEventToListeners(WebInspector.DataGrid.Events.SortingChanged); 938 }, 939 940 /** 941 * @param {string} columnIdentifier 942 * @param {!WebInspector.DataGrid.Order} sortOrder 943 */ 944 markColumnAsSortedBy: function(columnIdentifier, sortOrder) 945 { 946 if (this._sortColumnCell) 947 this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+"); 948 this._sortColumnCell = this._headerTableHeaders[columnIdentifier]; 949 this._sortColumnCell.classList.add("sort-" + sortOrder); 950 }, 951 952 headerTableHeader: function(columnIdentifier) 953 { 954 return this._headerTableHeaders[columnIdentifier]; 955 }, 956 957 _mouseDownInDataTable: function(event) 958 { 959 var gridNode = this.dataGridNodeFromNode(event.target); 960 if (!gridNode || !gridNode.selectable) 961 return; 962 963 if (gridNode.isEventWithinDisclosureTriangle(event)) 964 return; 965 966 if (event.metaKey) { 967 if (gridNode.selected) 968 gridNode.deselect(); 969 else 970 gridNode.select(); 971 } else 972 gridNode.select(); 973 }, 974 975 _contextMenuInDataTable: function(event) 976 { 977 var contextMenu = new WebInspector.ContextMenu(event); 978 979 var gridNode = this.dataGridNodeFromNode(event.target); 980 if (this._refreshCallback && (!gridNode || gridNode !== this.creationNode)) 981 contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this)); 982 983 if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) { 984 // FIXME: Use the column names for Editing, instead of just "Edit". 985 if (this._editCallback) { 986 if (gridNode === this.creationNode) 987 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add new" : "Add New"), this._startEditing.bind(this, event.target)); 988 else { 989 var columnIdentifier = this.columnIdentifierFromNode(event.target); 990 if (columnIdentifier && this.columns[columnIdentifier].editable) 991 contextMenu.appendItem(WebInspector.UIString("Edit"), this._startEditing.bind(this, event.target)); 992 } 993 } 994 if (this._deleteCallback && gridNode !== this.creationNode) 995 contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode)); 996 if (this._contextMenuCallback) 997 this._contextMenuCallback(contextMenu, gridNode); 998 } 999 1000 contextMenu.show(); 1001 }, 1002 1003 _clickInDataTable: function(event) 1004 { 1005 var gridNode = this.dataGridNodeFromNode(event.target); 1006 if (!gridNode || !gridNode.hasChildren) 1007 return; 1008 1009 if (!gridNode.isEventWithinDisclosureTriangle(event)) 1010 return; 1011 1012 if (gridNode.expanded) { 1013 if (event.altKey) 1014 gridNode.collapseRecursively(); 1015 else 1016 gridNode.collapse(); 1017 } else { 1018 if (event.altKey) 1019 gridNode.expandRecursively(); 1020 else 1021 gridNode.expand(); 1022 } 1023 }, 1024 1025 get resizeMethod() 1026 { 1027 if (typeof this._resizeMethod === "undefined") 1028 return WebInspector.DataGrid.ResizeMethod.Nearest; 1029 return this._resizeMethod; 1030 }, 1031 1032 set resizeMethod(method) 1033 { 1034 this._resizeMethod = method; 1035 }, 1036 1037 /** 1038 * @return {boolean} 1039 */ 1040 _startResizerDragging: function(event) 1041 { 1042 this._currentResizer = event.target; 1043 return !!this._currentResizer.rightNeighboringColumnIndex; 1044 }, 1045 1046 _resizerDragging: function(event) 1047 { 1048 var resizer = this._currentResizer; 1049 if (!resizer) 1050 return; 1051 1052 var tableWidth = this._dataTable.offsetWidth; // Cache it early, before we invalidate layout. 1053 1054 // Constrain the dragpoint to be within the containing div of the 1055 // datagrid. 1056 var dragPoint = event.clientX - this.element.totalOffsetLeft(); 1057 // Constrain the dragpoint to be within the space made up by the 1058 // column directly to the left and the column directly to the right. 1059 var leftCellIndex = resizer.leftNeighboringColumnIndex; 1060 var rightCellIndex = resizer.rightNeighboringColumnIndex; 1061 var firstRowCells = this.headerTableBody.rows[0].cells; 1062 var leftEdgeOfPreviousColumn = 0; 1063 for (var i = 0; i < leftCellIndex; i++) 1064 leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth; 1065 1066 // Differences for other resize methods 1067 if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) { 1068 rightCellIndex = this.resizers.length; 1069 } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) { 1070 leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth; 1071 leftCellIndex = 0; 1072 } 1073 1074 var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth; 1075 1076 // Give each column some padding so that they don't disappear. 1077 var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding; 1078 var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding; 1079 if (leftMinimum > rightMaximum) 1080 return; 1081 1082 dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum); 1083 1084 resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px"; 1085 1086 var percentLeftColumn = (100 * (dragPoint - leftEdgeOfPreviousColumn) / tableWidth) + "%"; 1087 this._headerTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn; 1088 this._dataTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn; 1089 1090 var percentRightColumn = (100 * (rightEdgeOfNextColumn - dragPoint) / tableWidth) + "%"; 1091 this._headerTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn; 1092 this._dataTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn; 1093 1094 var leftColumn = this._columnsArray[leftCellIndex]; 1095 var rightColumn = this._columnsArray[rightCellIndex]; 1096 if (leftColumn.weight || rightColumn.weight) { 1097 var sumOfWeights = leftColumn.weight + rightColumn.weight; 1098 var delta = rightEdgeOfNextColumn - leftEdgeOfPreviousColumn; 1099 leftColumn.weight = (dragPoint - leftEdgeOfPreviousColumn) * sumOfWeights / delta; 1100 rightColumn.weight = (rightEdgeOfNextColumn - dragPoint) * sumOfWeights / delta; 1101 } 1102 1103 this._positionResizers(); 1104 event.preventDefault(); 1105 this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized); 1106 }, 1107 1108 _endResizerDragging: function(event) 1109 { 1110 this._currentResizer = null; 1111 this._saveColumnWeights(); 1112 this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized); 1113 }, 1114 1115 ColumnResizePadding: 24, 1116 1117 CenterResizerOverBorderAdjustment: 3, 1118 1119 __proto__: WebInspector.View.prototype 1120 } 1121 1122 WebInspector.DataGrid.ResizeMethod = { 1123 Nearest: "nearest", 1124 First: "first", 1125 Last: "last" 1126 } 1127 1128 /** 1129 * @constructor 1130 * @extends {WebInspector.Object} 1131 * @param {?Object.<string, *>=} data 1132 * @param {boolean=} hasChildren 1133 */ 1134 WebInspector.DataGridNode = function(data, hasChildren) 1135 { 1136 this._expanded = false; 1137 this._selected = false; 1138 this._shouldRefreshChildren = true; 1139 /** @type {!Object.<string, *>} */ 1140 this._data = data || {}; 1141 /** @type {boolean} */ 1142 this.hasChildren = hasChildren || false; 1143 /** @type {!Array.<!WebInspector.DataGridNode>} */ 1144 this.children = []; 1145 this.dataGrid = null; 1146 this.parent = null; 1147 /** @type {?WebInspector.DataGridNode} */ 1148 this.previousSibling = null; 1149 /** @type {?WebInspector.DataGridNode} */ 1150 this.nextSibling = null; 1151 this.disclosureToggleWidth = 10; 1152 } 1153 1154 WebInspector.DataGridNode.prototype = { 1155 /** @type {boolean} */ 1156 selectable: true, 1157 1158 /** @type {boolean} */ 1159 _isRoot: false, 1160 1161 get element() 1162 { 1163 if (this._element) 1164 return this._element; 1165 1166 if (!this.dataGrid) 1167 return null; 1168 1169 this._element = document.createElement("tr"); 1170 this._element._dataGridNode = this; 1171 1172 if (this.hasChildren) 1173 this._element.classList.add("parent"); 1174 if (this.expanded) 1175 this._element.classList.add("expanded"); 1176 if (this.selected) 1177 this._element.classList.add("selected"); 1178 if (this.revealed) 1179 this._element.classList.add("revealed"); 1180 1181 this.createCells(); 1182 this._element.createChild("td", "corner"); 1183 1184 return this._element; 1185 }, 1186 1187 createCells: function() 1188 { 1189 var columnsArray = this.dataGrid._columnsArray; 1190 for (var i = 0; i < columnsArray.length; ++i) { 1191 var cell = this.createCell(columnsArray[i].identifier); 1192 this._element.appendChild(cell); 1193 } 1194 }, 1195 1196 get data() 1197 { 1198 return this._data; 1199 }, 1200 1201 set data(x) 1202 { 1203 this._data = x || {}; 1204 this.refresh(); 1205 }, 1206 1207 get revealed() 1208 { 1209 if ("_revealed" in this) 1210 return this._revealed; 1211 1212 var currentAncestor = this.parent; 1213 while (currentAncestor && !currentAncestor._isRoot) { 1214 if (!currentAncestor.expanded) { 1215 this._revealed = false; 1216 return false; 1217 } 1218 1219 currentAncestor = currentAncestor.parent; 1220 } 1221 1222 this._revealed = true; 1223 return true; 1224 }, 1225 1226 set hasChildren(x) 1227 { 1228 if (this._hasChildren === x) 1229 return; 1230 1231 this._hasChildren = x; 1232 1233 if (!this._element) 1234 return; 1235 1236 this._element.enableStyleClass("parent", this._hasChildren); 1237 this._element.enableStyleClass("expanded", this._hasChildren && this.expanded); 1238 }, 1239 1240 get hasChildren() 1241 { 1242 return this._hasChildren; 1243 }, 1244 1245 set revealed(x) 1246 { 1247 if (this._revealed === x) 1248 return; 1249 1250 this._revealed = x; 1251 1252 if (this._element) 1253 this._element.enableStyleClass("revealed", this._revealed); 1254 1255 for (var i = 0; i < this.children.length; ++i) 1256 this.children[i].revealed = x && this.expanded; 1257 }, 1258 1259 get depth() 1260 { 1261 if ("_depth" in this) 1262 return this._depth; 1263 if (this.parent && !this.parent._isRoot) 1264 this._depth = this.parent.depth + 1; 1265 else 1266 this._depth = 0; 1267 return this._depth; 1268 }, 1269 1270 get leftPadding() 1271 { 1272 if (typeof this._leftPadding === "number") 1273 return this._leftPadding; 1274 1275 this._leftPadding = this.depth * this.dataGrid.indentWidth; 1276 return this._leftPadding; 1277 }, 1278 1279 get shouldRefreshChildren() 1280 { 1281 return this._shouldRefreshChildren; 1282 }, 1283 1284 set shouldRefreshChildren(x) 1285 { 1286 this._shouldRefreshChildren = x; 1287 if (x && this.expanded) 1288 this.expand(); 1289 }, 1290 1291 get selected() 1292 { 1293 return this._selected; 1294 }, 1295 1296 set selected(x) 1297 { 1298 if (x) 1299 this.select(); 1300 else 1301 this.deselect(); 1302 }, 1303 1304 get expanded() 1305 { 1306 return this._expanded; 1307 }, 1308 1309 /** 1310 * @param {boolean} x 1311 */ 1312 set expanded(x) 1313 { 1314 if (x) 1315 this.expand(); 1316 else 1317 this.collapse(); 1318 }, 1319 1320 refresh: function() 1321 { 1322 if (!this._element || !this.dataGrid) 1323 return; 1324 1325 this._element.removeChildren(); 1326 this.createCells(); 1327 this._element.createChild("td", "corner"); 1328 }, 1329 1330 /** 1331 * @param {string} columnIdentifier 1332 * @return {!Element} 1333 */ 1334 createTD: function(columnIdentifier) 1335 { 1336 var cell = document.createElement("td"); 1337 cell.className = columnIdentifier + "-column"; 1338 cell.columnIdentifier_ = columnIdentifier; 1339 1340 var alignment = this.dataGrid.columns[columnIdentifier].align; 1341 if (alignment) 1342 cell.classList.add(alignment); 1343 1344 return cell; 1345 }, 1346 1347 /** 1348 * @param {string} columnIdentifier 1349 * @return {!Element} 1350 */ 1351 createCell: function(columnIdentifier) 1352 { 1353 var cell = this.createTD(columnIdentifier); 1354 1355 var data = this.data[columnIdentifier]; 1356 var div = document.createElement("div"); 1357 if (data instanceof Node) 1358 div.appendChild(data); 1359 else { 1360 div.textContent = data; 1361 if (this.dataGrid.columns[columnIdentifier].longText) 1362 div.title = data; 1363 } 1364 cell.appendChild(div); 1365 1366 if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) { 1367 cell.classList.add("disclosure"); 1368 if (this.leftPadding) 1369 cell.style.setProperty("padding-left", this.leftPadding + "px"); 1370 } 1371 1372 return cell; 1373 }, 1374 1375 /** 1376 * @return {number} 1377 */ 1378 nodeHeight: function() 1379 { 1380 var rowHeight = 16; 1381 if (!this.revealed) 1382 return 0; 1383 if (!this.expanded) 1384 return rowHeight; 1385 var result = rowHeight; 1386 for (var i = 0; i < this.children.length; i++) 1387 result += this.children[i].nodeHeight(); 1388 return result; 1389 }, 1390 1391 /** 1392 * @param {!WebInspector.DataGridNode} child 1393 */ 1394 appendChild: function(child) 1395 { 1396 this.insertChild(child, this.children.length); 1397 }, 1398 1399 /** 1400 * @param {!WebInspector.DataGridNode} child 1401 * @param {number} index 1402 */ 1403 insertChild: function(child, index) 1404 { 1405 if (!child) 1406 throw("insertChild: Node can't be undefined or null."); 1407 if (child.parent === this) 1408 throw("insertChild: Node is already a child of this node."); 1409 1410 if (child.parent) 1411 child.parent.removeChild(child); 1412 1413 this.children.splice(index, 0, child); 1414 this.hasChildren = true; 1415 1416 child.parent = this; 1417 child.dataGrid = this.dataGrid; 1418 child._recalculateSiblings(index); 1419 1420 delete child._depth; 1421 delete child._revealed; 1422 delete child._attached; 1423 child._shouldRefreshChildren = true; 1424 1425 var current = child.children[0]; 1426 while (current) { 1427 current.dataGrid = this.dataGrid; 1428 delete current._depth; 1429 delete current._revealed; 1430 delete current._attached; 1431 current._shouldRefreshChildren = true; 1432 current = current.traverseNextNode(false, child, true); 1433 } 1434 1435 if (this.expanded) 1436 child._attach(); 1437 if (!this.revealed) 1438 child.revealed = false; 1439 }, 1440 1441 /** 1442 * @param {!WebInspector.DataGridNode} child 1443 */ 1444 removeChild: function(child) 1445 { 1446 if (!child) 1447 throw("removeChild: Node can't be undefined or null."); 1448 if (child.parent !== this) 1449 throw("removeChild: Node is not a child of this node."); 1450 1451 child.deselect(); 1452 child._detach(); 1453 1454 this.children.remove(child, true); 1455 1456 if (child.previousSibling) 1457 child.previousSibling.nextSibling = child.nextSibling; 1458 if (child.nextSibling) 1459 child.nextSibling.previousSibling = child.previousSibling; 1460 1461 child.dataGrid = null; 1462 child.parent = null; 1463 child.nextSibling = null; 1464 child.previousSibling = null; 1465 1466 if (this.children.length <= 0) 1467 this.hasChildren = false; 1468 }, 1469 1470 removeChildren: function() 1471 { 1472 for (var i = 0; i < this.children.length; ++i) { 1473 var child = this.children[i]; 1474 child.deselect(); 1475 child._detach(); 1476 1477 child.dataGrid = null; 1478 child.parent = null; 1479 child.nextSibling = null; 1480 child.previousSibling = null; 1481 } 1482 1483 this.children = []; 1484 this.hasChildren = false; 1485 }, 1486 1487 /** 1488 * @param {number} myIndex 1489 */ 1490 _recalculateSiblings: function(myIndex) 1491 { 1492 if (!this.parent) 1493 return; 1494 1495 var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null); 1496 1497 if (previousChild) { 1498 previousChild.nextSibling = this; 1499 this.previousSibling = previousChild; 1500 } else 1501 this.previousSibling = null; 1502 1503 var nextChild = this.parent.children[myIndex + 1]; 1504 1505 if (nextChild) { 1506 nextChild.previousSibling = this; 1507 this.nextSibling = nextChild; 1508 } else 1509 this.nextSibling = null; 1510 }, 1511 1512 collapse: function() 1513 { 1514 if (this._isRoot) 1515 return; 1516 if (this._element) 1517 this._element.classList.remove("expanded"); 1518 1519 this._expanded = false; 1520 1521 for (var i = 0; i < this.children.length; ++i) 1522 this.children[i].revealed = false; 1523 }, 1524 1525 collapseRecursively: function() 1526 { 1527 var item = this; 1528 while (item) { 1529 if (item.expanded) 1530 item.collapse(); 1531 item = item.traverseNextNode(false, this, true); 1532 } 1533 }, 1534 1535 populate: function() { }, 1536 1537 expand: function() 1538 { 1539 if (!this.hasChildren || this.expanded) 1540 return; 1541 if (this._isRoot) 1542 return; 1543 1544 if (this.revealed && !this._shouldRefreshChildren) 1545 for (var i = 0; i < this.children.length; ++i) 1546 this.children[i].revealed = true; 1547 1548 if (this._shouldRefreshChildren) { 1549 for (var i = 0; i < this.children.length; ++i) 1550 this.children[i]._detach(); 1551 1552 this.populate(); 1553 1554 if (this._attached) { 1555 for (var i = 0; i < this.children.length; ++i) { 1556 var child = this.children[i]; 1557 if (this.revealed) 1558 child.revealed = true; 1559 child._attach(); 1560 } 1561 } 1562 1563 delete this._shouldRefreshChildren; 1564 } 1565 1566 if (this._element) 1567 this._element.classList.add("expanded"); 1568 1569 this._expanded = true; 1570 }, 1571 1572 expandRecursively: function() 1573 { 1574 var item = this; 1575 while (item) { 1576 item.expand(); 1577 item = item.traverseNextNode(false, this); 1578 } 1579 }, 1580 1581 reveal: function() 1582 { 1583 if (this._isRoot) 1584 return; 1585 var currentAncestor = this.parent; 1586 while (currentAncestor && !currentAncestor._isRoot) { 1587 if (!currentAncestor.expanded) 1588 currentAncestor.expand(); 1589 currentAncestor = currentAncestor.parent; 1590 } 1591 1592 this.element.scrollIntoViewIfNeeded(false); 1593 }, 1594 1595 /** 1596 * @param {boolean=} supressSelectedEvent 1597 */ 1598 select: function(supressSelectedEvent) 1599 { 1600 if (!this.dataGrid || !this.selectable || this.selected) 1601 return; 1602 1603 if (this.dataGrid.selectedNode) 1604 this.dataGrid.selectedNode.deselect(); 1605 1606 this._selected = true; 1607 this.dataGrid.selectedNode = this; 1608 1609 if (this._element) 1610 this._element.classList.add("selected"); 1611 1612 if (!supressSelectedEvent) 1613 this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Events.SelectedNode); 1614 }, 1615 1616 revealAndSelect: function() 1617 { 1618 if (this._isRoot) 1619 return; 1620 this.reveal(); 1621 this.select(); 1622 }, 1623 1624 /** 1625 * @param {boolean=} supressDeselectedEvent 1626 */ 1627 deselect: function(supressDeselectedEvent) 1628 { 1629 if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected) 1630 return; 1631 1632 this._selected = false; 1633 this.dataGrid.selectedNode = null; 1634 1635 if (this._element) 1636 this._element.classList.remove("selected"); 1637 1638 if (!supressDeselectedEvent) 1639 this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Events.DeselectedNode); 1640 }, 1641 1642 /** 1643 * @param {boolean} skipHidden 1644 * @param {?WebInspector.DataGridNode=} stayWithin 1645 * @param {boolean=} dontPopulate 1646 * @param {!Object=} info 1647 * @return {?WebInspector.DataGridNode} 1648 */ 1649 traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info) 1650 { 1651 if (!dontPopulate && this.hasChildren) 1652 this.populate(); 1653 1654 if (info) 1655 info.depthChange = 0; 1656 1657 var node = (!skipHidden || this.revealed) ? this.children[0] : null; 1658 if (node && (!skipHidden || this.expanded)) { 1659 if (info) 1660 info.depthChange = 1; 1661 return node; 1662 } 1663 1664 if (this === stayWithin) 1665 return null; 1666 1667 node = (!skipHidden || this.revealed) ? this.nextSibling : null; 1668 if (node) 1669 return node; 1670 1671 node = this; 1672 while (node && !node._isRoot && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) { 1673 if (info) 1674 info.depthChange -= 1; 1675 node = node.parent; 1676 } 1677 1678 if (!node) 1679 return null; 1680 1681 return (!skipHidden || node.revealed) ? node.nextSibling : null; 1682 }, 1683 1684 /** 1685 * @param {boolean} skipHidden 1686 * @param {boolean=} dontPopulate 1687 * @return {?WebInspector.DataGridNode} 1688 */ 1689 traversePreviousNode: function(skipHidden, dontPopulate) 1690 { 1691 var node = (!skipHidden || this.revealed) ? this.previousSibling : null; 1692 if (!dontPopulate && node && node.hasChildren) 1693 node.populate(); 1694 1695 while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) { 1696 if (!dontPopulate && node.hasChildren) 1697 node.populate(); 1698 node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null); 1699 } 1700 1701 if (node) 1702 return node; 1703 1704 if (!this.parent || this.parent._isRoot) 1705 return null; 1706 1707 return this.parent; 1708 }, 1709 1710 /** 1711 * @return {boolean} 1712 */ 1713 isEventWithinDisclosureTriangle: function(event) 1714 { 1715 if (!this.hasChildren) 1716 return false; 1717 var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); 1718 if (!cell.classList.contains("disclosure")) 1719 return false; 1720 1721 var left = cell.totalOffsetLeft() + this.leftPadding; 1722 return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth; 1723 }, 1724 1725 _attach: function() 1726 { 1727 if (!this.dataGrid || this._attached) 1728 return; 1729 1730 this._attached = true; 1731 1732 var nextNode = null; 1733 var previousNode = this.traversePreviousNode(true, true); 1734 if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling) 1735 nextNode = previousNode.element.nextSibling; 1736 if (!nextNode) 1737 nextNode = this.dataGrid.dataTableBody.firstChild; 1738 this.dataGrid.dataTableBody.insertBefore(this.element, nextNode); 1739 1740 if (this.expanded) 1741 for (var i = 0; i < this.children.length; ++i) 1742 this.children[i]._attach(); 1743 }, 1744 1745 _detach: function() 1746 { 1747 if (!this._attached) 1748 return; 1749 1750 this._attached = false; 1751 1752 if (this._element) 1753 this._element.remove(); 1754 1755 for (var i = 0; i < this.children.length; ++i) 1756 this.children[i]._detach(); 1757 1758 this.wasDetached(); 1759 }, 1760 1761 wasDetached: function() 1762 { 1763 }, 1764 1765 savePosition: function() 1766 { 1767 if (this._savedPosition) 1768 return; 1769 1770 if (!this.parent) 1771 throw("savePosition: Node must have a parent."); 1772 this._savedPosition = { 1773 parent: this.parent, 1774 index: this.parent.children.indexOf(this) 1775 }; 1776 }, 1777 1778 restorePosition: function() 1779 { 1780 if (!this._savedPosition) 1781 return; 1782 1783 if (this.parent !== this._savedPosition.parent) 1784 this._savedPosition.parent.insertChild(this, this._savedPosition.index); 1785 1786 delete this._savedPosition; 1787 }, 1788 1789 __proto__: WebInspector.Object.prototype 1790 } 1791 1792 /** 1793 * @constructor 1794 * @extends {WebInspector.DataGridNode} 1795 */ 1796 WebInspector.CreationDataGridNode = function(data, hasChildren) 1797 { 1798 WebInspector.DataGridNode.call(this, data, hasChildren); 1799 this.isCreationNode = true; 1800 } 1801 1802 WebInspector.CreationDataGridNode.prototype = { 1803 makeNormal: function() 1804 { 1805 delete this.isCreationNode; 1806 delete this.makeNormal; 1807 }, 1808 1809 __proto__: WebInspector.DataGridNode.prototype 1810 } 1811