| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 | /* ========================================================= * bootstrap-treeview.js v1.0.0 * ========================================================= * Copyright 2013 Jonathan Miles  * Project URL : http://www.jondmiles.com/bootstrap-treeview *	 * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ========================================================= */;(function($, window, document, undefined) {	/*global jQuery, console*/	'use strict';	var pluginName = 'treeview';	var Tree = function(element, options) {		this.$element = $(element);		this._element = element;		this._elementId = this._element.id;		this._styleId = this._elementId + '-style';		this.tree = [];		this.nodes = [];		this.selectedNode = {};				this._init(options);	};	Tree.defaults = {		injectStyle: true,		levels: 2,		expandIcon: 'glyphicon glyphicon-plus',		collapseIcon: 'glyphicon glyphicon-minus',		nodeIcon: 'glyphicon glyphicon-stop',		color: undefined, // '#000000',		backColor: undefined, // '#FFFFFF',		borderColor: undefined, // '#dddddd',		onhoverColor: '#F5F5F5',		selectedColor: '#FFFFFF',		selectedBackColor: '#428bca',		enableLinks: false,		highlightSelected: true,		showBorder: true,		showTags: false,		// Event handler for when a node is selected		onNodeSelected: undefined	};	Tree.prototype = {		remove: function() {			this._destroy();			$.removeData(this, 'plugin_' + pluginName);			$('#' + this._styleId).remove();		},		_destroy: function() {			if (this.initialized) {				this.$wrapper.remove();				this.$wrapper = null;				// Switch off events				this._unsubscribeEvents();			}			// Reset initialized flag			this.initialized = false;		},		_init: function(options) {					if (options.data) {				if (typeof options.data === 'string') {					options.data = $.parseJSON(options.data);				}				this.tree = $.extend(true, [], options.data);				delete options.data;			}			this.options = $.extend({}, Tree.defaults, options);			this._setInitialLevels(this.tree, 0);			this._destroy();			this._subscribeEvents();			this._render();		},		_unsubscribeEvents: function() {			this.$element.off('click');		},		_subscribeEvents: function() {			this._unsubscribeEvents();			this.$element.on('click', $.proxy(this._clickHandler, this));			if (typeof (this.options.onNodeSelected) === 'function') {				this.$element.on('nodeSelected', this.options.onNodeSelected);			}		},		_clickHandler: function(event) {			if (!this.options.enableLinks) { event.preventDefault(); }						var target = $(event.target),				classList = target.attr('class') ? target.attr('class').split(' ') : [],				node = this._findNode(target);			if ((classList.indexOf('click-expand') != -1) ||					(classList.indexOf('click-collapse') != -1)) {				// Expand or collapse node by toggling child node visibility				this._toggleNodes(node);				this._render();			}			else if (node) {				this._setSelectedNode(node);			}		},		// Looks up the DOM for the closest parent list item to retrieve the 		// data attribute nodeid, which is used to lookup the node in the flattened structure.		_findNode: function(target) {			var nodeId = target.closest('li.list-group-item').attr('data-nodeid'),				node = this.nodes[nodeId];			if (!node) {				console.log('Error: node does not exist');			}			return node;		},		// Actually triggers the nodeSelected event		_triggerNodeSelectedEvent: function(node) {			this.$element.trigger('nodeSelected', [$.extend(true, {}, node)]);		},		// Handles selecting and unselecting of nodes, 		// as well as determining whether or not to trigger the nodeSelected event		_setSelectedNode: function(node) {			if (!node) { return; }						if (node === this.selectedNode) {				this.selectedNode = {};			}			else {				this._triggerNodeSelectedEvent(this.selectedNode = node);			}						this._render();		},		// On initialization recurses the entire tree structure 		// setting expanded / collapsed states based on initial levels		_setInitialLevels: function(nodes, level) {			if (!nodes) { return; }			level += 1;			var self = this;			$.each(nodes, function addNodes(id, node) {								if (level >= self.options.levels) {					self._toggleNodes(node);				}				// Need to traverse both nodes and _nodes to ensure 				// all levels collapsed beyond levels				var nodes = node.nodes ? node.nodes : node._nodes ? node._nodes : undefined;				if (nodes) {					return self._setInitialLevels(nodes, level);				}			});		},		// Toggle renaming nodes -> _nodes, _nodes -> nodes		// to simulate expanding or collapsing a node.		_toggleNodes: function(node) {			if (!node.nodes && !node._nodes) {				return;			}			if (node.nodes) {				node._nodes = node.nodes;				delete node.nodes;			}			else {				node.nodes = node._nodes;				delete node._nodes;			}		},		_render: function() {			var self = this;			if (!self.initialized) {				// Setup first time only components				self.$element.addClass(pluginName);				self.$wrapper = $(self._template.list);				self._injectStyle();								self.initialized = true;			}			self.$element.empty().append(self.$wrapper.empty());			// Build tree			self.nodes = [];			self._buildTree(self.tree, 0);		},		// Starting from the root node, and recursing down the 		// structure we build the tree one node at a time		_buildTree: function(nodes, level) {			if (!nodes) { return; }			level += 1;			var self = this;			$.each(nodes, function addNodes(id, node) {				node.nodeId = self.nodes.length;				self.nodes.push(node);				var treeItem = $(self._template.item)					.addClass('node-' + self._elementId)					.addClass((node === self.selectedNode) ? 'node-selected' : '')					.attr('data-nodeid', node.nodeId)					.attr('style', self._buildStyleOverride(node));				// Add indent/spacer to mimic tree structure				for (var i = 0; i < (level - 1); i++) {					treeItem.append(self._template.indent);				}				// Add expand, collapse or empty spacer icons 				// to facilitate tree structure navigation				if (node._nodes) {					treeItem						.append($(self._template.iconWrapper)							.append($(self._template.icon)								.addClass('click-expand')								.addClass(self.options.expandIcon))						);				}				else if (node.nodes) {					treeItem						.append($(self._template.iconWrapper)							.append($(self._template.icon)								.addClass('click-collapse')								.addClass(self.options.collapseIcon))						);				}				else {					treeItem						.append($(self._template.iconWrapper)							.append($(self._template.icon)								.addClass('glyphicon'))						);				}				// Add node icon				treeItem					.append($(self._template.iconWrapper)						.append($(self._template.icon)							.addClass(node.icon ? node.icon : self.options.nodeIcon))					);				// Add text				if (self.options.enableLinks) {					// Add hyperlink					treeItem						.append($(self._template.link)							.attr('href', node.href)							.append(node.text)						);				}				else {					// otherwise just text					treeItem						.append(node.text);				}				// Add tags as badges				if (self.options.showTags && node.tags) {					$.each(node.tags, function addTag(id, tag) {						treeItem							.append($(self._template.badge)								.append(tag)							);					});				}				// Add item to the tree				self.$wrapper.append(treeItem);				// Recursively add child ndoes				if (node.nodes) {					return self._buildTree(node.nodes, level);				}			});		},		// Define any node level style override for		// 1. selectedNode		// 2. node|data assigned color overrides		_buildStyleOverride: function(node) {			var style = '';			if (this.options.highlightSelected && (node === this.selectedNode)) {				style += 'color:' + this.options.selectedColor + ';';			}			else if (node.color) {				style += 'color:' + node.color + ';';			}			if (this.options.highlightSelected && (node === this.selectedNode)) {				style += 'background-color:' + this.options.selectedBackColor + ';';			}			else if (node.backColor) {				style += 'background-color:' + node.backColor + ';';			}			return style;		},		// Add inline style into head 		_injectStyle: function() {			if (this.options.injectStyle && !document.getElementById(this._styleId)) {				$('<style type="text/css" id="' + this._styleId + '"> ' + this._buildStyle() + ' </style>').appendTo('head');			}		},		// Construct trees style based on user options		_buildStyle: function() {			var style = '.node-' + this._elementId + '{';			if (this.options.color) {				style += 'color:' + this.options.color + ';';			}			if (this.options.backColor) {				style += 'background-color:' + this.options.backColor + ';';			}			if (!this.options.showBorder) {				style += 'border:none;';			}			else if (this.options.borderColor) {				style += 'border:1px solid ' + this.options.borderColor + ';';			}			style += '}';			if (this.options.onhoverColor) {				style += '.node-' + this._elementId + ':hover{' +				'background-color:' + this.options.onhoverColor + ';' +				'}';			}			return this._css + style;		},		_template: {			list: '<ul class="list-group"></ul>',			item: '<li class="list-group-item"></li>',			indent: '<span class="indent"></span>',			iconWrapper: '<span class="icon"></span>',			icon: '<i></i>',			link: '<a href="#" style="color:inherit;"></a>',			badge: '<span class="badge"></span>'		},		_css: '.list-group-item{cursor:pointer;}span.indent{margin-left:10px;margin-right:10px}span.icon{margin-right:5px}'		// _css: '.list-group-item{cursor:pointer;}.list-group-item:hover{background-color:#f5f5f5;}span.indent{margin-left:10px;margin-right:10px}span.icon{margin-right:5px}'	};	var logError = function(message) {        if(window.console) {            window.console.error(message);        }    };	// Prevent against multiple instantiations,	// handle updates and method calls	$.fn[pluginName] = function(options, args) {		return this.each(function() {			var self = $.data(this, 'plugin_' + pluginName);			if (typeof options === 'string') {				if (!self) {					logError('Not initialized, can not call method : ' + options);				}				else if (!$.isFunction(self[options]) || options.charAt(0) === '_') {					logError('No such method : ' + options);				}				else {					if (typeof args === 'string') {						args = [args];					}					self[options].apply(self, args);				}			}			else {				if (!self) {					$.data(this, 'plugin_' + pluginName, new Tree(this, $.extend(true, {}, options)));				}				else {					self._init(options);				}			}		});	};})(jQuery, window, document);
 |