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