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