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