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 _results.push(node.collapse()); 258 } 259 return _results; 260 }; 261 262 Tree.prototype.expandAll = function() { 263 var node, _i, _len, _ref, _results; 264 _ref = this.nodes; 265 _results = []; 266 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 267 node = _ref[_i]; 268 _results.push(node.expand()); 269 } 270 return _results; 271 }; 272 273 Tree.prototype.findLastNode = function (node) { 274 if (node.children.length > 0) { 275 return this.findLastNode(node.children[node.children.length - 1]); 276 } else { 277 return node; 278 } 279 }; 280 281 Tree.prototype.loadRows = function(rows) { 282 var node, row, i; 283 284 if (rows != null) { 285 for (i = 0; i < rows.length; i++) { 286 row = $(rows[i]); 287 288 if (row.data(this.settings.nodeIdAttr) != null) { 289 node = new Node(row, this.tree, this.settings); 290 this.nodes.push(node); 291 this.tree[node.id] = node; 292 293 if (node.parentId != null && this.tree[node.parentId]) { 294 this.tree[node.parentId].addChild(node); 295 } else { 296 this.roots.push(node); 297 } 298 } 299 } 300 } 301 302 for (i = 0; i < this.nodes.length; i++) { 303 node = this.nodes[i].updateBranchLeafClass(); 304 } 305 306 return this; 307 }; 308 309 Tree.prototype.move = function(node, destination) { 310 // Conditions: 311 // 1: +node+ should not be inserted as a child of +node+ itself. 312 // 2: +destination+ should not be the same as +node+'s current parent (this 313 // prevents +node+ from being moved to the same location where it already 314 // is). 315 // 3: +node+ should not be inserted in a location in a branch if this would 316 // result in +node+ being an ancestor of itself. 317 var nodeParent = node.parentNode(); 318 if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) { 319 node.setParent(destination); 320 this._moveRows(node, destination); 321 322 // Re-render parentNode if this is its first child node, and therefore 323 // doesn't have the expander yet. 324 if (node.parentNode().children.length === 1) { 325 node.parentNode().render(); 326 } 327 } 328 329 if(nodeParent){ 330 nodeParent.updateBranchLeafClass(); 331 } 332 if(node.parentNode()){ 333 node.parentNode().updateBranchLeafClass(); 334 } 335 node.updateBranchLeafClass(); 336 return this; 337 }; 338 339 Tree.prototype.removeNode = function(node) { 340 // Recursively remove all descendants of +node+ 341 this.unloadBranch(node); 342 343 // Remove node from DOM (<tr>) 344 node.row.remove(); 345 346 // Remove node from parent children list 347 if (node.parentId != null) { 348 node.parentNode().removeChild(node); 349 } 350 351 // Clean up Tree object (so Node objects are GC-ed) 352 delete this.tree[node.id]; 353 this.nodes.splice($.inArray(node, this.nodes), 1); 354 355 return this; 356 } 357 358 Tree.prototype.render = function() { 359 var root, _i, _len, _ref; 360 _ref = this.roots; 361 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 362 root = _ref[_i]; 363
364 // Naming is confusing (show/render). I do not call render on node from 365 // here. 366 root.show(); 367 } 368 return this; 369 }; 370 371 Tree.prototype.sortBranch = function(node, sortFun) { 372 // First sort internal array of children 373 node.children.sort(sortFun); 374 375 // Next render rows in correct order on page 376 this._sortChildRows(node); 377 378 return this; 379 }; 380 381 Tree.prototype.unloadBranch = function(node) { 382 // Use a copy of the children array to not have other functions interfere 383 // with this function if they manipulate the children array 384 // (eg removeNode). 385 var children = node.children.slice(0), 386 i; 387 388 for (i = 0; i < children.length; i++) { 389 this.removeNode(children[i]); 390 } 391 392 // Reset node's collection of children 393 node.children = []; 394 395 node.updateBranchLeafClass(); 396 397 return this; 398 }; 399 400 Tree.prototype._moveRows = function(node, destination) { 401 var children = node.children, i; 402 403 node.row.insertAfter(destination.row); 404 node.render(); 405 406 // Loop backwards through children to have them end up on UI in correct 407 // order (see #112) 408 for (i = children.length - 1; i >= 0; i--) { 409 this._moveRows(children[i], node); 410 } 411 }; 412 413 // Special _moveRows case, move children to itself to force sorting 414 Tree.prototype._sortChildRows = function(parentNode) { 415 return this._moveRows(parentNode, parentNode); 416 }; 417 418 return Tree; 419 })(); 420 421 // jQuery Plugin 422 methods = { 423 init: function(options, force) { 424 var settings; 425 426 settings = $.extend({ 427 branchAttr: "ttBranch", 428 clickableNodeNames: false, 429 column: 0, 430 columnElType: "td", // i.e. 'td', 'th' or 'td,th' 431 expandable: false, 432 expanderTemplate: "<a href='#'> </a>", 433 indent: 19, 434 indenterTemplate: "<span class='indenter'></span>", 435 initialState: "collapsed", 436 nodeIdAttr: "ttId", // maps to data-tt-id 437 parentIdAttr: "ttParentId", // maps to data-tt-parent-id 438 stringExpand: "Expand", 439 stringCollapse: "Collapse", 440 441 // Events 442 onInitialized: null, 443 onNodeCollapse: null, 444 onNodeExpand: null, 445 onNodeInitialized: null 446 }, options); 447 448 return this.each(function() { 449 var el = $(this), tree; 450 451 if (force || el.data("treetable") === undefined) { 452 tree = new Tree(this, settings); 453 tree.loadRows(this.rows).render(); 454 455 el.addClass("treetable").data("treetable", tree); 456 457 if (settings.onInitialized != null) { 458 settings.onInitialized.apply(tree); 459 } 460 } 461 462 return el; 463 }); 464 }, 465 466 destroy: function() { 467 return this.each(function() { 468 return $(this).removeData("treetable").removeClass("treetable"); 469 }); 470 }, 471 472 collapseAll: function() { 473 this.data("treetable").collapseAll(); 474 return this; 475 }, 476 477 collapseNode: function(id) { 478 var node = this.data("treetable").tree[id]; 479 480 if (node) { 481 node.collapse(); 482 } else { 483 throw new Error("Unknown node '" + id + "'"); 484 } 485 486 return this; 487 }, 488 489 expandAll: function() { 490 this.data("treetable").expandAll(); 491 return this; 492 }, 493 494 expandNode: function(id) { 495 var node = this.data("treetable").tree[id]; 496 497 if (node) { 498 if (!node.initialized) { 499 node._initialize(); 500 } 501 502 node.expand(); 503 } else { 504 throw new Error("Unknown node '" + id + "'"); 505 } 506 507 return this; 508 }, 509 510 loadBranch: function(node, rows) { 511 var settings = this.data("treetable").settings, 512 tree = this.data("treetable").tree; 513 514 // TODO Switch to $.parseHTML 515 rows = $(rows); 516 517 if (node == null) { // Inserting new root nodes 518 this.append(rows); 519 } else { 520 var lastNode = this.data("treetable").findLastNode(node); 521 rows.insertAfter(lastNode.row); 522 } 523 524 this.data("treetable").loadRows(rows); 525 526 // Make sure nodes are properly initialized 527 rows.filter("tr").each(function() { 528 tree[$(this).data(settings.nodeIdAttr)].show(); 529 }); 530 531 if (node != null) { 532 // Re-render parent to ensure expander icon is shown (#79) 533 node.render().expand(); 534 } 535 536 return this; 537 }, 538 539 move: function(nodeId, destinationId) { 540 var destination, node; 541 542 node = this.data("treetable").tree[nodeId]; 543 destination = this.data("treetable").tree[destinationId]; 544 this.data("treetable").move(node, destination); 545 546 return this; 547 }, 548 549 node: function(id) { 550 return this.data("treetable").tree[id]; 551 }, 552 553 removeNode: function(id) { 554 var node = this.data("treetable").tree[id]; 555 556 if (node) { 557 this.data("treetable").removeNode(node); 558 } else { 559 throw new Error("Unknown node '" + id + "'"); 560 } 561 562 return this; 563 }, 564 565 reveal: function(id) { 566 var node = this.data("treetable").tree[id]; 567 568 if (node) { 569 node.reveal(); 570 } else { 571 throw new Error("Unknown node '" + id + "'"); 572 } 573 574 return this; 575 }, 576 577 sortBranch: function(node, columnOrFunction) { 578 var settings = this.data("treetable").settings, 579 prepValue, 580 sortFun; 581 582 columnOrFunction = columnOrFunction || settings.column; 583 sortFun = columnOrFunction; 584 585 if ($.isNumeric(columnOrFunction)) { 586 sortFun = function(a, b) { 587 var extractValue, valA, valB; 588 589 extractValue = function(node) { 590 var val = node.row.find("td:eq(" + columnOrFunction + ")").text(); 591 // Ignore trailing/leading whitespace and use uppercase values for 592 // case insensitive ordering 593 return $.trim(val).toUpperCase(); 594 } 595 596 valA = extractValue(a); 597 valB = extractValue(b); 598 599 if (valA < valB) return -1; 600 if (valA > valB) return 1; 601 return 0; 602 }; 603 } 604 605 this.data("treetable").sortBranch(node, sortFun); 606 return this; 607 }, 608 609 unloadBranch: function(node) { 610 this.data("treetable").unloadBranch(node); 611 return this; 612 } 613 }; 614 615 $.fn.treetable = function(method) { 616 if (methods[method]) { 617 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 618 } else if (typeof method === 'object' || !method) { 619 return methods.init.apply(this, arguments); 620 } else { 621 return $.error("Method " + method + " does not exist on jQuery.treetable"); 622 } 623 }; 624 625 // Expose classes to world 626 this.TreeTable || (this.TreeTable = {}); 627 this.TreeTable.Node = Node;
628 this.TreeTable.Tree = Tree; 629})(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.