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