|
|
@@ -0,0 +1,160 @@
|
|
|
+
|
|
|
+
|
|
|
+window.makeMultiselect = (items => {
|
|
|
+ class MultiSelect {
|
|
|
+ #htmlSelectElement;
|
|
|
+ #htmlRootElement;
|
|
|
+ #htmlInput;
|
|
|
+ #htmlInputList;
|
|
|
+ #htmlWrapper;
|
|
|
+ #htmlDropdown;
|
|
|
+ #ignoreMouseOut = false;
|
|
|
+ #options = [];
|
|
|
+ #eventHandlers = [];
|
|
|
+
|
|
|
+ constructor(htmlElement) {
|
|
|
+ this.#htmlSelectElement = htmlElement;
|
|
|
+ this.#htmlRootElement = htmlElement.parentElement;
|
|
|
+ for (let i of this.#htmlSelectElement.querySelectorAll("option"))
|
|
|
+ this.#options.push({
|
|
|
+ value: i.textContent,
|
|
|
+ checked: !!i.selected,
|
|
|
+ indeterminate: !!i.indeterminate,
|
|
|
+ htmlListItem: null
|
|
|
+ });
|
|
|
+ }
|
|
|
+ addEventListener(fncHandler) {
|
|
|
+ this.#eventHandlers.push(fncHandler);
|
|
|
+ }
|
|
|
+ #onUpdate() {
|
|
|
+ for (let i of this.#eventHandlers)
|
|
|
+ i(this.#options);
|
|
|
+ }
|
|
|
+ #updateTextInputValue() {
|
|
|
+ this.#htmlInputList.textContent = "";
|
|
|
+ this.#htmlInputList.appendChild(this.#htmlInput);
|
|
|
+ for (let i in this.#options) {
|
|
|
+ if (this.#options[i].checked) {
|
|
|
+ let li = document.createElement("li");
|
|
|
+ let closeBt = document.createElement("a");
|
|
|
+ closeBt.href = "#";
|
|
|
+ closeBt.textContent = "x";
|
|
|
+ closeBt.addEventListener("click", e => {
|
|
|
+ this.#options[i].checked = false;
|
|
|
+ this.#options[i].indeterminate = false;
|
|
|
+ this.#rebuildDropdownContent();
|
|
|
+ this.#updateTextInputValue();
|
|
|
+ this.#onUpdate();
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+ li.textContent = this.#options[i].value;
|
|
|
+ li.className = "checked";
|
|
|
+ li.appendChild(closeBt);
|
|
|
+ if (this.#options[i].indeterminate)
|
|
|
+ li.classList.add("indeterminate");
|
|
|
+ this.#htmlInputList.insertBefore(li, this.#htmlInput);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #rebuildDropdownContent() {
|
|
|
+ this.#htmlDropdown.textContent = "";
|
|
|
+ for (let i in this.#options) {
|
|
|
+ let wrapper = document.createElement("li");
|
|
|
+ let lbl = document.createElement("label");
|
|
|
+ let chk = document.createElement("input");
|
|
|
+ let span = document.createElement("span");
|
|
|
+ span.textContent = this.#options[i].value;
|
|
|
+ chk.type = "checkbox";
|
|
|
+ chk.checked = !!this.#options[i].checked;
|
|
|
+ chk.indeterminate = !!this.#options[i].indeterminate;
|
|
|
+ chk.index = i;
|
|
|
+ lbl.appendChild(chk);
|
|
|
+ lbl.appendChild(span);
|
|
|
+ wrapper.appendChild(lbl);
|
|
|
+ this.#htmlDropdown.appendChild(wrapper);
|
|
|
+ this.#options[i].htmlListItem = wrapper;
|
|
|
+ chk.addEventListener("change", e => {
|
|
|
+ let state = this.#options[e.currentTarget.index];
|
|
|
+ if (!state.checked) {
|
|
|
+ e.currentTarget.checked = true;
|
|
|
+ e.currentTarget.indeterminate = false;
|
|
|
+ } else if (!state.indeterminate) {
|
|
|
+ e.currentTarget.checked = true;
|
|
|
+ e.currentTarget.indeterminate = true;
|
|
|
+ } else {
|
|
|
+ e.currentTarget.checked = false;
|
|
|
+ e.currentTarget.indeterminate = false;
|
|
|
+ }
|
|
|
+ state.checked = e.currentTarget.checked;
|
|
|
+ state.indeterminate = e.currentTarget.indeterminate;
|
|
|
+ e.stopImmediatePropagation();
|
|
|
+ this.#updateTextInputValue();
|
|
|
+ this.#onUpdate();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ this.#htmlSelectElement.style.display = "none";
|
|
|
+ if (!this.#htmlInput) {
|
|
|
+ this.#htmlInput = document.createElement("input");
|
|
|
+ this.#htmlInput.className = "multiselect-input";
|
|
|
+ this.#htmlInput.type = "text";
|
|
|
+ this.#htmlInput.addEventListener("input", e => {
|
|
|
+ let input = e.currentTarget.value.toLocaleLowerCase();
|
|
|
+ for (let i of this.#options) {
|
|
|
+ if (i.value.toLocaleLowerCase().indexOf(input) >= 0) {
|
|
|
+ i.htmlListItem.classList.remove("hidden");
|
|
|
+ } else {
|
|
|
+ i.htmlListItem.classList.add("hidden");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.#htmlInputList && this.#htmlInputList.appendChild(this.#htmlInput);
|
|
|
+ }
|
|
|
+ if (!this.#htmlInputList) {
|
|
|
+ this.#htmlInputList = document.createElement("ul");
|
|
|
+ this.#htmlInputList.className = "multiselect-inputlist";
|
|
|
+ this.#htmlRootElement && this.#htmlRootElement.appendChild(this.#htmlInputList);
|
|
|
+ this.#htmlInputList.appendChild(this.#htmlInput);
|
|
|
+ }
|
|
|
+ if (!this.#htmlDropdown) {
|
|
|
+ this.#htmlDropdown = document.createElement("ul");
|
|
|
+ this.#htmlDropdown.className = "multiselect-dropdown";
|
|
|
+ this.#htmlDropdown.classList.add("hidden");
|
|
|
+ this.#htmlRootElement && this.#htmlRootElement.appendChild(this.#htmlDropdown);
|
|
|
+ }
|
|
|
+ if (!this.#htmlWrapper) {
|
|
|
+ this.#htmlWrapper = document.createElement("div");
|
|
|
+ this.#htmlWrapper.className = "multiselect-wrapper";
|
|
|
+ this.#htmlWrapper.appendChild(this.#htmlInputList);
|
|
|
+ this.#htmlWrapper.appendChild(this.#htmlDropdown);
|
|
|
+ this.#htmlRootElement.appendChild(this.#htmlWrapper);
|
|
|
+ this.#htmlRootElement.addEventListener("mousedown", e => {
|
|
|
+ this.#ignoreMouseOut = setTimeout(() => { this.#ignoreMouseOut = false; }, 500);
|
|
|
+ });
|
|
|
+ this.#htmlWrapper.addEventListener("focusin", e => {
|
|
|
+ this.#htmlDropdown.classList.remove("hidden");
|
|
|
+ });
|
|
|
+ this.#htmlWrapper.addEventListener("focusout", e => {
|
|
|
+ this.#ignoreMouseOut === false && this.#htmlDropdown.classList.add("hidden");
|
|
|
+ });
|
|
|
+ document.body.addEventListener("click", e => {
|
|
|
+ this.#ignoreMouseOut === false && this.#htmlDropdown.classList.add("hidden");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ this.#rebuildDropdownContent();
|
|
|
+ this.#updateTextInputValue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ _makeMultiSelect = htmlElement => {
|
|
|
+ let select = new MultiSelect(htmlElement);
|
|
|
+ select.render();
|
|
|
+ return select;
|
|
|
+ };
|
|
|
+ if (items instanceof HTMLElement)
|
|
|
+ return _makeMultiSelect(items);
|
|
|
+ else if (items.length)
|
|
|
+ return Array.from(items).map(i => window.makeMultiselect(i));
|
|
|
+});
|
|
|
+
|