UNPKG

irisrad-ui

Version:

UI elements developered for IRIS R&D Group Inc

272 lines (249 loc) 8.56 kB
let debounceTimeout; let searchTerm = ""; let onListBlurred; let onKeyDown; let onLabelClicked; let onListItemClicked; export default class MultiSelect { constructor(element) { this.multiSelectWrapper = element; this.values = element.values || []; this.onChange = element.onChange; this.label = element.querySelectorAll(".iris-multi-select-text-area")[0]; this.list = element.querySelector(".iris-custom-select-list"); this.optionObjects = getOptionObjects( element.querySelector("[iris-multi-select-list]"), this.values ); this.multiSelectWrapper.tabIndex = 0; this.registerEventListeners(); } toggleList(isShown) { this.list.classList.toggle("show", isShown); } /** * @description if the values array contains the value, remove the value from the array * otherwise, add the value to the array * * @param {String} value value of the option */ toggleValues(value) { const index = this.values.indexOf(value); if (index < 0) { this.values.push(value); } else { this.values.splice(index, 1); } // this.label.value = this.values.join(", "); this.label.scrollTo(0, this.label.scrollHeight); // this.label.innerText = this.values.join(", "); if (this.onChange) { this.onChange(this.values); } const labels = []; this.optionObjects.map((option) => { if (this.values.indexOf(option.value) >= 0) { labels.push(option.label); } }); this.label.innerText = labels.join(", "); } /** * @description loop through all the options within the optionObjects, if the value of the option * matches the one from the passed in option, removed the css class that highlighed the option, * otherwise, add the highlighted css class to it. * * @param {HTMLElement} option the option element that contains the input checkbox */ highLightOption(option) { this.searchedOption = option; const { element, label } = option; element.scrollIntoView({ block: "center" }); this.optionObjects.map((option) => { const { element } = option; const { classList } = element; classList.toggle("searched", option.label === label); }); } /** * @description this method would be called when user presses the "enter" key while an * option is highlighted. * * It toggle the checked state of the option's element's input. Also, the values within the * property values of the class would also be updated. * * @see toggleValues */ toggleSearchedOption() { if (this.searchedOption) { const { value, isChecked, element } = this.searchedOption; element.querySelector("input").checked = !isChecked; this.searchedOption.isChecked = !isChecked; this.toggleValues(value); } } /** * @description this method should be called when the option list component is closed * or when a user click on one of the options on the list. * * While the user act one of the above options, if there was searched option which is * highlighted, the option should be unhighlighted the the referece of the searchedOption * should be removed from the class. * */ clearSearchedOption() { if (this.searchedOption) { const { element } = this.searchedOption; element.classList.remove("searched"); this.searchedOption = undefined; } } /** * @returns index of the searchedOption within the optionObjects array. */ get searchedOptionIndex() { if (this.searchedOption === undefined) { return -1; } else { return this.optionObjects.findIndex( (option) => option.value === this.searchedOption.value ); } } registerEventListeners() { onListBlurred = handleOnBlur.bind(this); onLabelClicked = handleOnLableClick.bind(this); onKeyDown = handleKeyDown.bind(this); onListItemClicked = handleListItemClick.bind(this); this.multiSelectWrapper.addEventListener("blur", onListBlurred); this.multiSelectWrapper.addEventListener("click", onLabelClicked); this.multiSelectWrapper.addEventListener("keydown", onKeyDown); this.optionObjects.map((option) => { option.element.addEventListener("click", onListItemClicked); }); } unRegisterEventListeners() { this.multiSelectWrapper.removeEventListener("blur", onListBlurred); this.multiSelectWrapper.removeEventListener("click", onLabelClicked); this.multiSelectWrapper.removeEventListener("keydown", onKeyDown); this.optionObjects.map((option) => { option.element.removeEventListener("click", onListItemClicked); }); } } /** * @description * set up the blur event listener for the wrapper so that when it is blur, the shown muti select list should be dismissed * set up keydown event listeners, for instance, space, escape to toggle the show or dismiss of the multi select list * with the miti select list is shown, arrow up or arrow down to navigate between options, text string to search and jump * to corresponding option * * set up click event for each options within the optionObjects array so the checkbox within each of them would be toggled * properly and corresponding value could be added or removed from the values array of the multSeelctEle object. * * @param {HTMLElement} multiSelectEle the HTMLElement that contains the wrapper, the toggle * and the multi select list components */ /** * @description given an array of HTMLElement in the shape of * <li> * <input * type="checkbox" * name="brilliance" * value="value" * /> * <span for="brilliance">inner_text_value</span> * </li> * * * return an array of objects in the shape of * { * value: value, * label: inner_text_value, * isChecked: whether_check_box_is_checked, * element: the_HTMLElement_itself, * }; * * so that the access and manipulation of each HTMLElement would * be easier and the code could be more concise. * * @param {Array} options an array of HTMLElements (LI) * @param {Array} values an array of strings * @returns */ function getOptionObjects(options, values) { const elements = options.querySelectorAll("li"); return [...elements].map((element) => { const value = element.querySelector("input").value; const shouldCheck = values.indexOf(value) >= 0; element.querySelector("input").checked = shouldCheck; const label = element.querySelector("span").innerText; return { value, label, isChecked: shouldCheck, element, }; }); } function handleOnBlur() { // alert("blurred"); this.list.classList.remove("show"); } function handleOnLableClick() { this.list.classList.toggle("show"); } function handleKeyDown(event) { switch (event.code.toLowerCase()) { case "space": // open list this.toggleList(true); break; case "escape": // close list this.list.classList.remove("show"); this.clearSearchedOption(); break; case "enter": // check or uncheck currently highlighted option this.toggleSearchedOption(); break; case "arrowup": { const index = this.searchedOptionIndex; if (index - 1 >= 0) { this.highLightOption(this.optionObjects[index - 1]); } break; } case "arrowdown": { const index = this.searchedOptionIndex; if (index + 1 < this.optionObjects.length) { this.highLightOption(this.optionObjects[index + 1]); } break; } default: { clearTimeout(debounceTimeout); searchTerm += event.key; debounceTimeout = setTimeout(() => { searchTerm = ""; }, 500); const searchedOption = this.optionObjects.find((option) => { return option.label.toLowerCase().startsWith(searchTerm); }); if (searchedOption) { this.highLightOption(searchedOption); } } } } function handleListItemClick(event) { event.stopPropagation(); const label = event.target.innerText; const ele = this.optionObjects.find((object) => object.label === label); const { value, isChecked } = ele; this.clearSearchedOption(); this.toggleValues(value); ele.element.querySelector("input").checked = !isChecked; ele.isChecked = !isChecked; }