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);
|