22 |
Process Tree
23 |
24 |
25 |
26 |
27 | -
28 |
29 | Parent 1
30 |
31 | -
32 |
33 |
34 | Child 1
35 |
36 | -
37 |
38 |
39 | Child 2
40 |
- Parent 2
- Parent 3
- Parent 4
- Parent 5
41 |
42 |
43 |
74 |
78 |
79 |
Additional Data
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/treeview/css/bootstrap-treeview.css:
--------------------------------------------------------------------------------
1 | /* =========================================================
2 | * bootstrap-treeview.css v1.2.0
3 | * =========================================================
4 | * Copyright 2013 Jonathan Miles
5 | * Project URL : http://www.jondmiles.com/bootstrap-treeview
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================= */
19 |
20 | .treeview .list-group-item {
21 | cursor: pointer;
22 | }
23 |
24 | .treeview span.indent {
25 | margin-left: 10px;
26 | margin-right: 10px;
27 | }
28 |
29 | .treeview span.icon {
30 | width: 12px;
31 | margin-right: 5px;
32 | }
33 |
34 | .treeview .node-disabled {
35 | color: silver;
36 | cursor: not-allowed;
37 | }
--------------------------------------------------------------------------------
/treeview/js/bootstrap-treeview.js:
--------------------------------------------------------------------------------
1 | /* =========================================================
2 | * bootstrap-treeview.js v1.2.0
3 | * =========================================================
4 | * Copyright 2013 Jonathan Miles
5 | * Project URL : http://www.jondmiles.com/bootstrap-treeview
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================= */
19 |
20 | ;(function ($, window, document, undefined) {
21 |
22 | /*global jQuery, console*/
23 |
24 | 'use strict';
25 |
26 | var pluginName = 'treeview';
27 |
28 | var _default = {};
29 |
30 | _default.settings = {
31 |
32 | injectStyle: true,
33 |
34 | levels: 2,
35 |
36 | expandIcon: 'glyphicon glyphicon-plus',
37 | collapseIcon: 'glyphicon glyphicon-minus',
38 | emptyIcon: 'glyphicon',
39 | nodeIcon: '',
40 | selectedIcon: '',
41 | checkedIcon: 'glyphicon glyphicon-check',
42 | uncheckedIcon: 'glyphicon glyphicon-unchecked',
43 |
44 | color: undefined, // '#000000',
45 | backColor: undefined, // '#FFFFFF',
46 | borderColor: undefined, // '#dddddd',
47 | onhoverColor: '#F5F5F5',
48 | selectedColor: '#FFFFFF',
49 | selectedBackColor: '#428bca',
50 | searchResultColor: '#D9534F',
51 | searchResultBackColor: undefined, //'#FFFFFF',
52 |
53 | enableLinks: false,
54 | highlightSelected: true,
55 | highlightSearchResults: true,
56 | showBorder: true,
57 | showIcon: true,
58 | showCheckbox: false,
59 | showTags: false,
60 | multiSelect: false,
61 |
62 | // Event handlers
63 | onNodeChecked: undefined,
64 | onNodeCollapsed: undefined,
65 | onNodeDisabled: undefined,
66 | onNodeEnabled: undefined,
67 | onNodeExpanded: undefined,
68 | onNodeSelected: undefined,
69 | onNodeUnchecked: undefined,
70 | onNodeUnselected: undefined,
71 | onSearchComplete: undefined,
72 | onSearchCleared: undefined
73 | };
74 |
75 | _default.options = {
76 | silent: false,
77 | ignoreChildren: false
78 | };
79 |
80 | _default.searchOptions = {
81 | ignoreCase: true,
82 | exactMatch: false,
83 | revealResults: true
84 | };
85 |
86 | var Tree = function (element, options) {
87 |
88 | this.$element = $(element);
89 | this.elementId = element.id;
90 | this.styleId = this.elementId + '-style';
91 |
92 | this.init(options);
93 |
94 | return {
95 |
96 | // Options (public access)
97 | options: this.options,
98 |
99 | // Initialize / destroy methods
100 | init: $.proxy(this.init, this),
101 | remove: $.proxy(this.remove, this),
102 |
103 | // Get methods
104 | getNode: $.proxy(this.getNode, this),
105 | getParent: $.proxy(this.getParent, this),
106 | getSiblings: $.proxy(this.getSiblings, this),
107 | getSelected: $.proxy(this.getSelected, this),
108 | getUnselected: $.proxy(this.getUnselected, this),
109 | getExpanded: $.proxy(this.getExpanded, this),
110 | getCollapsed: $.proxy(this.getCollapsed, this),
111 | getChecked: $.proxy(this.getChecked, this),
112 | getUnchecked: $.proxy(this.getUnchecked, this),
113 | getDisabled: $.proxy(this.getDisabled, this),
114 | getEnabled: $.proxy(this.getEnabled, this),
115 |
116 | // Select methods
117 | selectNode: $.proxy(this.selectNode, this),
118 | unselectNode: $.proxy(this.unselectNode, this),
119 | toggleNodeSelected: $.proxy(this.toggleNodeSelected, this),
120 |
121 | // Expand / collapse methods
122 | collapseAll: $.proxy(this.collapseAll, this),
123 | collapseNode: $.proxy(this.collapseNode, this),
124 | expandAll: $.proxy(this.expandAll, this),
125 | expandNode: $.proxy(this.expandNode, this),
126 | toggleNodeExpanded: $.proxy(this.toggleNodeExpanded, this),
127 | revealNode: $.proxy(this.revealNode, this),
128 |
129 | // Expand / collapse methods
130 | checkAll: $.proxy(this.checkAll, this),
131 | checkNode: $.proxy(this.checkNode, this),
132 | uncheckAll: $.proxy(this.uncheckAll, this),
133 | uncheckNode: $.proxy(this.uncheckNode, this),
134 | toggleNodeChecked: $.proxy(this.toggleNodeChecked, this),
135 |
136 | // Disable / enable methods
137 | disableAll: $.proxy(this.disableAll, this),
138 | disableNode: $.proxy(this.disableNode, this),
139 | enableAll: $.proxy(this.enableAll, this),
140 | enableNode: $.proxy(this.enableNode, this),
141 | toggleNodeDisabled: $.proxy(this.toggleNodeDisabled, this),
142 |
143 | // Search methods
144 | search: $.proxy(this.search, this),
145 | clearSearch: $.proxy(this.clearSearch, this)
146 | };
147 | };
148 |
149 | Tree.prototype.init = function (options) {
150 |
151 | this.tree = [];
152 | this.nodes = [];
153 |
154 | if (options.data) {
155 | if (typeof options.data === 'string') {
156 | options.data = $.parseJSON(options.data);
157 | }
158 | this.tree = $.extend(true, [], options.data);
159 | delete options.data;
160 | }
161 | this.options = $.extend({}, _default.settings, options);
162 |
163 | this.destroy();
164 | this.subscribeEvents();
165 | this.setInitialStates({ nodes: this.tree }, 0);
166 | this.render();
167 | };
168 |
169 | Tree.prototype.remove = function () {
170 | this.destroy();
171 | $.removeData(this, pluginName);
172 | $('#' + this.styleId).remove();
173 | };
174 |
175 | Tree.prototype.destroy = function () {
176 |
177 | if (!this.initialized) return;
178 |
179 | this.$wrapper.remove();
180 | this.$wrapper = null;
181 |
182 | // Switch off events
183 | this.unsubscribeEvents();
184 |
185 | // Reset this.initialized flag
186 | this.initialized = false;
187 | };
188 |
189 | Tree.prototype.unsubscribeEvents = function () {
190 |
191 | this.$element.off('click');
192 | this.$element.off('nodeChecked');
193 | this.$element.off('nodeCollapsed');
194 | this.$element.off('nodeDisabled');
195 | this.$element.off('nodeEnabled');
196 | this.$element.off('nodeExpanded');
197 | this.$element.off('nodeSelected');
198 | this.$element.off('nodeUnchecked');
199 | this.$element.off('nodeUnselected');
200 | this.$element.off('searchComplete');
201 | this.$element.off('searchCleared');
202 | };
203 |
204 | Tree.prototype.subscribeEvents = function () {
205 |
206 | this.unsubscribeEvents();
207 |
208 | this.$element.on('click', $.proxy(this.clickHandler, this));
209 |
210 | if (typeof (this.options.onNodeChecked) === 'function') {
211 | this.$element.on('nodeChecked', this.options.onNodeChecked);
212 | }
213 |
214 | if (typeof (this.options.onNodeCollapsed) === 'function') {
215 | this.$element.on('nodeCollapsed', this.options.onNodeCollapsed);
216 | }
217 |
218 | if (typeof (this.options.onNodeDisabled) === 'function') {
219 | this.$element.on('nodeDisabled', this.options.onNodeDisabled);
220 | }
221 |
222 | if (typeof (this.options.onNodeEnabled) === 'function') {
223 | this.$element.on('nodeEnabled', this.options.onNodeEnabled);
224 | }
225 |
226 | if (typeof (this.options.onNodeExpanded) === 'function') {
227 | this.$element.on('nodeExpanded', this.options.onNodeExpanded);
228 | }
229 |
230 | if (typeof (this.options.onNodeSelected) === 'function') {
231 | this.$element.on('nodeSelected', this.options.onNodeSelected);
232 | }
233 |
234 | if (typeof (this.options.onNodeUnchecked) === 'function') {
235 | this.$element.on('nodeUnchecked', this.options.onNodeUnchecked);
236 | }
237 |
238 | if (typeof (this.options.onNodeUnselected) === 'function') {
239 | this.$element.on('nodeUnselected', this.options.onNodeUnselected);
240 | }
241 |
242 | if (typeof (this.options.onSearchComplete) === 'function') {
243 | this.$element.on('searchComplete', this.options.onSearchComplete);
244 | }
245 |
246 | if (typeof (this.options.onSearchCleared) === 'function') {
247 | this.$element.on('searchCleared', this.options.onSearchCleared);
248 | }
249 | };
250 |
251 | /*
252 | Recurse the tree structure and ensure all nodes have
253 | valid initial states. User defined states will be preserved.
254 | For performance we also take this opportunity to
255 | index nodes in a flattened structure
256 | */
257 | Tree.prototype.setInitialStates = function (node, level) {
258 |
259 | if (!node.nodes) return;
260 | level += 1;
261 |
262 | var parent = node;
263 | var _this = this;
264 | $.each(node.nodes, function checkStates(index, node) {
265 |
266 | // nodeId : unique, incremental identifier
267 | node.nodeId = _this.nodes.length;
268 |
269 | // parentId : transversing up the tree
270 | node.parentId = parent.nodeId;
271 |
272 | // if not provided set selectable default value
273 | if (!node.hasOwnProperty('selectable')) {
274 | node.selectable = true;
275 | }
276 |
277 | // where provided we should preserve states
278 | node.state = node.state || {};
279 |
280 | // set checked state; unless set always false
281 | if (!node.state.hasOwnProperty('checked')) {
282 | node.state.checked = false;
283 | }
284 |
285 | // set enabled state; unless set always false
286 | if (!node.state.hasOwnProperty('disabled')) {
287 | node.state.disabled = false;
288 | }
289 |
290 | // set expanded state; if not provided based on levels
291 | if (!node.state.hasOwnProperty('expanded')) {
292 | if (!node.state.disabled &&
293 | (level < _this.options.levels) &&
294 | (node.nodes && node.nodes.length > 0)) {
295 | node.state.expanded = true;
296 | }
297 | else {
298 | node.state.expanded = false;
299 | }
300 | }
301 |
302 | // set selected state; unless set always false
303 | if (!node.state.hasOwnProperty('selected')) {
304 | node.state.selected = false;
305 | }
306 |
307 | // index nodes in a flattened structure for use later
308 | _this.nodes.push(node);
309 |
310 | // recurse child nodes and transverse the tree
311 | if (node.nodes) {
312 | _this.setInitialStates(node, level);
313 | }
314 | });
315 | };
316 |
317 | Tree.prototype.clickHandler = function (event) {
318 |
319 | if (!this.options.enableLinks) event.preventDefault();
320 |
321 | var target = $(event.target);
322 | var node = this.findNode(target);
323 | if (!node || node.state.disabled) return;
324 |
325 | var classList = target.attr('class') ? target.attr('class').split(' ') : [];
326 | if ((classList.indexOf('expand-icon') !== -1)) {
327 |
328 | this.toggleExpandedState(node, _default.options);
329 | this.render();
330 | }
331 | else if ((classList.indexOf('check-icon') !== -1)) {
332 |
333 | this.toggleCheckedState(node, _default.options);
334 | this.render();
335 | }
336 | else {
337 |
338 | if (node.selectable) {
339 | this.toggleSelectedState(node, _default.options);
340 | } else {
341 | this.toggleExpandedState(node, _default.options);
342 | }
343 |
344 | this.render();
345 | }
346 | };
347 |
348 | // Looks up the DOM for the closest parent list item to retrieve the
349 | // data attribute nodeid, which is used to lookup the node in the flattened structure.
350 | Tree.prototype.findNode = function (target) {
351 |
352 | var nodeId = target.closest('li.list-group-item').attr('data-nodeid');
353 | var node = this.nodes[nodeId];
354 |
355 | if (!node) {
356 | console.log('Error: node does not exist');
357 | }
358 | return node;
359 | };
360 |
361 | Tree.prototype.toggleExpandedState = function (node, options) {
362 | if (!node) return;
363 | this.setExpandedState(node, !node.state.expanded, options);
364 | };
365 |
366 | Tree.prototype.setExpandedState = function (node, state, options) {
367 |
368 | if (state === node.state.expanded) return;
369 |
370 | if (state && node.nodes) {
371 |
372 | // Expand a node
373 | node.state.expanded = true;
374 | if (!options.silent) {
375 | this.$element.trigger('nodeExpanded', $.extend(true, {}, node));
376 | }
377 | }
378 | else if (!state) {
379 |
380 | // Collapse a node
381 | node.state.expanded = false;
382 | if (!options.silent) {
383 | this.$element.trigger('nodeCollapsed', $.extend(true, {}, node));
384 | }
385 |
386 | // Collapse child nodes
387 | if (node.nodes && !options.ignoreChildren) {
388 | $.each(node.nodes, $.proxy(function (index, node) {
389 | this.setExpandedState(node, false, options);
390 | }, this));
391 | }
392 | }
393 | };
394 |
395 | Tree.prototype.toggleSelectedState = function (node, options) {
396 | if (!node) return;
397 | this.setSelectedState(node, !node.state.selected, options);
398 | };
399 |
400 | Tree.prototype.setSelectedState = function (node, state, options) {
401 |
402 | if (state === node.state.selected) return;
403 |
404 | if (state) {
405 |
406 | // If multiSelect false, unselect previously selected
407 | if (!this.options.multiSelect) {
408 | $.each(this.findNodes('true', 'g', 'state.selected'), $.proxy(function (index, node) {
409 | this.setSelectedState(node, false, options);
410 | }, this));
411 | }
412 |
413 | // Continue selecting node
414 | node.state.selected = true;
415 | if (!options.silent) {
416 | this.$element.trigger('nodeSelected', $.extend(true, {}, node));
417 | }
418 | }
419 | else {
420 |
421 | // Unselect node
422 | node.state.selected = false;
423 | if (!options.silent) {
424 | this.$element.trigger('nodeUnselected', $.extend(true, {}, node));
425 | }
426 | }
427 | };
428 |
429 | Tree.prototype.toggleCheckedState = function (node, options) {
430 | if (!node) return;
431 | this.setCheckedState(node, !node.state.checked, options);
432 | };
433 |
434 | Tree.prototype.setCheckedState = function (node, state, options) {
435 |
436 | if (state === node.state.checked) return;
437 |
438 | if (state) {
439 |
440 | // Check node
441 | node.state.checked = true;
442 |
443 | if (!options.silent) {
444 | this.$element.trigger('nodeChecked', $.extend(true, {}, node));
445 | }
446 | }
447 | else {
448 |
449 | // Uncheck node
450 | node.state.checked = false;
451 | if (!options.silent) {
452 | this.$element.trigger('nodeUnchecked', $.extend(true, {}, node));
453 | }
454 | }
455 | };
456 |
457 | Tree.prototype.setDisabledState = function (node, state, options) {
458 |
459 | if (state === node.state.disabled) return;
460 |
461 | if (state) {
462 |
463 | // Disable node
464 | node.state.disabled = true;
465 |
466 | // Disable all other states
467 | this.setExpandedState(node, false, options);
468 | this.setSelectedState(node, false, options);
469 | this.setCheckedState(node, false, options);
470 |
471 | if (!options.silent) {
472 | this.$element.trigger('nodeDisabled', $.extend(true, {}, node));
473 | }
474 | }
475 | else {
476 |
477 | // Enabled node
478 | node.state.disabled = false;
479 | if (!options.silent) {
480 | this.$element.trigger('nodeEnabled', $.extend(true, {}, node));
481 | }
482 | }
483 | };
484 |
485 | Tree.prototype.render = function () {
486 |
487 | if (!this.initialized) {
488 |
489 | // Setup first time only components
490 | this.$element.addClass(pluginName);
491 | this.$wrapper = $(this.template.list);
492 |
493 | this.injectStyle();
494 |
495 | this.initialized = true;
496 | }
497 |
498 | this.$element.empty().append(this.$wrapper.empty());
499 |
500 | // Build tree
501 | this.buildTree(this.tree, 0);
502 | };
503 |
504 | // Starting from the root node, and recursing down the
505 | // structure we build the tree one node at a time
506 | Tree.prototype.buildTree = function (nodes, level) {
507 |
508 | if (!nodes) return;
509 | level += 1;
510 |
511 | var _this = this;
512 | $.each(nodes, function addNodes(id, node) {
513 |
514 | var treeItem = $(_this.template.item)
515 | .addClass('node-' + _this.elementId)
516 | .addClass(node.state.checked ? 'node-checked' : '')
517 | .addClass(node.state.disabled ? 'node-disabled': '')
518 | .addClass(node.state.selected ? 'node-selected' : '')
519 | .addClass(node.searchResult ? 'search-result' : '')
520 | .attr('data-nodeid', node.nodeId)
521 | .attr('style', _this.buildStyleOverride(node));
522 |
523 | // Add indent/spacer to mimic tree structure
524 | for (var i = 0; i < (level - 1); i++) {
525 | treeItem.append(_this.template.indent);
526 | }
527 |
528 | // Add expand, collapse or empty spacer icons
529 | var classList = [];
530 | if (node.nodes) {
531 | classList.push('expand-icon');
532 | if (node.state.expanded) {
533 | classList.push(_this.options.collapseIcon);
534 | }
535 | else {
536 | classList.push(_this.options.expandIcon);
537 | }
538 | }
539 | else {
540 | classList.push(_this.options.emptyIcon);
541 | }
542 |
543 | treeItem
544 | .append($(_this.template.icon)
545 | .addClass(classList.join(' '))
546 | );
547 |
548 |
549 | // Add node icon
550 | if (_this.options.showIcon) {
551 |
552 | var classList = ['node-icon'];
553 |
554 | classList.push(node.icon || _this.options.nodeIcon);
555 | if (node.state.selected) {
556 | classList.pop();
557 | classList.push(node.selectedIcon || _this.options.selectedIcon ||
558 | node.icon || _this.options.nodeIcon);
559 | }
560 |
561 | treeItem
562 | .append($(_this.template.icon)
563 | .addClass(classList.join(' '))
564 | );
565 | }
566 |
567 | // Add check / unchecked icon
568 | if (_this.options.showCheckbox) {
569 |
570 | var classList = ['check-icon'];
571 | if (node.state.checked) {
572 | classList.push(_this.options.checkedIcon);
573 | }
574 | else {
575 | classList.push(_this.options.uncheckedIcon);
576 | }
577 |
578 | treeItem
579 | .append($(_this.template.icon)
580 | .addClass(classList.join(' '))
581 | );
582 | }
583 |
584 | // Add text
585 | if (_this.options.enableLinks) {
586 | // Add hyperlink
587 | treeItem
588 | .append($(_this.template.link)
589 | .attr('href', node.href)
590 | .append(node.text)
591 | );
592 | }
593 | else {
594 | // otherwise just text
595 | treeItem
596 | .append(node.text);
597 | }
598 |
599 | // Add tags as badges
600 | if (_this.options.showTags && node.tags) {
601 | $.each(node.tags, function addTag(id, tag) {
602 | treeItem
603 | .append($(_this.template.badge)
604 | .append(tag)
605 | );
606 | });
607 | }
608 |
609 | // Add item to the tree
610 | _this.$wrapper.append(treeItem);
611 |
612 | // Recursively add child ndoes
613 | if (node.nodes && node.state.expanded && !node.state.disabled) {
614 | return _this.buildTree(node.nodes, level);
615 | }
616 | });
617 | };
618 |
619 | // Define any node level style override for
620 | // 1. selectedNode
621 | // 2. node|data assigned color overrides
622 | Tree.prototype.buildStyleOverride = function (node) {
623 |
624 | if (node.state.disabled) return '';
625 |
626 | var color = node.color;
627 | var backColor = node.backColor;
628 |
629 | if (this.options.highlightSelected && node.state.selected) {
630 | if (this.options.selectedColor) {
631 | color = this.options.selectedColor;
632 | }
633 | if (this.options.selectedBackColor) {
634 | backColor = this.options.selectedBackColor;
635 | }
636 | }
637 |
638 | if (this.options.highlightSearchResults && node.searchResult && !node.state.disabled) {
639 | if (this.options.searchResultColor) {
640 | color = this.options.searchResultColor;
641 | }
642 | if (this.options.searchResultBackColor) {
643 | backColor = this.options.searchResultBackColor;
644 | }
645 | }
646 |
647 | return 'color:' + color +
648 | ';background-color:' + backColor + ';';
649 | };
650 |
651 | // Add inline style into head
652 | Tree.prototype.injectStyle = function () {
653 |
654 | if (this.options.injectStyle && !document.getElementById(this.styleId)) {
655 | $('').appendTo('head');
656 | }
657 | };
658 |
659 | // Construct trees style based on user options
660 | Tree.prototype.buildStyle = function () {
661 |
662 | var style = '.node-' + this.elementId + '{';
663 |
664 | if (this.options.color) {
665 | style += 'color:' + this.options.color + ';';
666 | }
667 |
668 | if (this.options.backColor) {
669 | style += 'background-color:' + this.options.backColor + ';';
670 | }
671 |
672 | if (!this.options.showBorder) {
673 | style += 'border:none;';
674 | }
675 | else if (this.options.borderColor) {
676 | style += 'border:1px solid ' + this.options.borderColor + ';';
677 | }
678 | style += '}';
679 |
680 | if (this.options.onhoverColor) {
681 | style += '.node-' + this.elementId + ':not(.node-disabled):hover{' +
682 | 'background-color:' + this.options.onhoverColor + ';' +
683 | '}';
684 | }
685 |
686 | return this.css + style;
687 | };
688 |
689 | Tree.prototype.template = {
690 | list: '
',
691 | item: '
',
692 | indent: '
',
693 | icon: '
',
694 | link: '
',
695 | badge: '
'
696 | };
697 |
698 | Tree.prototype.css = '.treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}'
699 |
700 |
701 | /**
702 | Returns a single node object that matches the given node id.
703 | @param {Number} nodeId - A node's unique identifier
704 | @return {Object} node - Matching node
705 | */
706 | Tree.prototype.getNode = function (nodeId) {
707 | return this.nodes[nodeId];
708 | };
709 |
710 | /**
711 | Returns the parent node of a given node, if valid otherwise returns undefined.
712 | @param {Object|Number} identifier - A valid node or node id
713 | @returns {Object} node - The parent node
714 | */
715 | Tree.prototype.getParent = function (identifier) {
716 | var node = this.identifyNode(identifier);
717 | return this.nodes[node.parentId];
718 | };
719 |
720 | /**
721 | Returns an array of sibling nodes for a given node, if valid otherwise returns undefined.
722 | @param {Object|Number} identifier - A valid node or node id
723 | @returns {Array} nodes - Sibling nodes
724 | */
725 | Tree.prototype.getSiblings = function (identifier) {
726 | var node = this.identifyNode(identifier);
727 | var parent = this.getParent(node);
728 | var nodes = parent ? parent.nodes : this.tree;
729 | return nodes.filter(function (obj) {
730 | return obj.nodeId !== node.nodeId;
731 | });
732 | };
733 |
734 | /**
735 | Returns an array of selected nodes.
736 | @returns {Array} nodes - Selected nodes
737 | */
738 | Tree.prototype.getSelected = function () {
739 | return this.findNodes('true', 'g', 'state.selected');
740 | };
741 |
742 | /**
743 | Returns an array of unselected nodes.
744 | @returns {Array} nodes - Unselected nodes
745 | */
746 | Tree.prototype.getUnselected = function () {
747 | return this.findNodes('false', 'g', 'state.selected');
748 | };
749 |
750 | /**
751 | Returns an array of expanded nodes.
752 | @returns {Array} nodes - Expanded nodes
753 | */
754 | Tree.prototype.getExpanded = function () {
755 | return this.findNodes('true', 'g', 'state.expanded');
756 | };
757 |
758 | /**
759 | Returns an array of collapsed nodes.
760 | @returns {Array} nodes - Collapsed nodes
761 | */
762 | Tree.prototype.getCollapsed = function () {
763 | return this.findNodes('false', 'g', 'state.expanded');
764 | };
765 |
766 | /**
767 | Returns an array of checked nodes.
768 | @returns {Array} nodes - Checked nodes
769 | */
770 | Tree.prototype.getChecked = function () {
771 | return this.findNodes('true', 'g', 'state.checked');
772 | };
773 |
774 | /**
775 | Returns an array of unchecked nodes.
776 | @returns {Array} nodes - Unchecked nodes
777 | */
778 | Tree.prototype.getUnchecked = function () {
779 | return this.findNodes('false', 'g', 'state.checked');
780 | };
781 |
782 | /**
783 | Returns an array of disabled nodes.
784 | @returns {Array} nodes - Disabled nodes
785 | */
786 | Tree.prototype.getDisabled = function () {
787 | return this.findNodes('true', 'g', 'state.disabled');
788 | };
789 |
790 | /**
791 | Returns an array of enabled nodes.
792 | @returns {Array} nodes - Enabled nodes
793 | */
794 | Tree.prototype.getEnabled = function () {
795 | return this.findNodes('false', 'g', 'state.disabled');
796 | };
797 |
798 |
799 | /**
800 | Set a node state to selected
801 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
802 | @param {optional Object} options
803 | */
804 | Tree.prototype.selectNode = function (identifiers, options) {
805 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
806 | this.setSelectedState(node, true, options);
807 | }, this));
808 |
809 | this.render();
810 | };
811 |
812 | /**
813 | Set a node state to unselected
814 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
815 | @param {optional Object} options
816 | */
817 | Tree.prototype.unselectNode = function (identifiers, options) {
818 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
819 | this.setSelectedState(node, false, options);
820 | }, this));
821 |
822 | this.render();
823 | };
824 |
825 | /**
826 | Toggles a node selected state; selecting if unselected, unselecting if selected.
827 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
828 | @param {optional Object} options
829 | */
830 | Tree.prototype.toggleNodeSelected = function (identifiers, options) {
831 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
832 | this.toggleSelectedState(node, options);
833 | }, this));
834 |
835 | this.render();
836 | };
837 |
838 |
839 | /**
840 | Collapse all tree nodes
841 | @param {optional Object} options
842 | */
843 | Tree.prototype.collapseAll = function (options) {
844 | var identifiers = this.findNodes('true', 'g', 'state.expanded');
845 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
846 | this.setExpandedState(node, false, options);
847 | }, this));
848 |
849 | this.render();
850 | };
851 |
852 | /**
853 | Collapse a given tree node
854 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
855 | @param {optional Object} options
856 | */
857 | Tree.prototype.collapseNode = function (identifiers, options) {
858 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
859 | this.setExpandedState(node, false, options);
860 | }, this));
861 |
862 | this.render();
863 | };
864 |
865 | /**
866 | Expand all tree nodes
867 | @param {optional Object} options
868 | */
869 | Tree.prototype.expandAll = function (options) {
870 | options = $.extend({}, _default.options, options);
871 |
872 | if (options && options.levels) {
873 | this.expandLevels(this.tree, options.levels, options);
874 | }
875 | else {
876 | var identifiers = this.findNodes('false', 'g', 'state.expanded');
877 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
878 | this.setExpandedState(node, true, options);
879 | }, this));
880 | }
881 |
882 | this.render();
883 | };
884 |
885 | /**
886 | Expand a given tree node
887 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
888 | @param {optional Object} options
889 | */
890 | Tree.prototype.expandNode = function (identifiers, options) {
891 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
892 | this.setExpandedState(node, true, options);
893 | if (node.nodes && (options && options.levels)) {
894 | this.expandLevels(node.nodes, options.levels-1, options);
895 | }
896 | }, this));
897 |
898 | this.render();
899 | };
900 |
901 | Tree.prototype.expandLevels = function (nodes, level, options) {
902 | options = $.extend({}, _default.options, options);
903 |
904 | $.each(nodes, $.proxy(function (index, node) {
905 | this.setExpandedState(node, (level > 0) ? true : false, options);
906 | if (node.nodes) {
907 | this.expandLevels(node.nodes, level-1, options);
908 | }
909 | }, this));
910 | };
911 |
912 | /**
913 | Reveals a given tree node, expanding the tree from node to root.
914 | @param {Object|Number|Array} identifiers - A valid node, node id or array of node identifiers
915 | @param {optional Object} options
916 | */
917 | Tree.prototype.revealNode = function (identifiers, options) {
918 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
919 | var parentNode = this.getParent(node);
920 | while (parentNode) {
921 | this.setExpandedState(parentNode, true, options);
922 | parentNode = this.getParent(parentNode);
923 | };
924 | }, this));
925 |
926 | this.render();
927 | };
928 |
929 | /**
930 | Toggles a nodes expanded state; collapsing if expanded, expanding if collapsed.
931 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
932 | @param {optional Object} options
933 | */
934 | Tree.prototype.toggleNodeExpanded = function (identifiers, options) {
935 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
936 | this.toggleExpandedState(node, options);
937 | }, this));
938 |
939 | this.render();
940 | };
941 |
942 |
943 | /**
944 | Check all tree nodes
945 | @param {optional Object} options
946 | */
947 | Tree.prototype.checkAll = function (options) {
948 | var identifiers = this.findNodes('false', 'g', 'state.checked');
949 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
950 | this.setCheckedState(node, true, options);
951 | }, this));
952 |
953 | this.render();
954 | };
955 |
956 | /**
957 | Check a given tree node
958 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
959 | @param {optional Object} options
960 | */
961 | Tree.prototype.checkNode = function (identifiers, options) {
962 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
963 | this.setCheckedState(node, true, options);
964 | }, this));
965 |
966 | this.render();
967 | };
968 |
969 | /**
970 | Uncheck all tree nodes
971 | @param {optional Object} options
972 | */
973 | Tree.prototype.uncheckAll = function (options) {
974 | var identifiers = this.findNodes('true', 'g', 'state.checked');
975 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
976 | this.setCheckedState(node, false, options);
977 | }, this));
978 |
979 | this.render();
980 | };
981 |
982 | /**
983 | Uncheck a given tree node
984 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
985 | @param {optional Object} options
986 | */
987 | Tree.prototype.uncheckNode = function (identifiers, options) {
988 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
989 | this.setCheckedState(node, false, options);
990 | }, this));
991 |
992 | this.render();
993 | };
994 |
995 | /**
996 | Toggles a nodes checked state; checking if unchecked, unchecking if checked.
997 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
998 | @param {optional Object} options
999 | */
1000 | Tree.prototype.toggleNodeChecked = function (identifiers, options) {
1001 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1002 | this.toggleCheckedState(node, options);
1003 | }, this));
1004 |
1005 | this.render();
1006 | };
1007 |
1008 |
1009 | /**
1010 | Disable all tree nodes
1011 | @param {optional Object} options
1012 | */
1013 | Tree.prototype.disableAll = function (options) {
1014 | var identifiers = this.findNodes('false', 'g', 'state.disabled');
1015 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1016 | this.setDisabledState(node, true, options);
1017 | }, this));
1018 |
1019 | this.render();
1020 | };
1021 |
1022 | /**
1023 | Disable a given tree node
1024 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1025 | @param {optional Object} options
1026 | */
1027 | Tree.prototype.disableNode = function (identifiers, options) {
1028 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1029 | this.setDisabledState(node, true, options);
1030 | }, this));
1031 |
1032 | this.render();
1033 | };
1034 |
1035 | /**
1036 | Enable all tree nodes
1037 | @param {optional Object} options
1038 | */
1039 | Tree.prototype.enableAll = function (options) {
1040 | var identifiers = this.findNodes('true', 'g', 'state.disabled');
1041 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1042 | this.setDisabledState(node, false, options);
1043 | }, this));
1044 |
1045 | this.render();
1046 | };
1047 |
1048 | /**
1049 | Enable a given tree node
1050 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1051 | @param {optional Object} options
1052 | */
1053 | Tree.prototype.enableNode = function (identifiers, options) {
1054 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1055 | this.setDisabledState(node, false, options);
1056 | }, this));
1057 |
1058 | this.render();
1059 | };
1060 |
1061 | /**
1062 | Toggles a nodes disabled state; disabling is enabled, enabling if disabled.
1063 | @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1064 | @param {optional Object} options
1065 | */
1066 | Tree.prototype.toggleNodeDisabled = function (identifiers, options) {
1067 | this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1068 | this.setDisabledState(node, !node.state.disabled, options);
1069 | }, this));
1070 |
1071 | this.render();
1072 | };
1073 |
1074 |
1075 | /**
1076 | Common code for processing multiple identifiers
1077 | */
1078 | Tree.prototype.forEachIdentifier = function (identifiers, options, callback) {
1079 |
1080 | options = $.extend({}, _default.options, options);
1081 |
1082 | if (!(identifiers instanceof Array)) {
1083 | identifiers = [identifiers];
1084 | }
1085 |
1086 | $.each(identifiers, $.proxy(function (index, identifier) {
1087 | callback(this.identifyNode(identifier), options);
1088 | }, this));
1089 | };
1090 |
1091 | /*
1092 | Identifies a node from either a node id or object
1093 | */
1094 | Tree.prototype.identifyNode = function (identifier) {
1095 | return ((typeof identifier) === 'number') ?
1096 | this.nodes[identifier] :
1097 | identifier;
1098 | };
1099 |
1100 | /**
1101 | Searches the tree for nodes (text) that match given criteria
1102 | @param {String} pattern - A given string to match against
1103 | @param {optional Object} options - Search criteria options
1104 | @return {Array} nodes - Matching nodes
1105 | */
1106 | Tree.prototype.search = function (pattern, options) {
1107 | options = $.extend({}, _default.searchOptions, options);
1108 |
1109 | this.clearSearch({ render: false });
1110 |
1111 | var results = [];
1112 | if (pattern && pattern.length > 0) {
1113 |
1114 | if (options.exactMatch) {
1115 | pattern = '^' + pattern + '$';
1116 | }
1117 |
1118 | var modifier = 'g';
1119 | if (options.ignoreCase) {
1120 | modifier += 'i';
1121 | }
1122 |
1123 | results = this.findNodes(pattern, modifier);
1124 |
1125 | // Add searchResult property to all matching nodes
1126 | // This will be used to apply custom styles
1127 | // and when identifying result to be cleared
1128 | $.each(results, function (index, node) {
1129 | node.searchResult = true;
1130 | })
1131 | }
1132 |
1133 | // If revealResults, then render is triggered from revealNode
1134 | // otherwise we just call render.
1135 | if (options.revealResults) {
1136 | this.revealNode(results);
1137 | }
1138 | else {
1139 | this.render();
1140 | }
1141 |
1142 | this.$element.trigger('searchComplete', $.extend(true, {}, results));
1143 |
1144 | return results;
1145 | };
1146 |
1147 | /**
1148 | Clears previous search results
1149 | */
1150 | Tree.prototype.clearSearch = function (options) {
1151 |
1152 | options = $.extend({}, { render: true }, options);
1153 |
1154 | var results = $.each(this.findNodes('true', 'g', 'searchResult'), function (index, node) {
1155 | node.searchResult = false;
1156 | });
1157 |
1158 | if (options.render) {
1159 | this.render();
1160 | }
1161 |
1162 | this.$element.trigger('searchCleared', $.extend(true, {}, results));
1163 | };
1164 |
1165 | /**
1166 | Find nodes that match a given criteria
1167 | @param {String} pattern - A given string to match against
1168 | @param {optional String} modifier - Valid RegEx modifiers
1169 | @param {optional String} attribute - Attribute to compare pattern against
1170 | @return {Array} nodes - Nodes that match your criteria
1171 | */
1172 | Tree.prototype.findNodes = function (pattern, modifier, attribute) {
1173 |
1174 | modifier = modifier || 'g';
1175 | attribute = attribute || 'text';
1176 |
1177 | var _this = this;
1178 | return $.grep(this.nodes, function (node) {
1179 | var val = _this.getNodeValue(node, attribute);
1180 | if (typeof val === 'string') {
1181 | return val.match(new RegExp(pattern, modifier));
1182 | }
1183 | });
1184 | };
1185 |
1186 | /**
1187 | Recursive find for retrieving nested attributes values
1188 | All values are return as strings, unless invalid
1189 | @param {Object} obj - Typically a node, could be any object
1190 | @param {String} attr - Identifies an object property using dot notation
1191 | @return {String} value - Matching attributes string representation
1192 | */
1193 | Tree.prototype.getNodeValue = function (obj, attr) {
1194 | var index = attr.indexOf('.');
1195 | if (index > 0) {
1196 | var _obj = obj[attr.substring(0, index)];
1197 | var _attr = attr.substring(index + 1, attr.length);
1198 | return this.getNodeValue(_obj, _attr);
1199 | }
1200 | else {
1201 | if (obj.hasOwnProperty(attr)) {
1202 | return obj[attr].toString();
1203 | }
1204 | else {
1205 | return undefined;
1206 | }
1207 | }
1208 | };
1209 |
1210 | var logError = function (message) {
1211 | if (window.console) {
1212 | window.console.error(message);
1213 | }
1214 | };
1215 |
1216 | // Prevent against multiple instantiations,
1217 | // handle updates and method calls
1218 | $.fn[pluginName] = function (options, args) {
1219 |
1220 | var result;
1221 |
1222 | this.each(function () {
1223 | var _this = $.data(this, pluginName);
1224 | if (typeof options === 'string') {
1225 | if (!_this) {
1226 | logError('Not initialized, can not call method : ' + options);
1227 | }
1228 | else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') {
1229 | logError('No such method : ' + options);
1230 | }
1231 | else {
1232 | if (!(args instanceof Array)) {
1233 | args = [ args ];
1234 | }
1235 | result = _this[options].apply(_this, args);
1236 | }
1237 | }
1238 | else if (typeof options === 'boolean') {
1239 | result = _this;
1240 | }
1241 | else {
1242 | $.data(this, pluginName, new Tree(this, $.extend(true, {}, options)));
1243 | }
1244 | });
1245 |
1246 | return result || this;
1247 | };
1248 |
1249 | })(jQuery, window, document);
1250 |
--------------------------------------------------------------------------------