Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / token / js / jquery.treetable.js
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='#'>&nbsp;</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);