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)); });