You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1257 lines
34 KiB
1257 lines
34 KiB
/* jQuery Tree Multiselect v2.6.3 | (c) Patrick Tsai | MIT Licensed */
|
|
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
(function ($) {
|
|
'use strict';
|
|
|
|
$.fn.treeMultiselect = require('./tree-multiselect/main');
|
|
})(jQuery);
|
|
|
|
},{"./tree-multiselect/main":6}],2:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
var SEARCH_HIT_ATTR = 'searchhit';
|
|
var SEARCH_HIT_ATTR_VAL_TRUE = 'true';
|
|
var SEARCH_HIT_ATTR_VAL_FALSE = 'false';
|
|
|
|
exports.addSearchHitMarker = function (node, isSearchHit) {
|
|
if (node) {
|
|
isSearchHit = isSearchHit ? SEARCH_HIT_ATTR_VAL_TRUE : SEARCH_HIT_ATTR_VAL_FALSE;
|
|
node.setAttribute(SEARCH_HIT_ATTR, isSearchHit);
|
|
}
|
|
};
|
|
|
|
exports.removeSearchHitMarker = function (node, isSearchHit) {
|
|
if (node) {
|
|
node.removeAttribute(SEARCH_HIT_ATTR);
|
|
}
|
|
};
|
|
|
|
exports.isNotSearchHit = function (node) {
|
|
return node && node.getAttribute(SEARCH_HIT_ATTR) === SEARCH_HIT_ATTR_VAL_FALSE;
|
|
};
|
|
|
|
},{}],3:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
var Item = require('./item');
|
|
|
|
var Section = require('./section');
|
|
|
|
exports.createLookup = function (arr) {
|
|
return {
|
|
arr: arr,
|
|
children: {}
|
|
};
|
|
};
|
|
|
|
exports.createSection = function (obj) {
|
|
return new Section(obj);
|
|
};
|
|
|
|
exports.createItem = function (obj) {
|
|
return new Item(obj);
|
|
};
|
|
|
|
},{"./item":4,"./section":5}],4:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
var AstCommon = require('./common');
|
|
|
|
var Util = require('../utility');
|
|
|
|
function Item(obj) {
|
|
obj = obj || {};
|
|
this.treeId = obj.treeId;
|
|
this.id = obj.id;
|
|
this.value = obj.value;
|
|
this.text = obj.text;
|
|
this.description = obj.description;
|
|
this.initialIndex = obj.initialIndex ? parseInt(obj.initialIndex) : null;
|
|
this.section = obj.section;
|
|
this.disabled = obj.disabled;
|
|
this.selected = obj.selected;
|
|
this.node = null;
|
|
}
|
|
|
|
Item.prototype.isSection = function () {
|
|
return false;
|
|
};
|
|
|
|
Item.prototype.isItem = function () {
|
|
return true;
|
|
};
|
|
|
|
Item.prototype.addSearchHitMarker = function (isSearchHit) {
|
|
AstCommon.addSearchHitMarker(this.node, isSearchHit);
|
|
};
|
|
|
|
Item.prototype.removeSearchHitMarker = function (isSearchHit) {
|
|
AstCommon.removeSearchHitMarker(this.node, isSearchHit);
|
|
};
|
|
|
|
Item.prototype.isNotSearchHit = function () {
|
|
return AstCommon.isNotSearchHit(this.node);
|
|
};
|
|
|
|
Item.prototype.render = function (createCheckboxes, disableCheckboxes) {
|
|
if (!this.node) {
|
|
this.node = Util.dom.createSelection(this, createCheckboxes, disableCheckboxes);
|
|
}
|
|
|
|
return this.node;
|
|
};
|
|
|
|
module.exports = Item;
|
|
|
|
},{"../utility":12,"./common":2}],5:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
var AstCommon = require('./common');
|
|
|
|
var Util = require('../utility');
|
|
|
|
function Section(obj) {
|
|
obj = obj || {};
|
|
this.treeId = obj.treeId;
|
|
this.id = obj.id;
|
|
this.name = obj.name;
|
|
this.items = [];
|
|
this.node = null;
|
|
}
|
|
|
|
Section.prototype.isSection = function () {
|
|
return true;
|
|
};
|
|
|
|
Section.prototype.isItem = function () {
|
|
return false;
|
|
};
|
|
|
|
Section.prototype.addSearchHitMarker = function (isSearchHit) {
|
|
AstCommon.addSearchHitMarker(this.node, isSearchHit);
|
|
};
|
|
|
|
Section.prototype.removeSearchHitMarker = function (isSearchHit) {
|
|
AstCommon.removeSearchHitMarker(this.node, isSearchHit);
|
|
};
|
|
|
|
Section.prototype.isNotSearchHit = function () {
|
|
return AstCommon.isNotSearchHit(this.node);
|
|
};
|
|
|
|
Section.prototype.render = function (createCheckboxes, disableCheckboxes) {
|
|
if (!this.node) {
|
|
this.node = Util.dom.createSection(this, createCheckboxes, disableCheckboxes);
|
|
}
|
|
|
|
return this.node;
|
|
};
|
|
|
|
module.exports = Section;
|
|
|
|
},{"../utility":12,"./common":2}],6:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
var Tree = require('./tree');
|
|
|
|
var uniqueId = 0;
|
|
|
|
function treeMultiselect(opts) {
|
|
var _this = this;
|
|
|
|
var options = mergeDefaultOptions(opts);
|
|
return this.map(function () {
|
|
var $originalSelect = _this;
|
|
$originalSelect.attr('multiple', '').css('display', 'none');
|
|
var tree = new Tree(uniqueId, $originalSelect, options);
|
|
tree.initialize();
|
|
++uniqueId;
|
|
return {
|
|
reload: function reload() {
|
|
tree.reload();
|
|
},
|
|
remove: function remove() {
|
|
tree.remove();
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
;
|
|
|
|
function mergeDefaultOptions(options) {
|
|
var defaults = {
|
|
allowBatchSelection: true,
|
|
collapsible: true,
|
|
enableSelectAll: false,
|
|
selectAllText: 'Select All',
|
|
unselectAllText: 'Unselect All',
|
|
freeze: false,
|
|
hideSidePanel: false,
|
|
maxSelections: 0,
|
|
onChange: null,
|
|
onlyBatchSelection: false,
|
|
searchable: false,
|
|
searchParams: ['value', 'text', 'description', 'section'],
|
|
sectionDelimiter: '/',
|
|
showSectionOnSelected: true,
|
|
sortable: false,
|
|
startCollapsed: false
|
|
};
|
|
return jQuery.extend({}, defaults, options);
|
|
}
|
|
|
|
module.exports = treeMultiselect;
|
|
|
|
},{"./tree":8}],7:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
var Util = require('./utility');
|
|
|
|
var MAX_SAMPLE_SIZE = 3;
|
|
|
|
function Search(searchHitAttr, astItems, astSections, searchParams) {
|
|
this.searchHitAttr = searchHitAttr;
|
|
this.index = {}; // key: at most three-letter combinations, value: array of data-key
|
|
// key: data-key, value: DOM node
|
|
|
|
this.astItems = astItems;
|
|
this.astItemKeys = Object.keys(astItems);
|
|
this.astSections = astSections;
|
|
this.astSectionKeys = Object.keys(astSections);
|
|
this.setSearchParams(searchParams);
|
|
this.buildIndex();
|
|
}
|
|
|
|
Search.prototype.setSearchParams = function (searchParams) {
|
|
Util.assert(Array.isArray(searchParams));
|
|
var allowedParams = {
|
|
value: true,
|
|
text: true,
|
|
description: true,
|
|
section: true
|
|
};
|
|
this.searchParams = [];
|
|
|
|
for (var ii = 0; ii < searchParams.length; ++ii) {
|
|
if (allowedParams[searchParams[ii]]) {
|
|
this.searchParams.push(searchParams[ii]);
|
|
}
|
|
}
|
|
};
|
|
|
|
Search.prototype.buildIndex = function () {
|
|
var _this = this;
|
|
|
|
var _loop = function _loop(astItemKey) {
|
|
var astItem = _this.astItems[astItemKey];
|
|
var searchItems = [];
|
|
|
|
_this.searchParams.forEach(function (searchParam) {
|
|
searchItems.push(astItem[searchParam]);
|
|
});
|
|
|
|
Util.array.removeFalseyExceptZero(searchItems);
|
|
var searchWords = searchItems.map(function (item) {
|
|
return item.toLowerCase();
|
|
});
|
|
searchWords.forEach(function (searchWord) {
|
|
var words = searchWord.split(' ');
|
|
words.forEach(function (word) {
|
|
_this._addToIndex(word, astItem.id);
|
|
});
|
|
});
|
|
};
|
|
|
|
// trigrams
|
|
for (var astItemKey in this.astItems) {
|
|
_loop(astItemKey);
|
|
}
|
|
};
|
|
|
|
Search.prototype._addToIndex = function (key, id) {
|
|
for (var sampleSize = 1; sampleSize <= MAX_SAMPLE_SIZE; ++sampleSize) {
|
|
for (var startOffset = 0; startOffset < key.length - sampleSize + 1; ++startOffset) {
|
|
var minikey = key.substring(startOffset, startOffset + sampleSize);
|
|
|
|
if (!this.index[minikey]) {
|
|
this.index[minikey] = [];
|
|
} // don't duplicate
|
|
// this takes advantage of the fact that the minikeys with same id's are added sequentially
|
|
|
|
|
|
var length = this.index[minikey].length;
|
|
|
|
if (length === 0 || this.index[minikey][length - 1] !== id) {
|
|
this.index[minikey].push(id);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Search.prototype.search = function (value) {
|
|
var _this2 = this;
|
|
|
|
value = value.trim();
|
|
|
|
if (!value) {
|
|
this.astItemKeys.forEach(function (id) {
|
|
_this2.astItems[id].removeSearchHitMarker();
|
|
});
|
|
this.astSectionKeys.forEach(function (id) {
|
|
_this2.astSections[id].removeSearchHitMarker();
|
|
});
|
|
return;
|
|
}
|
|
|
|
value = value.toLowerCase();
|
|
var searchWords = value.split(' ').filter(function (word) {
|
|
return word;
|
|
});
|
|
var searchChunks = [];
|
|
searchWords.forEach(function (searchWord) {
|
|
var chunks = splitWord(searchWord);
|
|
chunks.forEach(function (chunk) {
|
|
searchChunks.push(_this2.index[chunk] || []);
|
|
});
|
|
});
|
|
var matchedNodeIds = Util.array.intersectMany(searchChunks); // now we have id's that match search query
|
|
|
|
this.handleNodeVisibilities(matchedNodeIds);
|
|
};
|
|
|
|
Search.prototype.handleNodeVisibilities = function (shownNodeIds) {
|
|
var _this3 = this;
|
|
|
|
var shownNodeIdsHash = {};
|
|
var sectionsToNotHideHash = {};
|
|
shownNodeIds.forEach(function (id) {
|
|
shownNodeIdsHash[id] = true;
|
|
var node = _this3.astItems[id].node; // now search for parent sections
|
|
|
|
node = node.parentNode;
|
|
|
|
while (!node.className.match(/tree-multiselect/)) {
|
|
if (node.className.match(/section/)) {
|
|
var key = Util.getKey(node);
|
|
Util.assert(key || key === 0);
|
|
|
|
if (sectionsToNotHideHash[key]) {
|
|
break;
|
|
} else {
|
|
sectionsToNotHideHash[key] = true;
|
|
}
|
|
}
|
|
|
|
node = node.parentNode;
|
|
}
|
|
}); // hide selections
|
|
|
|
this.astItemKeys.forEach(function (id) {
|
|
var isSearchHit = !!shownNodeIdsHash[id];
|
|
|
|
_this3.astItems[id].addSearchHitMarker(isSearchHit);
|
|
});
|
|
this.astSectionKeys.forEach(function (id) {
|
|
var isSearchHit = !!sectionsToNotHideHash[id];
|
|
|
|
_this3.astSections[id].addSearchHitMarker(isSearchHit);
|
|
});
|
|
}; // split word into three letter (or less) pieces
|
|
|
|
|
|
function splitWord(word) {
|
|
Util.assert(word);
|
|
|
|
if (word.length < MAX_SAMPLE_SIZE) {
|
|
return [word];
|
|
}
|
|
|
|
var chunks = [];
|
|
|
|
for (var ii = 0; ii < word.length - MAX_SAMPLE_SIZE + 1; ++ii) {
|
|
chunks.push(word.substring(ii, ii + MAX_SAMPLE_SIZE));
|
|
}
|
|
|
|
return chunks;
|
|
}
|
|
|
|
module.exports = Search;
|
|
|
|
},{"./utility":12}],8:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
|
|
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
|
|
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
|
|
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
|
|
|
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
|
|
|
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
|
|
var Ast = require('./ast');
|
|
|
|
var Search = require('./search');
|
|
|
|
var UiBuilder = require('./ui-builder');
|
|
|
|
var Util = require('./utility');
|
|
|
|
var SEARCH_HIT_ATTR = 'searchhit';
|
|
|
|
function Tree(id, $originalSelect, params) {
|
|
this.id = id;
|
|
this.$originalSelect = $originalSelect;
|
|
this.params = params;
|
|
this.resetState();
|
|
}
|
|
|
|
Tree.prototype.initialize = function () {
|
|
this.generateSelections(this.$selectionContainer[0]);
|
|
this.popupDescriptionHover();
|
|
|
|
if (this.params.allowBatchSelection) {
|
|
this.handleSectionCheckboxMarkings();
|
|
}
|
|
|
|
if (this.params.collapsible) {
|
|
this.addCollapsibility();
|
|
}
|
|
|
|
if (this.params.searchable || this.params.enableSelectAll) {
|
|
var auxiliaryBox = Util.dom.createNode('div', {
|
|
"class": 'auxiliary'
|
|
});
|
|
this.$selectionContainer.prepend(auxiliaryBox, this.$selectionContainer.firstChild);
|
|
|
|
if (this.params.searchable) {
|
|
this.createSearchBar(auxiliaryBox);
|
|
}
|
|
|
|
if (this.params.enableSelectAll) {
|
|
this.createSelectAllButtons(auxiliaryBox);
|
|
}
|
|
}
|
|
|
|
this.armRemoveSelectedOnClick();
|
|
this.updateSelectedAndOnChange();
|
|
this.render(true);
|
|
this.uiBuilder.attach();
|
|
};
|
|
|
|
Tree.prototype.remove = function () {
|
|
this.uiBuilder.remove();
|
|
this.resetState();
|
|
};
|
|
|
|
Tree.prototype.reload = function () {
|
|
var _this = this;
|
|
|
|
var selectedOptions = {};
|
|
this.selectedKeys.forEach(function (key) {
|
|
var value = _this.astItems[key].value;
|
|
selectedOptions[value] = true;
|
|
});
|
|
this.remove();
|
|
this.$originalSelect.children('option').each(function (idx, element) {
|
|
var value = element.value;
|
|
|
|
if (selectedOptions[value]) {
|
|
element.setAttribute('selected', 'selected');
|
|
} else {
|
|
element.removeAttribute('selected');
|
|
}
|
|
});
|
|
this.initialize();
|
|
this.render(true);
|
|
};
|
|
|
|
Tree.prototype.resetState = function () {
|
|
this.uiBuilder = new UiBuilder(this.$originalSelect, this.params.hideSidePanel);
|
|
this.$treeContainer = this.uiBuilder.$treeContainer;
|
|
this.$selectionContainer = this.uiBuilder.$selectionContainer;
|
|
this.$selectedContainer = this.uiBuilder.$selectedContainer; // data-key is key, provides DOM node
|
|
|
|
this.astItems = {};
|
|
this.astSections = {};
|
|
this.selectedNodes = {};
|
|
this.selectedKeys = [];
|
|
this.keysToAdd = [];
|
|
this.keysToRemove = [];
|
|
};
|
|
|
|
Tree.prototype.generateSelections = function (parentNode) {
|
|
var options = this.$originalSelect.children('option');
|
|
var ast = this.createAst(options);
|
|
this.generateHtml(ast, parentNode);
|
|
};
|
|
|
|
Tree.prototype.createAst = function (options) {
|
|
var _this$keysToAdd;
|
|
|
|
var data = [];
|
|
var lookup = Ast.createLookup(data);
|
|
var self = this;
|
|
var itemId = 0;
|
|
var sectionId = 0;
|
|
var initialIndexItems = [];
|
|
var keysToAddAtEnd = [];
|
|
options.each(function () {
|
|
var option = this;
|
|
option.setAttribute('data-key', itemId);
|
|
var item = Ast.createItem({
|
|
treeId: self.id,
|
|
id: itemId,
|
|
value: option.value,
|
|
text: option.text,
|
|
description: option.getAttribute('data-description'),
|
|
initialIndex: option.getAttribute('data-index'),
|
|
section: option.getAttribute('data-section'),
|
|
disabled: option.hasAttribute('readonly'),
|
|
selected: option.hasAttribute('selected')
|
|
});
|
|
|
|
if (item.initialIndex && item.selected) {
|
|
initialIndexItems[item.initialIndex] = initialIndexItems[item.initialIndex] || [];
|
|
initialIndexItems[item.initialIndex].push(itemId);
|
|
} else if (item.selected) {
|
|
keysToAddAtEnd.push(itemId);
|
|
}
|
|
|
|
self.astItems[itemId] = item;
|
|
++itemId;
|
|
var lookupPosition = lookup;
|
|
var section = item.section;
|
|
var sectionParts = section && section.length > 0 ? section.split(self.params.sectionDelimiter) : [];
|
|
|
|
for (var ii = 0; ii < sectionParts.length; ++ii) {
|
|
var sectionPart = sectionParts[ii];
|
|
|
|
if (lookupPosition.children[sectionPart]) {
|
|
lookupPosition = lookupPosition.children[sectionPart];
|
|
} else {
|
|
var newSection = Ast.createSection({
|
|
treeId: self.id,
|
|
id: sectionId,
|
|
name: sectionPart
|
|
});
|
|
++sectionId;
|
|
lookupPosition.arr.push(newSection);
|
|
var newLookupNode = Ast.createLookup(newSection.items);
|
|
lookupPosition.children[sectionPart] = newLookupNode;
|
|
lookupPosition = newLookupNode;
|
|
}
|
|
}
|
|
|
|
lookupPosition.arr.push(item);
|
|
});
|
|
this.keysToAdd = Util.array.flatten(initialIndexItems);
|
|
Util.array.removeFalseyExceptZero(this.keysToAdd);
|
|
|
|
(_this$keysToAdd = this.keysToAdd).push.apply(_this$keysToAdd, keysToAddAtEnd);
|
|
|
|
Util.array.uniq(this.keysToAdd);
|
|
return data;
|
|
};
|
|
|
|
Tree.prototype.generateHtml = function (astArr, parentNode) {
|
|
for (var ii = 0; ii < astArr.length; ++ii) {
|
|
var astObj = astArr[ii];
|
|
|
|
if (astObj.isSection()) {
|
|
this.astSections[astObj.id] = astObj;
|
|
var createCheckboxes = this.params.allowBatchSelection;
|
|
var disableCheckboxes = this.params.freeze;
|
|
var node = astObj.render(createCheckboxes, disableCheckboxes);
|
|
parentNode.appendChild(node);
|
|
this.generateHtml(astObj.items, node);
|
|
} else if (astObj.isItem()) {
|
|
this.astItems[astObj.id] = astObj;
|
|
|
|
var _createCheckboxes = !this.params.onlyBatchSelection;
|
|
|
|
var _disableCheckboxes = this.params.freeze;
|
|
|
|
var _node = astObj.render(_createCheckboxes, _disableCheckboxes);
|
|
|
|
parentNode.appendChild(_node);
|
|
}
|
|
}
|
|
};
|
|
|
|
Tree.prototype.popupDescriptionHover = function () {
|
|
this.$selectionContainer.on('mouseenter', 'div.item > span.description', function () {
|
|
var $item = jQuery(this).parent();
|
|
var description = $item.attr('data-description');
|
|
var descriptionDiv = document.createElement('div');
|
|
descriptionDiv.className = 'temp-description-popup';
|
|
descriptionDiv.innerHTML = description;
|
|
descriptionDiv.style.position = 'absolute';
|
|
$item.append(descriptionDiv);
|
|
});
|
|
this.$selectionContainer.on('mouseleave', 'div.item > span.description', function () {
|
|
var $item = jQuery(this).parent();
|
|
$item.find('div.temp-description-popup').remove();
|
|
});
|
|
};
|
|
|
|
Tree.prototype.handleSectionCheckboxMarkings = function () {
|
|
var self = this;
|
|
this.$selectionContainer.on('click', 'input.section[type=checkbox]', function () {
|
|
var $section = jQuery(this).closest('div.section');
|
|
var $items = $section.find('div.item');
|
|
var keys = $items.map(function (idx, el) {
|
|
var key = Util.getKey(el);
|
|
var astItem = self.astItems[key];
|
|
|
|
if (!astItem.disabled && !astItem.isNotSearchHit()) {
|
|
return key;
|
|
}
|
|
|
|
return null;
|
|
}).get();
|
|
|
|
if (this.checked) {
|
|
var _self$keysToAdd;
|
|
|
|
// TODO why does this always take this branch
|
|
(_self$keysToAdd = self.keysToAdd).push.apply(_self$keysToAdd, _toConsumableArray(keys));
|
|
|
|
Util.array.uniq(self.keysToAdd);
|
|
} else {
|
|
var _self$keysToRemove;
|
|
|
|
(_self$keysToRemove = self.keysToRemove).push.apply(_self$keysToRemove, _toConsumableArray(keys));
|
|
|
|
Util.array.uniq(self.keysToRemove);
|
|
}
|
|
|
|
self.render();
|
|
});
|
|
};
|
|
|
|
Tree.prototype.redrawSectionCheckboxes = function ($section) {
|
|
$section = $section || this.$selectionContainer; // returns array; bit 1 is all children are true, bit 0 is all children are false
|
|
|
|
var returnVal = 3;
|
|
var self = this;
|
|
var $childSections = $section.find('> div.section');
|
|
$childSections.each(function () {
|
|
var result = self.redrawSectionCheckboxes(jQuery(this));
|
|
returnVal &= result;
|
|
});
|
|
|
|
if (returnVal) {
|
|
var $childCheckboxes = $section.find('> div.item > input[type=checkbox]');
|
|
|
|
for (var ii = 0; ii < $childCheckboxes.length; ++ii) {
|
|
if ($childCheckboxes[ii].checked) {
|
|
returnVal &= ~2;
|
|
} else {
|
|
returnVal &= ~1;
|
|
}
|
|
|
|
if (returnVal === 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
var sectionCheckbox = $section.find('> div.title > input[type=checkbox]');
|
|
|
|
if (sectionCheckbox.length) {
|
|
sectionCheckbox = sectionCheckbox[0];
|
|
|
|
if (returnVal & 1) {
|
|
sectionCheckbox.checked = true;
|
|
sectionCheckbox.indeterminate = false;
|
|
} else if (returnVal & 2) {
|
|
sectionCheckbox.checked = false;
|
|
sectionCheckbox.indeterminate = false;
|
|
} else {
|
|
sectionCheckbox.checked = false;
|
|
sectionCheckbox.indeterminate = true;
|
|
}
|
|
}
|
|
|
|
return returnVal;
|
|
};
|
|
|
|
Tree.prototype.addCollapsibility = function () {
|
|
var titleSelector = 'div.title';
|
|
var $titleDivs = this.$selectionContainer.find(titleSelector);
|
|
var collapseSpan = Util.dom.createNode('span', {
|
|
"class": 'collapse-section'
|
|
});
|
|
$titleDivs.prepend(collapseSpan);
|
|
var sectionSelector = 'div.section';
|
|
var $sectionDivs = this.$selectionContainer.find(sectionSelector);
|
|
|
|
if (this.params.startCollapsed) {
|
|
$sectionDivs.addClass('collapsed');
|
|
}
|
|
|
|
this.$selectionContainer.on('click', titleSelector, function (event) {
|
|
if (event.target.nodeName === 'INPUT') {
|
|
return;
|
|
}
|
|
|
|
var $section = jQuery(this).parent();
|
|
$section.toggleClass('collapsed');
|
|
event.stopPropagation();
|
|
});
|
|
};
|
|
|
|
Tree.prototype.createSearchBar = function (parentNode) {
|
|
var searchObj = new Search(SEARCH_HIT_ATTR, this.astItems, this.astSections, this.params.searchParams);
|
|
var searchNode = Util.dom.createNode('input', {
|
|
"class": 'search',
|
|
placeholder: 'Search...'
|
|
});
|
|
parentNode.appendChild(searchNode);
|
|
this.$selectionContainer.on('input', 'input.search', function () {
|
|
var searchText = this.value;
|
|
searchObj.search(searchText);
|
|
});
|
|
};
|
|
|
|
Tree.prototype.createSelectAllButtons = function (parentNode) {
|
|
var selectAllNode = Util.dom.createNode('span', {
|
|
"class": 'select-all',
|
|
text: this.params.selectAllText
|
|
});
|
|
var unselectAllNode = Util.dom.createNode('span', {
|
|
"class": 'unselect-all',
|
|
text: this.params.unselectAllText
|
|
});
|
|
var selectAllContainer = Util.dom.createNode('div', {
|
|
"class": 'select-all-container'
|
|
});
|
|
selectAllContainer.appendChild(selectAllNode);
|
|
selectAllContainer.appendChild(unselectAllNode);
|
|
parentNode.appendChild(selectAllContainer);
|
|
var self = this;
|
|
this.$selectionContainer.on('click', 'span.select-all', function () {
|
|
var _self$keysToAdd2;
|
|
|
|
(_self$keysToAdd2 = self.keysToAdd).push.apply(_self$keysToAdd2, _toConsumableArray(self.unfilteredNodeIds()));
|
|
|
|
self.render();
|
|
});
|
|
this.$selectionContainer.on('click', 'span.unselect-all', function () {
|
|
var _self$keysToRemove2;
|
|
|
|
(_self$keysToRemove2 = self.keysToRemove).push.apply(_self$keysToRemove2, _toConsumableArray(self.unfilteredNodeIds()));
|
|
|
|
self.render();
|
|
});
|
|
};
|
|
|
|
Tree.prototype.unfilteredNodeIds = function () {
|
|
var self = this;
|
|
return Object.keys(self.astItems).filter(function (key) {
|
|
return !self.astItems[key].node.hasAttribute(SEARCH_HIT_ATTR) || self.astItems[key].node.getAttribute(SEARCH_HIT_ATTR) === 'true';
|
|
});
|
|
};
|
|
|
|
Tree.prototype.armRemoveSelectedOnClick = function () {
|
|
var self = this;
|
|
this.$selectedContainer.on('click', 'span.remove-selected', function () {
|
|
var parentNode = this.parentNode;
|
|
var key = Util.getKey(parentNode);
|
|
self.keysToRemove.push(key);
|
|
self.render();
|
|
});
|
|
};
|
|
|
|
Tree.prototype.updateSelectedAndOnChange = function () {
|
|
var self = this;
|
|
this.$selectionContainer.on('click', 'input.option[type=checkbox]', function () {
|
|
var checkbox = this;
|
|
var selection = checkbox.parentNode;
|
|
var key = Util.getKey(selection);
|
|
Util.assert(key || key === 0);
|
|
|
|
if (checkbox.checked) {
|
|
self.keysToAdd.push(key);
|
|
} else {
|
|
self.keysToRemove.push(key);
|
|
}
|
|
|
|
self.render();
|
|
});
|
|
|
|
if (this.params.sortable && !this.params.freeze) {
|
|
var startIndex = null;
|
|
var endIndex = null;
|
|
this.$selectedContainer.sortable({
|
|
start: function start(event, ui) {
|
|
startIndex = ui.item.index();
|
|
},
|
|
stop: function stop(event, ui) {
|
|
endIndex = ui.item.index();
|
|
|
|
if (startIndex === endIndex) {
|
|
return;
|
|
}
|
|
|
|
Util.array.moveEl(self.selectedKeys, startIndex, endIndex);
|
|
self.render();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
Tree.prototype.render = function (noCallbacks) {
|
|
var _this$selectedKeys,
|
|
_this2 = this;
|
|
|
|
// fix arrays first
|
|
Util.array.uniq(this.keysToAdd);
|
|
Util.array.uniq(this.keysToRemove);
|
|
Util.array.subtract(this.keysToAdd, this.selectedKeys);
|
|
Util.array.intersect(this.keysToRemove, this.selectedKeys); // check for max number of selections
|
|
|
|
if (Util.isInteger(this.params.maxSelections) && this.params.maxSelections > 0) {
|
|
var currentLength = this.keysToAdd.length - this.keysToRemove.length + this.selectedKeys.length;
|
|
|
|
if (currentLength > this.params.maxSelections) {
|
|
var _this$keysToRemove;
|
|
|
|
var lengthToCut = currentLength - this.params.maxSelections;
|
|
var keysToCut = [];
|
|
|
|
if (lengthToCut > this.selectedKeys.length) {
|
|
keysToCut.push.apply(keysToCut, _toConsumableArray(this.selectedKeys));
|
|
lengthToCut -= this.selectedKeys.length;
|
|
keysToCut.push.apply(keysToCut, _toConsumableArray(this.keysToAdd.splice(0, lengthToCut)));
|
|
} else {
|
|
keysToCut.push.apply(keysToCut, _toConsumableArray(this.selectedKeys.slice(0, lengthToCut)));
|
|
}
|
|
|
|
(_this$keysToRemove = this.keysToRemove).push.apply(_this$keysToRemove, keysToCut);
|
|
}
|
|
} // remove items first
|
|
|
|
|
|
for (var ii = 0; ii < this.keysToRemove.length; ++ii) {
|
|
// remove the selected divs
|
|
var node = this.selectedNodes[this.keysToRemove[ii]];
|
|
|
|
if (node) {
|
|
// slightly more verbose than node.remove(), but more browser support
|
|
node.parentNode.removeChild(node);
|
|
this.selectedNodes[this.keysToRemove[ii]] = null;
|
|
} // uncheck these checkboxes
|
|
|
|
|
|
var selectionNode = this.astItems[this.keysToRemove[ii]].node;
|
|
selectionNode.getElementsByTagName('INPUT')[0].checked = false;
|
|
}
|
|
|
|
Util.array.subtract(this.selectedKeys, this.keysToRemove); // now add items
|
|
|
|
for (var jj = 0; jj < this.keysToAdd.length; ++jj) {
|
|
// create selected divs
|
|
var key = this.keysToAdd[jj];
|
|
var astItem = this.astItems[key];
|
|
this.selectedKeys.push(key);
|
|
var selectedNode = Util.dom.createSelected(astItem, this.params.freeze, this.params.showSectionOnSelected);
|
|
this.selectedNodes[astItem.id] = selectedNode;
|
|
this.$selectedContainer.append(selectedNode); // check the checkboxes
|
|
|
|
var inputNode = astItem.node.getElementsByTagName('INPUT')[0];
|
|
|
|
if (inputNode) {
|
|
inputNode.checked = true;
|
|
}
|
|
}
|
|
|
|
(_this$selectedKeys = this.selectedKeys).push.apply(_this$selectedKeys, _toConsumableArray(this.keysToAdd));
|
|
|
|
Util.array.uniq(this.selectedKeys); // redraw section checkboxes
|
|
|
|
this.redrawSectionCheckboxes(); // now fix original select
|
|
|
|
var originalValsHash = {}; // valHash hashes a value to an index
|
|
|
|
var valHash = {};
|
|
|
|
for (var kk = 0; kk < this.selectedKeys.length; ++kk) {
|
|
var value = this.astItems[this.selectedKeys[kk]].value;
|
|
originalValsHash[this.selectedKeys[kk]] = true;
|
|
valHash[value] = kk;
|
|
} // TODO is there a better way to sort the values other than by HTML?
|
|
// NOTE: the following does not work since jQuery duplicates option values with the same value
|
|
// this.$originalSelect.val(vals);
|
|
|
|
|
|
var options = this.$originalSelect.find('option').toArray();
|
|
options.sort(function (a, b) {
|
|
var aValue = valHash[a.value] || 0;
|
|
var bValue = valHash[b.value] || 0;
|
|
return aValue - bValue;
|
|
});
|
|
this.$originalSelect.html(options);
|
|
this.$originalSelect.find('option').each(function (idx, el) {
|
|
this.selected = !!originalValsHash[Util.getKey(el)];
|
|
});
|
|
this.$originalSelect.change();
|
|
|
|
if (!noCallbacks && this.params.onChange) {
|
|
var optionsSelected = this.selectedKeys.map(function (key) {
|
|
return _this2.astItems[key];
|
|
});
|
|
var optionsAdded = this.keysToAdd.map(function (key) {
|
|
return _this2.astItems[key];
|
|
});
|
|
var optionsRemoved = this.keysToRemove.map(function (key) {
|
|
return _this2.astItems[key];
|
|
});
|
|
this.params.onChange(optionsSelected, optionsAdded, optionsRemoved);
|
|
}
|
|
|
|
this.keysToRemove = [];
|
|
this.keysToAdd = [];
|
|
};
|
|
|
|
module.exports = Tree;
|
|
|
|
},{"./ast":3,"./search":7,"./ui-builder":9,"./utility":12}],9:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
function UiBuilder($el, hideSidePanel) {
|
|
var $tree = jQuery('<div class="tree-multiselect"></div>');
|
|
var $selections = jQuery('<div class="selections"></div>');
|
|
|
|
if (hideSidePanel) {
|
|
$selections.addClass('no-border');
|
|
}
|
|
|
|
$tree.append($selections);
|
|
var $selected = jQuery('<div class="selected"></div>');
|
|
|
|
if (!hideSidePanel) {
|
|
$tree.append($selected);
|
|
}
|
|
|
|
this.$el = $el;
|
|
this.$treeContainer = $tree;
|
|
this.$selectionContainer = $selections;
|
|
this.$selectedContainer = $selected;
|
|
}
|
|
|
|
UiBuilder.prototype.attach = function () {
|
|
this.$el.after(this.$treeContainer);
|
|
};
|
|
|
|
UiBuilder.prototype.remove = function () {
|
|
this.$treeContainer.remove();
|
|
};
|
|
|
|
module.exports = UiBuilder;
|
|
|
|
},{}],10:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
// keeps if pred is true
|
|
function filterInPlace(arr, pred) {
|
|
var idx = 0;
|
|
|
|
for (var ii = 0; ii < arr.length; ++ii) {
|
|
if (pred(arr[ii])) {
|
|
arr[idx] = arr[ii];
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
arr.length = idx;
|
|
}
|
|
|
|
exports.flatten = function (arr, r) {
|
|
if (!Array.isArray(arr)) {
|
|
return arr;
|
|
}
|
|
|
|
r = r || [];
|
|
|
|
for (var ii = 0; ii < arr.length; ++ii) {
|
|
if (Array.isArray(arr[ii])) {
|
|
r.concat(exports.flatten(arr[ii], r));
|
|
} else {
|
|
r.push(arr[ii]);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
};
|
|
|
|
exports.uniq = function (arr) {
|
|
var hash = {};
|
|
|
|
var pred = function pred(val) {
|
|
var returnVal = !hash[val];
|
|
hash[val] = true;
|
|
return returnVal;
|
|
};
|
|
|
|
filterInPlace(arr, pred);
|
|
};
|
|
|
|
exports.removeFalseyExceptZero = function (arr) {
|
|
var pred = function pred(val) {
|
|
return val || val === 0;
|
|
};
|
|
|
|
filterInPlace(arr, pred);
|
|
};
|
|
|
|
exports.moveEl = function (arr, oldPos, newPos) {
|
|
var el = arr[oldPos];
|
|
arr.splice(oldPos, 1);
|
|
arr.splice(newPos, 0, el);
|
|
};
|
|
|
|
exports.subtract = function (arr, arrExcluded) {
|
|
var hash = {};
|
|
|
|
for (var ii = 0; ii < arrExcluded.length; ++ii) {
|
|
hash[arrExcluded[ii]] = true;
|
|
}
|
|
|
|
var pred = function pred(val) {
|
|
return !hash[val];
|
|
};
|
|
|
|
filterInPlace(arr, pred);
|
|
};
|
|
|
|
exports.intersect = function (arr, arrExcluded) {
|
|
var hash = {};
|
|
|
|
for (var ii = 0; ii < arrExcluded.length; ++ii) {
|
|
hash[arrExcluded[ii]] = true;
|
|
}
|
|
|
|
var pred = function pred(val) {
|
|
return hash[val];
|
|
};
|
|
|
|
filterInPlace(arr, pred);
|
|
}; // takes in array of arrays
|
|
// arrays are presorted
|
|
|
|
|
|
exports.intersectMany = function (arrays) {
|
|
var indexLocations = [];
|
|
var maxIndexLocations = [];
|
|
arrays.forEach(function (array) {
|
|
indexLocations.push(0);
|
|
maxIndexLocations.push(array.length - 1);
|
|
});
|
|
var finalOutput = [];
|
|
|
|
for (; indexLocations.length > 0 && indexLocations[0] <= maxIndexLocations[0]; ++indexLocations[0]) {
|
|
// advance indices to be at least equal to first array element
|
|
var terminate = false;
|
|
|
|
for (var ii = 1; ii < arrays.length; ++ii) {
|
|
while (arrays[ii][indexLocations[ii]] < arrays[0][indexLocations[0]] && indexLocations[ii] <= maxIndexLocations[ii]) {
|
|
++indexLocations[ii];
|
|
}
|
|
|
|
if (indexLocations[ii] > maxIndexLocations[ii]) {
|
|
terminate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (terminate) {
|
|
break;
|
|
} // check element equality
|
|
|
|
|
|
var shouldAdd = true;
|
|
|
|
for (var jj = 1; jj < arrays.length; ++jj) {
|
|
if (arrays[0][indexLocations[0]] !== arrays[jj][indexLocations[jj]]) {
|
|
shouldAdd = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldAdd) {
|
|
finalOutput.push(arrays[0][indexLocations[0]]);
|
|
}
|
|
}
|
|
|
|
return finalOutput;
|
|
};
|
|
|
|
},{}],11:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
exports.createNode = function (tag, props) {
|
|
var node = document.createElement(tag);
|
|
|
|
if (props) {
|
|
for (var key in props) {
|
|
if (Object.prototype.hasOwnProperty.call(props, key) && key !== 'text') {
|
|
node.setAttribute(key, props[key]);
|
|
}
|
|
}
|
|
|
|
if (props.text) {
|
|
node.textContent = props.text;
|
|
}
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
exports.createSelection = function (astItem, createCheckboxes, disableCheckboxes) {
|
|
var props = {
|
|
"class": 'item',
|
|
'data-key': astItem.id,
|
|
'data-value': astItem.value
|
|
};
|
|
var hasDescription = !!astItem.description;
|
|
|
|
if (hasDescription) {
|
|
props['data-description'] = astItem.description;
|
|
}
|
|
|
|
if (astItem.initialIndex) {
|
|
props['data-index'] = astItem.initialIndex;
|
|
}
|
|
|
|
var selectionNode = exports.createNode('div', props);
|
|
|
|
if (hasDescription) {
|
|
var popup = exports.createNode('span', {
|
|
"class": 'description',
|
|
text: '?'
|
|
});
|
|
selectionNode.appendChild(popup);
|
|
}
|
|
|
|
if (!createCheckboxes) {
|
|
selectionNode.innerText = astItem.text || astItem.value;
|
|
} else {
|
|
var optionLabelCheckboxId = "treemultiselect-".concat(astItem.treeId, "-").concat(astItem.id);
|
|
var inputCheckboxProps = {
|
|
"class": 'option',
|
|
type: 'checkbox',
|
|
id: optionLabelCheckboxId
|
|
};
|
|
|
|
if (disableCheckboxes || astItem.disabled) {
|
|
inputCheckboxProps.disabled = true;
|
|
}
|
|
|
|
var inputCheckbox = exports.createNode('input', inputCheckboxProps); // prepend child
|
|
|
|
selectionNode.insertBefore(inputCheckbox, selectionNode.firstChild);
|
|
var labelProps = {
|
|
"class": astItem.disabled ? 'disabled' : '',
|
|
"for": optionLabelCheckboxId,
|
|
text: astItem.text || astItem.value
|
|
};
|
|
var label = exports.createNode('label', labelProps);
|
|
selectionNode.appendChild(label);
|
|
}
|
|
|
|
return selectionNode;
|
|
};
|
|
|
|
exports.createSelected = function (astItem, disableRemoval, showSectionOnSelected) {
|
|
var node = exports.createNode('div', {
|
|
"class": 'item',
|
|
'data-key': astItem.id,
|
|
'data-value': astItem.value,
|
|
text: astItem.text
|
|
});
|
|
|
|
if (!disableRemoval && !astItem.disabled) {
|
|
var removalSpan = exports.createNode('span', {
|
|
"class": 'remove-selected',
|
|
text: '×'
|
|
});
|
|
node.insertBefore(removalSpan, node.firstChild);
|
|
}
|
|
|
|
if (showSectionOnSelected) {
|
|
var sectionSpan = exports.createNode('span', {
|
|
"class": 'section-name',
|
|
text: astItem.section
|
|
});
|
|
node.appendChild(sectionSpan);
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
exports.createSection = function (astSection, createCheckboxes, disableCheckboxes) {
|
|
var sectionNode = exports.createNode('div', {
|
|
"class": 'section',
|
|
'data-key': astSection.id
|
|
});
|
|
var titleNode = exports.createNode('div', {
|
|
"class": 'title',
|
|
text: astSection.name
|
|
});
|
|
|
|
if (createCheckboxes) {
|
|
var checkboxProps = {
|
|
"class": 'section',
|
|
type: 'checkbox'
|
|
};
|
|
|
|
if (disableCheckboxes) {
|
|
checkboxProps.disabled = true;
|
|
}
|
|
|
|
var checkboxNode = exports.createNode('input', checkboxProps);
|
|
titleNode.insertBefore(checkboxNode, titleNode.firstChild);
|
|
}
|
|
|
|
sectionNode.appendChild(titleNode);
|
|
return sectionNode;
|
|
};
|
|
|
|
},{}],12:[function(require,module,exports){
|
|
"use strict";
|
|
|
|
exports.array = require('./array');
|
|
|
|
exports.assert = function (bool, message) {
|
|
if (!bool) {
|
|
throw new Error(message || 'Assertion failed');
|
|
}
|
|
};
|
|
|
|
exports.dom = require('./dom');
|
|
|
|
exports.getKey = function (el) {
|
|
exports.assert(el);
|
|
return parseInt(el.getAttribute('data-key'));
|
|
};
|
|
|
|
exports.isInteger = function (value) {
|
|
var x;
|
|
|
|
if (isNaN(value)) {
|
|
return false;
|
|
}
|
|
|
|
x = parseFloat(value);
|
|
return (x | 0) === x;
|
|
};
|
|
|
|
},{"./array":10,"./dom":11}]},{},[1]);
|
|
|