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 26WebInspector.DataGrid = function(columns) 27{ 28 this.element = document.createElement("div"); 29 this.element.className = "data-grid"; 30 this.element.tabIndex = 0; 31 this.element.addEventListener("keydown", this._keyDown.bind(this), false); 32 33 this._headerTable = document.createElement("table"); 34 this._headerTable.className = "header"; 35 36 this._dataTable = document.createElement("table"); 37 this._dataTable.className = "data"; 38 39 this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true); 40 this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true); 41 42 var scrollContainer = document.createElement("div"); 43 scrollContainer.className = "data-container"; 44 scrollContainer.appendChild(this._dataTable); 45 46 this.element.appendChild(this._headerTable); 47 this.element.appendChild(scrollContainer); 48 49 var headerRow = document.createElement("tr"); 50 var columnGroup = document.createElement("colgroup"); 51 var columnCount = 0; 52 53 for (var columnIdentifier in columns) { 54 var column = columns[columnIdentifier]; 55 if (column.disclosure) 56 this.disclosureColumnIdentifier = columnIdentifier; 57 58 var col = document.createElement("col"); 59 if (column.width) 60 col.style.width = column.width; 61 columnGroup.appendChild(col); 62 63 var cell = document.createElement("th"); 64 cell.className = columnIdentifier + "-column"; 65 cell.columnIdentifier = columnIdentifier; 66 67 var div = document.createElement("div"); 68 div.textContent = column.title; 69 cell.appendChild(div); 70 71 if (column.sort) { 72 cell.addStyleClass("sort-" + column.sort); 73 this._sortColumnCell = cell; 74 } 75 76 if (column.sortable) { 77 cell.addEventListener("click", this._clickInHeaderCell.bind(this), false); 78 cell.addStyleClass("sortable"); 79 } 80 81 headerRow.appendChild(cell); 82 83 ++columnCount; 84 } 85 86 columnGroup.span = columnCount; 87 88 var cell = document.createElement("th"); 89 cell.className = "corner"; 90 headerRow.appendChild(cell); 91 92 this._headerTableColumnGroup = columnGroup; 93 this._headerTable.appendChild(this._headerTableColumnGroup); 94 this.headerTableBody.appendChild(headerRow); 95 96 var fillerRow = document.createElement("tr"); 97 fillerRow.className = "filler"; 98 99 for (var i = 0; i < columnCount; ++i) { 100 var cell = document.createElement("td"); 101 fillerRow.appendChild(cell); 102 } 103 104 this._dataTableColumnGroup = columnGroup.cloneNode(true); 105 this._dataTable.appendChild(this._dataTableColumnGroup); 106 this.dataTableBody.appendChild(fillerRow); 107 108 this.columns = columns || {}; 109 this.children = []; 110 this.selectedNode = null; 111 this.expandNodesWhenArrowing = false; 112 this.root = true; 113 this.hasChildren = false; 114 this.expanded = true; 115 this.revealed = true; 116 this.selected = false; 117 this.dataGrid = this; 118 this.indentWidth = 15; 119 this.resizers = []; 120 this.columnWidthsInitialized = false; 121} 122 123WebInspector.DataGrid.prototype = { 124 get sortColumnIdentifier() 125 { 126 if (!this._sortColumnCell) 127 return null; 128 return this._sortColumnCell.columnIdentifier; 129 }, 130 131 get sortOrder() 132 { 133 if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending")) 134 return "ascending"; 135 if (this._sortColumnCell.hasStyleClass("sort-descending")) 136 return "descending"; 137 return null; 138 }, 139 140 get headerTableBody() 141 { 142 if ("_headerTableBody" in this) 143 return this._headerTableBody; 144 145 this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0]; 146 if (!this._headerTableBody) { 147 this._headerTableBody = this.element.ownerDocument.createElement("tbody"); 148 this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot); 149 } 150 151 return this._headerTableBody; 152 }, 153 154 get dataTableBody() 155 { 156 if ("_dataTableBody" in this) 157 return this._dataTableBody; 158 159 this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0]; 160 if (!this._dataTableBody) { 161 this._dataTableBody = this.element.ownerDocument.createElement("tbody"); 162 this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot); 163 } 164 165 return this._dataTableBody; 166 }, 167 168 // Updates the widths of the table, including the positions of the column 169 // resizers. 170 // 171 // IMPORTANT: This function MUST be called once after the element of the 172 // DataGrid is attached to its parent element and every subsequent time the 173 // width of the parent element is changed in order to make it possible to 174 // resize the columns. 175 // 176 // If this function is not called after the DataGrid is attached to its 177 // parent element, then the DataGrid's columns will not be resizable. 178 updateWidths: function() 179 { 180 var headerTableColumns = this._headerTableColumnGroup.children; 181 182 var left = 0; 183 var tableWidth = this._dataTable.offsetWidth; 184 var numColumns = headerTableColumns.length; 185 186 if (!this.columnWidthsInitialized) { 187 // Give all the columns initial widths now so that during a resize, 188 // when the two columns that get resized get a percent value for 189 // their widths, all the other columns already have percent values 190 // for their widths. 191 for (var i = 0; i < numColumns; i++) { 192 var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth; 193 var percentWidth = ((columnWidth / tableWidth) * 100) + "%"; 194 this._headerTableColumnGroup.children[i].style.width = percentWidth; 195 this._dataTableColumnGroup.children[i].style.width = percentWidth; 196 } 197 this.columnWidthsInitialized = true; 198 } 199 200 // Make n - 1 resizers for n columns. 201 for (var i = 0; i < numColumns - 1; i++) { 202 var resizer = this.resizers[i]; 203 204 if (!resizer) { 205 // This is the first call to updateWidth, so the resizers need 206 // to be created. 207 resizer = document.createElement("div"); 208 resizer.addStyleClass("data-grid-resizer"); 209 // This resizer is associated with the column to its right. 210 resizer.rightNeighboringColumnID = i + 1; 211 resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false); 212 this.element.appendChild(resizer); 213 this.resizers[i] = resizer; 214 } 215 216 // Get the width of the cell in the first (and only) row of the 217 // header table in order to determine the width of the column, since 218 // it is not possible to query a column for its width. 219 left += this.headerTableBody.rows[0].cells[i].offsetWidth; 220 221 resizer.style.left = left + "px"; 222 } 223 }, 224 225 addCreationNode: function(hasChildren) 226 { 227 if (this.creationNode) 228 this.creationNode.makeNormal(); 229 230 var emptyData = {}; 231 for (var column in this.columns) 232 emptyData[column] = ''; 233 this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren); 234 this.appendChild(this.creationNode); 235 }, 236 237 appendChild: function(child) 238 { 239 this.insertChild(child, this.children.length); 240 }, 241 242 insertChild: function(child, index) 243 { 244 if (!child) 245 throw("insertChild: Node can't be undefined or null."); 246 if (child.parent === this) 247 throw("insertChild: Node is already a child of this node."); 248 249 if (child.parent) 250 child.parent.removeChild(child); 251 252 this.children.splice(index, 0, child); 253 this.hasChildren = true; 254 255 child.parent = this; 256 child.dataGrid = this.dataGrid; 257 child._recalculateSiblings(index); 258 259 delete child._depth; 260 delete child._revealed; 261 delete child._attached; 262 263 var current = child.children[0]; 264 while (current) { 265 current.dataGrid = this.dataGrid; 266 delete current._depth; 267 delete current._revealed; 268 delete current._attached; 269 current = current.traverseNextNode(false, child, true); 270 } 271 272 if (this.expanded) 273 child._attach(); 274 }, 275 276 removeChild: function(child) 277 { 278 if (!child) 279 throw("removeChild: Node can't be undefined or null."); 280 if (child.parent !== this) 281 throw("removeChild: Node is not a child of this node."); 282 283 child.deselect(); 284 285 this.children.remove(child, true); 286 287 if (child.previousSibling) 288 child.previousSibling.nextSibling = child.nextSibling; 289 if (child.nextSibling) 290 child.nextSibling.previousSibling = child.previousSibling; 291 292 child.dataGrid = null; 293 child.parent = null; 294 child.nextSibling = null; 295 child.previousSibling = null; 296 297 if (this.children.length <= 0) 298 this.hasChildren = false; 299 }, 300 301 removeChildren: function() 302 { 303 for (var i = 0; i < this.children.length; ++i) { 304 var child = this.children[i]; 305 child.deselect(); 306 child._detach(); 307 308 child.dataGrid = null; 309 child.parent = null; 310 child.nextSibling = null; 311 child.previousSibling = null; 312 } 313 314 this.children = []; 315 this.hasChildren = false; 316 }, 317 318 removeChildrenRecursive: function() 319 { 320 var childrenToRemove = this.children; 321 322 var child = this.children[0]; 323 while (child) { 324 if (child.children.length) 325 childrenToRemove = childrenToRemove.concat(child.children); 326 child = child.traverseNextNode(false, this, true); 327 } 328 329 for (var i = 0; i < childrenToRemove.length; ++i) { 330 var child = childrenToRemove[i]; 331 child.deselect(); 332 child._detach(); 333 334 child.children = []; 335 child.dataGrid = null; 336 child.parent = null; 337 child.nextSibling = null; 338 child.previousSibling = null; 339 } 340 341 this.children = []; 342 }, 343 344 handleKeyEvent: function(event) 345 { 346 if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey) 347 return false; 348 349 var handled = false; 350 var nextSelectedNode; 351 if (event.keyIdentifier === "Up" && !event.altKey) { 352 nextSelectedNode = this.selectedNode.traversePreviousNode(true); 353 while (nextSelectedNode && !nextSelectedNode.selectable) 354 nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing); 355 handled = nextSelectedNode ? true : false; 356 } else if (event.keyIdentifier === "Down" && !event.altKey) { 357 nextSelectedNode = this.selectedNode.traverseNextNode(true); 358 while (nextSelectedNode && !nextSelectedNode.selectable) 359 nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing); 360 handled = nextSelectedNode ? true : false; 361 } else if (event.keyIdentifier === "Left") { 362 if (this.selectedNode.expanded) { 363 if (event.altKey) 364 this.selectedNode.collapseRecursively(); 365 else 366 this.selectedNode.collapse(); 367 handled = true; 368 } else if (this.selectedNode.parent && !this.selectedNode.parent.root) { 369 handled = true; 370 if (this.selectedNode.parent.selectable) { 371 nextSelectedNode = this.selectedNode.parent; 372 handled = nextSelectedNode ? true : false; 373 } else if (this.selectedNode.parent) 374 this.selectedNode.parent.collapse(); 375 } 376 } else if (event.keyIdentifier === "Right") { 377 if (!this.selectedNode.revealed) { 378 this.selectedNode.reveal(); 379 handled = true; 380 } else if (this.selectedNode.hasChildren) { 381 handled = true; 382 if (this.selectedNode.expanded) { 383 nextSelectedNode = this.selectedNode.children[0]; 384 handled = nextSelectedNode ? true : false; 385 } else { 386 if (event.altKey) 387 this.selectedNode.expandRecursively(); 388 else 389 this.selectedNode.expand(); 390 } 391 } 392 } 393 394 if (nextSelectedNode) { 395 nextSelectedNode.reveal(); 396 nextSelectedNode.select(); 397 } 398 399 if (handled) { 400 event.preventDefault(); 401 event.stopPropagation(); 402 } 403 404 return handled; 405 }, 406 407 expand: function() 408 { 409 // This is the root, do nothing. 410 }, 411 412 collapse: function() 413 { 414 // This is the root, do nothing. 415 }, 416 417 reveal: function() 418 { 419 // This is the root, do nothing. 420 }, 421 422 dataGridNodeFromEvent: function(event) 423 { 424 var rowElement = event.target.enclosingNodeOrSelfWithNodeName("tr"); 425 return rowElement._dataGridNode; 426 }, 427 428 dataGridNodeFromPoint: function(x, y) 429 { 430 var node = this._dataTable.ownerDocument.elementFromPoint(x, y); 431 var rowElement = node.enclosingNodeOrSelfWithNodeName("tr"); 432 return rowElement._dataGridNode; 433 }, 434 435 _keyDown: function(event) 436 { 437 this.handleKeyEvent(event); 438 }, 439 440 _clickInHeaderCell: function(event) 441 { 442 var cell = event.target.enclosingNodeOrSelfWithNodeName("th"); 443 if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable")) 444 return; 445 446 var sortOrder = this.sortOrder; 447 448 if (this._sortColumnCell) { 449 this._sortColumnCell.removeStyleClass("sort-ascending"); 450 this._sortColumnCell.removeStyleClass("sort-descending"); 451 } 452 453 if (cell == this._sortColumnCell) { 454 if (sortOrder == "ascending") 455 sortOrder = "descending"; 456 else 457 sortOrder = "ascending"; 458 } 459 460 this._sortColumnCell = cell; 461 462 cell.addStyleClass("sort-" + sortOrder); 463 464 this.dispatchEventToListeners("sorting changed"); 465 }, 466 467 _mouseDownInDataTable: function(event) 468 { 469 var gridNode = this.dataGridNodeFromEvent(event); 470 if (!gridNode || !gridNode.selectable) 471 return; 472 473 if (gridNode.isEventWithinDisclosureTriangle(event)) 474 return; 475 476 if (event.metaKey) { 477 if (gridNode.selected) 478 gridNode.deselect(); 479 else 480 gridNode.select(); 481 } else 482 gridNode.select(); 483 }, 484 485 _clickInDataTable: function(event) 486 { 487 var gridNode = this.dataGridNodeFromEvent(event); 488 if (!gridNode || !gridNode.hasChildren) 489 return; 490 491 if (!gridNode.isEventWithinDisclosureTriangle(event)) 492 return; 493 494 if (gridNode.expanded) { 495 if (event.altKey) 496 gridNode.collapseRecursively(); 497 else 498 gridNode.collapse(); 499 } else { 500 if (event.altKey) 501 gridNode.expandRecursively(); 502 else 503 gridNode.expand(); 504 } 505 }, 506 507 _startResizerDragging: function(event) 508 { 509 this.currentResizer = event.target; 510 if (!this.currentResizer.rightNeighboringColumnID) 511 return; 512 WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this), 513 this._endResizerDragging.bind(this), event, "col-resize"); 514 }, 515 516 _resizerDragging: function(event) 517 { 518 var resizer = this.currentResizer; 519 if (!resizer) 520 return; 521 522 // Constrain the dragpoint to be within the containing div of the 523 // datagrid. 524 var dragPoint = event.clientX - this.element.totalOffsetLeft; 525 // Constrain the dragpoint to be within the space made up by the 526 // column directly to the left and the column directly to the right. 527 var leftEdgeOfPreviousColumn = 0; 528 var firstRowCells = this.headerTableBody.rows[0].cells; 529 for (var i = 0; i < resizer.rightNeighboringColumnID - 1; i++) 530 leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth; 531 532 var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.rightNeighboringColumnID - 1].offsetWidth + firstRowCells[resizer.rightNeighboringColumnID].offsetWidth; 533 534 // Give each column some padding so that they don't disappear. 535 var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding; 536 var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding; 537 538 dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum); 539 540 resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px"; 541 542 var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%"; 543 this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn; 544 this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn; 545 546 var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%"; 547 this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn; 548 this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn; 549 550 event.preventDefault(); 551 }, 552 553 _endResizerDragging: function(event) 554 { 555 WebInspector.elementDragEnd(event); 556 this.currentResizer = null; 557 }, 558 559 ColumnResizePadding: 10, 560 561 CenterResizerOverBorderAdjustment: 3, 562} 563 564WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype; 565 566WebInspector.DataGridNode = function(data, hasChildren) 567{ 568 this._expanded = false; 569 this._selected = false; 570 this._shouldRefreshChildren = true; 571 this._data = data || {}; 572 this.hasChildren = hasChildren || false; 573 this.children = []; 574 this.dataGrid = null; 575 this.parent = null; 576 this.previousSibling = null; 577 this.nextSibling = null; 578 this.disclosureToggleWidth = 10; 579} 580 581WebInspector.DataGridNode.prototype = { 582 selectable: true, 583 584 get element() 585 { 586 if (this._element) 587 return this._element; 588 589 if (!this.dataGrid) 590 return null; 591 592 this._element = document.createElement("tr"); 593 this._element._dataGridNode = this; 594 595 if (this.hasChildren) 596 this._element.addStyleClass("parent"); 597 if (this.expanded) 598 this._element.addStyleClass("expanded"); 599 if (this.selected) 600 this._element.addStyleClass("selected"); 601 if (this.revealed) 602 this._element.addStyleClass("revealed"); 603 604 for (var columnIdentifier in this.dataGrid.columns) { 605 var cell = this.createCell(columnIdentifier); 606 this._element.appendChild(cell); 607 } 608 609 return this._element; 610 }, 611 612 get data() 613 { 614 return this._data; 615 }, 616 617 set data(x) 618 { 619 this._data = x || {}; 620 this.refresh(); 621 }, 622 623 get revealed() 624 { 625 if ("_revealed" in this) 626 return this._revealed; 627 628 var currentAncestor = this.parent; 629 while (currentAncestor && !currentAncestor.root) { 630 if (!currentAncestor.expanded) { 631 this._revealed = false; 632 return false; 633 } 634 635 currentAncestor = currentAncestor.parent; 636 } 637 638 this._revealed = true; 639 return true; 640 }, 641 642 set hasChildren(x) 643 { 644 if (this._hasChildren === x) 645 return; 646 647 this._hasChildren = x; 648 649 if (!this._element) 650 return; 651 652 if (this._hasChildren) 653 { 654 this._element.addStyleClass("parent"); 655 if (this.expanded) 656 this._element.addStyleClass("expanded"); 657 } 658 else 659 { 660 this._element.removeStyleClass("parent"); 661 this._element.removeStyleClass("expanded"); 662 } 663 }, 664 665 get hasChildren() 666 { 667 return this._hasChildren; 668 }, 669 670 set revealed(x) 671 { 672 if (this._revealed === x) 673 return; 674 675 this._revealed = x; 676 677 if (this._element) { 678 if (this._revealed) 679 this._element.addStyleClass("revealed"); 680 else 681 this._element.removeStyleClass("revealed"); 682 } 683 684 for (var i = 0; i < this.children.length; ++i) 685 this.children[i].revealed = x && this.expanded; 686 }, 687 688 get depth() 689 { 690 if ("_depth" in this) 691 return this._depth; 692 if (this.parent && !this.parent.root) 693 this._depth = this.parent.depth + 1; 694 else 695 this._depth = 0; 696 return this._depth; 697 }, 698 699 get shouldRefreshChildren() 700 { 701 return this._shouldRefreshChildren; 702 }, 703 704 set shouldRefreshChildren(x) 705 { 706 this._shouldRefreshChildren = x; 707 if (x && this.expanded) 708 this.expand(); 709 }, 710 711 get selected() 712 { 713 return this._selected; 714 }, 715 716 set selected(x) 717 { 718 if (x) 719 this.select(); 720 else 721 this.deselect(); 722 }, 723 724 get expanded() 725 { 726 return this._expanded; 727 }, 728 729 set expanded(x) 730 { 731 if (x) 732 this.expand(); 733 else 734 this.collapse(); 735 }, 736 737 refresh: function() 738 { 739 if (!this._element || !this.dataGrid) 740 return; 741 742 this._element.removeChildren(); 743 744 for (var columnIdentifier in this.dataGrid.columns) { 745 var cell = this.createCell(columnIdentifier); 746 this._element.appendChild(cell); 747 } 748 }, 749 750 createCell: function(columnIdentifier) 751 { 752 var cell = document.createElement("td"); 753 cell.className = columnIdentifier + "-column"; 754 755 var div = document.createElement("div"); 756 div.textContent = this.data[columnIdentifier]; 757 cell.appendChild(div); 758 759 if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) { 760 cell.addStyleClass("disclosure"); 761 if (this.depth) 762 cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); 763 } 764 765 return cell; 766 }, 767 768 // Share these functions with DataGrid. They are written to work with a DataGridNode this object. 769 appendChild: WebInspector.DataGrid.prototype.appendChild, 770 insertChild: WebInspector.DataGrid.prototype.insertChild, 771 removeChild: WebInspector.DataGrid.prototype.removeChild, 772 removeChildren: WebInspector.DataGrid.prototype.removeChildren, 773 removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive, 774 775 _recalculateSiblings: function(myIndex) 776 { 777 if (!this.parent) 778 return; 779 780 var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null); 781 782 if (previousChild) { 783 previousChild.nextSibling = this; 784 this.previousSibling = previousChild; 785 } else 786 this.previousSibling = null; 787 788 var nextChild = this.parent.children[myIndex + 1]; 789 790 if (nextChild) { 791 nextChild.previousSibling = this; 792 this.nextSibling = nextChild; 793 } else 794 this.nextSibling = null; 795 }, 796 797 collapse: function() 798 { 799 if (this._element) 800 this._element.removeStyleClass("expanded"); 801 802 this._expanded = false; 803 804 for (var i = 0; i < this.children.length; ++i) 805 this.children[i].revealed = false; 806 807 this.dispatchEventToListeners("collapsed"); 808 }, 809 810 collapseRecursively: function() 811 { 812 var item = this; 813 while (item) { 814 if (item.expanded) 815 item.collapse(); 816 item = item.traverseNextNode(false, this, true); 817 } 818 }, 819 820 expand: function() 821 { 822 if (!this.hasChildren || this.expanded) 823 return; 824 825 if (this.revealed && !this._shouldRefreshChildren) 826 for (var i = 0; i < this.children.length; ++i) 827 this.children[i].revealed = true; 828 829 if (this._shouldRefreshChildren) { 830 for (var i = 0; i < this.children.length; ++i) 831 this.children[i]._detach(); 832 833 this.dispatchEventToListeners("populate"); 834 835 if (this._attached) { 836 for (var i = 0; i < this.children.length; ++i) { 837 var child = this.children[i]; 838 if (this.revealed) 839 child.revealed = true; 840 child._attach(); 841 } 842 } 843 844 delete this._shouldRefreshChildren; 845 } 846 847 if (this._element) 848 this._element.addStyleClass("expanded"); 849 850 this._expanded = true; 851 852 this.dispatchEventToListeners("expanded"); 853 }, 854 855 expandRecursively: function() 856 { 857 var item = this; 858 while (item) { 859 item.expand(); 860 item = item.traverseNextNode(false, this); 861 } 862 }, 863 864 reveal: function() 865 { 866 var currentAncestor = this.parent; 867 while (currentAncestor && !currentAncestor.root) { 868 if (!currentAncestor.expanded) 869 currentAncestor.expand(); 870 currentAncestor = currentAncestor.parent; 871 } 872 873 this.element.scrollIntoViewIfNeeded(false); 874 875 this.dispatchEventToListeners("revealed"); 876 }, 877 878 select: function(supressSelectedEvent) 879 { 880 if (!this.dataGrid || !this.selectable || this.selected) 881 return; 882 883 if (this.dataGrid.selectedNode) 884 this.dataGrid.selectedNode.deselect(); 885 886 this._selected = true; 887 this.dataGrid.selectedNode = this; 888 889 if (this._element) 890 this._element.addStyleClass("selected"); 891 892 if (!supressSelectedEvent) 893 this.dispatchEventToListeners("selected"); 894 }, 895 896 deselect: function(supressDeselectedEvent) 897 { 898 if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected) 899 return; 900 901 this._selected = false; 902 this.dataGrid.selectedNode = null; 903 904 if (this._element) 905 this._element.removeStyleClass("selected"); 906 907 if (!supressDeselectedEvent) 908 this.dispatchEventToListeners("deselected"); 909 }, 910 911 traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info) 912 { 913 if (!dontPopulate && this.hasChildren) 914 this.dispatchEventToListeners("populate"); 915 916 if (info) 917 info.depthChange = 0; 918 919 var node = (!skipHidden || this.revealed) ? this.children[0] : null; 920 if (node && (!skipHidden || this.expanded)) { 921 if (info) 922 info.depthChange = 1; 923 return node; 924 } 925 926 if (this === stayWithin) 927 return null; 928 929 node = (!skipHidden || this.revealed) ? this.nextSibling : null; 930 if (node) 931 return node; 932 933 node = this; 934 while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) { 935 if (info) 936 info.depthChange -= 1; 937 node = node.parent; 938 } 939 940 if (!node) 941 return null; 942 943 return (!skipHidden || node.revealed) ? node.nextSibling : null; 944 }, 945 946 traversePreviousNode: function(skipHidden, dontPopulate) 947 { 948 var node = (!skipHidden || this.revealed) ? this.previousSibling : null; 949 if (!dontPopulate && node && node.hasChildren) 950 node.dispatchEventToListeners("populate"); 951 952 while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) { 953 if (!dontPopulate && node.hasChildren) 954 node.dispatchEventToListeners("populate"); 955 node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null); 956 } 957 958 if (node) 959 return node; 960 961 if (!this.parent || this.parent.root) 962 return null; 963 964 return this.parent; 965 }, 966 967 isEventWithinDisclosureTriangle: function(event) 968 { 969 if (!this.hasChildren) 970 return false; 971 var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); 972 if (!cell.hasStyleClass("disclosure")) 973 return false; 974 var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX); 975 var left = cell.totalOffsetLeft + computedLeftPadding; 976 return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth; 977 }, 978 979 _attach: function() 980 { 981 if (!this.dataGrid || this._attached) 982 return; 983 984 this._attached = true; 985 986 var nextNode = null; 987 var previousNode = this.traversePreviousNode(true, true); 988 if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling) 989 var nextNode = previousNode.element.nextSibling; 990 if (!nextNode) 991 nextNode = this.dataGrid.dataTableBody.lastChild; 992 this.dataGrid.dataTableBody.insertBefore(this.element, nextNode); 993 994 if (this.expanded) 995 for (var i = 0; i < this.children.length; ++i) 996 this.children[i]._attach(); 997 }, 998 999 _detach: function() 1000 { 1001 if (!this._attached) 1002 return; 1003 1004 this._attached = false; 1005 1006 if (this._element && this._element.parentNode) 1007 this._element.parentNode.removeChild(this._element); 1008 1009 for (var i = 0; i < this.children.length; ++i) 1010 this.children[i]._detach(); 1011 } 1012} 1013 1014WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype; 1015 1016WebInspector.CreationDataGridNode = function(data, hasChildren) 1017{ 1018 WebInspector.DataGridNode.call(this, data, hasChildren); 1019 this.isCreationNode = true; 1020} 1021 1022WebInspector.CreationDataGridNode.prototype = { 1023 makeNormal: function() 1024 { 1025 delete this.isCreationNode; 1026 delete this.makeNormal; 1027 } 1028} 1029 1030WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; 1031