You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DS7/DispatchWeb/Content/hplus/js/plugins/treeview/bootstrap-treeview.js

444 lines
11 KiB
JavaScript

/* =========================================================
* 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);