Created
November 6, 2016 19:32
-
-
Save ferdiunal/835f8a2d9a7914448a352a3ea3770bbb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ========================================================= | |
* bootstrap-treeview.js v1.2.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 _default = {}; | |
_default.settings = { | |
injectStyle: true, | |
levels: 2, | |
expandIcon: 'glyphicon glyphicon-plus', | |
collapseIcon: 'glyphicon glyphicon-minus', | |
emptyIcon: 'glyphicon', | |
nodeIcon: '', | |
selectedIcon: '', | |
checkedIcon: 'glyphicon glyphicon-check', | |
uncheckedIcon: 'glyphicon glyphicon-unchecked', | |
color: undefined, // '#000000', | |
backColor: undefined, // '#FFFFFF', | |
borderColor: undefined, // '#dddddd', | |
onhoverColor: '#F5F5F5', | |
selectedColor: '#FFFFFF', | |
selectedBackColor: '#428bca', | |
searchResultColor: '#D9534F', | |
searchResultBackColor: undefined, //'#FFFFFF', | |
enableLinks: false, | |
highlightSelected: true, | |
highlightSearchResults: true, | |
showBorder: true, | |
showIcon: true, | |
showCheckbox: false, | |
showTags: false, | |
multiSelect: false, | |
// Event handlers | |
onNodeChecked: undefined, | |
onNodeCollapsed: undefined, | |
onNodeDisabled: undefined, | |
onNodeEnabled: undefined, | |
onNodeExpanded: undefined, | |
onNodeSelected: undefined, | |
onNodeUnchecked: undefined, | |
onNodeUnselected: undefined, | |
onSearchComplete: undefined, | |
onSearchCleared: undefined | |
}; | |
_default.options = { | |
silent: false, | |
ignoreChildren: false | |
}; | |
_default.searchOptions = { | |
ignoreCase: true, | |
exactMatch: false, | |
revealResults: true | |
}; | |
var Tree = function (element, options) { | |
this.$element = $(element); | |
this.elementId = element.id; | |
this.styleId = this.elementId + '-style'; | |
this.init(options); | |
return { | |
// Options (public access) | |
options: this.options, | |
// Initialize / destroy methods | |
init: $.proxy(this.init, this), | |
remove: $.proxy(this.remove, this), | |
// Get methods | |
getNode: $.proxy(this.getNode, this), | |
getParent: $.proxy(this.getParent, this), | |
getSiblings: $.proxy(this.getSiblings, this), | |
getSelected: $.proxy(this.getSelected, this), | |
getUnselected: $.proxy(this.getUnselected, this), | |
getExpanded: $.proxy(this.getExpanded, this), | |
getCollapsed: $.proxy(this.getCollapsed, this), | |
getChecked: $.proxy(this.getChecked, this), | |
getUnchecked: $.proxy(this.getUnchecked, this), | |
getDisabled: $.proxy(this.getDisabled, this), | |
getEnabled: $.proxy(this.getEnabled, this), | |
// Select methods | |
selectNode: $.proxy(this.selectNode, this), | |
unselectNode: $.proxy(this.unselectNode, this), | |
toggleNodeSelected: $.proxy(this.toggleNodeSelected, this), | |
// Expand / collapse methods | |
collapseAll: $.proxy(this.collapseAll, this), | |
collapseNode: $.proxy(this.collapseNode, this), | |
expandAll: $.proxy(this.expandAll, this), | |
expandNode: $.proxy(this.expandNode, this), | |
toggleNodeExpanded: $.proxy(this.toggleNodeExpanded, this), | |
revealNode: $.proxy(this.revealNode, this), | |
// Expand / collapse methods | |
checkAll: $.proxy(this.checkAll, this), | |
checkNode: $.proxy(this.checkNode, this), | |
uncheckAll: $.proxy(this.uncheckAll, this), | |
uncheckNode: $.proxy(this.uncheckNode, this), | |
toggleNodeChecked: $.proxy(this.toggleNodeChecked, this), | |
// Disable / enable methods | |
disableAll: $.proxy(this.disableAll, this), | |
disableNode: $.proxy(this.disableNode, this), | |
enableAll: $.proxy(this.enableAll, this), | |
enableNode: $.proxy(this.enableNode, this), | |
toggleNodeDisabled: $.proxy(this.toggleNodeDisabled, this), | |
// Search methods | |
search: $.proxy(this.search, this), | |
clearSearch: $.proxy(this.clearSearch, this) | |
}; | |
}; | |
Tree.prototype.init = function (options) { | |
this.tree = []; | |
this.nodes = []; | |
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({}, _default.settings, options); | |
this.destroy(); | |
this.subscribeEvents(); | |
this.setInitialStates({ nodes: this.tree }, 0); | |
this.render(); | |
}; | |
Tree.prototype.remove = function () { | |
this.destroy(); | |
$.removeData(this, pluginName); | |
$('#' + this.styleId).remove(); | |
}; | |
Tree.prototype.destroy = function () { | |
if (!this.initialized) return; | |
this.$wrapper.remove(); | |
this.$wrapper = null; | |
// Switch off events | |
this.unsubscribeEvents(); | |
// Reset this.initialized flag | |
this.initialized = false; | |
}; | |
Tree.prototype.unsubscribeEvents = function () { | |
this.$element.off('click'); | |
this.$element.off('nodeChecked'); | |
this.$element.off('nodeCollapsed'); | |
this.$element.off('nodeDisabled'); | |
this.$element.off('nodeEnabled'); | |
this.$element.off('nodeExpanded'); | |
this.$element.off('nodeSelected'); | |
this.$element.off('nodeUnchecked'); | |
this.$element.off('nodeUnselected'); | |
this.$element.off('searchComplete'); | |
this.$element.off('searchCleared'); | |
}; | |
Tree.prototype.subscribeEvents = function () { | |
this.unsubscribeEvents(); | |
this.$element.on('click', $.proxy(this.clickHandler, this)); | |
if (typeof (this.options.onNodeChecked) === 'function') { | |
this.$element.on('nodeChecked', this.options.onNodeChecked); | |
} | |
if (typeof (this.options.onNodeCollapsed) === 'function') { | |
this.$element.on('nodeCollapsed', this.options.onNodeCollapsed); | |
} | |
if (typeof (this.options.onNodeDisabled) === 'function') { | |
this.$element.on('nodeDisabled', this.options.onNodeDisabled); | |
} | |
if (typeof (this.options.onNodeEnabled) === 'function') { | |
this.$element.on('nodeEnabled', this.options.onNodeEnabled); | |
} | |
if (typeof (this.options.onNodeExpanded) === 'function') { | |
this.$element.on('nodeExpanded', this.options.onNodeExpanded); | |
} | |
if (typeof (this.options.onNodeSelected) === 'function') { | |
this.$element.on('nodeSelected', this.options.onNodeSelected); | |
} | |
if (typeof (this.options.onNodeUnchecked) === 'function') { | |
this.$element.on('nodeUnchecked', this.options.onNodeUnchecked); | |
} | |
if (typeof (this.options.onNodeUnselected) === 'function') { | |
this.$element.on('nodeUnselected', this.options.onNodeUnselected); | |
} | |
if (typeof (this.options.onSearchComplete) === 'function') { | |
this.$element.on('searchComplete', this.options.onSearchComplete); | |
} | |
if (typeof (this.options.onSearchCleared) === 'function') { | |
this.$element.on('searchCleared', this.options.onSearchCleared); | |
} | |
}; | |
/* | |
Recurse the tree structure and ensure all nodes have | |
valid initial states. User defined states will be preserved. | |
For performance we also take this opportunity to | |
index nodes in a flattened structure | |
*/ | |
Tree.prototype.setInitialStates = function (node, level) { | |
if (!node.nodes) return; | |
level += 1; | |
var parent = node; | |
var _this = this; | |
$.each(node.nodes, function checkStates(index, node) { | |
// nodeId : unique, incremental identifier | |
node.nodeId = _this.nodes.length; | |
// parentId : transversing up the tree | |
node.parentId = parent.nodeId; | |
// if not provided set selectable default value | |
if (!node.hasOwnProperty('selectable')) { | |
node.selectable = true; | |
} | |
// where provided we should preserve states | |
node.state = node.state || {}; | |
// set checked state; unless set always false | |
if (!node.state.hasOwnProperty('checked')) { | |
node.state.checked = false; | |
} | |
// set enabled state; unless set always false | |
if (!node.state.hasOwnProperty('disabled')) { | |
node.state.disabled = false; | |
} | |
// set expanded state; if not provided based on levels | |
if (!node.state.hasOwnProperty('expanded')) { | |
if (!node.state.disabled && | |
(level < _this.options.levels) && | |
(node.nodes && node.nodes.length > 0)) { | |
node.state.expanded = true; | |
} | |
else { | |
node.state.expanded = false; | |
} | |
} | |
// set selected state; unless set always false | |
if (!node.state.hasOwnProperty('selected')) { | |
node.state.selected = false; | |
} | |
// index nodes in a flattened structure for use later | |
_this.nodes.push(node); | |
// recurse child nodes and transverse the tree | |
if (node.nodes) { | |
_this.setInitialStates(node, level); | |
} | |
}); | |
}; | |
Tree.prototype.clickHandler = function (event) { | |
if (!this.options.enableLinks) event.preventDefault(); | |
var target = $(event.target); | |
var node = this.findNode(target); | |
if (!node || node.state.disabled) return; | |
var classList = target.attr('class') ? target.attr('class').split(' ') : []; | |
if ((classList.indexOf('expand-icon') !== -1)) { | |
this.toggleExpandedState(node, _default.options); | |
this.render(); | |
} | |
else if ((classList.indexOf('check-icon') !== -1)) { | |
this.toggleCheckedState(node, _default.options); | |
this.render(); | |
} | |
else { | |
if (node.selectable) { | |
this.toggleSelectedState(node, _default.options); | |
this.toggleExpandedState(node, _default.options); | |
} | |
this.render(); | |
} | |
}; | |
// 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. | |
Tree.prototype.findNode = function (target) { | |
var nodeId = target.closest('li.list-group-item').attr('data-nodeid'); | |
var node = this.nodes[nodeId]; | |
if (!node) { | |
console.log('Error: node does not exist'); | |
} | |
return node; | |
}; | |
Tree.prototype.toggleExpandedState = function (node, options) { | |
if (!node) return; | |
this.setExpandedState(node, !node.state.expanded, options); | |
}; | |
Tree.prototype.setExpandedState = function (node, state, options) { | |
if (state === node.state.expanded) return; | |
if (state && node.nodes) { | |
// Expand a node | |
node.state.expanded = true; | |
if (!options.silent) { | |
this.$element.trigger('nodeExpanded', $.extend(true, {}, node)); | |
} | |
} | |
else if (!state) { | |
// Collapse a node | |
node.state.expanded = false; | |
if (!options.silent) { | |
this.$element.trigger('nodeCollapsed', $.extend(true, {}, node)); | |
} | |
// Collapse child nodes | |
if (node.nodes && !options.ignoreChildren) { | |
$.each(node.nodes, $.proxy(function (index, node) { | |
this.setExpandedState(node, false, options); | |
}, this)); | |
} | |
} | |
}; | |
Tree.prototype.toggleSelectedState = function (node, options) { | |
if (!node) return; | |
this.setSelectedState(node, !node.state.selected, options); | |
}; | |
Tree.prototype.setSelectedState = function (node, state, options) { | |
if (state === node.state.selected) return; | |
if (state) { | |
// If multiSelect false, unselect previously selected | |
if (!this.options.multiSelect) { | |
$.each(this.findNodes('true', 'g', 'state.selected'), $.proxy(function (index, node) { | |
this.setSelectedState(node, false, options); | |
}, this)); | |
} | |
// Continue selecting node | |
node.state.selected = true; | |
if (!options.silent) { | |
this.$element.trigger('nodeSelected', $.extend(true, {}, node)); | |
} | |
} | |
else { | |
// Unselect node | |
node.state.selected = false; | |
if (!options.silent) { | |
this.$element.trigger('nodeUnselected', $.extend(true, {}, node)); | |
} | |
} | |
}; | |
Tree.prototype.toggleCheckedState = function (node, options) { | |
if (!node) return; | |
this.setCheckedState(node, !node.state.checked, options); | |
}; | |
Tree.prototype.setCheckedState = function (node, state, options) { | |
if (state === node.state.checked) return; | |
if (state) { | |
// Check node | |
node.state.checked = true; | |
if (!options.silent) { | |
this.$element.trigger('nodeChecked', $.extend(true, {}, node)); | |
} | |
} | |
else { | |
// Uncheck node | |
node.state.checked = false; | |
if (!options.silent) { | |
this.$element.trigger('nodeUnchecked', $.extend(true, {}, node)); | |
} | |
} | |
}; | |
Tree.prototype.setDisabledState = function (node, state, options) { | |
if (state === node.state.disabled) return; | |
if (state) { | |
// Disable node | |
node.state.disabled = true; | |
// Disable all other states | |
this.setExpandedState(node, false, options); | |
this.setSelectedState(node, false, options); | |
this.setCheckedState(node, false, options); | |
if (!options.silent) { | |
this.$element.trigger('nodeDisabled', $.extend(true, {}, node)); | |
} | |
} | |
else { | |
// Enabled node | |
node.state.disabled = false; | |
if (!options.silent) { | |
this.$element.trigger('nodeEnabled', $.extend(true, {}, node)); | |
} | |
} | |
}; | |
Tree.prototype.render = function () { | |
if (!this.initialized) { | |
// Setup first time only components | |
this.$element.addClass(pluginName); | |
this.$wrapper = $(this.template.list); | |
this.injectStyle(); | |
this.initialized = true; | |
} | |
this.$element.empty().append(this.$wrapper.empty()); | |
// Build tree | |
this.buildTree(this.tree, 0); | |
}; | |
// Starting from the root node, and recursing down the | |
// structure we build the tree one node at a time | |
Tree.prototype.buildTree = function (nodes, level) { | |
if (!nodes) return; | |
level += 1; | |
var _this = this; | |
$.each(nodes, function addNodes(id, node) { | |
var treeItem = $(_this.template.item) | |
.addClass('node-' + _this.elementId) | |
.addClass(node.state.checked ? 'node-checked' : '') | |
.addClass(node.state.disabled ? 'node-disabled': '') | |
.addClass(node.state.selected ? 'node-selected' : '') | |
.addClass(node.searchResult ? 'search-result' : '') | |
.attr('data-nodeid', node.nodeId) | |
.attr('style', _this.buildStyleOverride(node)); | |
// Add indent/spacer to mimic tree structure | |
for (var i = 0; i < (level - 1); i++) { | |
treeItem.append(_this.template.indent); | |
} | |
// Add expand, collapse or empty spacer icons | |
var classList = []; | |
if (node.nodes) { | |
classList.push('expand-icon'); | |
if (node.state.expanded) { | |
classList.push(_this.options.collapseIcon); | |
} | |
else { | |
classList.push(_this.options.expandIcon); | |
} | |
} | |
else { | |
classList.push(_this.options.emptyIcon); | |
} | |
treeItem | |
.append($(_this.template.icon) | |
.addClass(classList.join(' ')) | |
); | |
// Add node icon | |
if (_this.options.showIcon) { | |
var classList = ['node-icon']; | |
classList.push(node.icon || _this.options.nodeIcon); | |
if (node.state.selected) { | |
classList.pop(); | |
classList.push(node.selectedIcon || _this.options.selectedIcon || | |
node.icon || _this.options.nodeIcon); | |
} | |
treeItem | |
.append($(_this.template.icon) | |
.addClass(classList.join(' ')) | |
); | |
} | |
// Add check / unchecked icon | |
if (_this.options.showCheckbox) { | |
var classList = ['check-icon']; | |
if (node.state.checked) { | |
classList.push(_this.options.checkedIcon); | |
} | |
else { | |
classList.push(_this.options.uncheckedIcon); | |
} | |
treeItem | |
.append($(_this.template.icon) | |
.addClass(classList.join(' ')) | |
); | |
} | |
// Add text | |
if (_this.options.enableLinks) { | |
// Add hyperlink | |
treeItem | |
.append($(_this.template.link) | |
.attr('href', node.href) | |
.append(node.text) | |
); | |
} | |
else { | |
// otherwise just text | |
treeItem | |
.append(node.text); | |
} | |
// Add tags as badges | |
if (_this.options.showTags && node.tags) { | |
$.each(node.tags, function addTag(id, tag) { | |
treeItem | |
.append($(_this.template.badge) | |
.append(tag) | |
); | |
}); | |
} | |
// Add item to the tree | |
_this.$wrapper.append(treeItem); | |
// Recursively add child ndoes | |
if (node.nodes && node.state.expanded && !node.state.disabled) { | |
return _this.buildTree(node.nodes, level); | |
} | |
}); | |
}; | |
// Define any node level style override for | |
// 1. selectedNode | |
// 2. node|data assigned color overrides | |
Tree.prototype.buildStyleOverride = function (node) { | |
if (node.state.disabled) return ''; | |
var color = node.color; | |
var backColor = node.backColor; | |
if (this.options.highlightSelected && node.state.selected) { | |
if (this.options.selectedColor) { | |
color = this.options.selectedColor; | |
} | |
if (this.options.selectedBackColor) { | |
backColor = this.options.selectedBackColor; | |
} | |
} | |
if (this.options.highlightSearchResults && node.searchResult && !node.state.disabled) { | |
if (this.options.searchResultColor) { | |
color = this.options.searchResultColor; | |
} | |
if (this.options.searchResultBackColor) { | |
backColor = this.options.searchResultBackColor; | |
} | |
} | |
return 'color:' + color + | |
';background-color:' + backColor + ';'; | |
}; | |
// Add inline style into head | |
Tree.prototype.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 | |
Tree.prototype.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 + ':not(.node-disabled):hover{' + | |
'background-color:' + this.options.onhoverColor + ';' + | |
'}'; | |
} | |
return this.css + style; | |
}; | |
Tree.prototype.template = { | |
list: '<ul class="list-group"></ul>', | |
item: '<li class="list-group-item"></li>', | |
indent: '<span class="indent"></span>', | |
icon: '<span class="icon"></span>', | |
link: '<a href="#" style="color:inherit;"></a>', | |
badge: '<span class="badge"></span>' | |
}; | |
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}' | |
/** | |
Returns a single node object that matches the given node id. | |
@param {Number} nodeId - A node's unique identifier | |
@return {Object} node - Matching node | |
*/ | |
Tree.prototype.getNode = function (nodeId) { | |
return this.nodes[nodeId]; | |
}; | |
/** | |
Returns the parent node of a given node, if valid otherwise returns undefined. | |
@param {Object|Number} identifier - A valid node or node id | |
@returns {Object} node - The parent node | |
*/ | |
Tree.prototype.getParent = function (identifier) { | |
var node = this.identifyNode(identifier); | |
return this.nodes[node.parentId]; | |
}; | |
/** | |
Returns an array of sibling nodes for a given node, if valid otherwise returns undefined. | |
@param {Object|Number} identifier - A valid node or node id | |
@returns {Array} nodes - Sibling nodes | |
*/ | |
Tree.prototype.getSiblings = function (identifier) { | |
var node = this.identifyNode(identifier); | |
var parent = this.getParent(node); | |
var nodes = parent ? parent.nodes : this.tree; | |
return nodes.filter(function (obj) { | |
return obj.nodeId !== node.nodeId; | |
}); | |
}; | |
/** | |
Returns an array of selected nodes. | |
@returns {Array} nodes - Selected nodes | |
*/ | |
Tree.prototype.getSelected = function () { | |
return this.findNodes('true', 'g', 'state.selected'); | |
}; | |
/** | |
Returns an array of unselected nodes. | |
@returns {Array} nodes - Unselected nodes | |
*/ | |
Tree.prototype.getUnselected = function () { | |
return this.findNodes('false', 'g', 'state.selected'); | |
}; | |
/** | |
Returns an array of expanded nodes. | |
@returns {Array} nodes - Expanded nodes | |
*/ | |
Tree.prototype.getExpanded = function () { | |
return this.findNodes('true', 'g', 'state.expanded'); | |
}; | |
/** | |
Returns an array of collapsed nodes. | |
@returns {Array} nodes - Collapsed nodes | |
*/ | |
Tree.prototype.getCollapsed = function () { | |
return this.findNodes('false', 'g', 'state.expanded'); | |
}; | |
/** | |
Returns an array of checked nodes. | |
@returns {Array} nodes - Checked nodes | |
*/ | |
Tree.prototype.getChecked = function () { | |
return this.findNodes('true', 'g', 'state.checked'); | |
}; | |
/** | |
Returns an array of unchecked nodes. | |
@returns {Array} nodes - Unchecked nodes | |
*/ | |
Tree.prototype.getUnchecked = function () { | |
return this.findNodes('false', 'g', 'state.checked'); | |
}; | |
/** | |
Returns an array of disabled nodes. | |
@returns {Array} nodes - Disabled nodes | |
*/ | |
Tree.prototype.getDisabled = function () { | |
return this.findNodes('true', 'g', 'state.disabled'); | |
}; | |
/** | |
Returns an array of enabled nodes. | |
@returns {Array} nodes - Enabled nodes | |
*/ | |
Tree.prototype.getEnabled = function () { | |
return this.findNodes('false', 'g', 'state.disabled'); | |
}; | |
/** | |
Set a node state to selected | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.selectNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setSelectedState(node, true, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Set a node state to unselected | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.unselectNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setSelectedState(node, false, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Toggles a node selected state; selecting if unselected, unselecting if selected. | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.toggleNodeSelected = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.toggleSelectedState(node, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Collapse all tree nodes | |
@param {optional Object} options | |
*/ | |
Tree.prototype.collapseAll = function (options) { | |
var identifiers = this.findNodes('true', 'g', 'state.expanded'); | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setExpandedState(node, false, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Collapse a given tree node | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.collapseNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setExpandedState(node, false, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Expand all tree nodes | |
@param {optional Object} options | |
*/ | |
Tree.prototype.expandAll = function (options) { | |
options = $.extend({}, _default.options, options); | |
if (options && options.levels) { | |
this.expandLevels(this.tree, options.levels, options); | |
} | |
else { | |
var identifiers = this.findNodes('false', 'g', 'state.expanded'); | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setExpandedState(node, true, options); | |
}, this)); | |
} | |
this.render(); | |
}; | |
/** | |
Expand a given tree node | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.expandNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setExpandedState(node, true, options); | |
if (node.nodes && (options && options.levels)) { | |
this.expandLevels(node.nodes, options.levels-1, options); | |
} | |
}, this)); | |
this.render(); | |
}; | |
Tree.prototype.expandLevels = function (nodes, level, options) { | |
options = $.extend({}, _default.options, options); | |
$.each(nodes, $.proxy(function (index, node) { | |
this.setExpandedState(node, (level > 0) ? true : false, options); | |
if (node.nodes) { | |
this.expandLevels(node.nodes, level-1, options); | |
} | |
}, this)); | |
}; | |
/** | |
Reveals a given tree node, expanding the tree from node to root. | |
@param {Object|Number|Array} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.revealNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
var parentNode = this.getParent(node); | |
while (parentNode) { | |
this.setExpandedState(parentNode, true, options); | |
parentNode = this.getParent(parentNode); | |
}; | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Toggles a nodes expanded state; collapsing if expanded, expanding if collapsed. | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.toggleNodeExpanded = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.toggleExpandedState(node, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Check all tree nodes | |
@param {optional Object} options | |
*/ | |
Tree.prototype.checkAll = function (options) { | |
var identifiers = this.findNodes('false', 'g', 'state.checked'); | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setCheckedState(node, true, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Check a given tree node | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.checkNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setCheckedState(node, true, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Uncheck all tree nodes | |
@param {optional Object} options | |
*/ | |
Tree.prototype.uncheckAll = function (options) { | |
var identifiers = this.findNodes('true', 'g', 'state.checked'); | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setCheckedState(node, false, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Uncheck a given tree node | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.uncheckNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setCheckedState(node, false, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Toggles a nodes checked state; checking if unchecked, unchecking if checked. | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.toggleNodeChecked = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.toggleCheckedState(node, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Disable all tree nodes | |
@param {optional Object} options | |
*/ | |
Tree.prototype.disableAll = function (options) { | |
var identifiers = this.findNodes('false', 'g', 'state.disabled'); | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setDisabledState(node, true, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Disable a given tree node | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.disableNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setDisabledState(node, true, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Enable all tree nodes | |
@param {optional Object} options | |
*/ | |
Tree.prototype.enableAll = function (options) { | |
var identifiers = this.findNodes('true', 'g', 'state.disabled'); | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setDisabledState(node, false, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Enable a given tree node | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.enableNode = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setDisabledState(node, false, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Toggles a nodes disabled state; disabling is enabled, enabling if disabled. | |
@param {Object|Number} identifiers - A valid node, node id or array of node identifiers | |
@param {optional Object} options | |
*/ | |
Tree.prototype.toggleNodeDisabled = function (identifiers, options) { | |
this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { | |
this.setDisabledState(node, !node.state.disabled, options); | |
}, this)); | |
this.render(); | |
}; | |
/** | |
Common code for processing multiple identifiers | |
*/ | |
Tree.prototype.forEachIdentifier = function (identifiers, options, callback) { | |
options = $.extend({}, _default.options, options); | |
if (!(identifiers instanceof Array)) { | |
identifiers = [identifiers]; | |
} | |
$.each(identifiers, $.proxy(function (index, identifier) { | |
callback(this.identifyNode(identifier), options); | |
}, this)); | |
}; | |
/* | |
Identifies a node from either a node id or object | |
*/ | |
Tree.prototype.identifyNode = function (identifier) { | |
return ((typeof identifier) === 'number') ? | |
this.nodes[identifier] : | |
identifier; | |
}; | |
/** | |
Searches the tree for nodes (text) that match given criteria | |
@param {String} pattern - A given string to match against | |
@param {optional Object} options - Search criteria options | |
@return {Array} nodes - Matching nodes | |
*/ | |
Tree.prototype.search = function (pattern, options) { | |
options = $.extend({}, _default.searchOptions, options); | |
this.clearSearch({ render: false }); | |
var results = []; | |
if (pattern && pattern.length > 0) { | |
if (options.exactMatch) { | |
pattern = '^' + pattern + '$'; | |
} | |
var modifier = 'g'; | |
if (options.ignoreCase) { | |
modifier += 'i'; | |
} | |
results = this.findNodes(pattern, modifier); | |
// Add searchResult property to all matching nodes | |
// This will be used to apply custom styles | |
// and when identifying result to be cleared | |
$.each(results, function (index, node) { | |
node.searchResult = true; | |
}) | |
} | |
// If revealResults, then render is triggered from revealNode | |
// otherwise we just call render. | |
if (options.revealResults) { | |
this.revealNode(results); | |
} | |
else { | |
this.render(); | |
} | |
this.$element.trigger('searchComplete', $.extend(true, {}, results)); | |
return results; | |
}; | |
/** | |
Clears previous search results | |
*/ | |
Tree.prototype.clearSearch = function (options) { | |
options = $.extend({}, { render: true }, options); | |
var results = $.each(this.findNodes('true', 'g', 'searchResult'), function (index, node) { | |
node.searchResult = false; | |
}); | |
if (options.render) { | |
this.render(); | |
} | |
this.$element.trigger('searchCleared', $.extend(true, {}, results)); | |
}; | |
/** | |
Find nodes that match a given criteria | |
@param {String} pattern - A given string to match against | |
@param {optional String} modifier - Valid RegEx modifiers | |
@param {optional String} attribute - Attribute to compare pattern against | |
@return {Array} nodes - Nodes that match your criteria | |
*/ | |
Tree.prototype.findNodes = function (pattern, modifier, attribute) { | |
modifier = modifier || 'g'; | |
attribute = attribute || 'text'; | |
var _this = this; | |
return $.grep(this.nodes, function (node) { | |
var val = _this.getNodeValue(node, attribute); | |
if (typeof val === 'string') { | |
return val.match(new RegExp(pattern, modifier)); | |
} | |
}); | |
}; | |
/** | |
Recursive find for retrieving nested attributes values | |
All values are return as strings, unless invalid | |
@param {Object} obj - Typically a node, could be any object | |
@param {String} attr - Identifies an object property using dot notation | |
@return {String} value - Matching attributes string representation | |
*/ | |
Tree.prototype.getNodeValue = function (obj, attr) { | |
var index = attr.indexOf('.'); | |
if (index > 0) { | |
var _obj = obj[attr.substring(0, index)]; | |
var _attr = attr.substring(index + 1, attr.length); | |
return this.getNodeValue(_obj, _attr); | |
} | |
else { | |
if (obj.hasOwnProperty(attr)) { | |
return obj[attr].toString(); | |
} | |
else { | |
return undefined; | |
} | |
} | |
}; | |
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) { | |
var result; | |
this.each(function () { | |
var _this = $.data(this, pluginName); | |
if (typeof options === 'string') { | |
if (!_this) { | |
logError('Not initialized, can not call method : ' + options); | |
} | |
else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') { | |
logError('No such method : ' + options); | |
} | |
else { | |
if (!(args instanceof Array)) { | |
args = [ args ]; | |
} | |
result = _this[options].apply(_this, args); | |
} | |
} | |
else if (typeof options === 'boolean') { | |
result = _this; | |
} | |
else { | |
$.data(this, pluginName, new Tree(this, $.extend(true, {}, options))); | |
} | |
}); | |
return result || this; | |
}; | |
})(jQuery, window, document); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment