心理咨询网
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

jquery.treetable.js 16KB

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