diff --git a/assets/controllers/elements/sidebar_tree_controller.js b/assets/controllers/elements/sidebar_tree_controller.js index ca9ac515..228bbe40 100644 --- a/assets/controllers/elements/sidebar_tree_controller.js +++ b/assets/controllers/elements/sidebar_tree_controller.js @@ -1,9 +1,6 @@ import {Controller} from "@hotwired/stimulus"; import {default as TreeController} from "./tree_controller"; -import "patternfly-bootstrap-treeview/src/css/bootstrap-treeview.css" -import "patternfly-bootstrap-treeview"; - export default class extends TreeController { static targets = [ "tree", 'sourceText' ]; diff --git a/assets/controllers/elements/tree_controller.js b/assets/controllers/elements/tree_controller.js index 44a4ee34..6fbc3ed9 100644 --- a/assets/controllers/elements/tree_controller.js +++ b/assets/controllers/elements/tree_controller.js @@ -1,14 +1,25 @@ import {Controller} from "@hotwired/stimulus"; -import "patternfly-bootstrap-treeview/src/css/bootstrap-treeview.css" -import "patternfly-bootstrap-treeview"; +import {BSTreeView, BSTreeViewNode, BS5Theme, FAIconTheme, EVENT_INITIALIZED} from "@jbtronics/bs-treeview"; +import "@jbtronics/bs-treeview/styles/bs-treeview.css"; export default class extends Controller { static targets = [ "tree" ]; + /** @type {string} */ _url = null; + /** @type {BSTreeViewNode[]} */ _data = null; + /** @type {boolean} */ + _showTags = false; + + /** + * @type {BSTreeView} + * @private + */ + _tree = null; + connect() { const treeElement = this.treeTarget; if (!treeElement) { @@ -19,6 +30,10 @@ export default class extends Controller { this._url = this.element.dataset.treeUrl; this._data = this.element.dataset.treeData; + if(this.element.dataset.treeShowTags === "true") { + this._showTags = true; + } + this.reinitTree(); } @@ -43,49 +58,58 @@ export default class extends Controller { } _fillTree(data) { - //Get primary color from css variable - const primary_color = getComputedStyle(document.documentElement).getPropertyValue('--bs-warning'); + if(this._tree) { + this._tree.remove(); + } - const tree = this.treeTarget; - - $(tree).treeview({ + this._tree = new BSTreeView(this.treeTarget, { + levels: 1, + showTags: this._showTags, data: data, - enableLinks: true, showIcon: false, - showBorder: true, - searchResultBackColor: primary_color, - searchResultColor: '#000', - onNodeSelected: function (event, data) { - if (data.href) { + onNodeSelected: (event) => { + const node = event.detail.node; + if (node.href) { //Simulate a click so we just change the inner frame let a = document.createElement('a'); - a.setAttribute('href', data.href); + a.setAttribute('href', node.href); a.innerHTML = ""; - $(tree).append(a); + this.element.appendChild(a); a.click(); a.remove(); } }, //onNodeContextmenu: contextmenu_handler, - expandIcon: "fas fa-plus fa-fw fa-treeview", - collapseIcon: "fas fa-minus fa-fw fa-treeview" - }) - .on('initialized', function () { - //Collapse all nodes after init - $(this).treeview('collapseAll', {silent: true}); + }, [BS5Theme, FAIconTheme]); - //Reveal the selected ones - $(this).treeview('revealNode', [$(this).treeview('getSelected')]); - }); + this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => { + /** @type {BSTreeView} */ + const treeView = event.detail.treeView; + treeView.revealNode(treeView.getSelected()); + + //Add contextmenu event listener to the tree, which allows us to open the links in a new tab with a right click + treeView.getTreeElement().addEventListener("contextmenu", this._onContextMenu.bind(this)); + }); + + } + + _onContextMenu(event) + { + //Find the node that was clicked and open link in new tab + const node = this._tree._domToNode(event.target); + if(node && node.href) { + event.preventDefault(); + window.open(node.href, '_blank'); + } } collapseAll() { - $(this.treeTarget).treeview('collapseAll', {silent: true}); + this._tree.collapseAll({silent: true}); } expandAll() { - $(this.treeTarget).treeview('expandAll', {silent: true}); + this._tree.expandAll({silent: true}); } searchInput(event) { @@ -93,8 +117,11 @@ export default class extends Controller { //Do nothing if no data was passed const tree = this.treeTarget; - $(tree).treeview('collapseAll', {silent: true}); - $(tree).treeview('search', [data]); + this._tree.collapseAll({silent: true}); + this._tree.search(data); + + //Rereveal the selected node again + this._tree.revealNode(this._tree.getSelected()); } /** @@ -102,15 +129,7 @@ export default class extends Controller { * @private */ _isInitialized() { - const $tree = $(this.treeTarget).treeview(true); - - //If the tree is not initialized yet, we just get an empty jquery object with the treeview functions missing - if(typeof $tree.findNodes === 'undefined' ) { - return false; - } - - return true; - + return this._tree !== null; } _getData() { diff --git a/assets/js/lib/bootstrap-treeview/src/css/bootstrap-treeview.css b/assets/js/lib/bootstrap-treeview/src/css/bootstrap-treeview.css new file mode 100644 index 00000000..35a92849 --- /dev/null +++ b/assets/js/lib/bootstrap-treeview/src/css/bootstrap-treeview.css @@ -0,0 +1,52 @@ +/* ========================================================= + * patternfly-bootstrap-treeview.css 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. + * ========================================================= */ + +.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; +} + +.treeview .node-hidden { + display: none; +} + +.treeview span.image { + display: inline-block; + width: 12px; + height: 1.19em; + vertical-align: middle; + background-size: contain; + background-repeat: no-repeat; + margin-right: 5px; + line-height: 1em; +} diff --git a/assets/js/lib/bootstrap-treeview/src/js/bootstrap-treeview.js b/assets/js/lib/bootstrap-treeview/src/js/bootstrap-treeview.js new file mode 100644 index 00000000..048a80b9 --- /dev/null +++ b/assets/js/lib/bootstrap-treeview/src/js/bootstrap-treeview.js @@ -0,0 +1,1948 @@ +/* ========================================================= + * 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', + tagsClass: 'badge', + + 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), + + // 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 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() + .addClass( + (typeof tag === 'object' ? tag.class : undefined) + || node.tagsClass + || this._options.tagsClass + ) + .append( + (typeof tag === 'object' ? tag.text : undefined) + || 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)) { + $('').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 + '}'; + } + + if (node.iconColor) { + var innerStyle = 'color:' + node.iconColor + ';'; + style += '.node-' + this._elementId + '[data-nodeId="' + node.nodeId + '"] .node-icon{' + innerStyle + '}'; + } + }, this)); + + return this._css + style; + }; + + Tree.prototype._template = { + tree: $(''), + node: $('
  • '), + indent: $(''), + icon: { + node: $(''), + expand: $(''), + check: $(''), + empty: $('') + }, + image: $(''), + badge: $(''), + text: $('') + }; + + 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 + var targetNodes; + var parentNode = this._nodes[node.parentId]; + if (parentNode) { + targetNodes = parentNode.nodes; + } else { + targetNodes = this._tree; + } + targetNodes.splice(node.index, 1, 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; + } + } + }; + + 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); diff --git a/assets/tsconfig.json b/assets/tsconfig.json index 9c05d57d..adc8b23b 100644 --- a/assets/tsconfig.json +++ b/assets/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "commonjs", - "target": "es5", + "target": "es6", "sourceMap": true, "typeRoots": ["../node_modules"], - "types": ["jquery", "bootstrap", "jquery.form", "bootstrap-treeview", "bootbox", "typeahead", "marked"] + "types": [] }, "exclude": [ "node_modules" diff --git a/package.json b/package.json index ca587e95..aea5ebc5 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@ckeditor/ckeditor5-upload": "^34.2.0", "@ckeditor/ckeditor5-watchdog": "^34.2.0", "@ckeditor/ckeditor5-word-count": "^34.2.0", + "@jbtronics/bs-treeview": "^1.0.1", "@zxing/library": "^0.19.1", "bootbox": "^5.4.0", "bootstrap-select": "v1.14.0-beta3", @@ -78,7 +79,6 @@ "jszip": "^3.2.0", "katex": "^0.16.0", "marked": "^4.0.3", - "patternfly-bootstrap-treeview": "^2.1.8", "pdfmake": "^0.2.2", "stimulus-use": "^0.50.0", "tom-select": "^2.1.0", diff --git a/templates/components/tree_macros.html.twig b/templates/components/tree_macros.html.twig index f6b45054..395769e2 100644 --- a/templates/components/tree_macros.html.twig +++ b/templates/components/tree_macros.html.twig @@ -40,7 +40,7 @@ {% endmacro %} {% macro treeview(entity) %} -
    +
    diff --git a/webpack.config.js b/webpack.config.js index 9bd1f8f4..3d161a7d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -93,14 +93,14 @@ Encore // enables @babel/preset-env polyfills .configureBabelPresetEnv((config) => { - config.useBuiltIns = 'usage'; + //config.useBuiltIns = 'usage'; config.corejs = 3; }) // enables Sass/SCSS support //.enableSassLoader() // uncomment if you use TypeScript - //.enableTypeScriptLoader() + .enableTypeScriptLoader() // uncomment if you use React //.enableReactPreset() diff --git a/yarn.lock b/yarn.lock index 7859bcdb..ac239b39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -44,9 +44,9 @@ semver "^6.3.0" "@babel/generator@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.10.tgz#794f328bfabdcbaf0ebf9bf91b5b57b61fa77a2a" - integrity sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA== + version "7.18.12" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.12.tgz#fa58daa303757bd6f5e4bbca91b342040463d9f4" + integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg== dependencies: "@babel/types" "^7.18.10" "@jridgewell/gen-mapping" "^0.3.2" @@ -235,13 +235,13 @@ integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== "@babel/helper-wrap-function@^7.18.9": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.10.tgz#a7fcd3ab9b1be4c9b52cf7d7fdc1e88c2ce93396" - integrity sha512-95NLBP59VWdfK2lyLKe6eTMq9xg+yWKzxzxbJ1wcYNi1Auz200+83fMDADjRxBvc2QQor5zja2yTQzXGhk2GtQ== + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb" + integrity sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w== dependencies: "@babel/helper-function-name" "^7.18.9" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.18.10" + "@babel/traverse" "^7.18.11" "@babel/types" "^7.18.10" "@babel/helpers@^7.18.9": @@ -262,10 +262,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.18.10", "@babel/parser@^7.18.9": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.10.tgz#94b5f8522356e69e8277276adf67ed280c90ecc1" - integrity sha512-TYk3OA0HKL6qNryUayb5UUEhM/rkOQozIBEA5ITXh5DWrSp0TlUQXMyZmnWxG/DizSWBeeQ0Zbc5z8UGaaqoeg== +"@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.18.9": + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" + integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -877,10 +877,10 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.9": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.10.tgz#37ad97d1cb00efa869b91dd5d1950f8a6cf0cb08" - integrity sha512-J7ycxg0/K9XCtLyHf0cz2DqDihonJeIo+z+HEdRe9YuT8TY4A66i+Ab2/xZCEW7Ro60bPCBBfqqboHSamoV3+g== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.9": + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f" + integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== dependencies: "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.18.10" @@ -888,7 +888,7 @@ "@babel/helper-function-name" "^7.18.9" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.18.10" + "@babel/parser" "^7.18.11" "@babel/types" "^7.18.10" debug "^4.1.0" globals "^11.1.0" @@ -958,14 +958,14 @@ "@ckeditor/ckeditor5-utils" "^34.2.0" lodash-es "^4.17.15" -"@ckeditor/ckeditor5-dev-utils@^30.3.1", "@ckeditor/ckeditor5-dev-utils@^30.3.5": - version "30.3.5" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-30.3.5.tgz#7c4592ed37e1f7d87fb4187cd0a15266b4ab2db9" - integrity sha512-SQABOw+vda+IASTzAuZVMu8cZomvJ/Qk4QIdMJ6coyh29SR2pve/RiqcyaJucpI3W6ypInvwDp0QClrZqsO0LA== +"@ckeditor/ckeditor5-dev-utils@^30.3.1", "@ckeditor/ckeditor5-dev-utils@^30.4.0": + version "30.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-30.4.0.tgz#8165169fbe9fba2533034663b28a2eb54a77f946" + integrity sha512-FCuRQ5MzrJeWWKUULil6iI+ylzI0kZ2PqXBME66KZS9KIE7UEKabSAAJGmtc7xFCuGmbik6g2uAk3J0t+PWHjQ== dependencies: "@babel/parser" "^7.18.9" "@babel/traverse" "^7.18.9" - "@ckeditor/ckeditor5-dev-webpack-plugin" "^30.3.5" + "@ckeditor/ckeditor5-dev-webpack-plugin" "^30.4.0" chalk "^3.0.0" cli-cursor "^3.1.0" cli-spinners "^2.6.1" @@ -988,12 +988,12 @@ through2 "^3.0.1" ts-loader "^9.3.0" -"@ckeditor/ckeditor5-dev-webpack-plugin@^30.3.1", "@ckeditor/ckeditor5-dev-webpack-plugin@^30.3.5": - version "30.3.5" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-webpack-plugin/-/ckeditor5-dev-webpack-plugin-30.3.5.tgz#5c5de1b633d0471bd50c69c2c1a476037c198a38" - integrity sha512-CsH4fSRLYJ1YeraetX+JjazP7vcCLrRBH2+VxKuk+VoEZ09dAHCV2jg2SNdAucAK8OQQX6PWguY0gQWv8fsr6g== +"@ckeditor/ckeditor5-dev-webpack-plugin@^30.3.1", "@ckeditor/ckeditor5-dev-webpack-plugin@^30.4.0": + version "30.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-webpack-plugin/-/ckeditor5-dev-webpack-plugin-30.4.0.tgz#d2b46a5be3655bae199ba2adcb79cc47adba952b" + integrity sha512-wXux5YwysXvBw7ynBdt7M5c/0l2PRIpj/wTMK3yHSWo2pQAWMp76O3YNDbh2TdSSyNtFNj6OjCHd7e06lloL9w== dependencies: - "@ckeditor/ckeditor5-dev-utils" "^30.3.5" + "@ckeditor/ckeditor5-dev-utils" "^30.4.0" chalk "^4.0.0" rimraf "^3.0.2" semver "^7.3.4" @@ -1344,6 +1344,11 @@ resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.1.0.tgz#27e44e0e3dc5bd1d4bda0766d579cf5a14091cd7" integrity sha512-Q8kGjqwPqER+CtpQudbH+3Zgs2X4zb6pBAlr6NsKTXadg45pAOvxI9i4QpuHbwSzR2+x87HUm+rot9F/Pe8rxA== +"@jbtronics/bs-treeview@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@jbtronics/bs-treeview/-/bs-treeview-1.0.3.tgz#19a176fc84fbee5c7cc86c190ee351f480bdaf3d" + integrity sha512-9KU5bnrZr5saEfkTvUY5/sg1AS1tf5OvDiQmjHSnfygtVW77FI91jyuMOmn/7gGwPypPYI354wlMVvlgf9HroA== + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1384,10 +1389,10 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.14" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" - integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== +"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.15" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" + integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -1455,9 +1460,9 @@ integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== "@popperjs/core@^2.10.2": - version "2.11.5" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64" - integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw== + version "2.11.6" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" + integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== "@symfony/stimulus-bridge@^3.2.0": version "3.2.1" @@ -1609,9 +1614,9 @@ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/mime@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.0.tgz#e9a9903894405c6a6551f1774df4e64d9804d69c" - integrity sha512-fccbsHKqFDXClBZTDLA43zl0+TbxyIwyzIzwwhvoJvhNjOErCdeX2xJbURimv2EbSVUGav001PaCJg4mZxMl4w== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== "@types/minimatch@*": version "3.0.5" @@ -1619,9 +1624,9 @@ integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/node@*": - version "18.6.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.3.tgz#4e4a95b6fe44014563ceb514b2598b3e623d1c98" - integrity sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg== + version "18.7.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.2.tgz#22306626110c459aedd2cdf131c749ec781e3b34" + integrity sha512-ce7MIiaYWCFv6A83oEultwhBXb22fxwNOQf5DIxWA4WXvDQ7K+L0fbWl/YOfCzlR5B/uFkSnVBhPcOfOECcWvA== "@types/parse-json@^4.0.0": version "4.0.0" @@ -2162,30 +2167,16 @@ bootbox@^5.4.0: resolved "https://registry.yarnpkg.com/bootbox/-/bootbox-5.5.3.tgz#6b32da9c401a22b089e1544797cc9a91f1c35b23" integrity sha512-B4mnm1DYgNHzoNtD7I0L/fixqvya4EEQy5bFF/yNmGI2Eq3WwVVwdfWf3hoF8KS+EaV4f0uIMqtxB1EAZwZPhQ== -bootstrap-fileinput@^5.0.1: - version "5.5.0" - resolved "https://registry.yarnpkg.com/bootstrap-fileinput/-/bootstrap-fileinput-5.5.0.tgz#6ec1dee4c9a4177a4cea76ca422b8bea7b4da624" - integrity sha512-ZwDoW4Ws8WCxAOpQqLqNaJuCnwKtATUPW1R/rl1O2LvF0UIoEsXHFy4P1G+25SIQX0k59iQ4sJm3XquiY9SXOA== - dependencies: - bootstrap ">= 3.4.1" - jquery ">= 1.9.0" - opencollective-postinstall "^2.0.2" - bootstrap-select@v1.14.0-beta3: version "1.14.0-beta3" resolved "https://registry.yarnpkg.com/bootstrap-select/-/bootstrap-select-1.14.0-beta3.tgz#dc15083fe51d0ac7b38a3b99dfd492dcc0a783b0" integrity sha512-wYUDY4NAYBcNydXybE7wh3+ucyf+AcUOhZ+e0TFIoZ38A+k/3BVT1RPl5f0CiPxAexP1IQuqALKMqI8wtZS71A== -"bootstrap@>= 3.4.1", bootstrap@^5.1.3: +bootstrap@^5.1.3: version "5.2.0" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.0.tgz#838727fb60f1630db370fe57c63cbcf2962bb3d3" integrity sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A== -bootstrap@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72" - integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA== - bootswatch@^5.1.3: version "5.2.0" resolved "https://registry.yarnpkg.com/bootswatch/-/bootswatch-5.2.0.tgz#c02a0d84e0382552f8a7b9bdd055f36b758ffed9" @@ -2337,9 +2328,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370: - version "1.0.30001373" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz#2dc3bc3bfcb5d5a929bec11300883040d7b4b4be" - integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ== + version "1.0.30001375" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz#8e73bc3d1a4c800beb39f3163bf0190d7e5d7672" + integrity sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw== chalk@^2.0.0, chalk@^2.3.2: version "2.4.2" @@ -2481,9 +2472,9 @@ color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colord@^2.9.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" - integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== colorette@^2.0.10, colorette@^2.0.14: version "2.0.19" @@ -2762,9 +2753,9 @@ cssnano-utils@^3.1.0: integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== cssnano@^5.0.0, cssnano@^5.1.8: - version "5.1.12" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.12.tgz#bcd0b64d6be8692de79332c501daa7ece969816c" - integrity sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ== + version "5.1.13" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.13.tgz#83d0926e72955332dc4802a7070296e6258efc0a" + integrity sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ== dependencies: cssnano-preset-default "^5.2.12" lilconfig "^2.0.3" @@ -2969,7 +2960,7 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.1.3: +define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== @@ -3125,9 +3116,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.202: - version "1.4.210" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.210.tgz#12611fe874b833a3bf3671438b5893aba7858980" - integrity sha512-kSiX4tuyZijV7Cz0MWVmGT8K2siqaOA4Z66K5dCttPPRh0HicOcOAEj1KlC8O8J1aOS/1M8rGofOzksLKaHWcQ== + version "1.4.218" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.218.tgz#d6b817b5454499a92c85888b42dc2ad075e4493a" + integrity sha512-INDylKH//YIf2w67D+IjkfVnGVrZ/D94DAU/FPPm6T4jEPbEDQvo9r2wTj0ncFdtJH8+V8BggZTaN8Rzk5wkgw== emoji-regex@^8.0.0: version "8.0.0" @@ -3750,7 +3741,7 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: +has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== @@ -3998,9 +3989,9 @@ is-binary-path@~2.1.0: binary-extensions "^2.0.0" is-core-module@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== dependencies: has "^1.0.3" @@ -4142,7 +4133,7 @@ jest-worker@^27.4.5, jest-worker@^27.5.1: merge-stream "^2.0.0" supports-color "^8.0.0" -"jquery@>= 1.9.0", jquery@>=1.11, jquery@>=1.7, jquery@^3.4.1, jquery@^3.5.1: +jquery@>=1.11, jquery@>=1.7, jquery@^3.5.1: version "3.6.0" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== @@ -4621,13 +4612,13 @@ object-keys@^1.1.1: integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + version "4.1.3" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.3.tgz#d36b7700ddf0019abb6b1df1bb13f6445f79051f" + integrity sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" object-keys "^1.1.1" obuf@^1.0.0, obuf@^1.1.2: @@ -4670,11 +4661,6 @@ open@^8.0.9: is-docker "^2.1.1" is-wsl "^2.2.0" -opencollective-postinstall@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" - integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== - opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -4829,14 +4815,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -patternfly-bootstrap-treeview@^2.1.8: - version "2.1.10" - resolved "https://registry.yarnpkg.com/patternfly-bootstrap-treeview/-/patternfly-bootstrap-treeview-2.1.10.tgz#f96043734bb4ecac951783e2745e3f9a8873ffd4" - integrity sha512-P9+iFu34CwX+R5Fd7/EWbxTug0q9mDj53PnZIIh5ie54KX2kD0+54lCWtpD9SVylDwDtDv3n3A6gbFVkx7HsuA== - dependencies: - bootstrap "^3.4.1" - jquery "^3.4.1" - pdfmake@^0.2.2: version "0.2.5" resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.2.5.tgz#48b17670d69dae3860a5d8721ff12f7988140613" @@ -5197,9 +5175,9 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.2.14, postcss@^8.4.12, postcss@^8.4.13, postcss@^8.4.7: - version "8.4.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" - integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + version "8.4.16" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" + integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== dependencies: nanoid "^3.3.4" picocolors "^1.0.0" @@ -6033,17 +6011,17 @@ terser-webpack-plugin@^4.2.3: webpack-sources "^1.4.3" terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.0: - version "5.3.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz#8033db876dd5875487213e87c627bca323e5ed90" - integrity sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ== + version "5.3.4" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.4.tgz#f4d31e265883d20fda3ca9c0fc6a53f173ae62e3" + integrity sha512-SmnkUhBxLDcBfTIeaq+ZqJXLVEyXxSaNcCeSezECdKjfkMrTTnPvapBILylYwyEvHFZAn2cJ8dtiXel5XnfOfQ== dependencies: - "@jridgewell/trace-mapping" "^0.3.7" + "@jridgewell/trace-mapping" "^0.3.14" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.0" - terser "^5.7.2" + terser "^5.14.1" -terser@^5.3.4, terser@^5.7.2: +terser@^5.14.1, terser@^5.3.4: version "5.14.2" resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== @@ -6189,9 +6167,9 @@ type@^1.0.1: integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.7.0.tgz#aaff4ac90514e0dc1095b54af70505ef16cf00a2" - integrity sha512-NybX0NBIssNEj1efLf1mqKAtO4Q/Np5mqpa57be81ud7/tNHIXn48FDVXiyGMBF90FfXc5o7RPsuRQrPzgMOMA== + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== typedarray@^0.0.6: version "0.0.6" @@ -6395,9 +6373,9 @@ webpack-dev-middleware@^5.3.1: schema-utils "^4.0.0" webpack-dev-server@^4.8.0: - version "4.9.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9" - integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw== + version "4.10.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz#de270d0009eba050546912be90116e7fd740a9ca" + integrity sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ== dependencies: "@types/bonjour" "^3.5.9" "@types/connect-history-api-fallback" "^1.3.5" @@ -6596,9 +6574,9 @@ yaml@^1.10.0, yaml@^1.10.2: integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yargs-parser@^21.0.0: - version "21.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.0.tgz#a11d06a3bf57f064e951aa3ef55fcf3a5705f876" - integrity sha512-xzm2t63xTV/f7+bGMSRzLhUNk1ajv/tDoaD5OeGyC3cFo2fl7My9Z4hS3q2VdQ7JaLvTxErO8Jp5pRIFGMD/zg== + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yocto-queue@^0.1.0: version "0.1.0"