UNPKG

8.75 kBJavaScriptView Raw
1
2import { Dropdown, DropdownUI, DropdownConfig } from './Dropdown';
3import {initObjectExtensions} from "./utils/core";
4
5/**
6 * CustomSelect
7 * Wrapper of Bootstrap 4 dropdown list
8 * Requires dropdown js
9 */
10
11export const CustomSelectConfig = Object.assign({}, DropdownConfig, {
12
13 // override
14 useTagNames: true,
15
16 tagName: 'customselect',
17
18 roleMenu: 'textbox',
19 roleMenuItem: 'option',
20
21});
22
23export const CustomSelectUI = Object.assign({}, DropdownUI, {
24
25 Config: CustomSelectConfig,
26
27 /**
28 *
29 * @param customSelect
30 * @returns {HTMLSelectElement|boolean}
31 */
32 getHiddenSelect(customSelect) {
33 return customSelect.getElementsByTagName('select')[0] || false;
34 },
35
36 createHiddenSelect(selectAttributes, dropdownItems) {
37 const e = document.createElement('select');
38 e.setAttribute('hidden', '');
39 e.setAttribute('role', 'textbox');
40
41 for(let attribute in selectAttributes) {
42 e.setAttribute(attribute, selectAttributes[attribute]);
43 }
44
45 [].forEach.call(dropdownItems, dropdownItem => {
46 const o = document.createElement('option');
47 const val = this.getItemValue(dropdownItem);
48 if (val === undefined) {
49 throw new Error('CustomSelect: each item must have data-value attribute');
50 }
51 o.value = val;
52 if (dropdownItem.hasAttribute('aria-selected')) {
53 o.setAttribute('selected', '');
54 }
55 e.appendChild(o);
56 });
57
58 return e;
59 },
60
61 /**
62 * Synchronizes default value and selected options with UI
63 * If dropdown item has aria-selected but has no active class, add it
64 * If dropdown item has no aria-selected but has active class, remove it
65 * If no dropdown item selected, select 1st item and hidden option
66 * If customselect has value attribute, sets selected option according to it in highest priority
67 *
68 * @param {HTMLElement} customSelect
69 * @param {String|Array} defaultValue
70 * @param {boolean} isMultiple
71 */
72 syncUI(customSelect, defaultValue, isMultiple) {
73 console.log(defaultValue, isMultiple);
74 const menuItems = this.getMenuItems(customSelect);
75 const tglBtn = this.getToggleBtn(customSelect);
76 let hasSelected = false;
77 [].forEach.call(menuItems, menuItem => {
78 if (defaultValue !== '') {
79 const value = this.getItemValue(menuItem);
80 if (isMultiple) {
81 for (let k = 0; k < defaultValue.length; k++) {
82 if (defaultValue[k] == value) {
83 this.setItemActive(menuItem);
84 hasSelected = true;
85 this.getOptionByValue(customSelect, value).selected = true;
86 break;
87 }
88 }
89 } else if (value == defaultValue) {
90 this.setItemActive(menuItem);
91 hasSelected = true;
92 this.getOptionByValue(customSelect, value).selected = true;
93 if (tglBtn.innerHTML === '') {
94 tglBtn.innerHTML = menuItem.innerHTML;
95 }
96 }
97 } else if (menuItem.hasAttribute('aria-selected')) {
98 this.setItemActive(menuItem);
99 hasSelected = true;
100 if (tglBtn.innerHTML === '') {
101 tglBtn.innerHTML = menuItem.innerHTML;
102 }
103 } else if (menuItem.classList.contains(this.Config.classNameActive)) {
104 this.setItemInactive(menuItem);
105 }
106 });
107
108 if (!hasSelected) {
109 this.setItemActive(menuItems[0]);
110 this.getHiddenSelect(customSelect).options[0].setAttribute('selected', '');
111 if (tglBtn.innerHTML === '') {
112 tglBtn.innerHTML = menuItems[0].innerHTML;
113 }
114 }
115 },
116
117 insertHiddenSelect(customSelect, hiddenSelect) {
118 customSelect.appendChild(hiddenSelect);
119 },
120
121 getItemByValue(customSelect, value) {
122 const menu = this.getMenu(customSelect);
123 return menu.querySelector(`[data-value="${value}"]`) || false;
124 },
125
126 getLabelByValue(customSelect, value) {
127 return this.getItemByValue(customSelect, value).innerHTML;
128 },
129
130 getOptionByValue(customSelect, value) {
131 const hiddenSelect = this.getHiddenSelect(customSelect);
132 return hiddenSelect.querySelector(`[value="${value}"]`) || false;
133 },
134
135 setToggleByValue(customSelect, value) {
136 const tglBtn = this.getToggleBtn(customSelect);
137 const item = this.getItemByValue(customSelect, value);
138 tglBtn.innerHTML = item.innerHTML;
139 }
140
141});
142
143export const CustomSelect = Object.assign({}, Dropdown, {
144
145 UI: CustomSelectUI,
146 Config: CustomSelectConfig,
147
148 // override methods
149
150 init(customSelect) {
151 if (customSelect.__bunny_customselect !== undefined) {
152 return false;
153 }
154
155 if (customSelect.dataset.name === undefined) {
156 throw new Error('CustomSelect: data-name attribute missing');
157 }
158
159 if (!this.UI.getMenu(customSelect)) {
160 throw new Error('CustomSelect: no menu found!');
161 }
162
163 if (!this.UI.getToggleBtn(customSelect)) {
164 throw new Error('CustomSelect: toggle button not found!');
165 }
166
167 customSelect.__bunny_customselect = {};
168
169 const hiddenSelect = this.UI.createHiddenSelect(
170 this.getAttributesForSelect(customSelect),
171 this.UI.getMenuItems(customSelect),
172 this.UI.getToggleBtn(customSelect).textContent
173 );
174 this.UI.insertHiddenSelect(customSelect, hiddenSelect);
175 const defaultValue = this.getDefaultValue(customSelect);
176 this.UI.syncUI(customSelect, defaultValue, this.isMultiple(customSelect));
177
178 this._addCustomSelectEvents(customSelect);
179 this._setARIA(customSelect);
180
181 initObjectExtensions(this, customSelect);
182
183 return true;
184 },
185
186 _addCustomSelectEvents(customSelect) {
187 this.onItemSelect(customSelect, (item) => {
188 if (item !== null) {
189 this.select(customSelect, this.UI.getItemValue(item));
190 }
191 });
192 },
193
194 getAttributesForSelect(customSelect) {
195 let selectAttributes = {};
196 for(let k in customSelect.dataset) {
197 selectAttributes[k] = customSelect.dataset[k];
198 }
199 return selectAttributes;
200 },
201
202 /**
203 * Get default value from value="" attribute
204 * which might be a string representing a single selected option value
205 * or a JSON array representing selected options in multiple select
206 *
207 * This attribute has highest priority over aria-selected which will be updated in syncUI()
208 * If value is empty string or no value attribute found then 1st option is selected
209 *
210 * @param customSelect
211 * @returns {String|Array}
212 */
213 getDefaultValue(customSelect) {
214 const val = customSelect.getAttribute('value');
215 if (val === null) {
216 return '';
217 }
218 const firstChar = val[0];
219 if (firstChar === undefined) {
220 return '';
221 } else if (firstChar === '[') {
222 return JSON.parse(val);
223 }
224 return val;
225 },
226
227 isMultiple(customSelect) {
228 return customSelect.dataset.multiple !== undefined;
229 },
230
231 select(customSelect, value) {
232 const option = this.UI.getOptionByValue(customSelect, value);
233 if (this.isMultiple(customSelect)) {
234 if (option.selected) {
235 this.deselect(customSelect, value);
236 } else {
237 const item = this.UI.getItemByValue(customSelect, value);
238 this.UI.setItemActive(item);
239 option.selected = true;
240 }
241 } else {
242 if (!option.selected) {
243 const curValue = this.getSelectedValue(customSelect);
244 if (curValue != value) {
245 this.deselect(customSelect, curValue);
246 }
247
248 const item = this.UI.getItemByValue(customSelect, value);
249 this.UI.setItemActive(item);
250 option.selected = true;
251 this.UI.setToggleByValue(customSelect, value);
252 }
253 }
254 },
255
256 deselect(customSelect, value) {
257 const option = this.UI.getOptionByValue(customSelect, value);
258 if (option.selected) {
259 const item = this.UI.getItemByValue(customSelect, value);
260 this.UI.setItemInactive(item);
261 option.selected = false;
262 }
263 },
264
265 /**
266 * Get selected value
267 * If select is multiple then returns array
268 *
269 * @param customSelect
270 * @returns {String|Array}
271 */
272 getSelectedValue(customSelect) {
273 const hiddenSelect = this.UI.getHiddenSelect(customSelect);
274 if (this.isMultiple(customSelect)) {
275 let selectedOptions = [];
276 [].forEach.call(hiddenSelect.options, option => {
277 if (option.selected) {
278 selectedOptions.push(option.value);
279 }
280 });
281 return selectedOptions;
282 } else {
283 return hiddenSelect.options[hiddenSelect.selectedIndex].value;
284 }
285 }
286
287});
288
289document.addEventListener('DOMContentLoaded', () => {
290 CustomSelect.initAll();
291});