1const MultiSelectDropdown = (params) => { 2 let config = { 3 search: true, 4 hideX: false, 5 useStyles: true, 6 placeholder: 'Select...', 7 txtSelected: 'Selected', 8 txtAll: 'All', 9 txtRemove: 'Remove', 10 txtSearch: 'Search...', 11 minWidth: '160px', 12 maxWidth: '360px', 13 maxHeight: '180px', 14 borderRadius: 6, 15 ...params 16 }; 17 18 const newElement = (tag, params) => { 19 let element = document.createElement(tag); 20 if (params) { 21 Object.keys(params).forEach((key) => { 22 if (key === 'class') { 23 Array.isArray(params[key]) 24 ? params[key].forEach((o) => (o !== '' ? element.classList.add(o) : 0)) 25 : params[key] !== '' 26 ? element.classList.add(params[key]) 27 : 0; 28 } else if (key === 'style') { 29 Object.keys(params[key]).forEach((value) => { 30 element.style[value] = params[key][value]; 31 }); 32 } else if (key === 'text') { 33 params[key] === '' ? (element.innerHTML = ' ') : (element.innerText = params[key]); 34 } else { 35 element[key] = params[key]; 36 } 37 }); 38 } 39 return element; 40 }; 41 42 document.querySelectorAll('select[multiple]').forEach((multiSelect) => { 43 let div = newElement('div', { class: 'multiselect-dropdown' }); 44 multiSelect.style.display = 'none'; 45 multiSelect.parentNode.insertBefore(div, multiSelect.nextSibling); 46 let dropdownListWrapper = newElement('div', { class: 'multiselect-dropdown-list-wrapper' }); 47 let dropdownList = newElement('div', { class: 'multiselect-dropdown-list' }); 48 let search = newElement('input', { 49 class: ['multiselect-dropdown-search'].concat([config.searchInput?.class ?? 'form-control']), 50 style: { 51 width: '100%', 52 display: config.search ? 'block' : multiSelect.attributes.search?.value === 'true' ? 'block' : 'none' 53 }, 54 placeholder: config.txtSearch 55 }); 56 dropdownListWrapper.appendChild(search); 57 div.appendChild(dropdownListWrapper); 58 dropdownListWrapper.appendChild(dropdownList); 59 60 multiSelect.loadOptions = () => { 61 dropdownList.innerHTML = ''; 62 63 if (config.selectAll || multiSelect.attributes['select-all']?.value === 'true') { 64 let optionElementAll = newElement('div', { class: 'multiselect-dropdown-all-selector' }); 65 let optionCheckbox = newElement('input', { type: 'checkbox' }); 66 optionElementAll.appendChild(optionCheckbox); 67 optionElementAll.appendChild(newElement('label', { text: config.txtAll })); 68 69 optionElementAll.addEventListener('click', () => { 70 optionElementAll.classList.toggle('checked'); 71 optionElementAll.querySelector('input').checked = !optionElementAll.querySelector('input').checked; 72 73 let ch = optionElementAll.querySelector('input').checked; 74 dropdownList.querySelectorAll(':scope > div:not(.multiselect-dropdown-all-selector)').forEach((i) => { 75 if (i.style.display !== 'none') { 76 i.querySelector('input').checked = ch; 77 i.optEl.selected = ch; 78 } 79 }); 80 81 multiSelect.dispatchEvent(new Event('change')); 82 }); 83 optionCheckbox.addEventListener('click', () => { 84 optionCheckbox.checked = !optionCheckbox.checked; 85 }); 86 87 dropdownList.appendChild(optionElementAll); 88 } 89 90 Array.from(multiSelect.options).map((option) => { 91 let optionElement = newElement('div', { class: option.selected ? 'checked' : '', srcElement: option }); 92 let optionCheckbox = newElement('input', { type: 'checkbox', checked: option.selected }); 93 optionElement.appendChild(optionCheckbox); 94 optionElement.appendChild(newElement('label', { text: option.text })); 95 96 optionElement.addEventListener('click', () => { 97 optionElement.classList.toggle('checked'); 98 optionElement.querySelector('input').checked = !optionElement.querySelector('input').checked; 99 optionElement.srcElement.selected = !optionElement.srcElement.selected; 100 multiSelect.dispatchEvent(new Event('change')); 101 }); 102 optionCheckbox.addEventListener('click', () => { 103 optionCheckbox.checked = !optionCheckbox.checked; 104 }); 105 option.optionElement = optionElement; 106 dropdownList.appendChild(optionElement); 107 }); 108 div.dropdownListWrapper = dropdownListWrapper; 109 110 div.refresh = () => { 111 div.querySelectorAll('span.optext, span.placeholder').forEach((placeholder) => div.removeChild(placeholder)); 112 let selected = Array.from(multiSelect.selectedOptions); 113 if (selected.length > (multiSelect.attributes['max-items']?.value ?? 5)) { 114 div.appendChild( 115 newElement('span', { 116 class: ['optext', 'maxselected'], 117 text: selected.length + ' ' + config.txtSelected 118 }) 119 ); 120 } else { 121 selected.map((option) => { 122 let span = newElement('span', {
123 class: 'optext', 124 text: option.text, 125 srcElement: option 126 }); 127 if (!config.hideX) { 128 span.appendChild( 129 newElement('span', { 130 class: 'optdel', 131 text: 'ð', 132 title: config.txtRemove, 133 onclick: (e) => { 134 span.srcElement.optionElement.dispatchEvent(new Event('click')); 135 div.refresh(); 136 e.stopPropagation(); 137 } 138 }) 139 ); 140 } 141 div.appendChild(span); 142 }); 143 } 144 if (multiSelect.selectedOptions?.length === 0) { 145 div.appendChild( 146 newElement('span', { 147 class: 'placeholder', 148 text: multiSelect.attributes?.placeholder?.value ?? config.placeholder 149 }) 150 ); 151 } 152 }; 153 div.refresh(); 154 }; 155 multiSelect.loadOptions(); 156 157 search.addEventListener('input', () => { 158 dropdownList.querySelectorAll(':scope div:not(.multiselect-dropdown-all-selector)').forEach((div) => { 159 let innerText = div.querySelector('label').innerText.toLowerCase(); 160 div.style.display = innerText.includes(search.value.toLowerCase()) ? 'flex' : 'none'; 161 }); 162 }); 163 164 div.addEventListener('click', () => { 165 div.dropdownListWrapper.style.display = 'block'; 166 search.focus(); 167 search.select(); 168 }); 169 170 document.addEventListener('click', (e) => { 171 if (!div.contains(e.target)) { 172 dropdownListWrapper.style.display = 'none'; 173 div.refresh(); 174 } 175 }); 176 }); 177 178 const createStyles = () => { 179 let styles = { 180 ':root': { 181 '--color-background': '#ffffff', 182 '--color-border': '#efeff2', 183 '--color-background--option': '#d6dde6',
184 '--color-background--option--hover': '#cbd5e0a1', 185 '--color-text--normal': '#0c0c0c', 186 '--color-text--grey': '#24262c', 187 '--color-text--red': '#cc6666', 188 '--color-text--placeholder': '#ced4da', 189 '--border-radius--base': `${parseInt(config.borderRadius)}px` || '6px', 190 '--border-radius--small': `${parseInt(config.borderRadius) * 0.75}px` || '4px' 191 }, 192 '.multiselect-dropdown': { 193 position: 'relative', 194 display: 'inline-flex', 195 'flex-wrap': 'wrap', 196 padding: '12px 38px 10px 12px', 197 gap: '6px', 198 'border-radius': 'var(--border-radius--base)', 199 border: 'solid 1px var(--color-border)', 200 background: 'var(--color-background)', 201 'background-image': 202 "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e\")", 203 'background-repeat': 'no-repeat', 204 'background-position': 'right 6px center', 205 'background-size': '16px 12px', 206 'min-width': `${config.minWidth}` ?? '140px', 207 cursor: 'pointer' 208 }, 209 'span.optext, span.placeholder': { 210 display: 'inline-flex', 211 'justify-content': 'center', 212 'align-items': 'center', 213 'font-size': '16px', 214 'border-radius': 'var(--border-radius--small)' 215 }, 216 'span.optext': { 217 'background-color': 'var(--color-background--option)', 218 padding: '0 12px 2px 6px', 219 cursor: 'default', 220 '-webkit-user-select': 'none', 221 '-moz-user-select': 'none', 222 '-ms-user-select': 'none', 223 'user-select': 'none' 224 }, 225 'span.optext .optdel': { 226 float: 'right', 227 margin: '0 -6px 1px 6px', 228 'font-size': '12px', 229 cursor: 'pointer', 230 color: 'var(--color-text--grey)' 231 }, 232 'span.optext .optdel:hover': { 233 color: 'var(--color-text--red)' 234 }, 235 'span.placeholder': { 236 color: 'var(--color-text--placeholder)' 237 }, 238 '.multiselect-dropdown-list-wrapper': { 239 'z-index': 100, 240 'border-radius': 'var(--border-radius--base)', 241 border: 'solid 1px var(--color-border)', 242 display: 'none', 243 margin: '-1px', 244 position: 'absolute', 245 top: 0, 246 left: 0, 247 right: 0, 248 background: 'var(--color-background)' 249 }, 250 '.multiselect-dropdown-search': { 251 padding: '5px 6px 6px 5px', 252 'border-top-left-radius': 'var(--border-radius--base)', 253 'border-top-right-radius': 'var(--border-radius--base)', 254 border: 'solid 1px transparent', 255 'border-bottom': 'solid 1px var(--color-border)', 256 'font-size': 'inherit' 257 }, 258 '.multiselect-dropdown-search::placeholder': { 259 color: 'var(--color-text--placeholder)', 260 'font-size': '16px' 261 }, 262 '.multiselect-dropdown-search:focus, .multiselect-dropdown-search:focus-visible': { 263 outline: 'none' 264 }, 265 '.multiselect-dropdown-list': { 266 'overflow-y': 'auto', 267 'overflow-x': 'hidden', 268 height: '100%', 269 'max-height': `${config.maxHeight}` ?? '160px' 270 }, 271 '.multiselect-dropdown-list::-webkit-scrollbar': { 272 width: '4px' 273 }, 274 '.multiselect-dropdown-list::-webkit-scrollbar-thumb': { 275 'background-color': 'var(--color-background--option)', 276 'border-radius': '1000px' 277 }, 278 '.multiselect-dropdown-list div, .multiselect-dropdown-list div > input, .multiselect-dropdown-list div > label': 279 { 280 cursor: 'pointer', 281 'border-radius': 'var(--border-radius--base)' 282 }, 283 '.multiselect-dropdown-list div': { 284 display: 'flex', 285 'align-items': 'center', 286 'justify-content': 'flex-start', 287 'column-gap': '6px', 288 padding: '6px', 289 margin: '6px 8px 6px 6px', 290 transition: '100ms cubic-bezier(0.455, 0.03, 0.515, 0.955)' 291 }, 292 '.multiselect-dropdown-list div:hover': { 293 'background-color': 'var(--color-background--option--hover)' 294 }, 295 '.multiselect-dropdown-list-input': { 296 height: '14px', 297 width: '14px', 298 border: 'solid 1px var(--color-text--grey)', 299 margin: 0 300 }, 301 '.multiselect-dropdown span.maxselected': { 302 width: '100%' 303 }, 304 '.multiselect-dropdown-all-selector': { 305 'border-bottom': 'solid 1px var(--color-border)' 306 } 307 }; 308 const style = document.createElement('style'); 309 style.setAttribute('type', 'text/css'); 310 style.innerHTML = `${Object.keys(styles) 311 .map( 312 (selector) => 313 `${selector} { ${Object.keys(styles[selector]) 314 .map((property) => `${property}: ${styles[selector][property]}`) 315 .join('; ')} }` 316 ) 317 .join('\n')}`; 318 document.head.appendChild(style); 319 }; 320 321 config.useStyles && createStyles(); 322}; 323 324window.addEventListener('load', () => { 325 MultiSelectDropdown(window.MultiSelectDropdownOptions); 326});
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.