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.
496 lines
19 KiB
496 lines
19 KiB
/*
|
|
Copyright (C) Philippe Meyer 2019
|
|
Distributed under the MIT License
|
|
vanillaSelectBox : v0.30 : The menu stops moving around on window resize and scroll + z-index in order of creation for multiple instances
|
|
vanillaSelectBox : v0.26 : Corrected bug in stayOpen mode with disable() function
|
|
vanillaSelectBox : v0.25 : New option stayOpen, and the dropbox is no longer a dropbox but a nice multi-select
|
|
previous version : v0.24 : corrected bug affecting options with more than one class
|
|
https://github.com/PhilippeMarcMeyer/vanillaSelectBox
|
|
*/
|
|
|
|
let VSBoxCounter = function () {
|
|
let count = 0;
|
|
return {
|
|
set: function () {
|
|
return ++count;
|
|
}
|
|
};
|
|
}();
|
|
|
|
function vanillaSelectBox(domSelector, options) {
|
|
let self = this;
|
|
this.instanceOffset = VSBoxCounter.set();
|
|
this.domSelector = domSelector;
|
|
this.root = document.querySelector(domSelector)
|
|
this.main;
|
|
this.button;
|
|
this.title;
|
|
this.isMultiple = this.root.hasAttribute("multiple");
|
|
this.multipleSize = this.isMultiple && this.root.hasAttribute("size") ? parseInt(this.root.getAttribute("size")) : -1;
|
|
this.drop;
|
|
this.top;
|
|
this.left;
|
|
this.options;
|
|
this.listElements;
|
|
this.isDisabled = false;
|
|
this.search = false;
|
|
this.searchZone = null;
|
|
this.inputBox = null;
|
|
this.ulminWidth = 200;
|
|
this.ulminHeight = 25;
|
|
this.userOptions = {
|
|
maxWidth: 500,
|
|
maxHeight: 400,
|
|
translations: { "all": "All", "items": "items" },
|
|
search: false,
|
|
placeHolder: "",
|
|
stayOpen:false
|
|
}
|
|
if (options) {
|
|
if (options.maxWidth != undefined) {
|
|
this.userOptions.maxWidth = options.maxWidth;
|
|
}
|
|
if (options.maxHeight != undefined) {
|
|
this.userOptions.maxHeight = options.maxHeight;
|
|
}
|
|
if (options.translations != undefined) {
|
|
this.userOptions.translations = options.translations;
|
|
}
|
|
if (options.placeHolder != undefined) {
|
|
this.userOptions.placeHolder = options.placeHolder;
|
|
}
|
|
if (options.search != undefined) {
|
|
this.search = options.search;
|
|
}
|
|
if (options.stayOpen != undefined) {
|
|
this.userOptions.stayOpen = options.stayOpen;
|
|
}
|
|
}
|
|
this.repositionMenu = function(){
|
|
let rect = self.main.getBoundingClientRect();
|
|
this.drop.style.left = rect.left+"px";
|
|
this.drop.style.top = rect.bottom+"px";
|
|
}
|
|
|
|
this.init = function () {
|
|
let self = this;
|
|
this.root.style.display = "none";
|
|
let already = document.getElementById("btn-group-" + self.domSelector);
|
|
if (already) {
|
|
already.remove();
|
|
}
|
|
this.main = document.createElement("div");
|
|
this.root.parentNode.insertBefore(this.main, this.root.nextSibling);
|
|
this.main.classList.add("vsb-main");
|
|
this.main.setAttribute("id", "btn-group-" + this.domSelector);
|
|
this.main.style.marginLeft = this.main.style.marginLeft;
|
|
if(self.userOptions.stayOpen){
|
|
this.main.style.minHeight = (this.userOptions.maxHeight+10) + "px";
|
|
}
|
|
|
|
let btnTag = self.userOptions.stayOpen ? "div" : "button";
|
|
this.button = document.createElement(btnTag);
|
|
|
|
let presentValue = this.main.value;
|
|
this.main.appendChild(this.button);
|
|
this.title = document.createElement("span");
|
|
this.button.appendChild(this.title);
|
|
this.title.classList.add("title");
|
|
let caret = document.createElement("span");
|
|
this.button.appendChild(caret);
|
|
caret.classList.add("caret");
|
|
caret.style.position = "absolute";
|
|
caret.style.right = "8px";
|
|
caret.style.marginTop = "8px";
|
|
if(self.userOptions.stayOpen){
|
|
caret.style.display = "none";
|
|
this.title.style.paddingLeft = "20px";
|
|
this.title.style.fontStyle = "italic";
|
|
this.title.style.verticalAlign = "20%";
|
|
}
|
|
let rect = this.button.getBoundingClientRect();
|
|
this.top = rect.bottom;
|
|
this.left = rect.left;
|
|
this.drop = document.createElement("div");
|
|
this.main.appendChild(this.drop);
|
|
this.drop.classList.add("vsb-menu");
|
|
this.drop.style.zIndex = 2000 - this.instanceOffset;
|
|
this.ul = document.createElement("ul");
|
|
this.drop.appendChild(this.ul);
|
|
|
|
if(!this.userOptions.stayOpen ){
|
|
window.addEventListener("resize", function (e) {
|
|
self.repositionMenu();
|
|
});
|
|
|
|
window.addEventListener("scroll", function (e) {
|
|
self.repositionMenu();
|
|
});
|
|
}
|
|
|
|
this.ul.style.maxHeight = this.userOptions.maxHeight + "px";
|
|
this.ul.style.minWidth = this.ulminWidth + "px";
|
|
this.ul.style.minHeight = this.ulminHeight + "px";
|
|
if (this.isMultiple) {
|
|
this.ul.classList.add("multi");
|
|
}
|
|
let selectedTexts = ""
|
|
let sep = "";
|
|
let nrActives = 0;
|
|
|
|
if (this.search) {
|
|
this.searchZone = document.createElement("div");
|
|
this.ul.appendChild(this.searchZone);
|
|
this.searchZone.classList.add("vsb-js-search-zone");
|
|
this.searchZone.style.zIndex = 2001 - this.instanceOffset;
|
|
this.inputBox = document.createElement("input");
|
|
this.searchZone.appendChild(this.inputBox);
|
|
this.inputBox.setAttribute("type", "text");
|
|
this.inputBox.setAttribute("id", "search_" + this.domSelector);
|
|
|
|
let fontSizeForP = this.isMultiple ? "8px" : "6px";
|
|
var para = document.createElement("p");
|
|
this.ul.appendChild(para);
|
|
para.style.fontSize = fontSizeForP;
|
|
para.innerHTML = " ";
|
|
this.ul.addEventListener("scroll", function (e) {
|
|
var y = this.scrollTop;
|
|
self.searchZone.parentNode.style.top = y + "px";
|
|
});
|
|
}
|
|
this.options = document.querySelectorAll(this.domSelector + " option");
|
|
Array.prototype.slice.call(this.options).forEach(function (x) {
|
|
let text = x.textContent;
|
|
let value = x.value;
|
|
let classes = x.getAttribute("class");
|
|
if(classes)
|
|
{
|
|
classes=classes.split(" ");
|
|
}
|
|
else
|
|
{
|
|
classes=[];
|
|
}
|
|
let li = document.createElement("li");
|
|
let isSelected = x.hasAttribute("selected");
|
|
self.ul.appendChild(li);
|
|
li.setAttribute("data-value", value);
|
|
li.setAttribute("data-text", text);
|
|
if (classes.length != 0) {
|
|
classes.forEach(function(x){
|
|
li.classList.add(x);
|
|
});
|
|
|
|
}
|
|
if (isSelected) {
|
|
nrActives++;
|
|
selectedTexts += sep + text;
|
|
sep = ",";
|
|
li.classList.add("active");
|
|
if (!self.isMultiple) {
|
|
self.title.textContent = text;
|
|
if (classes.length != 0) {
|
|
classes.forEach(function(x){
|
|
self.title.classList.add(x);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
li.appendChild(document.createTextNode(text));
|
|
});
|
|
if (self.multipleSize != -1) {
|
|
if (nrActives > self.multipleSize) {
|
|
let wordForItems = self.userOptions.translations.items || "items"
|
|
selectedTexts = nrActives + " " + wordForItems;
|
|
}
|
|
}
|
|
if (self.isMultiple) {
|
|
self.title.innerHTML = selectedTexts;
|
|
}
|
|
if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
|
|
self.title.textContent = self.userOptions.placeHolder;
|
|
}
|
|
this.listElements = this.drop.querySelectorAll("li");
|
|
if (self.search) {
|
|
self.inputBox.addEventListener("keyup", function (e) {
|
|
let searchValue = e.target.value.toUpperCase();
|
|
let searchValueLength = searchValue.length;
|
|
if (searchValueLength < 2) {
|
|
Array.prototype.slice.call(self.listElements).forEach(function (x) {
|
|
x.classList.remove("hide");
|
|
});
|
|
} else {
|
|
Array.prototype.slice.call(self.listElements).forEach(function (x) {
|
|
let text = x.getAttribute("data-text").toUpperCase();
|
|
if (text.indexOf(searchValue) == -1) {
|
|
x.classList.add("hide");
|
|
} else {
|
|
x.classList.remove("hide");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if(self.userOptions.stayOpen){
|
|
self.drop.style.display = "block";
|
|
self.drop.style.boxShadow = "none";
|
|
self.drop.style.minHeight = (this.userOptions.maxHeight+10) + "px";
|
|
self.drop.style.position = "relative";
|
|
self.drop.style.left = "0px";
|
|
self.drop.style.top = "0px";
|
|
self.button.style.border = "none";
|
|
}else{
|
|
this.main.addEventListener("click", function (e) {
|
|
if (self.isDisabled) return;
|
|
self.drop.style.left = self.left + "px";
|
|
self.drop.style.top = self.top + "px";
|
|
self.drop.style.display = "block";
|
|
document.addEventListener("click", docListener);
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if(!self.userOptions.stayOpen ){
|
|
self.repositionMenu();
|
|
}
|
|
});
|
|
}
|
|
this.drop.addEventListener("click", function (e) {
|
|
if (self.isDisabled) return;
|
|
if (!e.target.hasAttribute("data-value")) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return;
|
|
}
|
|
let choiceValue = e.target.getAttribute("data-value");
|
|
let choiceText = e.target.getAttribute("data-text");
|
|
let className = e.target.getAttribute("class");
|
|
if (!self.isMultiple) {
|
|
self.root.value = choiceValue;
|
|
self.title.textContent = choiceText;
|
|
if (className) {
|
|
self.title.setAttribute("class", className + " title");
|
|
} else {
|
|
self.title.setAttribute("class", "title");
|
|
}
|
|
Array.prototype.slice.call(self.listElements).forEach(function (x) {
|
|
x.classList.remove("active");
|
|
});
|
|
if (choiceText != "") {
|
|
e.target.classList.add("active");
|
|
}
|
|
self.privateSendChange();
|
|
if(!self.userOptions.stayOpen){
|
|
docListener();
|
|
}
|
|
} else {
|
|
let wasActive = false;
|
|
if (className) {
|
|
wasActive = className.indexOf("active") != -1;
|
|
}
|
|
if (wasActive) {
|
|
e.target.classList.remove("active");
|
|
} else {
|
|
e.target.classList.add("active");
|
|
}
|
|
let selectedTexts = ""
|
|
let sep = "";
|
|
let nrActives = 0;
|
|
let nrAll = 0;
|
|
for (let i = 0; i < self.options.length; i++) {
|
|
nrAll++;
|
|
if (self.options[i].value == choiceValue) {
|
|
self.options[i].selected = !wasActive;
|
|
}
|
|
if (self.options[i].selected) {
|
|
nrActives++;
|
|
selectedTexts += sep + self.options[i].textContent;
|
|
sep = ",";
|
|
}
|
|
}
|
|
if (nrAll == nrActives) {
|
|
let wordForAll = self.userOptions.translations.all || "all";
|
|
selectedTexts = wordForAll;
|
|
} else if (self.multipleSize != -1) {
|
|
if (nrActives > self.multipleSize) {
|
|
let wordForItems = self.userOptions.translations.items || "items"
|
|
selectedTexts = nrActives + " " + wordForItems;
|
|
}
|
|
}
|
|
self.title.textContent = selectedTexts;
|
|
self.privateSendChange();
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
|
|
self.title.textContent = self.userOptions.placeHolder;
|
|
}
|
|
});
|
|
function docListener() {
|
|
document.removeEventListener("click", docListener);
|
|
self.drop.style.display = "none";
|
|
if(self.search){
|
|
self.inputBox.value = "";
|
|
Array.prototype.slice.call(self.listElements).forEach(function (x) {
|
|
x.classList.remove("hide");
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
this.init();
|
|
}
|
|
|
|
|
|
vanillaSelectBox.prototype.setValue = function (values) {
|
|
let self = this;
|
|
if (values == null || values == undefined || values == "") {
|
|
self.empty();
|
|
} else {
|
|
if (self.isMultiple) {
|
|
if (type(values) == "string") {
|
|
if (values == "all") {
|
|
values = [];
|
|
Array.prototype.slice.call(self.options).forEach(function (x) {
|
|
values.push(x.value);
|
|
});
|
|
} else {
|
|
values = values.split(",");
|
|
}
|
|
}
|
|
let foundValues = [];
|
|
if (type(values) == "array") {
|
|
Array.prototype.slice.call(self.options).forEach(function (x) {
|
|
if (values.indexOf(x.value) != -1) {
|
|
x.selected = true;
|
|
foundValues.push(x.value);
|
|
} else {
|
|
x.selected = false;
|
|
}
|
|
});
|
|
let selectedTexts = ""
|
|
let sep = "";
|
|
let nrActives = 0;
|
|
let nrAll = 0;
|
|
Array.prototype.slice.call(self.listElements).forEach(function (x) {
|
|
nrAll++;
|
|
if (foundValues.indexOf(x.getAttribute("data-value")) != -1) {
|
|
x.classList.add("active");
|
|
nrActives++;
|
|
selectedTexts += sep + x.getAttribute("data-text");
|
|
sep = ",";
|
|
} else {
|
|
x.classList.remove("active");
|
|
}
|
|
});
|
|
if (nrAll == nrActives) {
|
|
let wordForAll = self.userOptions.translations.all || "all";
|
|
selectedTexts = wordForAll;
|
|
} else if (self.multipleSize != -1) {
|
|
if (nrActives > self.multipleSize) {
|
|
let wordForItems = self.userOptions.translations.items || "items"
|
|
selectedTexts = nrActives + " " + wordForItems;
|
|
}
|
|
}
|
|
self.title.textContent = selectedTexts;
|
|
self.privateSendChange();
|
|
}
|
|
} else {
|
|
let found = false;
|
|
let text = "";
|
|
let classNames = ""
|
|
Array.prototype.slice.call(self.listElements).forEach(function (x) {
|
|
if (x.getAttribute("data-value") == values) {
|
|
x.classList.add("active");
|
|
found = true;
|
|
text = x.getAttribute("data-text")
|
|
} else {
|
|
x.classList.remove("active");
|
|
}
|
|
});
|
|
Array.prototype.slice.call(self.options).forEach(function (x) {
|
|
if (x.value == values) {
|
|
x.selected = true;
|
|
className = x.getAttribute("class");
|
|
if (!className) className = "";
|
|
} else {
|
|
x.selected = false;
|
|
}
|
|
});
|
|
if (found) {
|
|
self.title.textContent = text;
|
|
if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
|
|
self.title.textContent = self.userOptions.placeHolder;
|
|
}
|
|
if (className != "") {
|
|
self.title.setAttribute("class", className + " title");
|
|
} else {
|
|
self.title.setAttribute("class", "title");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function type(target) {
|
|
const computedType = Object.prototype.toString.call(target);
|
|
const stripped = computedType.replace("[object ", "").replace("]", "");
|
|
const lowercased = stripped.toLowerCase();
|
|
return lowercased;
|
|
}
|
|
}
|
|
|
|
vanillaSelectBox.prototype.privateSendChange = function () {
|
|
let event = document.createEvent('HTMLEvents');
|
|
event.initEvent('change', true, false);
|
|
this.root.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
vanillaSelectBox.prototype.empty = function () {
|
|
Array.prototype.slice.call(this.listElements).forEach(function (x) {
|
|
x.classList.remove("active");
|
|
});
|
|
Array.prototype.slice.call(this.options).forEach(function (x) {
|
|
x.selected = false;
|
|
});
|
|
this.title.textContent = "";
|
|
if (this.userOptions.placeHolder != "" && this.title.textContent == "") {
|
|
this.title.textContent = this.userOptions.placeHolder;
|
|
}
|
|
this.privateSendChange();
|
|
}
|
|
|
|
vanillaSelectBox.prototype.destroy = function () {
|
|
let already = document.getElementById("btn-group-" + this.domSelector);
|
|
if (already) {
|
|
already.remove();
|
|
this.root.style.display = "inline-block";
|
|
}
|
|
}
|
|
vanillaSelectBox.prototype.disable = function () {
|
|
let already = document.getElementById("btn-group-" + this.domSelector);
|
|
if (already) {
|
|
button = already.querySelector("button")
|
|
if(button) button.classList.add("disabled");
|
|
this.isDisabled = true;
|
|
}
|
|
}
|
|
vanillaSelectBox.prototype.enable = function () {
|
|
let already = document.getElementById("btn-group-" + this.domSelector);
|
|
if (already) {
|
|
button = already.querySelector("button")
|
|
if(button) button.classList.remove("disabled");
|
|
this.isDisabled = false;
|
|
}
|
|
}
|
|
|
|
vanillaSelectBox.prototype.showOptions = function(){
|
|
console.log(this.userOptions);
|
|
}
|
|
// Polyfills for IE
|
|
if (!('remove' in Element.prototype)) {
|
|
Element.prototype.remove = function () {
|
|
if (this.parentNode) {
|
|
this.parentNode.removeChild(this);
|
|
}
|
|
};
|
|
}
|