2 Copyright (C) Philippe Meyer 2019-2021
3 Distributed under the MIT License
5 vanillaSelectBox : v1.05 : setValue() bug correction on single mode. You could not set the value
6 vanillaSelectBox : v1.04 : select all issue fixed by https://github.com/arthur911016
7 vanillaSelectBox : v1.03 : getResult() an new fonction to get the selected values in an array
8 vanillaSelectBox : v1.02 : Adding 2 new options "itemsSeparator" to change the default "," item separator showing in the button and translations.item to show the item in singular if there is only one.
9 vanillaSelectBox : v1.01 : Removing useless code line 550,551 issue 71 by chchch
10 vanillaSelectBox : v1.00 : Adding a package.json file
11 vanillaSelectBox : v0.78 : Stop using inline styles in the main button. You can steal use keepInlineStyles:true to use the legacy behaviour
12 vanillaSelectBox : v0.77 : Work on place holder with bastoune help => still seems to lose placeholder value on multiple dropdown checkall
13 vanillaSelectBox : v0.76 : New changeTree function : to rebuild the original tree with new data + correcting empty() function
14 vanillaSelectBox : v0.75 : Remote search ready + local search modification : when a check on optgroup checks children only
15 if they not excluded from search.
16 vanillaSelectBox : v0.72 : Remote search (WIP) bugfix [x] Select all duplicated
17 vanillaSelectBox : v0.71 : Remote search (WIP) better code
18 vanillaSelectBox : v0.70 : Remote search (WIP) for users to test
19 vanillaSelectBox : v0.65 : Two levels: bug fix : groups are checked/unchecked when check all/uncheck all is clicked
20 vanillaSelectBox : v0.64 : Two levels: groups are now checkable to check/uncheck the children options
21 vanillaSelectBox : v0.63 : Two levels: one click on the group selects / unselects children
22 vanillaSelectBox : v0.62 : New option: maxOptionWidth set a maximum width for each option for narrow menus
23 vanillaSelectBox : v0.61 : New option: maxSelect, set a maximum to the selectable options in a multiple choice menu
24 vanillaSelectBox : v0.60 : Two levels: Optgroups are now used to show two level dropdowns
25 vanillaSelectBox : v0.59 : Bug fix : search box was overlapping first item in single selects
26 vanillaSelectBox : v0.58 : Bug fixes
27 vanillaSelectBox : v0.57 : Bug fix (minWidth option not honored)
28 vanillaSelectBox : v0.56 : The multiselect checkboxes are a little smaller, maxWidth option is now working + added minWidth option as well
29 The button has now a style attribute to protect its appearance
30 vanillaSelectBox : v0.55 : All attributes from the original select options are copied to the selectBox element
31 vanillaSelectBox : v0.54 : if all the options of the select are selected by the user then the check all checkbox is checked
32 vanillaSelectBox : v0.53 : if all the options of the select are selected then the check all checkbox is checked
33 vanillaSelectBox : v0.52 : Better support of select('all') => command is consistent with checkbox and selecting / deselecting while searching select / uncheck only the found items
34 vanillaSelectBox : v0.51 : Translations for select all/clear all + minor css corrections + don't select disabled items
35 vanillaSelectBox : v0.50 : PR by jaguerra2017 adding a select all/clear all check button + optgroup support !
36 vanillaSelectBox : v0.41 : Bug corrected, the menu content was misplaced if a css transform was applied on a parent
37 vanillaSelectBox : v0.40 : A click on one selectBox close the other opened boxes
38 vanillaSelectBox : v0.35 : You can enable and disable items
39 vanillaSelectBox : v0.30 : The menu stops moving around on window resize and scroll + z-index in order of creation for multiple instances
40 vanillaSelectBox : v0.26 : Corrected bug in stayOpen mode with disable() function
41 vanillaSelectBox : v0.25 : New option stayOpen, and the dropbox is no longer a dropbox but a nice multi-select
42 previous version : v0.24 : corrected bug affecting options with more than one class
43 https://github.com/PhilippeMarcMeyer/vanillaSelectBox
46 let VSBoxCounter = function () {
50 set: function (instancePtr) {
51 instances.push({ offset: ++count, ptr: instancePtr });
52 return instances[instances.length - 1].offset;
54 remove: function (instanceNr) {
55 let temp = instances.filter(function (x) {
56 return x.offset != instanceNr;
58 instances = temp.splice(0);
60 closeAllButMe: function (instanceNr) {
61 instances.forEach(function (x) {
62 if (x.offset != instanceNr) {
70 function vanillaSelectBox(domSelector, options) {
72 this.instanceOffset = VSBoxCounter.set(self);
73 this.domSelector = domSelector;
74 this.root = document.querySelector(domSelector);
75 this.rootToken = null;
79 this.isMultiple = this.root.hasAttribute("multiple");
80 this.multipleSize = this.isMultiple && this.root.hasAttribute("size") ? parseInt(this.root.getAttribute("size")) : -1;
81 this.isOptgroups = false;
82 this.currentOptgroup = 0;
88 this.isDisabled = false;
90 this.searchZone = null;
92 this.disabledItems = [];
93 this.ulminWidth = 140;
94 this.ulmaxWidth = 280;
95 this.ulminHeight = 25;
96 this.maxOptionWidth = Infinity;
97 this.maxSelect = Infinity;
98 this.isInitRemote = false;
99 this.isSearchRemote = false;
101 this.onSearch = null; // if isRemote is true : a user defined function that loads more options from the back
102 this.onInitSize = null;
103 this.forbidenAttributes = ["class", "selected", "disabled", "data-text", "data-value", "style"];
104 this.forbidenClasses = ["active", "disabled"];
109 translations: { "all": "All", "item": "item","items": "items", "selectAll": "Select All", "clearAll": "Clear All" },
113 disableSelectAll: false,
114 buttonItemsSeparator : ","
116 this.keepInlineStyles = true;
117 this.keepInlineCaretStyles = true;
119 if(options.itemsSeparator!= undefined){
120 this.userOptions.buttonItemsSeparator = options.itemsSeparator;
122 if (options.maxWidth != undefined) {
123 this.userOptions.maxWidth = options.maxWidth;
125 if (options.minWidth != undefined) {
126 this.userOptions.minWidth = options.minWidth;
128 if (options.maxHeight != undefined) {
129 this.userOptions.maxHeight = options.maxHeight;
131 if (options.translations != undefined) {
132 for (var property in options.translations) {
133 if (options.translations.hasOwnProperty(property)) {
134 if (this.userOptions.translations[property]) {
135 this.userOptions.translations[property] = options.translations[property];
140 if (options.placeHolder != undefined) {
141 this.userOptions.placeHolder = options.placeHolder;
143 if (options.search != undefined) {
144 this.search = options.search;
146 if (options.remote != undefined && options.remote) {
148 // user defined onInit function
149 if (options.remote.onInit!= undefined && typeof options.remote.onInit === 'function') {
150 this.onInit = options.remote.onInit;
151 this.isInitRemote = true;
153 if (options.remote.onInitSize != undefined) {
154 this.onInitSize = options.remote.onInitSize;
155 if (this.onInitSize < 3) this.onInitSize = 3;
157 // user defined remote search function
158 if (options.remote.onSearch != undefined && typeof options.remote.onSearch === 'function') {
159 this.onSearch = options.remote.onSearch;
160 this.isSearchRemote = true;
164 if (options.stayOpen != undefined) {
165 this.userOptions.stayOpen = options.stayOpen;
168 if (options.disableSelectAll != undefined) {
169 this.userOptions.disableSelectAll = options.disableSelectAll;
172 if (options.maxSelect != undefined && !isNaN(options.maxSelect) && options.maxSelect >= 1) {
173 this.maxSelect = options.maxSelect;
174 this.userOptions.disableSelectAll = true;
177 if (options.maxOptionWidth != undefined && !isNaN(options.maxOptionWidth) && options.maxOptionWidth >= 20) {
178 this.maxOptionWidth = options.maxOptionWidth;
179 this.ulminWidth = options.maxOptionWidth + 60;
180 this.ulmaxWidth = options.maxOptionWidth + 60;
183 if(options.keepInlineStyles != undefined ) {
184 this.keepInlineStyles = options.keepInlineStyles;
186 if(options.keepInlineCaretStyles != undefined ) {
187 this.keepInlineCaretStyles = options.keepInlineCaretStyles;
192 this.closeOrder = function () {
194 if (!self.userOptions.stayOpen) {
195 self.drop.style.visibility = "hidden";
197 self.inputBox.value = "";
198 Array.prototype.slice.call(self.listElements).forEach(function (x) {
199 x.classList.remove("hide");
205 this.getCssArray = function (selector) {
206 // Why inline css ? To protect the button display from foreign css files
208 if (selector === ".vsb-main button") {
210 { "key": "min-width", "value": "120px" },
211 { "key": "border-radius", "value": "0" },
212 { "key": "width", "value": "100%" },
213 { "key": "text-align", "value": "left" },
214 { "key": "z-index", "value": "1" },
215 { "key": "color", "value": "#333" },
216 { "key": "background", "value": "white !important" },
217 { "key": "border", "value": "1px solid #999 !important" },
218 { "key": "line-height", "value": "20px" },
219 { "key": "font-size", "value": "14px" },
220 { "key": "padding", "value": "6px 12px" }
224 return cssArrayToString(cssArray);
226 function cssArrayToString(cssList) {
228 cssList.forEach(function (x) {
229 list += x.key + ":" + x.value + ";";
235 this.init = function () {
237 if (self.isInitRemote) {
238 self.onInit("",self.onInitSize)
239 .then(function (data) {
240 self.buildSelect(data);
248 this.getResult = function () {
251 let collection = self.root.querySelectorAll("option");
252 collection.forEach(function (x) {
254 result.push(x.value);
260 this.createTree = function () {
262 this.rootToken = self.domSelector.replace(/[^A-Za-z0-9]+/, "")
263 this.root.style.display = "none";
264 let already = document.getElementById("btn-group-" + this.rootToken);
268 this.main = document.createElement("div");
269 this.root.parentNode.insertBefore(this.main, this.root.nextSibling);
270 this.main.classList.add("vsb-main");
271 this.main.setAttribute("id", "btn-group-" + this.rootToken);
272 this.main.style.marginLeft = this.main.style.marginLeft;
273 if (self.userOptions.stayOpen) {
274 this.main.style.minHeight = (this.userOptions.maxHeight + 10) + "px";
277 if (self.userOptions.stayOpen) {
278 this.button = document.createElement("div");
280 this.button = document.createElement("button");
281 if(this.keepInlineStyles) {
282 var cssList = self.getCssArray(".vsb-main button");
283 this.button.setAttribute("style", cssList);
286 this.button.style.maxWidth = this.userOptions.maxWidth + "px";
287 if (this.userOptions.minWidth !== -1) {
288 this.button.style.minWidth = this.userOptions.minWidth + "px";
291 this.main.appendChild(this.button);
292 this.title = document.createElement("span");
293 this.button.appendChild(this.title);
294 this.title.classList.add("title");
295 let caret = document.createElement("span");
296 this.button.appendChild(caret);
298 caret.classList.add("caret");
299 if(this.keepInlineCaretStyles) {
300 caret.style.position = "absolute";
301 caret.style.right = "8px";
302 caret.style.marginTop = "8px";
305 if (self.userOptions.stayOpen) {
306 caret.style.display = "none";
307 this.title.style.paddingLeft = "20px";
308 this.title.style.fontStyle = "italic";
309 this.title.style.verticalAlign = "20%";
312 this.drop = document.createElement("div");
313 this.main.appendChild(this.drop);
314 this.drop.classList.add("vsb-menu");
315 this.drop.style.zIndex = 2000 - this.instanceOffset;
316 this.ul = document.createElement("ul");
317 this.drop.appendChild(this.ul);
319 this.ul.style.maxHeight = this.userOptions.maxHeight + "px";
320 this.ul.style.minWidth = this.ulminWidth + "px";
321 this.ul.style.maxWidth = this.ulmaxWidth + "px";
322 this.ul.style.minHeight = this.ulminHeight + "px";
323 if (this.isMultiple) {
324 this.ul.classList.add("multi");
325 if (!self.userOptions.disableSelectAll) {
326 let selectAll = document.createElement("option");
327 selectAll.setAttribute("value", 'all');
328 selectAll.innerText = self.userOptions.translations.selectAll;
329 this.root.insertBefore(selectAll, (this.root.hasChildNodes())
330 ? this.root.childNodes[0]
334 let selectedTexts = ""
339 this.searchZone = document.createElement("div");
340 this.ul.appendChild(this.searchZone);
341 this.searchZone.classList.add("vsb-js-search-zone");
342 this.searchZone.style.zIndex = 2001 - this.instanceOffset;
343 this.inputBox = document.createElement("input");
344 this.searchZone.appendChild(this.inputBox);
345 this.inputBox.setAttribute("type", "text");
346 this.inputBox.setAttribute("id", "search_" + this.rootToken);
347 if (this.maxOptionWidth < Infinity) {
348 this.searchZone.style.maxWidth = self.maxOptionWidth + 30 + "px";
349 this.inputBox.style.maxWidth = self.maxOptionWidth + 30 + "px";
352 var para = document.createElement("p");
353 this.ul.appendChild(para);
354 para.style.fontSize = "12px";
355 para.innerHTML = " ";
356 this.ul.addEventListener("scroll", function (e) {
357 var y = this.scrollTop;
358 self.searchZone.parentNode.style.top = y + "px";
362 this.options = document.querySelectorAll(this.domSelector + " > option");
363 Array.prototype.slice.call(this.options).forEach(function (x) {
364 let text = x.textContent;
367 if (x.hasAttributes()) {
368 originalAttrs = Array.prototype.slice.call(x.attributes)
369 .filter(function (a) {
370 return self.forbidenAttributes.indexOf(a.name) === -1
373 let classes = x.getAttribute("class");
377 .filter(function (c) {
378 return self.forbidenClasses.indexOf(c) === -1
383 let li = document.createElement("li");
384 let isSelected = x.hasAttribute("selected");
385 let isDisabled = x.hasAttribute("disabled");
387 self.ul.appendChild(li);
388 li.setAttribute("data-value", value);
389 li.setAttribute("data-text", text);
391 if (originalAttrs !== undefined) {
392 originalAttrs.forEach(function (a) {
393 li.setAttribute(a.name, a.value);
397 classes.forEach(function (x) {
401 if (self.maxOptionWidth < Infinity) {
402 li.classList.add("short");
403 li.style.maxWidth = self.maxOptionWidth + "px";
408 selectedTexts += sep + text;
409 sep = self.userOptions.buttonItemsSeparator;
410 li.classList.add("active");
411 if (!self.isMultiple) {
412 self.title.textContent = text;
413 if (classes.length != 0) {
414 classes.forEach(function (x) {
415 self.title.classList.add(x);
421 li.classList.add("disabled");
423 li.appendChild(document.createTextNode(" " + text));
426 if (document.querySelector(self.domSelector + ' optgroup') !== null) {
427 self.isOptgroups = true;
428 self.options = document.querySelectorAll(self.domSelector + " option");
429 let groups = document.querySelectorAll(self.domSelector + ' optgroup');
430 Array.prototype.slice.call(groups).forEach(function (group) {
431 let groupOptions = group.querySelectorAll('option');
432 let li = document.createElement("li");
433 let span = document.createElement("span");
434 let iCheck = document.createElement("i");
435 let labelElement = document.createElement("b");
436 let dataWay = group.getAttribute("data-way");
437 if (!dataWay) dataWay = "closed";
438 if (!dataWay || (dataWay !== "closed" && dataWay !== "open")) dataWay = "closed";
439 li.appendChild(span);
440 li.appendChild(iCheck);
441 self.ul.appendChild(li);
442 li.classList.add('grouped-option');
443 li.classList.add(dataWay);
444 self.currentOptgroup++;
445 let optId = self.rootToken + "-opt-" + self.currentOptgroup;
447 li.appendChild(labelElement);
448 labelElement.appendChild(document.createTextNode(group.label));
449 li.setAttribute("data-text", group.label);
450 self.ul.appendChild(li);
452 Array.prototype.slice.call(groupOptions).forEach(function (x) {
453 let text = x.textContent;
455 let classes = x.getAttribute("class");
457 classes = classes.split(" ");
462 classes.push(dataWay);
463 let li = document.createElement("li");
464 let isSelected = x.hasAttribute("selected");
465 self.ul.appendChild(li);
466 li.setAttribute("data-value", value);
467 li.setAttribute("data-text", text);
468 li.setAttribute("data-parent", optId);
469 if (classes.length != 0) {
470 classes.forEach(function (x) {
476 selectedTexts += sep + text;
477 sep = self.userOptions.buttonItemsSeparator;
478 li.classList.add("active");
479 if (!self.isMultiple) {
480 self.title.textContent = text;
481 if (classes.length != 0) {
482 classes.forEach(function (x) {
483 self.title.classList.add(x);
488 li.appendChild(document.createTextNode(text));
493 let optionsLength = self.options.length - Number(!self.userOptions.disableSelectAll);
495 if (optionsLength == nrActives) { // Bastoune idea to preserve the placeholder
496 let wordForAll = self.userOptions.translations.all;
497 selectedTexts = wordForAll;
498 } else if (self.multipleSize != -1) {
499 if (nrActives > self.multipleSize) {
500 let wordForItems = nrActives === 1 ? self.userOptions.translations.item : self.userOptions.translations.items;
501 selectedTexts = nrActives + " " + wordForItems;
504 if (self.isMultiple) {
505 self.title.innerHTML = selectedTexts;
507 if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
508 self.title.textContent = self.userOptions.placeHolder;
510 self.listElements = self.drop.querySelectorAll("li:not(.grouped-option)");
512 self.inputBox.addEventListener("keyup", function (e) {
513 let searchValue = e.target.value.toUpperCase();
514 let searchValueLength = searchValue.length;
517 let selectAll = null;
518 if (self.isSearchRemote) {
519 if (searchValueLength == 0) {
520 self.remoteSearchIntegrate(null);
521 } else if (searchValueLength >= 3) {
522 self.onSearch(searchValue)
523 .then(function (data) {
524 self.remoteSearchIntegrate(data);
528 if (searchValueLength < 3) {
529 Array.prototype.slice.call(self.listElements).forEach(function (x) {
530 if (x.getAttribute('data-value') === 'all') {
533 x.classList.remove("hidden-search");
535 nrChecked += x.classList.contains('active');
539 Array.prototype.slice.call(self.listElements).forEach(function (x) {
540 if (x.getAttribute('data-value') !== 'all') {
541 let text = x.getAttribute("data-text").toUpperCase();
542 if (text.indexOf(searchValue) === -1 && x.getAttribute('data-value') !== 'all') {
543 x.classList.add("hidden-search");
546 x.classList.remove("hidden-search");
547 nrChecked += x.classList.contains('active');
556 selectAll.classList.add('disabled');
558 selectAll.classList.remove('disabled');
560 if (nrChecked !== nrFound) {
561 selectAll.classList.remove("active");
562 selectAll.innerText = self.userOptions.translations.selectAll;
563 selectAll.setAttribute('data-selected', 'false')
565 selectAll.classList.add("active");
566 selectAll.innerText = self.userOptions.translations.clearAll;
567 selectAll.setAttribute('data-selected', 'true')
574 if (self.userOptions.stayOpen) {
575 self.drop.style.visibility = "visible";
576 self.drop.style.boxShadow = "none";
577 self.drop.style.minHeight = (this.userOptions.maxHeight + 10) + "px";
578 self.drop.style.position = "relative";
579 self.drop.style.left = "0px";
580 self.drop.style.top = "0px";
581 self.button.style.border = "none";
583 this.main.addEventListener("click", function (e) {
584 if (self.isDisabled) return;
585 self.drop.style.visibility = "visible";
586 document.addEventListener("click", docListener);
589 if (!self.userOptions.stayOpen) {
590 VSBoxCounter.closeAllButMe(self.instanceOffset);
595 this.drop.addEventListener("click", function (e) {
596 if (self.isDisabled) return;
597 if (e.target.tagName === 'INPUT') return;
598 let isShowHideCommand = e.target.tagName === 'SPAN';
599 let isCheckCommand = e.target.tagName === 'I';
600 let liClicked = e.target.parentElement;
601 if (!liClicked.hasAttribute("data-value")) {
602 if (liClicked.classList.contains("grouped-option")) {
603 if (!isShowHideCommand && !isCheckCommand) return;
604 let oldClass, newClass;
605 if (isCheckCommand) { // check or uncheck children
606 self.checkUncheckFromParent(liClicked);
607 } else { //open or close
608 if (liClicked.classList.contains("open")) {
615 liClicked.classList.remove(oldClass);
616 liClicked.classList.add(newClass);
617 let theChildren = self.drop.querySelectorAll("[data-parent='" + liClicked.id + "']");
618 theChildren.forEach(function (x) {
619 x.classList.remove(oldClass);
620 x.classList.add(newClass);
626 let choiceValue = e.target.getAttribute("data-value");
627 let choiceText = e.target.getAttribute("data-text");
628 let className = e.target.getAttribute("class");
630 if (className && className.indexOf("disabled") != -1) {
634 if (className && className.indexOf("overflow") != -1) {
638 if (choiceValue === 'all') {
639 if (e.target.hasAttribute('data-selected')
640 && e.target.getAttribute('data-selected') === 'true') {
641 self.setValue('none')
643 self.setValue('all');
648 if (!self.isMultiple) {
649 self.root.value = choiceValue;
650 self.title.textContent = choiceText;
652 self.title.setAttribute("class", className + " title");
654 self.title.setAttribute("class", "title");
656 Array.prototype.slice.call(self.listElements).forEach(function (x) {
657 x.classList.remove("active");
659 if (choiceText != "") {
660 e.target.classList.add("active");
662 self.privateSendChange();
663 if (!self.userOptions.stayOpen) {
667 let wasActive = false;
669 wasActive = className.indexOf("active") != -1;
672 e.target.classList.remove("active");
674 e.target.classList.add("active");
676 if (e.target.hasAttribute("data-parent")) {
677 self.checkUncheckFromChild(e.target);
680 let selectedTexts = ""
684 for (let i = 0; i < self.options.length; i++) {
686 if (self.options[i].value == choiceValue) {
687 self.options[i].selected = !wasActive;
689 if (self.options[i].selected) {
691 selectedTexts += sep + self.options[i].textContent;
692 sep = self.userOptions.buttonItemsSeparator;
695 if (nrAll == nrActives - Number(!self.userOptions.disableSelectAll)) {
696 let wordForAll = self.userOptions.translations.all;
697 selectedTexts = wordForAll;
698 } else if (self.multipleSize != -1) {
699 if (nrActives > self.multipleSize) {
700 let wordForItems = nrActives === 1 ? self.userOptions.translations.item : self.userOptions.translations.items;
701 selectedTexts = nrActives + " " + wordForItems;
704 self.title.textContent = selectedTexts;
705 self.checkSelectMax(nrActives);
706 self.checkUncheckAll();
707 self.privateSendChange();
711 if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
712 self.title.textContent = self.userOptions.placeHolder;
715 function docListener() {
716 document.removeEventListener("click", docListener);
717 self.drop.style.visibility = "hidden";
719 self.inputBox.value = "";
720 Array.prototype.slice.call(self.listElements).forEach(function (x) {
721 x.classList.remove("hidden-search");
727 this.checkUncheckAll();
730 vanillaSelectBox.prototype.buildSelect = function (data) {
732 if(data == null || data.length < 1) return;
733 if(!self.isOptgroups){
734 self.isOptgroups = data[0].parent != undefined && data[0].parent != "";
737 if(self.isOptgroups){
739 data = data.filter(function(x){
740 return x.parent != undefined && x.parent != "";
743 data.forEach(function (x) {
744 if(!groups[x.parent]){
745 groups[x.parent] = true;
748 for (let group in groups) {
749 let anOptgroup = document.createElement("optgroup");
750 anOptgroup.setAttribute("label", group);
752 options = data.filter(function(x){
753 return x.parent == group;
755 options.forEach(function (x) {
756 let anOption = document.createElement("option");
757 anOption.value = x.value;
758 anOption.text = x.text;
760 anOption.setAttribute("selected",true)
762 anOptgroup.appendChild(anOption);
764 self.root.appendChild(anOptgroup);
767 data.forEach(function (x) {
768 let anOption = document.createElement("option");
769 anOption.value = x.value;
770 anOption.text = x.text;
772 anOption.setAttribute("selected",true)
774 self.root.appendChild(anOption);
779 vanillaSelectBox.prototype.remoteSearchIntegrate = function (data) {
782 if (data == null || data.length == 0) {
783 let dataChecked = self.optionsCheckedToData();
785 data = dataChecked.slice(0);
786 self.remoteSearchIntegrateIt(data);
788 let dataChecked = self.optionsCheckedToData();
789 if (dataChecked.length > 0){
790 for (var i = data.length - 1; i >= 0; i--) {
791 if(dataChecked.indexOf(data[i].id) !=-1){
796 data = data.concat(dataChecked);
798 self.remoteSearchIntegrateIt(data);
802 vanillaSelectBox.prototype.optionsCheckedToData = function () {
804 let dataChecked = [];
805 let treeOptions = self.ul.querySelectorAll("li.active:not(.grouped-option)");
806 let keepParents = {};
808 Array.prototype.slice.call(treeOptions).forEach(function (x) {
809 let oneData = {"value":x.getAttribute("data-value"),"text":x.getAttribute("data-text"),"selected":true};
810 if(oneData.value !== "all"){
811 if(self.isOptgroups){
812 let parentId = x.getAttribute("data-parent");
813 if(keepParents[parentId]!=undefined){
814 oneData.parent = keepParents[parentId];
816 let parentPtr = self.ul.querySelector("#"+parentId);
817 let parentName = parentPtr.getAttribute("data-text");
818 keepParents[parentId] = parentName;
819 oneData.parent = parentName;
822 dataChecked.push(oneData);
829 vanillaSelectBox.prototype.removeOptionsNotChecked = function (data) {
831 let minimumSize = self.onInitSize;
832 let newSearchSize = data == null ? 0 : data.length;
833 let presentSize = self.root.length;
834 if (presentSize + newSearchSize > minimumSize) {
835 let maxToRemove = presentSize + newSearchSize - minimumSize - 1;
837 for (var i = self.root.length - 1; i >= 0; i--) {
838 if (self.root.options[i].selected == false) {
839 if (removed <= maxToRemove) {
848 vanillaSelectBox.prototype.changeTree = function (data, options) {
851 self.remoteSearchIntegrateIt(data);
852 if (options && options.onSearch) {
853 if (typeof options.onSearch === 'function') {
854 self.onSearch = options.onSearch;
855 self.isSearchRemote = true;
858 self.listElements = this.drop.querySelectorAll("li:not(.grouped-option)");
861 vanillaSelectBox.prototype.remoteSearchIntegrateIt = function (data) {
863 if (data == null || data.length == 0) return;
864 while(self.root.firstChild)
865 self.root.removeChild(self.root.firstChild);
867 self.buildSelect(data);
871 vanillaSelectBox.prototype.reloadTree = function () {
873 let lis = self.ul.querySelectorAll("li");
875 for (var i = lis.length - 1; i >= 0; i--) {
876 if (lis[i].getAttribute('data-value') !== 'all') {
877 self.ul.removeChild(lis[i]);
882 let selectedTexts = ""
887 if (self.isOptgroups) {
888 if (document.querySelector(self.domSelector + ' optgroup') !== null) {
889 self.options = document.querySelectorAll(this.domSelector + " option");
890 let groups = document.querySelectorAll(this.domSelector + ' optgroup');
891 Array.prototype.slice.call(groups).forEach(function (group) {
892 let groupOptions = group.querySelectorAll('option');
893 let li = document.createElement("li");
894 let span = document.createElement("span");
895 let iCheck = document.createElement("i");
896 let labelElement = document.createElement("b");
897 let dataWay = group.getAttribute("data-way");
898 if (!dataWay) dataWay = "closed";
899 if (!dataWay || (dataWay !== "closed" && dataWay !== "open")) dataWay = "closed";
900 li.appendChild(span);
901 li.appendChild(iCheck);
902 self.ul.appendChild(li);
903 li.classList.add('grouped-option');
904 li.classList.add(dataWay);
905 self.currentOptgroup++;
906 let optId = self.rootToken + "-opt-" + self.currentOptgroup;
908 li.appendChild(labelElement);
909 labelElement.appendChild(document.createTextNode(group.label));
910 li.setAttribute("data-text", group.label);
911 self.ul.appendChild(li);
913 Array.prototype.slice.call(groupOptions).forEach(function (x) {
914 let text = x.textContent;
916 let classes = x.getAttribute("class");
918 classes = classes.split(" ");
923 classes.push(dataWay);
924 let li = document.createElement("li");
925 let isSelected = x.hasAttribute("selected");
926 self.ul.appendChild(li);
927 li.setAttribute("data-value", value);
928 li.setAttribute("data-text", text);
929 li.setAttribute("data-parent", optId);
930 if (classes.length != 0) {
931 classes.forEach(function (x) {
937 selectedTexts += sep + text;
938 sep = self.userOptions.buttonItemsSeparator;
939 li.classList.add("active");
940 if (!self.isMultiple) {
941 self.title.textContent = text;
942 if (classes.length != 0) {
943 classes.forEach(function (x) {
944 self.title.classList.add(x);
949 li.appendChild(document.createTextNode(text));
953 self.listElements = this.drop.querySelectorAll("li:not(.grouped-option)");
955 self.options = self.root.querySelectorAll("option");
956 Array.prototype.slice.call(self.options).forEach(function (x) {
957 let text = x.textContent;
959 if (value != "all") {
961 if (x.hasAttributes()) {
962 originalAttrs = Array.prototype.slice.call(x.attributes)
963 .filter(function (a) {
964 return self.forbidenAttributes.indexOf(a.name) === -1
967 let classes = x.getAttribute("class");
971 .filter(function (c) {
972 return self.forbidenClasses.indexOf(c) === -1
977 let li = document.createElement("li");
978 let isSelected = x.hasAttribute("selected");
980 let isDisabled = x.disabled;
982 self.ul.appendChild(li);
983 li.setAttribute("data-value", value);
984 li.setAttribute("data-text", text);
986 if (originalAttrs !== undefined) {
987 originalAttrs.forEach(function (a) {
988 li.setAttribute(a.name, a.value);
992 classes.forEach(function (x) {
996 if (self.maxOptionWidth < Infinity) {
997 li.classList.add("short");
998 li.style.maxWidth = self.maxOptionWidth + "px";
1003 selectedTexts += sep + text;
1004 sep = self.userOptions.buttonItemsSeparator;
1005 li.classList.add("active");
1006 if (!self.isMultiple) {
1007 self.title.textContent = text;
1008 if (classes.length != 0) {
1009 classes.forEach(function (x) {
1010 self.title.classList.add(x);
1016 li.classList.add("disabled");
1018 li.appendChild(document.createTextNode(" " + text));
1025 vanillaSelectBox.prototype.disableItems = function (values) {
1027 let foundValues = [];
1028 if (vanillaSelectBox_type(values) == "string") {
1029 values = values.split(",");
1032 if (vanillaSelectBox_type(values) == "array") {
1033 Array.prototype.slice.call(self.options).forEach(function (x) {
1034 if (values.indexOf(x.value) != -1) {
1035 foundValues.push(x.value);
1036 x.setAttribute("disabled", "");
1040 Array.prototype.slice.call(self.listElements).forEach(function (x) {
1041 let val = x.getAttribute("data-value");
1042 if (foundValues.indexOf(val) != -1) {
1043 x.classList.add("disabled");
1048 vanillaSelectBox.prototype.enableItems = function (values) {
1050 let foundValues = [];
1051 if (vanillaSelectBox_type(values) == "string") {
1052 values = values.split(",");
1055 if (vanillaSelectBox_type(values) == "array") {
1056 Array.prototype.slice.call(self.options).forEach(function (x) {
1057 if (values.indexOf(x.value) != -1) {
1058 foundValues.push(x.value);
1059 x.removeAttribute("disabled");
1064 Array.prototype.slice.call(self.listElements).forEach(function (x) {
1065 if (foundValues.indexOf(x.getAttribute("data-value")) != -1) {
1066 x.classList.remove("disabled");
1071 vanillaSelectBox.prototype.checkSelectMax = function (nrActives) {
1073 if (self.maxSelect == Infinity || !self.isMultiple) return;
1074 if (self.maxSelect <= nrActives) {
1075 Array.prototype.slice.call(self.listElements).forEach(function (x) {
1076 if (x.hasAttribute('data-value')) {
1077 if (!x.classList.contains('disabled') && !x.classList.contains('active')) {
1078 x.classList.add("overflow");
1083 Array.prototype.slice.call(self.listElements).forEach(function (x) {
1084 if (x.classList.contains('overflow')) {
1085 x.classList.remove("overflow");
1091 vanillaSelectBox.prototype.checkUncheckFromChild = function (liClicked) {
1093 let parentId = liClicked.getAttribute('data-parent');
1094 let parentLi = document.getElementById(parentId);
1095 if (!self.isMultiple) return;
1096 let listElements = self.drop.querySelectorAll("li");
1097 let childrenElements = Array.prototype.slice.call(listElements).filter(function (el) {
1098 return el.hasAttribute("data-parent") && el.getAttribute('data-parent') == parentId && !el.classList.contains('hidden-search') ;
1101 let nrCheckable = childrenElements.length;
1102 if (nrCheckable == 0) return;
1103 childrenElements.forEach(function (el) {
1104 if (el.classList.contains('active')) nrChecked++;
1106 if (nrChecked === nrCheckable || nrChecked === 0) {
1107 if (nrChecked === 0) {
1108 parentLi.classList.remove("checked");
1110 parentLi.classList.add("checked");
1113 parentLi.classList.remove("checked");
1117 vanillaSelectBox.prototype.checkUncheckFromParent = function (liClicked) {
1119 let parentId = liClicked.id;
1120 if (!self.isMultiple) return;
1121 let listElements = self.drop.querySelectorAll("li");
1122 let childrenElements = Array.prototype.slice.call(listElements).filter(function (el) {
1123 return el.hasAttribute("data-parent") && el.getAttribute('data-parent') == parentId && !el.classList.contains('hidden-search');
1126 let nrCheckable = childrenElements.length;
1127 if (nrCheckable == 0) return;
1128 childrenElements.forEach(function (el) {
1129 if (el.classList.contains('active')) nrChecked++;
1131 if (nrChecked === nrCheckable || nrChecked === 0) {
1132 //check all or uncheckAll : just do the opposite
1133 childrenElements.forEach(function (el) {
1134 var event = document.createEvent('HTMLEvents');
1135 event.initEvent('click', true, false);
1136 el.dispatchEvent(event);
1138 if (nrChecked === 0) {
1139 liClicked.classList.add("checked");
1141 liClicked.classList.remove("checked");
1145 liClicked.classList.remove("checked");
1146 childrenElements.forEach(function (el) {
1147 if (!el.classList.contains('active')) {
1148 var event = document.createEvent('HTMLEvents');
1149 event.initEvent('click', true, false);
1150 el.dispatchEvent(event);
1156 vanillaSelectBox.prototype.checkUncheckAll = function () {
1158 if (!self.isMultiple) return;
1160 let nrCheckable = 0;
1161 let totalAvailableElements = 0;
1162 let checkAllElement = null;
1163 if (self.listElements == null) return;
1164 Array.prototype.slice.call(self.listElements).forEach(function (x) {
1165 if (x.hasAttribute('data-value')) {
1166 if (x.getAttribute('data-value') === 'all') {
1167 checkAllElement = x;
1169 if (x.getAttribute('data-value') !== 'all'
1170 && !x.classList.contains('hidden-search')
1171 && !x.classList.contains('disabled')) {
1173 nrChecked += x.classList.contains('active');
1175 if (x.getAttribute('data-value') !== 'all'
1176 && !x.classList.contains('disabled')) {
1177 totalAvailableElements++;
1182 if (checkAllElement) {
1183 if (nrChecked === nrCheckable) {
1184 // check the checkAll checkbox
1185 if (nrChecked === totalAvailableElements) {
1186 self.title.textContent = self.userOptions.translations.all;
1188 checkAllElement.classList.add("active");
1189 checkAllElement.innerText = self.userOptions.translations.clearAll;
1190 checkAllElement.setAttribute('data-selected', 'true')
1191 } else if (nrChecked === 0) {
1192 // uncheck the checkAll checkbox
1193 self.title.textContent = self.userOptions.placeHolder;
1194 checkAllElement.classList.remove("active");
1195 checkAllElement.innerText = self.userOptions.translations.selectAll;
1196 checkAllElement.setAttribute('data-selected', 'false')
1201 vanillaSelectBox.prototype.setValue = function (values) {
1203 let listElements = self.drop.querySelectorAll("li");
1205 if (values == null || values == undefined || values == "") {
1208 if (self.isMultiple) {
1209 if (vanillaSelectBox_type(values) == "string") {
1210 if (values === "all") {
1212 Array.prototype.slice.call(listElements).forEach(function (x) {
1213 if (x.hasAttribute('data-value')) {
1214 let value = x.getAttribute('data-value');
1215 if (value !== 'all') {
1216 if (!x.classList.contains('hidden-search') && !x.classList.contains('disabled')) {
1217 values.push(x.getAttribute('data-value'));
1219 // already checked (but hidden by search)
1220 if (x.classList.contains('active')) {
1221 if (x.classList.contains('hidden-search') || x.classList.contains('disabled')) {
1226 x.classList.add("active");
1228 } else if (x.classList.contains('grouped-option')) {
1229 x.classList.add("checked");
1232 } else if (values === "none") {
1234 Array.prototype.slice.call(listElements).forEach(function (x) {
1235 if (x.hasAttribute('data-value')) {
1236 let value = x.getAttribute('data-value');
1237 if (value !== 'all') {
1238 if (x.classList.contains('active')) {
1239 if (x.classList.contains('hidden-search') || x.classList.contains('disabled')) {
1244 } else if (x.classList.contains('grouped-option')) {
1245 x.classList.remove("checked");
1249 values = values.split(",");
1252 let foundValues = [];
1253 if (vanillaSelectBox_type(values) == "array") {
1254 Array.prototype.slice.call(self.options).forEach(function (x) {
1255 if (values.indexOf(x.value) !== -1) {
1257 foundValues.push(x.value);
1262 let selectedTexts = ""
1266 Array.prototype.slice.call(listElements).forEach(function (x) {
1267 if (x.value !== 'all') {
1270 if (foundValues.indexOf(x.getAttribute("data-value")) != -1) {
1271 x.classList.add("active");
1273 selectedTexts += sep + x.getAttribute("data-text");
1274 sep = self.userOptions.buttonItemsSeparator;
1276 x.classList.remove("active");
1279 if (nrAll == nrActives - Number(!self.userOptions.disableSelectAll)) {
1280 let wordForAll = self.userOptions.translations.all;
1281 selectedTexts = wordForAll;
1282 } else if (self.multipleSize != -1) {
1283 if (nrActives > self.multipleSize) {
1284 let wordForItems = nrActives === 1 ? self.userOptions.translations.item : self.userOptions.translations.items;
1285 selectedTexts = nrActives + " " + wordForItems;
1288 self.title.textContent = selectedTexts;
1289 self.privateSendChange();
1291 self.checkUncheckAll();
1296 Array.prototype.slice.call(listElements).forEach(function (x) {
1297 let liVal = x.getAttribute("data-value");
1298 if (liVal == values) {
1299 x.classList.add("active");
1301 text = x.getAttribute("data-text")
1303 x.classList.remove("active");
1306 Array.prototype.slice.call(self.options).forEach(function (x) {
1307 if (x.value == values) {
1309 className = x.getAttribute("class");
1310 if (!className) className = "";
1316 self.title.textContent = text;
1317 if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
1318 self.title.textContent = self.userOptions.placeHolder;
1320 if (className != "") {
1321 self.title.setAttribute("class", className + " title");
1323 self.title.setAttribute("class", "title");
1330 vanillaSelectBox.prototype.privateSendChange = function () {
1331 let event = document.createEvent('HTMLEvents');
1332 event.initEvent('change', true, false);
1333 this.root.dispatchEvent(event);
1336 vanillaSelectBox.prototype.empty = function () {
1337 Array.prototype.slice.call(this.listElements).forEach(function (x) {
1338 x.classList.remove("active");
1340 let parentElements = this.drop.querySelectorAll("li.grouped-option");
1342 Array.prototype.slice.call(parentElements).forEach(function (x) {
1343 x.classList.remove("checked");
1346 Array.prototype.slice.call(this.options).forEach(function (x) {
1349 this.title.textContent = "";
1350 if (this.userOptions.placeHolder != "" && this.title.textContent == "") {
1351 this.title.textContent = this.userOptions.placeHolder;
1353 this.checkUncheckAll();
1354 this.privateSendChange();
1357 vanillaSelectBox.prototype.destroy = function () {
1358 let already = document.getElementById("btn-group-" + this.rootToken);
1360 VSBoxCounter.remove(this.instanceOffset);
1362 this.root.style.display = "inline-block";
1365 vanillaSelectBox.prototype.disable = function () {
1366 this.main.addEventListener("click", function (e) {
1368 e.stopPropagation();
1370 let already = document.getElementById("btn-group-" + this.rootToken);
1372 button = already.querySelector("button")
1373 if (button) button.classList.add("disabled");
1374 this.isDisabled = true;
1377 vanillaSelectBox.prototype.enable = function () {
1378 let already = document.getElementById("btn-group-" + this.rootToken);
1380 button = already.querySelector("button")
1381 if (button) button.classList.remove("disabled");
1382 this.isDisabled = false;
1386 vanillaSelectBox.prototype.showOptions = function () {
1387 console.log(this.userOptions);
1390 if (!('remove' in Element.prototype)) {
1391 Element.prototype.remove = function () {
1392 if (this.parentNode) {
1393 this.parentNode.removeChild(this);
1398 function vanillaSelectBox_type(target) {
1399 const computedType = Object.prototype.toString.call(target);
1400 const stripped = computedType.replace("[object ", "").replace("]", "");
1401 const lowercased = stripped.toLowerCase();