PageSourceSearch

https://skin-new.unipa.it//plugins/bootstrap-treeview/bootstrap-treeview.js

js unipa.it collected 2026-10-01 08:55:49 UTC 33,803 bytes, 1,249 lines download raw bytes

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			$('<style type="text/css" id="' + this.styleId + '"> ' + this.buildStyle() + ' </style>').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: '<ul class="list-group"></ul>',
691		item: '<li class="list-group-item"></li>',
692		indent: '<span class="indent"></span>',
693		icon: '<span class="icon"></span>',
694		link: '<a href="#" style="color:inherit;"></a>',
695		badge: '<span class="badge"></span>'
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);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.