1/* 2 * jQuery treetable Plugin 3.2.0 3 * http://ludo.cubicphuse.nl/jquery-treetable 4 * 5 * Copyright 2013, Ludo van den Boom 6 * Dual licensed under the MIT or GPL Version 2 licenses. 7 */ 8(function($) { 9 var Node, Tree, methods; 10 11 Node = (function() { 12 function Node(row, tree, settings) { 13 var parentId; 14 15 this.row = row; 16 this.tree = tree; 17 this.settings = settings; 18 19 // TODO Ensure id/parentId is always a string (not int) 20 this.id = this.row.data(this.settings.nodeIdAttr); 21 22 // TODO Move this to a setParentId function? 23 parentId = this.row.data(this.settings.parentIdAttr); 24 if (parentId != null && parentId !== "") { 25 this.parentId = parentId; 26 } 27 28 this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]); 29 this.expander = $(this.settings.expanderTemplate); 30 this.indenter = $(this.settings.indenterTemplate); 31 this.children = []; 32 this.initialized = false; 33 this.treeCell.prepend(this.indenter); 34 } 35 36 Node.prototype.addChild = function(child) { 37 return this.children.push(child); 38 }; 39 40 Node.prototype.ancestors = function() { 41 var ancestors, node; 42 node = this; 43 ancestors = []; 44 while (node = node.parentNode()) { 45 ancestors.push(node); 46 } 47 return ancestors; 48 }; 49 50 Node.prototype.collapse = function() { 51 if (this.collapsed()) { 52 return this; 53 } 54 55 this.row.removeClass("expanded").addClass("collapsed"); 56 57 this._hideChildren(); 58 this.expander.attr("title", this.settings.stringExpand); 59 60 if (this.initialized && this.settings.onNodeCollapse != null) { 61 this.settings.onNodeCollapse.apply(this); 62 } 63 64 return this; 65 }; 66 67 Node.prototype.collapsed = function() { 68 return this.row.hasClass("collapsed"); 69 }; 70 71 // TODO destroy: remove event handlers, expander, indenter, etc. 72 73 Node.prototype.expand = function() { 74 if (this.expanded()) { 75 return this; 76 } 77 78 this.row.removeClass("collapsed").addClass("expanded"); 79 80 if (this.initialized && this.settings.onNodeExpand != null) { 81 this.settings.onNodeExpand.apply(this); 82 } 83 84 if ($(this.row).is(":visible")) { 85 this._showChildren(); 86 } 87 88 this.expander.attr("title", this.settings.stringCollapse); 89 90 return this; 91 }; 92 93 Node.prototype.expanded = function() { 94 return this.row.hasClass("expanded"); 95 }; 96 97 Node.prototype.hide = function() { 98 this._hideChildren(); 99 this.row.hide(); 100 return this; 101 }; 102 103 Node.prototype.isBranchNode = function() { 104 if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) { 105 return true; 106 } else { 107 return false; 108 } 109 }; 110 111 Node.prototype.updateBranchLeafClass = function(){ 112 this.row.removeClass('branch'); 113 this.row.removeClass('leaf'); 114 this.row.addClass(this.isBranchNode() ? 'branch' : 'leaf'); 115 }; 116 117 Node.prototype.level = function() { 118 return this.ancestors().length; 119 }; 120 121 Node.prototype.parentNode = function() { 122 if (this.parentId != null) { 123 return this.tree[this.parentId]; 124 } else { 125 return null; 126 } 127 }; 128 129 Node.prototype.removeChild = function(child) { 130 var i = $.inArray(child, this.children); 131 return this.children.splice(i, 1) 132 }; 133 134 Node.prototype.render = function() { 135 var handler, 136 settings = this.settings, 137 target; 138 139 if (settings.expandable === true && this.isBranchNode()) { 140 handler = function(e) { 141 $(this).parents("table").treetable("node", $(this).parents("tr").data(settings.nodeIdAttr)).toggle(); 142 return e.preventDefault(); 143 }; 144 145 this.indenter.html(this.expander); 146 target = settings.clickableNodeNames === true ? this.treeCell : this.expander; 147 148 target.off("click.treetable").on("click.treetable", handler); 149 target.off("keydown.treetable").on("keydown.treetable", function(e) { 150 if (e.keyCode == 13) { 151 handler.apply(this, [e]); 152 } 153 }); 154 } 155 156 this.indenter[0].style.paddingLeft = "" + (this.level() * settings.indent) + "px"; 157 158 return this; 159 }; 160 161 Node.prototype.reveal = function() { 162 if (this.parentId != null) { 163 this.parentNode().reveal(); 164 } 165 return this.expand(); 166 }; 167 168 Node.prototype.setParent = function(node) { 169 if (this.parentId != null) { 170 this.tree[this.parentId].removeChild(this); 171 } 172 this.parentId = node.id; 173 this.row.data(this.settings.parentIdAttr, node.id); 174 return node.addChild(this); 175 }; 176 177 Node.prototype.show = function() { 178 if (!this.initialized) { 179 this._initialize(); 180 } 181 this.row.show(); 182 if (this.expanded()) { 183 this._showChildren();
184 } 185 return this; 186 }; 187 188 Node.prototype.toggle = function() { 189 if (this.expanded()) { 190 this.collapse(); 191 } else { 192 this.expand(); 193 } 194 return this; 195 }; 196 197 Node.prototype._hideChildren = function() { 198 var child, _i, _len, _ref, _results; 199 _ref = this.children; 200 _results = []; 201 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 202 child = _ref[_i]; 203 _results.push(child.hide()); 204 } 205 return _results; 206 }; 207 208 Node.prototype._initialize = function() { 209 var settings = this.settings; 210 211 this.render(); 212 213 if (settings.expandable === true && settings.initialState === "collapsed") { 214 this.collapse(); 215 } else { 216 this.expand(); 217 } 218 219 if (settings.onNodeInitialized != null) { 220 settings.onNodeInitialized.apply(this); 221 } 222 223 return this.initialized = true; 224 }; 225 226 Node.prototype._showChildren = function() { 227 var child, _i, _len, _ref, _results; 228 _ref = this.children; 229 _results = []; 230 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 231 child = _ref[_i]; 232 _results.push(child.show()); 233 } 234 return _results; 235 }; 236 237 return Node; 238 })();
239 240 Tree = (function() { 241 function Tree(table, settings) { 242 this.table = table; 243 this.settings = settings; 244 this.tree = {}; 245 246 // Cache the nodes and roots in simple arrays for quick access/iteration 247 this.nodes = []; 248 this.roots = []; 249 } 250 251 Tree.prototype.collapseAll = function() { 252 var node, _i, _len, _ref, _results; 253 _ref = this.nodes; 254 _results = []; 255 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 256 node = _ref[_i]; 257 if (typeof node !== "undefined") { 258 _results.push(node.collapse()); 259 } 260 } 261 return _results; 262 }; 263 264 Tree.prototype.expandAll = function() { 265 var node, _i, _len, _ref, _results; 266 _ref = this.nodes; 267 _results = []; 268 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 269 node = _ref[_i]; 270 _results.push(node.expand()); 271 } 272 return _results; 273 }; 274 275 Tree.prototype.findLastNode = function (node) { 276 if (node.children.length > 0) { 277 return this.findLastNode(node.children[node.children.length - 1]); 278 } else { 279 return node; 280 } 281 }; 282 283 Tree.prototype.loadRows = function(rows) { 284 var node, row, i; 285 286 if (rows != null) { 287 for (i = 0; i < rows.length; i++) { 288 row = $(rows[i]); 289 290 if (row.data(this.settings.nodeIdAttr) != null) { 291 node = new Node(row, this.tree, this.settings); 292 this.nodes.push(node); 293 this.tree[node.id] = node; 294 295 if (node.parentId != null && this.tree[node.parentId]) { 296 this.tree[node.parentId].addChild(node); 297 } else { 298 this.roots.push(node); 299 } 300 } 301 } 302 } 303 304 for (i = 0; i < this.nodes.length; i++) { 305 node = this.nodes[i].updateBranchLeafClass(); 306 } 307 308 return this; 309 }; 310 311 Tree.prototype.move = function(node, destination) { 312 // Conditions: 313 // 1: +node+ should not be inserted as a child of +node+ itself. 314 // 2: +destination+ should not be the same as +node+'s current parent (this 315 // prevents +node+ from being moved to the same location where it already 316 // is). 317 // 3: +node+ should not be inserted in a location in a branch if this would 318 // result in +node+ being an ancestor of itself. 319 var nodeParent = node.parentNode(); 320 if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) { 321 node.setParent(destination); 322 this._moveRows(node, destination); 323 324 // Re-render parentNode if this is its first child node, and therefore 325 // doesn't have the expander yet. 326 if (node.parentNode().children.length === 1) { 327 node.parentNode().render(); 328 } 329 } 330 331 if(nodeParent){ 332 nodeParent.updateBranchLeafClass(); 333 } 334 if(node.parentNode()){ 335 node.parentNode().updateBranchLeafClass(); 336 } 337 node.updateBranchLeafClass(); 338 return this; 339 }; 340 341 Tree.prototype.removeNode = function(node) { 342 // Recursively remove all descendants of +node+ 343 this.unloadBranch(node); 344 345 // Remove node from DOM (<tr>) 346 node.row.remove(); 347 348 // Remove node from parent children list 349 if (node.parentId != null) { 350 node.parentNode().removeChild(node); 351 } 352 353 // Clean up Tree object (so Node objects are GC-ed) 354 delete this.tree[node.id]; 355 this.nodes.splice($.inArray(node, this.nodes), 1); 356 357 return this; 358 } 359 360 Tree.prototype.render = function() { 361 var root, _i, _len, _ref; 362 _ref = this.roots; 363 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 364 root = _ref[_i]; 365 366 // Naming is confusing (show/render). I do not call render on node from 367 // here. 368 root.show(); 369 } 370 return this; 371 }; 372 373 Tree.prototype.sortBranch = function(node, sortFun) { 374 // First sort internal array of children 375 node.children.sort(sortFun); 376 377 // Next render rows in correct order on page 378 this._sortChildRows(node); 379 380 return this; 381 }; 382 383 Tree.prototype.unloadBranch = function(node) { 384 // Use a copy of the children array to not have other functions interfere 385 // with this function if they manipulate the children array 386 // (eg removeNode). 387 var children = node.children.slice(0), 388 i; 389 390 for (i = 0; i < children.length; i++) { 391 this.removeNode(children[i]); 392 } 393 394 // Reset node's collection of children 395 node.children = []; 396 397 node.updateBranchLeafClass(); 398 399 return this; 400 }; 401 402 Tree.prototype._moveRows = function(node, destination) { 403 var children = node.children, i; 404 405 node.row.insertAfter(destination.row); 406 node.render(); 407 408 // Loop backwards through children to have them end up on UI in correct 409 // order (see #112) 410 for (i = children.length - 1; i >= 0; i--) { 411 this._moveRows(children[i], node); 412 } 413 }; 414 415 // Special _moveRows case, move children to itself to force sorting 416 Tree.prototype._sortChildRows = function(parentNode) { 417 return this._moveRows(parentNode, parentNode); 418 }; 419 420 return Tree; 421 })();
422 423 // jQuery Plugin 424 methods = { 425 init: function(options, force) { 426 var settings; 427 428 settings = $.extend({ 429 branchAttr: "ttBranch", 430 clickableNodeNames: false, 431 column: 0, 432 columnElType: "td", // i.e. 'td', 'th' or 'td,th' 433 expandable: false, 434 expanderTemplate: "<a href='#'> </a>", 435 indent: 10, 436 indenterTemplate: "<span class='indenter'></span>", 437 initialState: "collapsed", 438 nodeIdAttr: "ttId", // maps to data-tt-id 439 parentIdAttr: "ttParentId", // maps to data-tt-parent-id 440 stringExpand: "Expand", 441 stringCollapse: "Collapse", 442 443 // Events 444 onInitialized: null, 445 onNodeCollapse: null, 446 onNodeExpand: null, 447 onNodeInitialized: null 448 }, options); 449 450 return this.each(function() { 451 var el = $(this), tree; 452 453 if (force || el.data("treetable") === undefined) { 454 tree = new Tree(this, settings); 455 tree.loadRows(this.rows).render(); 456 457 el.addClass("treetable").data("treetable", tree); 458 459 if (settings.onInitialized != null) { 460 settings.onInitialized.apply(tree); 461 } 462 } 463 464 return el; 465 }); 466 }, 467 468 destroy: function() { 469 return this.each(function() { 470 return $(this).removeData("treetable").removeClass("treetable"); 471 }); 472 }, 473 474 collapseAll: function() { 475 this.data("treetable").collapseAll(); 476 return this; 477 }, 478 479 collapseNode: function(id) { 480 var node = this.data("treetable").tree[id]; 481 482 if (node) { 483 node.collapse(); 484 } else { 485 throw new Error("Unknown node '" + id + "'"); 486 } 487 488 return this; 489 }, 490 491 expandAll: function() { 492 this.data("treetable").expandAll(); 493 return this; 494 }, 495 496 expandNode: function(id) { 497 var node = this.data("treetable").tree[id]; 498 499 if (node) { 500 if (!node.initialized) { 501 node._initialize(); 502 } 503 504 node.expand(); 505 } else { 506 throw new Error("Unknown node '" + id + "'"); 507 } 508 509 return this; 510 }, 511 512 loadBranch: function(node, rows) { 513 var settings = this.data("treetable").settings, 514 tree = this.data("treetable").tree; 515 516 // TODO Switch to $.parseHTML 517 rows = $(rows); 518 519 if (node == null) { // Inserting new root nodes 520 this.append(rows); 521 } else { 522 var lastNode = this.data("treetable").findLastNode(node); 523 rows.insertAfter(lastNode.row); 524 } 525 526 this.data("treetable").loadRows(rows); 527 528 // Make sure nodes are properly initialized 529 rows.filter("tr").each(function() { 530 tree[$(this).data(settings.nodeIdAttr)].show(); 531 }); 532 533 if (node != null) { 534 // Re-render parent to ensure expander icon is shown (#79) 535 node.render().expand(); 536 } 537 538 return this; 539 }, 540 541 move: function(nodeId, destinationId) { 542 var destination, node; 543 544 node = this.data("treetable").tree[nodeId]; 545 destination = this.data("treetable").tree[destinationId]; 546 this.data("treetable").move(node, destination); 547 548 return this; 549 }, 550 551 node: function(id) { 552 return this.data("treetable").tree[id]; 553 }, 554 555 removeNode: function(id) { 556 var node = this.data("treetable").tree[id]; 557 558 if (node) { 559 this.data("treetable").removeNode(node); 560 } else { 561 throw new Error("Unknown node '" + id + "'"); 562 } 563 564 return this; 565 }, 566 567 reveal: function(id) { 568 var node = this.data("treetable").tree[id]; 569 570 if (node) { 571 node.reveal(); 572 } else { 573 throw new Error("Unknown node '" + id + "'"); 574 } 575 576 return this; 577 }, 578 579 sortBranch: function(node, columnOrFunction) { 580 var settings = this.data("treetable").settings, 581 prepValue, 582 sortFun; 583 584 columnOrFunction = columnOrFunction || settings.column; 585 sortFun = columnOrFunction; 586 587 if ($.isNumeric(columnOrFunction)) { 588 sortFun = function(a, b) { 589 var extractValue, valA, valB; 590 591 extractValue = function(node) { 592 var val = node.row.find("td:eq(" + columnOrFunction + ")").text(); 593 // Ignore trailing/leading whitespace and use uppercase values for 594 // case insensitive ordering 595 return $.trim(val).toUpperCase(); 596 } 597 598 valA = extractValue(a); 599 valB = extractValue(b); 600 601 if (valA < valB) return -1; 602 if (valA > valB) return 1; 603 return 0; 604 }; 605 } 606 607 this.data("treetable").sortBranch(node, sortFun); 608 return this; 609 }, 610 611 unloadBranch: function(node) { 612 this.data("treetable").unloadBranch(node); 613 return this; 614 } 615 }; 616 617 $.fn.treetable = function(method) { 618 if (methods[method]) { 619 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 620 } else if (typeof method === 'object' || !method) { 621 return methods.init.apply(this, arguments); 622 } else { 623 return $.error("Method " + method + " does not exist on jQuery.treetable"); 624 } 625 }; 626 627 // Expose classes to world 628 this.TreeTable || (this.TreeTable = {}); 629 this.TreeTable.Node = Node; 630 this.TreeTable.Tree = Tree; 631})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.