PageSourceSearch

https://iccwbo.org/wp-content/plugins/facetwp/assets/vendor/fSelect/fSelect.js?ver=4.5

js iccwbo.org collected 2026-09-24 07:30:40 UTC 15,318 bytes, 423 lines download raw bytes

1window.fSelect = (() => {
2
3    var build = {};
4
5    class fSelect {
6
7        constructor(selector, options) {
8            let that = this;
9
10            var defaults = {
11                placeholder: 'Select some options',
12                numDisplayed: 3,
13                overflowText: '{n} selected',
14                searchText: 'Search',
15                noResultsText: 'No results found',
16                showSearch: true,
17                optionFormatter: false
18            };
19
20            that.settings = Object.assign({}, defaults, options);
21            build = {output: '', optgroup: 0, idx: 0};
22
23            if ('string' === typeof selector) {
24                var nodes = Array.from(document.querySelectorAll(selector));
25            }
26            else if (selector instanceof Node) {
27                var nodes = [selector];
28            }
29            else if (Array.isArray(selector)) {
30                var nodes = selector;
31            }
32            else {
33                var nodes = [];
34            }
35
36            if ('undefined' === typeof window.fSelectInit) {
37                window.fSelectInit = {
38                    searchCache: '',
39                    lastChoice: null,
40                    lastFocus: null,
41                    activeEl: null
42                };
43                that.bindEvents();
44            }
45
46            nodes.forEach((input) => {
47                if (typeof input.fselect === 'object') {
48                    input.fselect.destroy();
49                }
50
51                that.settings.multiple = input.matches('[multiple]');
52                input.fselect = that;
53                that.input = input;
54                that.create();
55            });
56        }
57
58        create() {
59            var that = this;
60            var options = that.buildOptions();
61            var label = that.getDropdownLabel();
62            var mode = (that.settings.multiple) ? 'multiple' : 'single';
63            var searchClass = (that.settings.showSearch) ? '' : ' fs-hidden';
64            var noResultsClass = (build.idx < 2) ? '' : ' fs-hidden';
65
66            var html = `
67            <div class="fs-wrap ${mode}" tabindex="0">
68                <div class="fs-label-wrap">
69                    <div class="fs-label">${label}</div>
70                    <span class="fs-arrow"></span>
71                </div>
72                <div class="fs-dropdown fs-hidden">
73                    <div class="fs-search${searchClass}">
74                        <input type="text" placeholder="${that.settings.searchText}" />
75                    </div>
76                    <div class="fs-no-results${noResultsClass}">${that.settings.noResultsText}</div>
77                    <div class="fs-options">${options}</div>
78                </div>
79            </div>
80            `;
81
82            var tpl = document.createElement('template');
83            tpl.innerHTML = html;
84            var wrap = tpl.content.querySelector('.fs-wrap');
85            that.input.parentNode.insertBefore(wrap, that.input.nextSibling);
86            that.input.classList.add('fs-hidden');
87
88            // add a relationship link
89            that.input._rel = wrap;
90            wrap._rel = that.input;
91        }
92
93        destroy() {
94            this.input._rel.remove();
95            this.input.classList.remove('fs-hidden');
96            delete this.input._rel;
97        }
98
99        reload() {
100            this.destroy();
101            this.create();
102        }
103
104        open() {
105            var wrap = this.input._rel;
106            wrap.classList.add('fs-open');
107            wrap.querySelector('.fs-dropdown').classList.remove('fs-hidden');
108
109            // don't auto-focus for touch devices
110            if (! window.matchMedia("(pointer: coarse)").matches) {
111                wrap.querySelector('.fs-search input').focus();
112            }
113
114            // restore searchCache if there is still an input in search
115            window.fSelectInit.searchCache = wrap.querySelector('.f
115s-search input')
116
117            window.fSelectInit.lastChoice = this.getSelectedOptions('value');
118            window.fSelectInit.activeEl = wrap;
119
120            this.trigger('fs:opened', wrap);
121        }
122
123        close() {
124            this.input._rel.classList.remove('fs-open');
125            this.input._rel.querySelector('.fs-dropdown').classList.add('fs-hidden');
126
127            window.fSelectInit.searchCache = '';
128            window.fSelectInit.lastChoice = null;
129            window.fSelectInit.lastFocus = null;
130            window.fSelectInit.activeEl = null;
131
132            this.trigger('fs:closed', this.input._rel);
133        }
134
135        buildOptions(parent) {
136            var that = this;
137            var parent = parent || that.input;
138
139            Array.from(parent.children).forEach((node) => {
140                if ('optgroup' === node.nodeName.toLowerCase()) {
141                    var opt = `<div class="fs-optgroup-label" data-group="${build.optgroup}">${node.label}</div>`;
142                    build.output += opt;
143                    that.buildOptions(node);
144                    build.optgroup++;
145                }
146                else {
147                    var val = node.value;
148
149                    // skip the first choice in multi-select mode
150                    if (0 === build.idx && '' === val && that.settings.multiple) {
151                        build.idx++;
152                        return;
153                    }
154
155                    var classes = ['fs-option', 'g' + build.optgroup];
156
157                    // append existing classes
158                    node.className.split(' ').forEach((className) => {
159                        if ('' !== className) {
160                            classes.push(className);
161                        }
162                    });
163
164                    if (node.matches('[disabled]')) classes.push('disabled');
165                    if (node.matches('[selected]')) classes.push('selected');
166                    classes = classes.join(' ');
167
168                    if ('function' === typeof that.settings.optionFormatter) {
169                        node.label = that.settings.optionFormatter(node.label, node);
170                    }
171
172                    var opt = `<div class="${classes}" data-value="${val}" data-idx="${build.idx}" tabindex="-1"><span class="fs-checkbox"><i></i></span><div class="fs-option-label">${node.label}</div></div>`;
173
174                    build.output += opt;
175                    build.idx++;
176                }
177            });
178
179            return build.output;
180        }
181
182        getSelectedOptions(field, context) {
183            var context = context || this.input;
184            return Array.from(context.selectedOptions).map((opt) => {
185                return (field) ? opt[field] : opt;
186            });
187        }
188
189        getAdjacentSibling(which) {
190            var that = this;
191            var which = which || 'next';
192            var sibling = window.fSelectInit.lastFocus;
193            var selector = '.fs-option:not(.fs-hidden):not(.disabled)';
194
195            if (sibling) {
196                sibling = sibling[which + 'ElementSibling'];
197
198                while (sibling) {
199                    if (sibling.matches(selector)) break;
200                    sibling = sibling[which + 'ElementSibling'];
201                }
202
203                return sibling;
204            }
205            else if ('next' == which) {
206                sibling = that.input._rel.querySelector(selector);
207            }
208
209            return sibling;
210        }
211
212        getDropdownLabel() {
213            var that = this;
214            var labelText = that.getSelectedOptions('text');
215
216            if (labelText.length < 1) {
217                labelText = that.settings.placeholder;
218            }
219            else if (labelText.length > that.settings.numDisplayed) {
220                labelText = that.settings.overflowText.replace('{n}', labelText.length);
221            }
222            else {
223                labelText = labelText.join(', ');
224            }
225
226            return labelText;
227        }
228
229        debounce(func, wait) {
230            var timeout;
231            return (...args) => {
232                var boundFunc = func.bind(this, ...args);
233                clearTimeout(timeout);
234                timeout = setTimeout(boundFunc, wait);
235            }
236        }
237
238        trigger(eventName, ...args) {
239            document.dispatchEvent(new CustomEvent(eventName, {detail: [...args]}));
240        }
241
242        on(eventName, elementSelector, handler) {
243            document.addEventListener(eventName, function(e) {
244                // loop parent nodes from the target to the delegation node
245                for (var target = e.target; target && target != this; target = target.parentNode) {
246                    if (target.matches(elementSelector)) {
247                        handler.call(target, e);
248                        break;
249                    }
250                }
251            }, false);
252        }
253
254        bindEvents() {
255            var that = this;
256            var optionSelector = '.fs-option:not(.fs-hidden):not(.disabled)';
257            var unaccented = (str) => str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
258
259            // debounce search for better performance
260            that.on('keyup', '.fs-search input', that.debounce(function(e) {
261                var wrap = e.target.closest('.fs-wrap');
262                var options = wrap._rel.options;
263
264                var matchOperators = /[|\\{}()[\]^$+*?.]/g;
265                var keywords = e.target.value.replace(matchOperators, '\\$&');
266                keywords = unaccented(keywords);
267
268                // if the searchCache already has a prefixed version of this search
269                // then don't un-hide the existing exclusions
270                if (0 !== keywords.indexOf(window.fSelectInit.searchCache)) {
271                    wrap.querySelectorAll('.fs-option, .fs-optgroup-label').forEach((node) => node.classList.remove('fs-hidden'));
272                }
273
274                window.fSelectInit.searchCache = keywords;
275
276                for (var i = 0; i < options.length; i++) {
277                    if ('' === options[i].value) continue;
278
279                    var needle = new RegExp(keywords, 'gi');
280                    var haystack = unaccented(options[i].text);
281
282                    if (null === haystack.match(needle)) {
283                        wrap.querySelector('.fs-option[data-idx="' + i + '"]').classList.add('fs-hidden');
284                    }
285                }
286
287                // hide optgroups if no choices
288                wrap.querySelectorAll('.fs-optgroup-label').forEach((node) => {
289                    var group = node.getAttribute('data-group');
290                    var container = node.closest('.fs-options');
291                    var count = container.querySelectorAll('.fs-option.g' + group + ':not(.fs-hidden)').length;
292
293                    if (count < 1) {
294                        node.classList.add('fs-hidden');
295                    }
296                });
297
298                // toggle the noResultsText div
299                if (wrap.querySelectorAll('.fs-option:not(.fs-hidden)').length) {
300                    wrap.querySelector('.fs-no-results').classList.add('fs-hidden');
301                }
302                else {
303                    wrap.querySelector('.fs-no-results').classList.remove('fs-hidden');
304                }
305
306            }, 100));
307
308            that.on('click', optionSelector, function(e) {
309                var wrap = this.closest('.fs-wrap');
310                var value = this.getAttribute('data-value');
311                var input = wrap._rel;
312                var isMultiple = wrap.classList.contains('multiple');
313
314                if (!isMultiple) {
315                    input.value = value;
316                    wrap.querySelectorAll('.fs-option.selected').forEach((node) => node.classList.remove('selected'));
317                }
318                else {
319                    var idx = parseInt(this.getAttribute('data-idx'));
320                    input.options[idx].selected = !this.classList.contains('selected');
321                }
322
323                this.classList.toggle('selected');
324
325                var label = input.fselect.getDropdownLabel();
326                wrap.querySelector('.fs-label').innerHTML = label;
327
328                // fire a change event
329                input.dispatchEvent(new Event('change', { bubbles: true }));
330                input.fselect.trigger('fs:changed', wrap);
331
332                if (!isMultiple) {
333                    input.fselect.close();
334                }
335
336                e.stopImmediatePropagation();
337            });
338
339            that.on('keydown', '*', function(e) {
340                var wrap = this.closest('.fs-wrap');
341
342                if (!wrap) return;
343
344                if (-1 < [38, 40, 27].indexOf(e.which)) {
345                    e.preventDefault();
346                }
347
348                if (32 == e.which || 13 == e.which) { // space, enter
349                    if (e.target.closest('.fs-search')) {
350                        // preserve spaces for search
351                    }
352                    else if (e.target.matches(optionSelector)) {
353                        e.target.click();
354                        e.preventDefault();
355                    }
356                    else {
357                        wrap.querySelector('.fs-label').click();
358                        e.preventDefault();
359                    }
360                }
361                else if (38 == e.which) { // up
362                    var sibling = wrap._rel.fselect.getAdjacentSibling('previous');
363                    window.fSelectInit.lastFocus = sibling; // stop at the search box
364
365                    if (sibling) {
366                        sibling.focus();
367                    }
368                    else {
369                        wrap.querySelector('.fs-search input').focus();
370                    }
371                }
372                else if (40 == e.which) { // down
373                    var sibling = wrap._rel.fselect.getAdjacentSibling('next');
374
375                    if (sibling) {
376                        sibling.focus();
377                        window.fSelectInit.lastFocus = sibling; // stop at the bottom
378                    }
379                }
380                else if (9 == e.which || 27 == e.which) { // tab, esc
381                    wrap._rel.fselect.close();
382                }
383            });
384
385            that.on('click', '*', function(e) {
386                var wrap = this.closest('.fs-wrap');
387                var lastActive = window.fSelectInit.activeEl;
388
389                if (wrap) {
390                    var labelWrap = this.closest('.fs-label-wrap');
391
392                    if (labelWrap) {
393                        if (lastActive) {
394                            lastActive._rel.fselect.close();
395                        }
396                        if (wrap !== lastActive) {
397                            wrap._rel.fselect.open();
398                        }
399                    }
400                }
401                else {
402                    if (lastActive) {
403                        lastActive._rel.fselect.close();
404                    }
405                }
406            });
407        }
408    }
409
410    var $ = (selector, options) => new fSelect(selector, options);
411
412    return $;
413
414})();
415
416if ('undefined' !== typeof fUtil) {
417    fUtil.fn.fSelect = function(opts) {
418        this.each(function() { // no arrow function to preserve "this"
419            fSelect(this, opts);
420        });
421        return this;
422    };
423}

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.