1/* 2Plugin: bs_multiselect 3Author: Tobias Ratzberger (RIS GmbH) 4Written: 09.11.2021 5*/ 6 7 8class Multiselect { 9 constructor() { 10 this.container_outer = null; 11 this.container_select = null; 12 this.container_fakeSelect = null; 13 14 this.container_fakeSelect_ValueOutput = null; 15 this.container_fakeSelect_listWrapper = null; 16 17 this.select_id = null; 18 this.select_options = null; 19 20 this.options = { 21 "labelSuffix": "__ddLabel", 22 "buttonSuffix": "__ddButton", 23 "fakeInputSuffix": "__fakeInput-", 24 texts: { 25 noValueText: "Choose an Option", 26 chosenValues: "You've chosen following options: ", 27 changeValues: "Click to change your selection!" 28 } 29 } 30 31 if (arguments[0] && arguments[0].nodeType === Node.ELEMENT_NODE) { 32 if (arguments[0].querySelectorAll(".bemInput__Dropdown__Select--Hidden").length == 1 && arguments[0].querySelectorAll("select").length == 1) { 33 this.container_outer = arguments[0]; 34 } else { 35 console.error("You passed an invalid Element-Structure"); 36 } 37 } else { 38 console.error("You passed an invalid Argument!"); 39 } 40 } 41 42 init() { 43 this.gatherFurtherProperties(); 44 this.buildFakeStructure(); 45 this.setupListData(); 46 } 47 48 buildFakeStructure__Label() { 49 let fakeSelect_label = this.createElement("label", { 50 "id": this.select_id + this.options.labelSuffix, 51 "for": this.select_id + this.options.buttonSuffix, 52 "class": String(this.container_select.querySelector("label").classList) 53 }); 54 55 fakeSelect_label.innerText = this.select_labelValue; 56 57 return fakeSelect_label; 58 } 59 60 buildFakeStructure__Button() { 61 //Button Element 62 const fakeSelect_button = this.createElement("button", { 63 "id": this.select_id + this.options.buttonSuffix, 64 "type": "button", 65 "data-bs-toggle": "dropdown", 66 "data-bs-auto-close": "outside", 67 "aria-expanded": "false", 68 "class": String(this.container_select.querySelector("select").classList) + " text-start" 69 }); 70 71 //NoValue / Placeholder Element 72 const placeholder = this.createElement("span", { 73 "aria-hidden": "true", 74 "class": "bemInput__Dropdown__Placeholder bemDropdownDisplay--onlyIfNoneSelectedValues" 75 }); 76 placeholder.innerText = this.options.texts.noValueText; 77 78 const selectedWrapper = this.createElement("span", { "class": "bemInput__Dropdown__SelectedValuesWrapper bemDropdownDisplay--onlyIfSelectedValues" }); 79 const selectedChosenTxt = this.createElement("span", { "class": "visually-hidden" }); 80 this.container_fakeSelect_ValueOutput = this.createElement("span", { "class": "bemInput__Dropdown__SelectedValues" }); 81 const selectedChangeTxt = this.createElement("span", { "class": "visually-hidden" }); 82 83 selectedChosenTxt.innerText = this.options.texts.chosenValues; 84 selectedChangeTxt.innerText = this.options.texts.changeValues; 85 86 //Appending Elements 87 selectedWrapper.append(selectedChosenTxt, this.container_fakeSelect_ValueOutput, selectedChangeTxt); 88 fakeSelect_button.append(placeholder, selectedWrapper); 89 return fakeSelect_button; 90 } 91 92 buildFakeStructure__ListWrapper() { 93 return this.createElement("ul", { 94 "aria-labelledby": this.select_id + this.options.labelSuffix, 95 "role": "group", 96 "class": "bemInput__Dropdown__OptionList dropdown-menu" 97 }); 98 } 99 100 buildFakeStructure() { 101 this.container_fakeSelect = this.createElement("div", { 102 class: "bemInput__Dropdown__Select--Fake dropdown" 103 }); 104 105 const label = this.buildFakeStructure__Label(); 106 const button = this.buildFakeStructure__Button(); 107 this.container_fakeSelect_listWrapper = this.buildFakeStructure__ListWrapper(); 108 this.container_fakeSelect.append(label, button, this.container_fakeSelect_listWrapper); 109 110 this.container_outer.append(this.container_fakeSelect); 111 } 112 113 setupListData() { 114 this.select_options.forEach((thisOption, idx) => { 115 if (thisOption.getAttribute("selected") && thisOption.getAttribute("selected").toLowerCase() == "true") { 116 thisOption.selected = true; 117 } 118 119 const optionItem = this.createListOptionItem(thisOption, idx); 120 this.container_fakeSelect_listWrapper.append(optionItem); 121 122 if (thisOption.selected) { 123 this.handleInputChange(thisOption, true); 124 } 125 }); 126 } 127 128 gatherFurtherProperties() { 129 this.container_select = this.container_outer.querySelector(".bemInput__Dropdown__Select--Hidden"); 130 this.select_id = this.container_select.querySelector("select").getAttribute("id"); 131 this.select_labelValue = this.container_select.querySelector("label").innerText; 132 this.select_options = this.container_select.querySelectorAll("select option"); 133 134 const noValueText = this.container_select.getAttribute("data-multiselect-text-noValue"); 135 if (noValueText?.length > 0) { 136 this.options.texts.noValueText = noValueText; 137 this.container_select.removeAttribute("data-multiselect-text-noValue"); 138 } 139 140 const chosenValuesText = this.container_select.getAttribute("data-multiselect-text-chosenValues"); 141 if (chosenValuesText?.length > 0) { 142 this.options.texts.chosenValues = chosenValuesText; 143 this.container_select.removeAttribute("data-multiselect-text-chosenValues"); 144 } 145 146 const changeValuesText = this.container_select.getAttribute("data-multiselect-text-changeValues"); 147 if (changeValuesText?.length > 0) { 148 this.options.texts.changeValues = changeValuesText; 149 this.container_select.removeAttribute("data-multiselect-text-changeValues"); 150 } 151 } 152 153 createListOptionItem(thisOption, idx) { 154 //Creating Elements 155 const option = this.createElement("li", { 156 "class": "bemInput__Dropdown__Option dropdown-item" 157 }); 158 159 const formWrapper = this.createElement("div", { "class": "form-check" }); 160 161 const formInput = this.createElement("input", { 162 "id": this.select_id + this.options.fakeInputSuffix + idx, 163 "type": "checkbox", 164 "name": this.select_id + this.options.fakeInputGroupSuffix, 165 "value": thisOption.value, 166 "class": "form-check-input" 167 }); 168 169 if (thisOption.selected) { 170 formInput.setAttribute("checked", true); 171 } 172 173 const formLabel = this.createElement("label", { 174 "for": this.select_id + this.options.fakeInputSuffix + idx, 175 "class": "form-check-label" 176 }); 177 178 //Inserting Texts 179 formLabel.innerText = thisOption.innerText; 180 181 //Adding EventListener 182 option.addEventListener("click", (event) => { 183 if (event.target != formInput && event.target.tagName != "LABEL") { 184 formInput.click(); 185 } 186 }); 187 188 formInput.addEventListener("change", (event) => this.handleInputChange(event.currentTarget)); 189 190 //Appending Elements 191 formWrapper.append(formInput, formLabel); 192 option.append(formWrapper); 193 return option; 194 } 195 196 handleInputChange(input = null, fromInitFunc) {
197 const selectedOptionsAsString = this.getSelectedOptionsAsString(this.container_fakeSelect_listWrapper); 198 const selectedOptionsAsArray = this.getSelectedOptionValuesAsArray(this.container_fakeSelect_listWrapper); 199 200 const containerHasSelectedValues = this.container_fakeSelect.getAttribute("data-multiselect-selected-values") === "true"; 201 if (selectedOptionsAsString.length > 0 && !containerHasSelectedValues) { 202 this.container_fakeSelect.setAttribute("data-multiselect-selected-values", true); 203 } else if (selectedOptionsAsString.length == 0 && containerHasSelectedValues) { 204 this.container_fakeSelect.setAttribute("data-multiselect-selected-values", false); 205 } 206 207 this.container_fakeSelect_ValueOutput.innerText = selectedOptionsAsString; 208 this.container_fakeSelect.querySelector("button[id*='" + this.options.buttonSuffix + "'").setAttribute("title", selectedOptionsAsString); 209 210 if (!fromInitFunc) { 211 if (input !== null) { 212 this.select_options.forEach(function (thisOption) { 213 thisOption.selected = (selectedOptionsAsArray.includes(thisOption.value)) ? true : false; 214 }); 215 } 216 217 this.container_fakeSelect.addEventListener("hide.bs.dropdown", this.triggerChangeOnSelect.bind(this)); 218 } 219 } 220 221 triggerChangeOnSelect() { 222 this.container_select.querySelector("select").dispatchEvent(new Event("change")); 223 } 224 225 // helper functions 226 createElement(tag, attributes) { 227 const newEl = document.createElement(tag); 228 229 for (let key in attributes) { 230 newEl.setAttribute(key, attributes[key]); 231 } 232 233 return newEl; 234 } 235 236 getSelectedOptionsAsString(element) { 237 let selectedOptionsStr = ""; 238 239 const selectedInputs = element.querySelectorAll("input:checked"); 240 selectedInputs.forEach(function (selectedInput, idx) { 241 const thisVal = selectedInput.parentElement.querySelector("label").innerText; 242 selectedOptionsStr += thisVal; 243 244 if ((idx + 1) < selectedInputs.length) { 245 selectedOptionsStr += ", "; 246 } 247 }); 248 249 return selectedOptionsStr; 250 } 251 252 getSelectedOptionValuesAsArray(element) { 253 let selectedOptionValuesArr = []; 254 255 const selectedInputs = element.querySelectorAll("input:checked"); 256 selectedInputs.forEach(function (selectedInput, idx) { 257 const thisVal = selectedInput.value; 258 selectedOptionValuesArr.push(thisVal); 259 }); 260 261 return selectedOptionValuesArr; 262 } 263}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.