1/* 2 * Created by David Adams 3 * https://codeshack.io/multi-select-dropdown-html-javascript/ 4 * 5 * Released under the MIT license 6 */ 7class MultiSelect { 8 9 constructor(element, options = {}) { 10 let defaults = { 11 placeholder: 'Select item(s)', 12 max: null, 13 search: true, 14 selectAll: true, 15 listAll: true, 16 closeListOnItemSelect: false, 17 name: '', 18 width: '', 19 height: '', 20 dropdownWidth: '', 21 dropdownHeight: '', 22 data: [], 23 onChange: function() {}, 24 onSelect: function() {}, 25 onUnselect: function() {} 26 }; 27 this.options = Object.assign(defaults, options); 28 this.selectElement = typeof element === 'string' ? document.querySelector(element) : element; 29 for(const prop in this.selectElement.dataset) { 30 if (this.options[prop] !== undefined) { 31 this.options[prop] = this.selectElement.dataset[prop]; 32 } 33 } 34 this.name = this.selectElement.getAttribute('name') ? this.selectElement.getAttribute('name') : 'multi-select-' + Math.floor(Math.random() * 1000000); 35 if (!this.options.data.length) { 36 let options = this.selectElement.querySelectorAll('option'); 37 for (let i = 0; i < options.length; i++) { 38 this.options.data.push({ 39 value: options[i].value, 40 text: options[i].innerHTML, 41 selected: options[i].selected, 42 html: options[i].getAttribute('data-html') 43 }); 44 } 45 } 46 this.element = this._template(); 47 this.selectElement.replaceWith(this.element); 48 this._updateSelected(); 49 this._eventHandlers(); 50 document.getElementById('btnSearch').removeAttribute('disabled'); 51 } 52 53 _template() { 54 let optionsHTML = ''; 55 for (let i = 0; i < this.data.length; i++) { 56 optionsHTML += ` 57 <div class="multi-select-option${this.selectedValues.includes(this.data[i].value) ? ' multi-select-selected' : ''}" data-value="${this.data[i].value}"> 58 <span class="multi-select-option-radio" tabindex="0" onkeydown="if(event.key === ' ') { event.preventDefault(); this.click(); }"></span> 59 <span class="multi-select-option-text">${this.data[i].html ? this.data[i].html : this.data[i].text}</span> 60 </div> 61 `; 62 } 63 let selectAllHTML = ''; 64 if (this.options.selectAll === true || this.options.selectAll === 'true') { 65 const isSelected = this.data.every(data => data.selected); 66 selectAllHTML = `<div class="multi-select-all${isSelected ? ' multi-select-selected' : ''}"> 67 <span class="multi-select-option-radio" tabindex="0" onkeydown="if(event.key === ' ') { event.preventDefault(); this.click(); }"></span> 68 <span class="multi-select-option-text">å ¨ã¦</span> 69 </div>`; 70 } 71 let template = ` 72 <div class="multi-select ${this.name}"${this.selectElement.id ? ' id="' + this.selectElement.id + '"' : ''} style="${this.width ? 'width:' + this.width + ';' : ''}${this.height ? 'height:' + this.height + ';' : ''}"> 73 ${this.selectedValues.map(value => `<input type="hidden" name="${this.name}[]" value="${value}">`).join('')} 74 <div class="multi-select-header" style="${this.width ? 'width:' + this.width + ';' : ''}${this.height ? 'height:' + this.height + ';' : ''}"> 75 <span class="multi-select-header-max">${this.options.max ? this.selectedValues.length + '/' + this.options.max : ''}</span> 76 <span class="multi-select-header-placeholder">${this.placeholder}</span> 77 </div> 78 <div class="multi-select-options" style="${this.options.dropdownWidth ? 'width:' + this.options.dropdownWidth + ';' : ''}${this.options.dropdownHeight ? 'height:' + this.options.dropdownHeight + ';' : ''}"> 79 ${this.options.search === true || this.options.search === 'true' ? '<input type="text" aria-label="ã«ãã´ãªã¼" class="multi-select-search" placeholder="鏿ãã¦ãã ãã">' : ''} 80 ${selectAllHTML} 81 ${optionsHTML} 82 </div> 83 </div> 84 `; 85 let element = document.createElement('div'); 86 element.innerHTML = template; 87 return element; 88 } 89 90 _eventHandlers() { 91 let headerElement = this.element.querySelector('.multi-select-header'); 92 this.element.querySelectorAll('.multi-select-option').forEach(option => { 93 option.onclick = () => { 94 let selected = true; 95 if (!option.classList.contains('multi-select-selected')) { 96 if (this.options.max && this.selectedValues.length >= this.options.max) { 97 return; 98 } 99 option.classList.add('multi-select-selected'); 100 if (this.options.listAll === true || this.options.listAll === 'true') { 101 if (this.element.querySelector('.multi-select-header-option')) { 102 let opt = Array.from(this.element.querySelectorAll('.multi-select-header-option')).pop(); 103 opt.insertAdjacentHTML('afterend', `<span class="multi-select-header-option" data-value="${option.dataset.value}">${option.querySelector('.multi-select-option-text').innerHTML}</span>`); 104 } else { 105 headerElement.insertAdjacentHTML('afterbegin', `<span class="multi-select-header-option" data-value="${option.dataset.value}">${option.querySelector('.multi-select-option-text').innerHTML}</span>`); 106 } 107 } 108 this.element.querySelector('.multi-select').insertAdjacentHTML('afterbegin', `<input type="hidden" name="${this.name}[]" value="${option.dataset.value}">`); 109 this.data.filter(data => data.value == option.dataset.value)[0].selected = true; 110 } else { 111 option.classList.remove('multi-select-selected'); 112 this.element.querySelectorAll('.multi-select-header-option').forEach(headerOption => headerOption.dataset.value == option.dataset.value ? headerOption.remove() : ''); 113 this.element.querySelector(`input[value="${option.dataset.value}"]`).remove(); 114 this.data.filter(data => data.value == option.dataset.value)[0].selected = false;
115 selected = false; 116 } 117 if (this.options.listAll === false || this.options.listAll === 'false') { 118 if (this.element.querySelector('.multi-select-header-option')) { 119 this.element.querySelector('.multi-select-header-option').remove(); 120 } 121 headerElement.insertAdjacentHTML('afterbegin', `<span class="multi-select-header-option">${this.selectedValues.length} selected</span>`); 122 } 123 if (!this.element.querySelector('.multi-select-header-option')) { 124 headerElement.insertAdjacentHTML('afterbegin', `<span class="multi-select-header-placeholder">${this.placeholder}</span>`); 125 } else if (this.element.querySelector('.multi-select-header-placeholder')) { 126 this.element.querySelector('.multi-select-header-placeholder').remove(); 127 } 128 if (this.options.max) { 129 this.element.querySelector('.multi-select-header-max').innerHTML = this.selectedValues.length + '/' + this.options.max; 130 } 131 if (this.options.search === true || this.options.search === 'true') { 132 this.element.querySelector('.multi-select-search').value = ''; 133 } 134 this.element.querySelectorAll('.multi-select-option').forEach(option => option.style.display = 'flex'); 135 if (this.options.closeListOnItemSelect === true || this.options.closeListOnItemSelect === 'true') { 136 headerElement.classList.remove('multi-select-header-active'); 137 } 138 this.options.onChange(option.dataset.value, option.querySelector('.multi-select-option-text').innerHTML, option); 139 if (selected) { 140 this.options.onSelect(option.dataset.value, option.querySelector('.multi-select-option-text').innerHTML, option); 141 } else { 142 this.options.onUnselect(option.dataset.value, option.querySelector('.multi-select-option-text').innerHTML, option); 143 } 144 }; 145 }); 146 headerElement.onclick = () => headerElement.classList.toggle('multi-select-header-active'); 147 if (this.options.search === true || this.options.search === 'true') { 148 let search = this.element.querySelector('.multi-select-search'); 149 search.oninput = () => { 150 this.element.querySelectorAll('.multi-select-option').forEach(option => { 151 option.style.display = option.querySelector('.multi-select-option-text').innerHTML.toLowerCase().indexOf(search.value.toLowerCase()) > -1 ? 'flex' : 'none'; 152 }); 153 }; 154 } 155 if (this.options.selectAll === true || this.options.selectAll === 'true') { 156 let selectAllButton = this.element.querySelector('.multi-select-all'); 157 selectAllButton.onclick = () => { 158 let allSelected = selectAllButton.classList.contains('multi-select-selected'); 159 this.element.querySelectorAll('.multi-select-option').forEach(option => { 160 let dataItem = this.data.find(data => data.value == option.dataset.value); 161 if (dataItem && ((allSelected && dataItem.selected) || (!allSelected && !dataItem.selected))) { 162 option.click(); 163 } 164 }); 165 selectAllButton.classList.toggle('multi-select-selected'); 166 }; 167 } 168 if (this.selectElement.id && document.querySelector('label[for="' + this.selectElement.id + '"]')) { 169 document.querySelector('label[for="' + this.selectElement.id + '"]').onclick = () => { 170 headerElement.classList.toggle('multi-select-header-active'); 171 }; 172 } 173 document.addEventListener('click', event => { 174 const isClickInside = this.element.contains(event.target) || 175 (this.selectElement.id && document.querySelector('label[for="' + this.selectElement.id + '"]').contains(event.target)); 176 if (!isClickInside) { 177 headerElement.classList.remove('multi-select-header-active'); 178 } 179 }); 180 } 181 182 _updateSelected() { 183 if (this.options.listAll === true || this.options.listAll === 'true') { 184 this.element.querySelectorAll('.multi-select-option').forEach(option => { 185 if (option.classList.contains('multi-select-selected')) { 186 this.element.querySelector('.multi-select-header').insertAdjacentHTML('afterbegin', `<span class="multi-select-header-option" data-value="${option.dataset.value}">${option.querySelector('.multi-select-option-text').innerHTML}</span>`); 187 } 188 }); 189 } else { 190 if (this.selectedValues.length > 0) { 191 this.element.querySelector('.multi-select-header').insertAdjacentHTML('afterbegin', `<span class="multi-select-header-option">${this.selectedValues.length} selected</span>`); 192 } 193 } 194 if (this.element.querySelector('.multi-select-header-option')) { 195 this.element.querySelector('.multi-select-header-placeholder').remove(); 196 } 197 } 198 199 get selectedValues() { 200 return this.data.filter(data => data.selected).map(data => data.value); 201 } 202 203 get selectedItems() { 204 return this.data.filter(data => data.selected); 205 } 206 207 set data(value) { 208 this.options.data = value; 209 } 210 211 get data() { 212 return this.options.data; 213 } 214 215 set selectElement(value) { 216 this.options.selectElement = value; 217 } 218 219 get selectElement() { 220 return this.options.selectElement; 221 } 222
223 set element(value) { 224 this.options.element = value; 225 } 226 227 get element() { 228 return this.options.element; 229 } 230 231 set placeholder(value) { 232 this.options.placeholder = value; 233 } 234 235 get placeholder() { 236 return this.options.placeholder; 237 } 238 239 set name(value) { 240 this.options.name = value; 241 } 242 243 get name() { 244 return this.options.name; 245 } 246 247 set width(value) { 248 this.options.width = value; 249 } 250 251 get width() { 252 return this.options.width; 253 } 254 255 set height(value) { 256 this.options.height = value; 257 } 258 259 get height() { 260 return this.options.height; 261 } 262 263} 264document.querySelectorAll('[data-multi-select]').forEach(select => new MultiSelect(select));
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.