PageSourceSearch

https://archaeology.jp/modules/contrib/notifications/js/MultiSelect.js?t=1790243454

js archaeology.jp collected 2026-09-24 09:51:16 UTC 11,974 bytes, 264 lines download raw bytes

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.