add jquery-treegrid
[qcg-portal.git] / qcg / static / qcg / treegrid / js / jquery.treegrid.js
1 /*
2  * jQuery treegrid Plugin 0.3.0
3  * https://github.com/maxazan/jquery-treegrid
4  *
5  * Copyright 2013, Pomazan Max
6  * Licensed under the MIT licenses.
7  */
8 (function($) {
9
10     var methods = {
11         /**
12          * Initialize tree
13          *
14          * @param {Object} options
15          * @returns {Object[]}
16          */
17         initTree: function(options) {
18             var settings = $.extend({}, this.treegrid.defaults, options);
19             return this.each(function() {
20                 var $this = $(this);
21                 $this.treegrid('setTreeContainer', $(this));
22                 $this.treegrid('setSettings', settings);
23                 settings.getRootNodes.apply(this, [$(this)]).treegrid('initNode', settings);
24                 $this.treegrid('getRootNodes').treegrid('render');
25             });
26         },
27         /**
28          * Initialize node
29          *
30          * @param {Object} settings
31          * @returns {Object[]}
32          */
33         initNode: function(settings) {
34             return this.each(function() {
35                 var $this = $(this);
36                 $this.treegrid('setTreeContainer', settings.getTreeGridContainer.apply(this));
37                 $this.treegrid('getChildNodes').treegrid('initNode', settings);
38                 $this.treegrid('initExpander').treegrid('initIndent').treegrid('initEvents').treegrid('initState').treegrid('initChangeEvent').treegrid("initSettingsEvents");
39             });
40         },
41         initChangeEvent: function() {
42             var $this = $(this);
43             //Save state on change
44             $this.on("change", function() {
45                 var $this = $(this);
46                 $this.treegrid('render');
47                 if ($this.treegrid('getSetting', 'saveState')) {
48                     $this.treegrid('saveState');
49                 }
50             });
51             return $this;
52         },
53         /**
54          * Initialize node events
55          *
56          * @returns {Node}
57          */
58         initEvents: function() {
59             var $this = $(this);
60             //Default behavior on collapse
61             $this.on("collapse", function() {
62                 var $this = $(this);
63                 $this.removeClass('treegrid-expanded');
64                 $this.addClass('treegrid-collapsed');
65             });
66             //Default behavior on expand
67             $this.on("expand", function() {
68                 var $this = $(this);
69                 $this.removeClass('treegrid-collapsed');
70                 $this.addClass('treegrid-expanded');
71             });
72
73             return $this;
74         },
75         /**
76          * Initialize events from settings
77          *
78          * @returns {Node}
79          */
80         initSettingsEvents: function() {
81             var $this = $(this);
82             //Save state on change
83             $this.on("change", function() {
84                 var $this = $(this);
85                 if (typeof($this.treegrid('getSetting', 'onChange')) === "function") {
86                     $this.treegrid('getSetting', 'onChange').apply($this);
87                 }
88             });
89             //Default behavior on collapse
90             $this.on("collapse", function() {
91                 var $this = $(this);
92                 if (typeof($this.treegrid('getSetting', 'onCollapse')) === "function") {
93                     $this.treegrid('getSetting', 'onCollapse').apply($this);
94                 }
95             });
96             //Default behavior on expand
97             $this.on("expand", function() {
98                 var $this = $(this);
99                 if (typeof($this.treegrid('getSetting', 'onExpand')) === "function") {
100                     $this.treegrid('getSetting', 'onExpand').apply($this);
101                 }
102
103             });
104
105             return $this;
106         },
107         /**
108          * Initialize expander for node
109          *
110          * @returns {Node}
111          */
112         initExpander: function() {
113             var $this = $(this);
114             var cell = $this.find('td').get($this.treegrid('getSetting', 'treeColumn'));
115             var tpl = $this.treegrid('getSetting', 'expanderTemplate');
116             var expander = $this.treegrid('getSetting', 'getExpander').apply(this);
117             if (expander) {
118                 expander.remove();
119             }
120             $(tpl).prependTo(cell).click(function() {
121                 $($(this).closest('tr')).treegrid('toggle');
122             });
123             return $this;
124         },
125         /**
126          * Initialize indent for node
127          *
128          * @returns {Node}
129          */
130         initIndent: function() {
131             var $this = $(this);
132             $this.find('.treegrid-indent').remove();
133             var tpl = $this.treegrid('getSetting', 'indentTemplate');
134             var expander = $this.find('.treegrid-expander');
135             var depth = $this.treegrid('getDepth');
136             for (var i = 0; i < depth; i++) {
137                 $(tpl).insertBefore(expander);
138             }
139             return $this;
140         },
141         /**
142          * Initialise state of node
143          *
144          * @returns {Node}
145          */
146         initState: function() {
147             var $this = $(this);
148             if ($this.treegrid('getSetting', 'saveState') && !$this.treegrid('isFirstInit')) {
149                 $this.treegrid('restoreState');
150             } else {
151                 if ($this.treegrid('getSetting', 'initialState') === "expanded") {
152                     $this.treegrid('expand');
153                 } else {
154                     $this.treegrid('collapse');
155                 }
156             }
157             return $this;
158         },
159         /**
160          * Return true if this tree was never been initialised
161          *
162          * @returns {Boolean}
163          */
164         isFirstInit: function() {
165             var tree = $(this).treegrid('getTreeContainer');
166             if (tree.data('first_init') === undefined) {
167                 tree.data('first_init', $.cookie(tree.treegrid('getSetting', 'saveStateName')) === undefined);
168             }
169             return tree.data('first_init');
170         },
171         /**
172          * Save state of current node
173          *
174          * @returns {Node}
175          */
176         saveState: function() {
177             var $this = $(this);
178             if ($this.treegrid('getSetting', 'saveStateMethod') === 'cookie') {
179
180                 var stateArrayString = $.cookie($this.treegrid('getSetting', 'saveStateName')) || '';
181                 var stateArray = (stateArrayString === '' ? [] : stateArrayString.split(','));
182                 var nodeId = $this.treegrid('getNodeId');
183
184                 if ($this.treegrid('isExpanded')) {
185                     if ($.inArray(nodeId, stateArray) === -1) {
186                         stateArray.push(nodeId);
187                     }
188                 } else if ($this.treegrid('isCollapsed')) {
189                     if ($.inArray(nodeId, stateArray) !== -1) {
190                         stateArray.splice($.inArray(nodeId, stateArray), 1);
191                     }
192                 }
193                 $.cookie($this.treegrid('getSetting', 'saveStateName'), stateArray.join(','));
194             }
195             return $this;
196         },
197         /**
198          * Restore state of current node.
199          *
200          * @returns {Node}
201          */
202         restoreState: function() {
203             var $this = $(this);
204             if ($this.treegrid('getSetting', 'saveStateMethod') === 'cookie') {
205                 var stateArray = $.cookie($this.treegrid('getSetting', 'saveStateName')).split(',');
206                 if ($.inArray($this.treegrid('getNodeId'), stateArray) !== -1) {
207                     $this.treegrid('expand');
208                 } else {
209                     $this.treegrid('collapse');
210                 }
211
212             }
213             return $this;
214         },
215         /**
216          * Method return setting by name
217          *
218          * @param {type} name
219          * @returns {unresolved}
220          */
221         getSetting: function(name) {
222             if (!$(this).treegrid('getTreeContainer')) {
223                 return null;
224             }
225             return $(this).treegrid('getTreeContainer').data('settings')[name];
226         },
227         /**
228          * Add new settings
229          *
230          * @param {Object} settings
231          */
232         setSettings: function(settings) {
233             $(this).treegrid('getTreeContainer').data('settings', settings);
234         },
235         /**
236          * Return tree container
237          *
238          * @returns {HtmlElement}
239          */
240         getTreeContainer: function() {
241             return $(this).data('treegrid');
242         },
243         /**
244          * Set tree container
245          *
246          * @param {HtmlE;ement} container
247          */
248         setTreeContainer: function(container) {
249             return $(this).data('treegrid', container);
250         },
251         /**
252          * Method return all root nodes of tree.
253          *
254          * Start init all child nodes from it.
255          *
256          * @returns {Array}
257          */
258         getRootNodes: function() {
259             return $(this).treegrid('getSetting', 'getRootNodes').apply(this, [$(this).treegrid('getTreeContainer')]);
260         },
261         /**
262          * Method return all nodes of tree.
263          *
264          * @returns {Array}
265          */
266         getAllNodes: function() {
267             return $(this).treegrid('getSetting', 'getAllNodes').apply(this, [$(this).treegrid('getTreeContainer')]);
268         },
269         /**
270          * Mthod return true if element is Node
271          *
272          * @returns {String}
273          */
274         isNode: function() {
275             return $(this).treegrid('getNodeId') !== null;
276         },
277         /**
278          * Mthod return id of node
279          *
280          * @returns {String}
281          */
282         getNodeId: function() {
283             if ($(this).treegrid('getSetting', 'getNodeId') === null) {
284                 return null;
285             } else {
286                 return $(this).treegrid('getSetting', 'getNodeId').apply(this);
287             }
288         },
289         /**
290          * Method return parent id of node or null if root node
291          *
292          * @returns {String}
293          */
294         getParentNodeId: function() {
295             return $(this).treegrid('getSetting', 'getParentNodeId').apply(this);
296         },
297         /**
298          * Method return parent node or null if root node
299          *
300          * @returns {Object[]}
301          */
302         getParentNode: function() {
303             if ($(this).treegrid('getParentNodeId') === null) {
304                 return null;
305             } else {
306                 return $(this).treegrid('getSetting', 'getNodeById').apply(this, [$(this).treegrid('getParentNodeId'), $(this).treegrid('getTreeContainer')]);
307             }
308         },
309         /**
310          * Method return array of child nodes or null if node is leaf
311          *
312          * @returns {Object[]}
313          */
314         getChildNodes: function() {
315             return $(this).treegrid('getSetting', 'getChildNodes').apply(this, [$(this).treegrid('getNodeId'), $(this).treegrid('getTreeContainer')]);
316         },
317         /**
318          * Method return depth of tree.
319          *
320          * This method is needs for calculate indent
321          *
322          * @returns {Number}
323          */
324         getDepth: function() {
325             if ($(this).treegrid('getParentNode') === null) {
326                 return 0;
327             }
328             return $(this).treegrid('getParentNode').treegrid('getDepth') + 1;
329         },
330         /**
331          * Method return true if node is root
332          *
333          * @returns {Boolean}
334          */
335         isRoot: function() {
336             return $(this).treegrid('getDepth') === 0;
337         },
338         /**
339          * Method return true if node has no child nodes
340          *
341          * @returns {Boolean}
342          */
343         isLeaf: function() {
344             return $(this).treegrid('getChildNodes').length === 0;
345         },
346         /**
347          * Method return true if node last in branch
348          *
349          * @returns {Boolean}
350          */
351         isLast: function() {
352             if ($(this).treegrid('isNode')) {
353                 var parentNode = $(this).treegrid('getParentNode');
354                 if (parentNode === null) {
355                     if ($(this).treegrid('getNodeId') === $(this).treegrid('getRootNodes').last().treegrid('getNodeId')) {
356                         return true;
357                     }
358                 } else {
359                     if ($(this).treegrid('getNodeId') === parentNode.treegrid('getChildNodes').last().treegrid('getNodeId')) {
360                         return true;
361                     }
362                 }
363             }
364             return false;
365         },
366         /**
367          * Method return true if node first in branch
368          *
369          * @returns {Boolean}
370          */
371         isFirst: function() {
372             if ($(this).treegrid('isNode')) {
373                 var parentNode = $(this).treegrid('getParentNode');
374                 if (parentNode === null) {
375                     if ($(this).treegrid('getNodeId') === $(this).treegrid('getRootNodes').first().treegrid('getNodeId')) {
376                         return true;
377                     }
378                 } else {
379                     if ($(this).treegrid('getNodeId') === parentNode.treegrid('getChildNodes').first().treegrid('getNodeId')) {
380                         return true;
381                     }
382                 }
383             }
384             return false;
385         },
386         /**
387          * Return true if node expanded
388          *
389          * @returns {Boolean}
390          */
391         isExpanded: function() {
392             return $(this).hasClass('treegrid-expanded');
393         },
394         /**
395          * Return true if node collapsed
396          *
397          * @returns {Boolean}
398          */
399         isCollapsed: function() {
400             return $(this).hasClass('treegrid-collapsed');
401         },
402         /**
403          * Return true if at least one of parent node is collapsed
404          *
405          * @returns {Boolean}
406          */
407         isOneOfParentsCollapsed: function() {
408             var $this = $(this);
409             if ($this.treegrid('isRoot')) {
410                 return false;
411             } else {
412                 if ($this.treegrid('getParentNode').treegrid('isCollapsed')) {
413                     return true;
414                 } else {
415                     return $this.treegrid('getParentNode').treegrid('isOneOfParentsCollapsed');
416                 }
417             }
418         },
419         /**
420          * Expand node
421          *
422          * @returns {Node}
423          */
424         expand: function() {
425             if (!this.treegrid('isLeaf') && !this.treegrid("isExpanded")) {
426                 this.trigger("expand");
427                 this.trigger("change");
428                 return this;
429             }
430             return this;
431         },
432         /**
433          * Expand all nodes
434          *
435          * @returns {Node}
436          */
437         expandAll: function() {
438             var $this = $(this);
439             $this.treegrid('getRootNodes').treegrid('expandRecursive');
440             return $this;
441         },
442         /**
443          * Expand current node and all child nodes begin from current
444          *
445          * @returns {Node}
446          */
447         expandRecursive: function() {
448             return $(this).each(function() {
449                 var $this = $(this);
450                 $this.treegrid('expand');
451                 if (!$this.treegrid('isLeaf')) {
452                     $this.treegrid('getChildNodes').treegrid('expandRecursive');
453                 }
454             });
455         },
456         /**
457          * Collapse node
458          *
459          * @returns {Node}
460          */
461         collapse: function() {
462             return $(this).each(function() {
463                 var $this = $(this);
464                 if (!$this.treegrid('isLeaf') && !$this.treegrid("isCollapsed")) {
465                     $this.trigger("collapse");
466                     $this.trigger("change");
467                 }
468             });
469         },
470         /**
471          * Collapse all nodes
472          *
473          * @returns {Node}
474          */
475         collapseAll: function() {
476             var $this = $(this);
477             $this.treegrid('getRootNodes').treegrid('collapseRecursive');
478             return $this;
479         },
480         /**
481          * Collapse current node and all child nodes begin from current
482          *
483          * @returns {Node}
484          */
485         collapseRecursive: function() {
486             return $(this).each(function() {
487                 var $this = $(this);
488                 $this.treegrid('collapse');
489                 if (!$this.treegrid('isLeaf')) {
490                     $this.treegrid('getChildNodes').treegrid('collapseRecursive');
491                 }
492             });
493         },
494         /**
495          * Expand if collapsed, Collapse if expanded
496          *
497          * @returns {Node}
498          */
499         toggle: function() {
500             var $this = $(this);
501             if ($this.treegrid('isExpanded')) {
502                 $this.treegrid('collapse');
503             } else {
504                 $this.treegrid('expand');
505             }
506             return $this;
507         },
508         /**
509          * Rendering node
510          *
511          * @returns {Node}
512          */
513         render: function() {
514             return $(this).each(function() {
515                 var $this = $(this);
516                 //if parent colapsed we hidden
517                 if ($this.treegrid('isOneOfParentsCollapsed')) {
518                     $this.hide();
519                 } else {
520                     $this.show();
521                 }
522                 if (!$this.treegrid('isLeaf')) {
523                     $this.treegrid('renderExpander');
524                     $this.treegrid('getChildNodes').treegrid('render');
525                 }
526             });
527         },
528         /**
529          * Rendering expander depends on node state
530          *
531          * @returns {Node}
532          */
533         renderExpander: function() {
534             return $(this).each(function() {
535                 var $this = $(this);
536                 var expander = $this.treegrid('getSetting', 'getExpander').apply(this);
537                 if (expander) {
538
539                     if (!$this.treegrid('isCollapsed')) {
540                         expander.removeClass($this.treegrid('getSetting', 'expanderCollapsedClass'));
541                         expander.addClass($this.treegrid('getSetting', 'expanderExpandedClass'));
542                     } else {
543                         expander.removeClass($this.treegrid('getSetting', 'expanderExpandedClass'));
544                         expander.addClass($this.treegrid('getSetting', 'expanderCollapsedClass'));
545                     }
546                 } else {
547                     $this.treegrid('initExpander');
548                     $this.treegrid('renderExpander');
549                 }
550             });
551         }
552     };
553     $.fn.treegrid = function(method) {
554         if (methods[method]) {
555             return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
556         } else if (typeof method === 'object' || !method) {
557             return methods.initTree.apply(this, arguments);
558         } else {
559             $.error('Method with name ' + method + ' does not exists for jQuery.treegrid');
560         }
561     };
562     /**
563      *  Plugin's default options
564      */
565     $.fn.treegrid.defaults = {
566         initialState: 'expanded',
567         saveState: false,
568         saveStateMethod: 'cookie',
569         saveStateName: 'tree-grid-state',
570         expanderTemplate: '<span class="treegrid-expander"></span>',
571         indentTemplate: '<span class="treegrid-indent"></span>',
572         expanderExpandedClass: 'treegrid-expander-expanded',
573         expanderCollapsedClass: 'treegrid-expander-collapsed',
574         treeColumn: 0,
575         getExpander: function() {
576             return $(this).find('.treegrid-expander');
577         },
578         getNodeId: function() {
579             var template = /treegrid-([A-Za-z0-9_-]+)/;
580             if (template.test($(this).attr('class'))) {
581                 return template.exec($(this).attr('class'))[1];
582             }
583             return null;
584         },
585         getParentNodeId: function() {
586             var template = /treegrid-parent-([A-Za-z0-9_-]+)/;
587             if (template.test($(this).attr('class'))) {
588                 return template.exec($(this).attr('class'))[1];
589             }
590             return null;
591         },
592         getNodeById: function(id, treegridContainer) {
593             var templateClass = "treegrid-" + id;
594             return treegridContainer.find('tr.' + templateClass);
595         },
596         getChildNodes: function(id, treegridContainer) {
597             var templateClass = "treegrid-parent-" + id;
598             return treegridContainer.find('tr.' + templateClass);
599         },
600         getTreeGridContainer: function() {
601             return $(this).closest('table');
602         },
603         getRootNodes: function(treegridContainer) {
604             var result = $.grep(treegridContainer.find('tr'), function(element) {
605                 var classNames = $(element).attr('class');
606                 var templateClass = /treegrid-([A-Za-z0-9_-]+)/;
607                 var templateParentClass = /treegrid-parent-([A-Za-z0-9_-]+)/;
608                 return templateClass.test(classNames) && !templateParentClass.test(classNames);
609             });
610             return $(result);
611         },
612         getAllNodes: function(treegridContainer) {
613             var result = $.grep(treegridContainer.find('tr'), function(element) {
614                 var classNames = $(element).attr('class');
615                 var templateClass = /treegrid-([A-Za-z0-9_-]+)/;
616                 return templateClass.test(classNames);
617             });
618             return $(result);
619         },
620         //Events
621         onCollapse: null,
622         onExpand: null,
623         onChange: null
624
625     };
626 })(jQuery);