|
@@ -0,0 +1,1951 @@
|
|
|
+/* =========================================================
|
|
|
+ * patternfly-bootstrap-treeview.js v2.1.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',
|
|
|
+ loadingIcon: 'glyphicon glyphicon-hourglass',
|
|
|
+ emptyIcon: 'glyphicon',
|
|
|
+ nodeIcon: '',
|
|
|
+ selectedIcon: '',
|
|
|
+ checkedIcon: 'glyphicon glyphicon-check',
|
|
|
+ partiallyCheckedIcon: 'glyphicon glyphicon-expand',
|
|
|
+ uncheckedIcon: 'glyphicon glyphicon-unchecked',
|
|
|
+
|
|
|
+ color: undefined,
|
|
|
+ backColor: undefined,
|
|
|
+ borderColor: undefined,
|
|
|
+ changedNodeColor: '#39A5DC',
|
|
|
+ onhoverColor: '#F5F5F5',
|
|
|
+ selectedColor: '#FFFFFF',
|
|
|
+ selectedBackColor: '#428bca',
|
|
|
+ searchResultColor: '#D9534F',
|
|
|
+ searchResultBackColor: undefined,
|
|
|
+
|
|
|
+ highlightSelected: true,
|
|
|
+ highlightSearchResults: true,
|
|
|
+ showBorder: true,
|
|
|
+ showIcon: true,
|
|
|
+ showImage: false,
|
|
|
+ showCheckbox: false,
|
|
|
+ checkboxFirst: false,
|
|
|
+ highlightChanges: false,
|
|
|
+ showTags: false,
|
|
|
+ multiSelect: false,
|
|
|
+ preventUnselect: false,
|
|
|
+ allowReselect: false,
|
|
|
+ hierarchicalCheck: false,
|
|
|
+ propagateCheckEvent: false,
|
|
|
+ wrapNodeText: false,
|
|
|
+
|
|
|
+ // Event handlers
|
|
|
+ onLoading: undefined,
|
|
|
+ onLoadingFailed: undefined,
|
|
|
+ onInitialized: undefined,
|
|
|
+ onNodeRendered: undefined,
|
|
|
+ onRendered: undefined,
|
|
|
+ onDestroyed: undefined,
|
|
|
+
|
|
|
+ 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
|
|
|
+ };
|
|
|
+
|
|
|
+ _default.dataUrl = {
|
|
|
+ method: 'GET',
|
|
|
+ dataType: 'json',
|
|
|
+ cache: false
|
|
|
+ };
|
|
|
+
|
|
|
+ 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),
|
|
|
+
|
|
|
+ // Query methods
|
|
|
+ findNodes: $.proxy(this.findNodes, this),
|
|
|
+ getNodes: $.proxy(this.getNodes, this), // todo document + test
|
|
|
+ getParents: $.proxy(this.getParents, 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),
|
|
|
+
|
|
|
+ // Tree manipulation methods
|
|
|
+ addNode: $.proxy(this.addNode, this),
|
|
|
+ addNodeAfter: $.proxy(this.addNodeAfter, this),
|
|
|
+ addNodeBefore: $.proxy(this.addNodeBefore, this),
|
|
|
+ removeNode: $.proxy(this.removeNode, this),
|
|
|
+ updateNode: $.proxy(this.updateNode, this),
|
|
|
+ editNode: $.proxy(this.editNode, 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),
|
|
|
+
|
|
|
+ // Check / uncheck 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),
|
|
|
+ unmarkCheckboxChanges: $.proxy(this.unmarkCheckboxChanges, 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._initialized = false;
|
|
|
+
|
|
|
+ this._options = $.extend({}, _default.settings, options);
|
|
|
+
|
|
|
+ // Cache empty icon DOM template
|
|
|
+ this._template.icon.empty.addClass(this._options.emptyIcon);
|
|
|
+
|
|
|
+ this._destroy();
|
|
|
+ this._subscribeEvents();
|
|
|
+
|
|
|
+ this._triggerEvent('loading', null, _default.options);
|
|
|
+ this._load(options)
|
|
|
+ .then($.proxy(function (data) {
|
|
|
+ // load done
|
|
|
+ return this._tree = $.extend(true, [], data);
|
|
|
+ }, this), $.proxy(function (error) {
|
|
|
+ // load fail
|
|
|
+ this._triggerEvent('loadingFailed', error, _default.options);
|
|
|
+ }, this))
|
|
|
+ .then($.proxy(function (treeData) {
|
|
|
+ // initialize data
|
|
|
+ return this._setInitialStates({ nodes: treeData }, 0);
|
|
|
+ }, this))
|
|
|
+ .then($.proxy(function () {
|
|
|
+ // render to DOM
|
|
|
+ this._render();
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._load = function (options) {
|
|
|
+ var done = new $.Deferred();
|
|
|
+ if (options.data) {
|
|
|
+ this._loadLocalData(options, done);
|
|
|
+ } else if (options.dataUrl) {
|
|
|
+ this._loadRemoteData(options, done);
|
|
|
+ }
|
|
|
+ return done.promise();
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._loadRemoteData = function (options, done) {
|
|
|
+ $.ajax($.extend(true, {}, _default.dataUrl, options.dataUrl))
|
|
|
+ .done(function (data) {
|
|
|
+ done.resolve(data);
|
|
|
+ })
|
|
|
+ .fail(function (xhr, status, error) {
|
|
|
+ done.reject(error);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._loadLocalData = function (options, done) {
|
|
|
+ done.resolve((typeof options.data === 'string') ?
|
|
|
+ JSON.parse(options.data) :
|
|
|
+ $.extend(true, [], options.data));
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._remove = function () {
|
|
|
+ this._destroy();
|
|
|
+ $.removeData(this, pluginName);
|
|
|
+ $('#' + this._styleId).remove();
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._destroy = function () {
|
|
|
+ if (!this._initialized) return;
|
|
|
+ this._initialized = false;
|
|
|
+
|
|
|
+ this._triggerEvent('destroyed', null, _default.options);
|
|
|
+
|
|
|
+ // Switch off events
|
|
|
+ this._unsubscribeEvents();
|
|
|
+
|
|
|
+ // Tear down
|
|
|
+ this.$wrapper.remove();
|
|
|
+ this.$wrapper = null;
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._unsubscribeEvents = function () {
|
|
|
+ this.$element.off('loading');
|
|
|
+ this.$element.off('loadingFailed');
|
|
|
+ this.$element.off('initialized');
|
|
|
+ this.$element.off('nodeRendered');
|
|
|
+ this.$element.off('rendered');
|
|
|
+ this.$element.off('destroyed');
|
|
|
+ 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();
|
|
|
+
|
|
|
+ if (typeof (this._options.onLoading) === 'function') {
|
|
|
+ this.$element.on('loading', this._options.onLoading);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof (this._options.onLoadingFailed) === 'function') {
|
|
|
+ this.$element.on('loadingFailed', this._options.onLoadingFailed);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof (this._options.onInitialized) === 'function') {
|
|
|
+ this.$element.on('initialized', this._options.onInitialized);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof (this._options.onNodeRendered) === 'function') {
|
|
|
+ this.$element.on('nodeRendered', this._options.onNodeRendered);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof (this._options.onRendered) === 'function') {
|
|
|
+ this.$element.on('rendered', this._options.onRendered);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof (this._options.onDestroyed) === 'function') {
|
|
|
+ this.$element.on('destroyed', this._options.onDestroyed);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._triggerEvent = function (event, data, options) {
|
|
|
+ if (options && !options.silent) {
|
|
|
+ this.$element.trigger(event, $.extend(true, {}, data));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ 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 ordered structure
|
|
|
+ */
|
|
|
+ Tree.prototype._setInitialStates = function (node, level) {
|
|
|
+ this._nodes = {};
|
|
|
+ return $.when.apply(this, this._setInitialState(node, level))
|
|
|
+ .done($.proxy(function () {
|
|
|
+ this._orderedNodes = this._sortNodes();
|
|
|
+ this._inheritCheckboxChanges();
|
|
|
+ this._triggerEvent('initialized', this._orderedNodes, _default.options);
|
|
|
+ return;
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._setInitialState = function (node, level, done) {
|
|
|
+ if (!node.nodes) return;
|
|
|
+ level += 1;
|
|
|
+ done = done || [];
|
|
|
+
|
|
|
+ var parent = node;
|
|
|
+ $.each(node.nodes, $.proxy(function (index, node) {
|
|
|
+ var deferred = new $.Deferred();
|
|
|
+ done.push(deferred.promise());
|
|
|
+
|
|
|
+ // level : hierarchical tree level, starts at 1
|
|
|
+ node.level = level;
|
|
|
+
|
|
|
+ // index : relative to siblings
|
|
|
+ node.index = index;
|
|
|
+
|
|
|
+ // nodeId : unique, hierarchical identifier
|
|
|
+ node.nodeId = (parent && parent.nodeId) ?
|
|
|
+ parent.nodeId + '.' + node.index :
|
|
|
+ (level - 1) + '.' + node.index;
|
|
|
+
|
|
|
+ // parentId : transversing up the tree
|
|
|
+ node.parentId = parent.nodeId;
|
|
|
+
|
|
|
+ // if not provided set selectable default value
|
|
|
+ if (!node.hasOwnProperty('selectable')) {
|
|
|
+ node.selectable = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if not provided set checkable default value
|
|
|
+ if (!node.hasOwnProperty('checkable')) {
|
|
|
+ node.checkable = 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // convert the undefined string if hierarchical checks are enabled
|
|
|
+ if (this._options.hierarchicalCheck && node.state.checked === 'undefined') {
|
|
|
+ node.state.checked = undefined;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // set visible state; based parent state plus levels
|
|
|
+ if ((parent && parent.state && parent.state.expanded) ||
|
|
|
+ (level <= this._options.levels)) {
|
|
|
+ node.state.visible = true;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ node.state.visible = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // recurse child nodes and transverse the tree, depth-first
|
|
|
+ if (node.nodes) {
|
|
|
+ if (node.nodes.length > 0) {
|
|
|
+ this._setInitialState(node, level, done);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ delete node.nodes;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // add / update indexed collection
|
|
|
+ this._nodes[node.nodeId] = node;
|
|
|
+
|
|
|
+ // mark task as complete
|
|
|
+ deferred.resolve();
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return done;
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._sortNodes = function () {
|
|
|
+ return $.map(Object.keys(this._nodes).sort(function (a, b) {
|
|
|
+ if (a === b) return 0;
|
|
|
+ var a = a.split('.').map(function (level) { return parseInt(level); });
|
|
|
+ var b = b.split('.').map(function (level) { return parseInt(level); });
|
|
|
+
|
|
|
+ var c = Math.max(a.length, b.length);
|
|
|
+ for (var i=0; i<c; i++) {
|
|
|
+ if (a[i] === undefined) return -1;
|
|
|
+ if (b[i] === undefined) return +1;
|
|
|
+ if (a[i] - b[i] > 0) return +1;
|
|
|
+ if (a[i] - b[i] < 0) return -1;
|
|
|
+ };
|
|
|
+
|
|
|
+ }), $.proxy(function (value, index) {
|
|
|
+ return this._nodes[value];
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._clickHandler = function (event) {
|
|
|
+
|
|
|
+ var target = $(event.target);
|
|
|
+ var node = this.targetNode(target);
|
|
|
+ if (!node || node.state.disabled) return;
|
|
|
+
|
|
|
+ var classList = target.attr('class') ? target.attr('class').split(' ') : [];
|
|
|
+ if ((classList.indexOf('expand-icon') !== -1)) {
|
|
|
+ this._toggleExpanded(node, $.extend({}, _default.options));
|
|
|
+ }
|
|
|
+ else if ((classList.indexOf('check-icon') !== -1)) {
|
|
|
+ if (node.checkable) {
|
|
|
+ this._toggleChecked(node, $.extend({}, _default.options));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if (node.selectable) {
|
|
|
+ this._toggleSelected(node, $.extend({}, _default.options));
|
|
|
+ } else {
|
|
|
+ this._toggleExpanded(node, $.extend({}, _default.options));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 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.targetNode = 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._toggleExpanded = function (node, options) {
|
|
|
+ if (!node) return;
|
|
|
+
|
|
|
+ // Lazy-load the child nodes if possible
|
|
|
+ if (typeof(this._options.lazyLoad) === 'function' && node.lazyLoad) {
|
|
|
+ this._lazyLoad(node);
|
|
|
+ } else {
|
|
|
+ this._setExpanded(node, !node.state.expanded, options);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._lazyLoad = function (node) {
|
|
|
+ // Show a different icon while loading the child nodes
|
|
|
+ node.$el.children('span.expand-icon')
|
|
|
+ .removeClass(this._options.expandIcon)
|
|
|
+ .addClass(this._options.loadingIcon);
|
|
|
+
|
|
|
+ var _this = this;
|
|
|
+ this._options.lazyLoad(node, function (nodes) {
|
|
|
+ // Adding the node will expand its parent automatically
|
|
|
+ _this.addNode(nodes, node);
|
|
|
+ });
|
|
|
+ // Only the first expand should do a lazy-load
|
|
|
+ delete node.lazyLoad;
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._setExpanded = function (node, state, options) {
|
|
|
+
|
|
|
+ // We never pass options when rendering, so the only time
|
|
|
+ // we need to validate state is from user interaction
|
|
|
+ if (options && state === node.state.expanded) return;
|
|
|
+
|
|
|
+ if (state && node.nodes) {
|
|
|
+
|
|
|
+ // Set node state
|
|
|
+ node.state.expanded = true;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.children('span.expand-icon')
|
|
|
+ .removeClass(this._options.expandIcon)
|
|
|
+ .removeClass(this._options.loadingIcon)
|
|
|
+ .addClass(this._options.collapseIcon);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Expand children
|
|
|
+ if (node.nodes && options) {
|
|
|
+ $.each(node.nodes, $.proxy(function (index, node) {
|
|
|
+ this._setVisible(node, true, options);
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeExpanded', node, options);
|
|
|
+ }
|
|
|
+ else if (!state) {
|
|
|
+
|
|
|
+ // Set node state
|
|
|
+ node.state.expanded = false;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.children('span.expand-icon')
|
|
|
+ .removeClass(this._options.collapseIcon)
|
|
|
+ .addClass(this._options.expandIcon);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Collapse children
|
|
|
+ if (node.nodes && options) {
|
|
|
+ $.each(node.nodes, $.proxy(function (index, node) {
|
|
|
+ this._setVisible(node, false, options);
|
|
|
+ this._setExpanded(node, false, options);
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeCollapsed', node, options);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._setVisible = function (node, state, options) {
|
|
|
+
|
|
|
+ if (options && state === node.state.visible) return;
|
|
|
+
|
|
|
+ if (state) {
|
|
|
+
|
|
|
+ // Set node state
|
|
|
+ node.state.visible = true;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.removeClass('node-hidden');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+
|
|
|
+ // Set node state to unchecked
|
|
|
+ node.state.visible = false;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.addClass('node-hidden');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._toggleSelected = function (node, options) {
|
|
|
+ if (!node) return;
|
|
|
+ this._setSelected(node, !node.state.selected, options);
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._setSelected = function (node, state, options) {
|
|
|
+
|
|
|
+ // We never pass options when rendering, so the only time
|
|
|
+ // we need to validate state is from user interaction
|
|
|
+ if (options && (state === node.state.selected)) return;
|
|
|
+
|
|
|
+ if (state) {
|
|
|
+
|
|
|
+ // If multiSelect false, unselect previously selected
|
|
|
+ if (!this._options.multiSelect) {
|
|
|
+ $.each(this._findNodes('true', 'state.selected'), $.proxy(function (index, node) {
|
|
|
+ this._setSelected(node, false, $.extend(options, {unselecting: true}));
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set node state
|
|
|
+ node.state.selected = true;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.addClass('node-selected');
|
|
|
+
|
|
|
+ if (node.selectedIcon || this._options.selectedIcon) {
|
|
|
+ node.$el.children('span.node-icon')
|
|
|
+ .removeClass(node.icon || this._options.nodeIcon)
|
|
|
+ .addClass(node.selectedIcon || this._options.selectedIcon);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeSelected', node, options);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+
|
|
|
+ // If preventUnselect true + only one remaining selection, disable unselect
|
|
|
+ if (this._options.preventUnselect &&
|
|
|
+ (options && !options.unselecting) &&
|
|
|
+ (this._findNodes('true', 'state.selected').length === 1)) {
|
|
|
+ // Fire the nodeSelected event if reselection is allowed
|
|
|
+ if (this._options.allowReselect) {
|
|
|
+ this._triggerEvent('nodeSelected', node, options);
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set node state
|
|
|
+ node.state.selected = false;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.removeClass('node-selected');
|
|
|
+
|
|
|
+ if (node.selectedIcon || this._options.selectedIcon) {
|
|
|
+ node.$el.children('span.node-icon')
|
|
|
+ .removeClass(node.selectedIcon || this._options.selectedIcon)
|
|
|
+ .addClass(node.icon || this._options.nodeIcon);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeUnselected', node, options);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._inheritCheckboxChanges = function () {
|
|
|
+ if (this._options.showCheckbox && this._options.highlightChanges) {
|
|
|
+ this._checkedNodes = $.grep(this._orderedNodes, function (node) {
|
|
|
+ return node.state.checked;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._toggleChecked = function (node, options) {
|
|
|
+ if (!node) return;
|
|
|
+
|
|
|
+ if (this._options.hierarchicalCheck) {
|
|
|
+ // Event propagation to the parent/child nodes
|
|
|
+ var childOptions = $.extend({}, options, {silent: options.silent || !this._options.propagateCheckEvent});
|
|
|
+
|
|
|
+ var state, currentNode = node;
|
|
|
+ // Temporarily swap the tree state
|
|
|
+ node.state.checked = !node.state.checked;
|
|
|
+
|
|
|
+ // Iterate through each parent node
|
|
|
+ while (currentNode = this._nodes[currentNode.parentId]) {
|
|
|
+
|
|
|
+ // Calculate the state
|
|
|
+ state = currentNode.nodes.reduce(function (acc, curr) {
|
|
|
+ return (acc === curr.state.checked) ? acc : undefined;
|
|
|
+ }, currentNode.nodes[0].state.checked);
|
|
|
+
|
|
|
+ // Set the state
|
|
|
+ this._setChecked(currentNode, state, childOptions);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (node.nodes && node.nodes.length > 0) {
|
|
|
+ // Copy the content of the array
|
|
|
+ var child, children = node.nodes.slice();
|
|
|
+ // Iterate through each child node
|
|
|
+ while (children && children.length > 0) {
|
|
|
+ child = children.pop();
|
|
|
+
|
|
|
+ // Set the state
|
|
|
+ this._setChecked(child, node.state.checked, childOptions);
|
|
|
+
|
|
|
+ // Append children to the end of the list
|
|
|
+ if (child.nodes && child.nodes.length > 0) {
|
|
|
+ children = children.concat(child.nodes.slice());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Swap back the tree state
|
|
|
+ node.state.checked = !node.state.checked;
|
|
|
+ }
|
|
|
+
|
|
|
+ this._setChecked(node, !node.state.checked, options);
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._setChecked = function (node, state, options) {
|
|
|
+
|
|
|
+ // We never pass options when rendering, so the only time
|
|
|
+ // we need to validate state is from user interaction
|
|
|
+ if (options && state === node.state.checked) return;
|
|
|
+
|
|
|
+ // Highlight the node if its checkbox has unsaved changes
|
|
|
+ if (this._options.highlightChanges) {
|
|
|
+ node.$el.toggleClass('node-check-changed', (this._checkedNodes.indexOf(node) == -1) == state);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (state) {
|
|
|
+
|
|
|
+ // Set node state
|
|
|
+ node.state.checked = true;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.addClass('node-checked').removeClass('node-checked-partial');
|
|
|
+ node.$el.children('span.check-icon')
|
|
|
+ .removeClass(this._options.uncheckedIcon)
|
|
|
+ .removeClass(this._options.partiallyCheckedIcon)
|
|
|
+ .addClass(this._options.checkedIcon);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeChecked', node, options);
|
|
|
+ }
|
|
|
+ else if (state === undefined && this._options.hierarchicalCheck) {
|
|
|
+
|
|
|
+ // Set node state to partially checked
|
|
|
+ node.state.checked = undefined;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.addClass('node-checked-partial').removeClass('node-checked');
|
|
|
+ node.$el.children('span.check-icon')
|
|
|
+ .removeClass(this._options.uncheckedIcon)
|
|
|
+ .removeClass(this._options.checkedIcon)
|
|
|
+ .addClass(this._options.partiallyCheckedIcon);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event, partially checked is technically unchecked
|
|
|
+ this._triggerEvent('nodeUnchecked', node, options);
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // Set node state to unchecked
|
|
|
+ node.state.checked = false;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.removeClass('node-checked node-checked-partial');
|
|
|
+ node.$el.children('span.check-icon')
|
|
|
+ .removeClass(this._options.checkedIcon)
|
|
|
+ .removeClass(this._options.partiallyCheckedIcon)
|
|
|
+ .addClass(this._options.uncheckedIcon);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeUnchecked', node, options);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._setDisabled = function (node, state, options) {
|
|
|
+
|
|
|
+ // We never pass options when rendering, so the only time
|
|
|
+ // we need to validate state is from user interaction
|
|
|
+ if (options && state === node.state.disabled) return;
|
|
|
+
|
|
|
+ if (state) {
|
|
|
+
|
|
|
+ // Set node state to disabled
|
|
|
+ node.state.disabled = true;
|
|
|
+
|
|
|
+ // Disable all other states
|
|
|
+ if (options && !options.keepState) {
|
|
|
+ this._setSelected(node, false, options);
|
|
|
+ this._setChecked(node, false, options);
|
|
|
+ this._setExpanded(node, false, options);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.addClass('node-disabled');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeDisabled', node, options);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+
|
|
|
+ // Set node state to enabled
|
|
|
+ node.state.disabled = false;
|
|
|
+
|
|
|
+ // Set element
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.removeClass('node-disabled');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Optionally trigger event
|
|
|
+ this._triggerEvent('nodeEnabled', node, options);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._setSearchResult = function (node, state, options) {
|
|
|
+ if (options && state === node.searchResult) return;
|
|
|
+
|
|
|
+ if (state) {
|
|
|
+
|
|
|
+ node.searchResult = true;
|
|
|
+
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.addClass('node-result');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+
|
|
|
+ node.searchResult = false;
|
|
|
+
|
|
|
+ if (node.$el) {
|
|
|
+ node.$el.removeClass('node-result');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._render = function () {
|
|
|
+ if (!this._initialized) {
|
|
|
+
|
|
|
+ // Setup first time only components
|
|
|
+ this.$wrapper = this._template.tree.clone();
|
|
|
+ this.$element.empty()
|
|
|
+ .addClass(pluginName)
|
|
|
+ .append(this.$wrapper);
|
|
|
+
|
|
|
+ this._injectStyle();
|
|
|
+
|
|
|
+ this._initialized = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ var previousNode;
|
|
|
+ $.each(this._orderedNodes, $.proxy(function (id, node) {
|
|
|
+ this._renderNode(node, previousNode);
|
|
|
+ previousNode = node;
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ this._triggerEvent('rendered', this._orderedNodes, _default.options);
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._renderNode = function (node, previousNode) {
|
|
|
+ if (!node) return;
|
|
|
+
|
|
|
+ if (!node.$el) {
|
|
|
+ node.$el = this._newNodeEl(node, previousNode)
|
|
|
+ .addClass('node-' + this._elementId);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ node.$el.empty();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Append .classes to the node
|
|
|
+ node.$el.addClass(node.class);
|
|
|
+
|
|
|
+ // Set the #id of the node if specified
|
|
|
+ if (node.id) {
|
|
|
+ node.$el.attr('id', node.id);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Append custom data- attributes to the node
|
|
|
+ if (node.dataAttr) {
|
|
|
+ $.each(node.dataAttr, function (key, value) {
|
|
|
+ node.$el.attr('data-' + key, value);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set / update nodeid; it can change as a result of addNode etc.
|
|
|
+ node.$el.attr('data-nodeId', node.nodeId);
|
|
|
+
|
|
|
+ // Set the tooltip attribute if present
|
|
|
+ if (node.tooltip) {
|
|
|
+ node.$el.attr('title', node.tooltip);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add indent/spacer to mimic tree structure
|
|
|
+ for (var i = 0; i < (node.level - 1); i++) {
|
|
|
+ node.$el.append(this._template.indent.clone());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add expand / collapse or empty spacer icons
|
|
|
+ node.$el
|
|
|
+ .append(
|
|
|
+ node.nodes || node.lazyLoad ? this._template.icon.expand.clone() : this._template.icon.empty.clone()
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add checkbox and node icons
|
|
|
+ if (this._options.checkboxFirst) {
|
|
|
+ this._addCheckbox(node);
|
|
|
+ this._addIcon(node);
|
|
|
+ this._addImage(node);
|
|
|
+ } else {
|
|
|
+ this._addIcon(node);
|
|
|
+ this._addImage(node);
|
|
|
+ this._addCheckbox(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add text
|
|
|
+ if (this._options.wrapNodeText) {
|
|
|
+ var wrapper = this._template.text.clone();
|
|
|
+ node.$el.append(wrapper);
|
|
|
+ wrapper.append(node.text);
|
|
|
+ } else {
|
|
|
+ node.$el.append(node.text);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add tags as badges
|
|
|
+ if (this._options.showTags && node.tags) {
|
|
|
+ $.each(node.tags, $.proxy(function addTag(id, tag) {
|
|
|
+ node.$el
|
|
|
+ .append(this._template.badge.clone()
|
|
|
+ .append(tag)
|
|
|
+ );
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set various node states
|
|
|
+ this._setSelected(node, node.state.selected);
|
|
|
+ this._setChecked(node, node.state.checked);
|
|
|
+ this._setSearchResult(node, node.searchResult);
|
|
|
+ this._setExpanded(node, node.state.expanded);
|
|
|
+ this._setDisabled(node, node.state.disabled);
|
|
|
+ this._setVisible(node, node.state.visible);
|
|
|
+
|
|
|
+ // Trigger nodeRendered event
|
|
|
+ this._triggerEvent('nodeRendered', node, _default.options);
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add checkable icon
|
|
|
+ Tree.prototype._addCheckbox = function (node) {
|
|
|
+ if (this._options.showCheckbox && (node.hideCheckbox === undefined || node.hideCheckbox === false)) {
|
|
|
+ node.$el
|
|
|
+ .append(this._template.icon.check.clone());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add node icon
|
|
|
+ Tree.prototype._addIcon = function (node) {
|
|
|
+ if (this._options.showIcon && !(this._options.showImage && node.image)) {
|
|
|
+ node.$el
|
|
|
+ .append(this._template.icon.node.clone()
|
|
|
+ .addClass(node.icon || this._options.nodeIcon)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Tree.prototype._addImage = function (node) {
|
|
|
+ if (this._options.showImage && node.image) {
|
|
|
+ node.$el
|
|
|
+ .append(this._template.image.clone()
|
|
|
+ .addClass('node-image')
|
|
|
+ .css('background-image', "url('" + node.image + "')")
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Creates a new node element from template and
|
|
|
+ // ensures the template is inserted at the correct position
|
|
|
+ Tree.prototype._newNodeEl = function (node, previousNode) {
|
|
|
+ var $el = this._template.node.clone();
|
|
|
+
|
|
|
+ if (previousNode) {
|
|
|
+ // typical usage, as nodes are rendered in
|
|
|
+ // sort order we add after the previous element
|
|
|
+ previousNode.$el.after($el);
|
|
|
+ } else {
|
|
|
+ // we use prepend instead of append,
|
|
|
+ // to cater for root inserts i.e. nodeId 0.0
|
|
|
+ this.$wrapper.prepend($el);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $el;
|
|
|
+ };
|
|
|
+
|
|
|
+ // Recursively remove node elements from DOM
|
|
|
+ Tree.prototype._removeNodeEl = function (node) {
|
|
|
+ if (!node) return;
|
|
|
+
|
|
|
+ if (node.nodes) {
|
|
|
+ $.each(node.nodes, $.proxy(function (index, node) {
|
|
|
+ this._removeNodeEl(node);
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+ node.$el.remove();
|
|
|
+ };
|
|
|
+
|
|
|
+ // Expand node, rendering it's immediate children
|
|
|
+ Tree.prototype._expandNode = function (node) {
|
|
|
+ if (!node.nodes) return;
|
|
|
+
|
|
|
+ $.each(node.nodes.slice(0).reverse(), $.proxy(function (index, childNode) {
|
|
|
+ childNode.level = node.level + 1;
|
|
|
+ this._renderNode(childNode, node.$el);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 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 + '{';
|
|
|
+
|
|
|
+ // Basic bootstrap style overrides
|
|
|
+ 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 + ';' +
|
|
|
+ '}';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Style search results
|
|
|
+ if (this._options.highlightSearchResults && (this._options.searchResultColor || this._options.searchResultBackColor)) {
|
|
|
+
|
|
|
+ var innerStyle = ''
|
|
|
+ if (this._options.searchResultColor) {
|
|
|
+ innerStyle += 'color:' + this._options.searchResultColor + ';';
|
|
|
+ }
|
|
|
+ if (this._options.searchResultBackColor) {
|
|
|
+ innerStyle += 'background-color:' + this._options.searchResultBackColor + ';';
|
|
|
+ }
|
|
|
+
|
|
|
+ style += '.node-' + this._elementId + '.node-result{' + innerStyle + '}';
|
|
|
+ style += '.node-' + this._elementId + '.node-result:hover{' + innerStyle + '}';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Style selected nodes
|
|
|
+ if (this._options.highlightSelected && (this._options.selectedColor || this._options.selectedBackColor)) {
|
|
|
+
|
|
|
+ var innerStyle = ''
|
|
|
+ if (this._options.selectedColor) {
|
|
|
+ innerStyle += 'color:' + this._options.selectedColor + ';';
|
|
|
+ }
|
|
|
+ if (this._options.selectedBackColor) {
|
|
|
+ innerStyle += 'background-color:' + this._options.selectedBackColor + ';';
|
|
|
+ }
|
|
|
+
|
|
|
+ style += '.node-' + this._elementId + '.node-selected{' + innerStyle + '}';
|
|
|
+ style += '.node-' + this._elementId + '.node-selected:hover{' + innerStyle + '}';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Style changed nodes
|
|
|
+ if (this._options.highlightChanges) {
|
|
|
+ var innerStyle = 'color: ' + this._options.changedNodeColor + ';';
|
|
|
+ style += '.node-' + this._elementId + '.node-check-changed{' + innerStyle + '}';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Node level style overrides
|
|
|
+ $.each(this._orderedNodes, $.proxy(function (index, node) {
|
|
|
+ if (node.color || node.backColor) {
|
|
|
+ var innerStyle = '';
|
|
|
+ if (node.color) {
|
|
|
+ innerStyle += 'color:' + node.color + ';';
|
|
|
+ }
|
|
|
+ if (node.backColor) {
|
|
|
+ innerStyle += 'background-color:' + node.backColor + ';';
|
|
|
+ }
|
|
|
+ style += '.node-' + this._elementId + '[data-nodeId="' + node.nodeId + '"]{' + innerStyle + '}';
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return this._css + style;
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._template = {
|
|
|
+ tree: $('<ul class="list-group"></ul>'),
|
|
|
+ node: $('<li class="list-group-item"></li>'),
|
|
|
+ indent: $('<span class="indent"></span>'),
|
|
|
+ icon: {
|
|
|
+ node: $('<span class="icon node-icon"></span>'),
|
|
|
+ expand: $('<span class="icon expand-icon"></span>'),
|
|
|
+ check: $('<span class="icon check-icon"></span>'),
|
|
|
+ empty: $('<span class="icon"></span>')
|
|
|
+ },
|
|
|
+ image: $('<span class="image"></span>'),
|
|
|
+ badge: $('<span class="badge"></span>'),
|
|
|
+ text: $('<span class="text"></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 an array of matching node objects.
|
|
|
+ @param {String} pattern - A pattern to match against a given field
|
|
|
+ @return {String} field - Field to query pattern against
|
|
|
+ */
|
|
|
+ Tree.prototype.findNodes = function (pattern, field) {
|
|
|
+ return this._findNodes(pattern, field);
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an ordered aarray of node objects.
|
|
|
+ @return {Array} nodes - An array of all nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getNodes = function () {
|
|
|
+ return this._orderedNodes;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns parent nodes for given nodes, if valid otherwise returns undefined.
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @returns {Array} nodes - An array of parent nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getParents = function (nodes) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ var parentNodes = [];
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ var parentNode = node.parentId ? this._nodes[node.parentId] : false;
|
|
|
+ if (parentNode) {
|
|
|
+ parentNodes.push(parentNode);
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ return parentNodes;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of sibling nodes for given nodes, if valid otherwise returns undefined.
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @returns {Array} nodes - An array of sibling nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getSiblings = function (nodes) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ var siblingNodes = [];
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ var parent = this.getParents([node]);
|
|
|
+ var nodes = parent[0] ? parent[0].nodes : this._tree;
|
|
|
+ siblingNodes = nodes.filter(function (obj) {
|
|
|
+ return obj.nodeId !== node.nodeId;
|
|
|
+ });
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // flatten possible nested array before returning
|
|
|
+ return $.map(siblingNodes, function (obj) {
|
|
|
+ return obj;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of selected nodes.
|
|
|
+ @returns {Array} nodes - Selected nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getSelected = function () {
|
|
|
+ return this._findNodes('true', 'state.selected');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of unselected nodes.
|
|
|
+ @returns {Array} nodes - Unselected nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getUnselected = function () {
|
|
|
+ return this._findNodes('false', 'state.selected');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of expanded nodes.
|
|
|
+ @returns {Array} nodes - Expanded nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getExpanded = function () {
|
|
|
+ return this._findNodes('true', 'state.expanded');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of collapsed nodes.
|
|
|
+ @returns {Array} nodes - Collapsed nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getCollapsed = function () {
|
|
|
+ return this._findNodes('false', 'state.expanded');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of checked nodes.
|
|
|
+ @returns {Array} nodes - Checked nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getChecked = function () {
|
|
|
+ return this._findNodes('true', 'state.checked');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of unchecked nodes.
|
|
|
+ @returns {Array} nodes - Unchecked nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getUnchecked = function () {
|
|
|
+ return this._findNodes('false', 'state.checked');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of disabled nodes.
|
|
|
+ @returns {Array} nodes - Disabled nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getDisabled = function () {
|
|
|
+ return this._findNodes('true', 'state.disabled');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Returns an array of enabled nodes.
|
|
|
+ @returns {Array} nodes - Enabled nodes
|
|
|
+ */
|
|
|
+ Tree.prototype.getEnabled = function () {
|
|
|
+ return this._findNodes('false', 'state.disabled');
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ Add nodes to the tree.
|
|
|
+ @param {Array} nodes - An array of nodes to add
|
|
|
+ @param {optional Object} parentNode - The node to which nodes will be added as children
|
|
|
+ @param {optional number} index - Zero based insert index
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.addNode = function (nodes, parentNode, index, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (parentNode instanceof Array) {
|
|
|
+ parentNode = parentNode[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ // identify target nodes; either the tree's root or a parent's child nodes
|
|
|
+ var targetNodes;
|
|
|
+ if (parentNode && parentNode.nodes) {
|
|
|
+ targetNodes = parentNode.nodes;
|
|
|
+ } else if (parentNode) {
|
|
|
+ targetNodes = parentNode.nodes = [];
|
|
|
+ } else {
|
|
|
+ targetNodes = this._tree;
|
|
|
+ }
|
|
|
+
|
|
|
+ // inserting nodes at specified positions
|
|
|
+ $.each(nodes, $.proxy(function (i, node) {
|
|
|
+ var insertIndex = (typeof(index) === 'number') ? (index + i) : (targetNodes.length + 1);
|
|
|
+ targetNodes.splice(insertIndex, 0, node);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // initialize new state and render changes
|
|
|
+ this._setInitialStates({nodes: this._tree}, 0)
|
|
|
+ .done($.proxy(function () {
|
|
|
+ if (parentNode && !parentNode.state.expanded) {
|
|
|
+ this._setExpanded(parentNode, true, options);
|
|
|
+ }
|
|
|
+ this._render();
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Add nodes to the tree after given node.
|
|
|
+ @param {Array} nodes - An array of nodes to add
|
|
|
+ @param {Object} node - The node to which nodes will be added after
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.addNodeAfter = function (nodes, node, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (node instanceof Array) {
|
|
|
+ node = node[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ this.addNode(nodes, this.getParents(node)[0], (node.index + 1), options);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Add nodes to the tree before given node.
|
|
|
+ @param {Array} nodes - An array of nodes to add
|
|
|
+ @param {Object} node - The node to which nodes will be added before
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.addNodeBefore = function (nodes, node, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (node instanceof Array) {
|
|
|
+ node = node[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ this.addNode(nodes, this.getParents(node)[0], node.index, options);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ Removes given nodes from the tree.
|
|
|
+ @param {Array} nodes - An array of nodes to remove
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.removeNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ var targetNodes, parentNode;
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+
|
|
|
+ // remove nodes from tree
|
|
|
+ parentNode = this._nodes[node.parentId];
|
|
|
+ if (parentNode) {
|
|
|
+ targetNodes = parentNode.nodes;
|
|
|
+ } else {
|
|
|
+ targetNodes = this._tree;
|
|
|
+ }
|
|
|
+ targetNodes.splice(node.index, 1);
|
|
|
+
|
|
|
+ // remove node from DOM
|
|
|
+ this._removeNodeEl(node);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // initialize new state and render changes
|
|
|
+ this._setInitialStates({nodes: this._tree}, 0)
|
|
|
+ .done(this._render.bind(this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Updates / replaces a given tree node
|
|
|
+ @param {Object} node - A single node to be replaced
|
|
|
+ @param {Object} newNode - THe replacement node
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.updateNode = function (node, newNode, options) {
|
|
|
+ if (node instanceof Array) {
|
|
|
+ node = node[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ // insert new node
|
|
|
+ $.extend(node,newNode);
|
|
|
+
|
|
|
+ // remove old node from DOM
|
|
|
+ //this._removeNodeEl(node);
|
|
|
+
|
|
|
+ // initialize new state and render changes
|
|
|
+ this._setInitialStates({nodes: this._tree}, 0)
|
|
|
+ .done(this._render.bind(this));
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ Selects given tree nodes
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.selectNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setSelected(node, true, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Unselects given tree nodes
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.unselectNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setSelected(node, false, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Toggles a node selected state; selecting if unselected, unselecting if selected.
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.toggleNodeSelected = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._toggleSelected(node, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ Collapse all tree nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.collapseAll = function (options) {
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+ options.levels = options.levels || 999;
|
|
|
+ this.collapseNode(this._tree, options);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Collapse a given tree node
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.collapseNode = function (nodes, options) {
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setExpanded(node, false, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Expand all tree nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.expandAll = function (options) {
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+ options.levels = options.levels || 999;
|
|
|
+ this.expandNode(this._tree, options);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Expand given tree nodes
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.expandNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ // Do not re-expand already expanded nodes
|
|
|
+ if (node.state.expanded) return;
|
|
|
+
|
|
|
+ if (typeof(this._options.lazyLoad) === 'function' && node.lazyLoad) {
|
|
|
+ this._lazyLoad(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ this._setExpanded(node, true, options);
|
|
|
+ if (node.nodes) {
|
|
|
+ this._expandLevels(node.nodes, options.levels-1, options);
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._expandLevels = function (nodes, level, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setExpanded(node, (level > 0) ? true : false, options);
|
|
|
+ if (node.nodes) {
|
|
|
+ this._expandLevels(node.nodes, level-1, options);
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Reveals given tree nodes, expanding the tree from node to root.
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.revealNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ var parentNode = node;
|
|
|
+ var tmpNode;
|
|
|
+ while (tmpNode = this.getParents([parentNode])[0]) {
|
|
|
+ parentNode = tmpNode;
|
|
|
+ this._setExpanded(parentNode, true, options);
|
|
|
+ };
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Toggles a node's expanded state; collapsing if expanded, expanding if collapsed.
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.toggleNodeExpanded = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._toggleExpanded(node, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ Check all tree nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.checkAll = function (options) {
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ var nodes = $.grep(this._orderedNodes, function (node) {
|
|
|
+ return !node.state.checked;
|
|
|
+ });
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setChecked(node, true, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Checks given tree nodes
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.checkNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setChecked(node, true, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Uncheck all tree nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.uncheckAll = function (options) {
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ var nodes = $.grep(this._orderedNodes, function (node) {
|
|
|
+ return node.state.checked || node.state.checked === undefined;
|
|
|
+ });
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setChecked(node, false, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Uncheck given tree nodes
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.uncheckNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setChecked(node, false, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Toggles a node's checked state; checking if unchecked, unchecking if checked.
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.toggleNodeChecked = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._toggleChecked(node, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Saves the current state of checkboxes as default, cleaning up any highlighted changes
|
|
|
+ */
|
|
|
+ Tree.prototype.unmarkCheckboxChanges = function () {
|
|
|
+ this._inheritCheckboxChanges();
|
|
|
+
|
|
|
+ $.each(this._nodes, function (index, node) {
|
|
|
+ node.$el.removeClass('node-check-changed');
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Disable all tree nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.disableAll = function (options) {
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ var nodes = this._findNodes('false', 'state.disabled');
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setDisabled(node, true, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Disable given tree nodes
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.disableNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setDisabled(node, true, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Enable all tree nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.enableAll = function (options) {
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ var nodes = this._findNodes('true', 'state.disabled');
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setDisabled(node, false, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Enable given tree nodes
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.enableNode = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setDisabled(node, false, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Toggles a node's disabled state; disabling is enabled, enabling if disabled.
|
|
|
+ @param {Array} nodes - An array of nodes
|
|
|
+ @param {optional Object} options
|
|
|
+ */
|
|
|
+ Tree.prototype.toggleNodeDisabled = function (nodes, options) {
|
|
|
+ if (!(nodes instanceof Array)) {
|
|
|
+ nodes = [nodes];
|
|
|
+ }
|
|
|
+
|
|
|
+ options = $.extend({}, _default.options, options);
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function (index, node) {
|
|
|
+ this._setDisabled(node, !node.state.disabled, options);
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ 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);
|
|
|
+
|
|
|
+ var previous = this._getSearchResults();
|
|
|
+ var results = [];
|
|
|
+
|
|
|
+ if (pattern && pattern.length > 0) {
|
|
|
+
|
|
|
+ if (options.exactMatch) {
|
|
|
+ pattern = '^' + pattern + '$';
|
|
|
+ }
|
|
|
+
|
|
|
+ var modifier = 'g';
|
|
|
+ if (options.ignoreCase) {
|
|
|
+ modifier += 'i';
|
|
|
+ }
|
|
|
+
|
|
|
+ results = this._findNodes(pattern, 'text', modifier);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Clear previous results no longer matched
|
|
|
+ $.each(this._diffArray(results, previous), $.proxy(function (index, node) {
|
|
|
+ this._setSearchResult(node, false, options);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // Set new results
|
|
|
+ $.each(this._diffArray(previous, results), $.proxy(function (index, node) {
|
|
|
+ this._setSearchResult(node, true, options);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // Reveal hidden nodes
|
|
|
+ if (results && options.revealResults) {
|
|
|
+ this.revealNode(results);
|
|
|
+ }
|
|
|
+
|
|
|
+ this._triggerEvent('searchComplete', results, options);
|
|
|
+
|
|
|
+ return results;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Clears previous search results
|
|
|
+ */
|
|
|
+ Tree.prototype.clearSearch = function (options) {
|
|
|
+ options = $.extend({}, { render: true }, options);
|
|
|
+
|
|
|
+ var results = $.each(this._getSearchResults(), $.proxy(function (index, node) {
|
|
|
+ this._setSearchResult(node, false, options);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ this._triggerEvent('searchCleared', results, options);
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._getSearchResults = function () {
|
|
|
+ return this._findNodes('true', 'searchResult');
|
|
|
+ };
|
|
|
+
|
|
|
+ Tree.prototype._diffArray = function (a, b) {
|
|
|
+ var diff = [];
|
|
|
+ $.grep(b, function (n) {
|
|
|
+ if ($.inArray(n, a) === -1) {
|
|
|
+ diff.push(n);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return diff;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ Find nodes that match a given criteria
|
|
|
+ @param {String} pattern - A given string to match against
|
|
|
+ @param {optional String} attribute - Attribute to compare pattern against
|
|
|
+ @param {optional String} modifier - Valid RegEx modifiers
|
|
|
+ @return {Array} nodes - Nodes that match your criteria
|
|
|
+ */
|
|
|
+ Tree.prototype._findNodes = function (pattern, attribute, modifier) {
|
|
|
+ attribute = attribute || 'text';
|
|
|
+ modifier = modifier || 'g';
|
|
|
+ return $.grep(this._orderedNodes, $.proxy(function (node) {
|
|
|
+ var val = this._getNodeValue(node, attribute);
|
|
|
+ if (typeof val === 'string') {
|
|
|
+ return val.match(new RegExp(pattern, modifier));
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ 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) && obj[attr] !== undefined) {
|
|
|
+ return obj[attr].toString();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ des:扩展bootstrap-treeview的编辑节点方法
|
|
|
+ 编辑节点
|
|
|
+ author:qlx 2017-3-31
|
|
|
+ */
|
|
|
+ Tree.prototype.editNode = function (identifiers, options) {
|
|
|
+ $.each(identifiers,$.proxy(function (i, node) {
|
|
|
+ this.setEditNode(node, options);
|
|
|
+ }, this));
|
|
|
+ this._setInitialStates({ nodes: this._tree }, 0);
|
|
|
+ this._render();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 编辑节点
|
|
|
+ */
|
|
|
+ Tree.prototype.setEditNode = function (node, options) {
|
|
|
+ if (options) {
|
|
|
+ $.extend(node, options);
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ 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;
|
|
|
+ if (this.length == 0) {
|
|
|
+ throw "No element has been found!";
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|