PageSourceSearch

https://www.fva-bw.de/_assets/fcf3e6f78651b50b159716156c65befc/Js/listFilter.js?1758113062

js fva-bw.de collected 2026-10-02 04:50:29 UTC 20,163 bytes, 572 lines download raw bytes

1$.widget('gd.listFilter', {
2
3    options: {
4        elementClass: 'listfilter__filter',
5        searchInputSelector: '[data-listfilter-role="search"]',
6        searchSubmitSelector: '[data-listfilter-role="submit"]',
7        searchResetSelector: '[data-listfilter-role="reset"]',
8        filtersWrapperSelector: '[data-listfilter-role="filters"]',
9        listSelector: '[data-listfilter-role="list"]',
10        itemSelector: '[data-listfilter-role="item"]',
11        listClass: 'listfilter-list',
12        cleanClass: 'listfilter-list--clean',
13        moreClass: 'listfilter-list_more',
14        moreSelector: '[data-listfilter-role="more"]',
15        noResultClass: 'listfilter-list_noresult',
16        queryParameter: 'listfilter',
17        listElement: null,
18        sysLanguage: 0,
19        isClean: true,
20        showAllText: '',
21        noResultText: '',
22        configuration: {}
23    },
24
25    _create: function () {
26        this._handleNextList();
27        this._setClean();
28        this._initFilters();
29        this._bindEvents();
30        this._handleUrlSearch();
31    },
32
33    /**
34     * Handles the next filterable list element
35     *
36     * @private
37     * @returns {JQuery}|null
38     */
39    _handleNextList: function () {
40        var nextList = null,
41            frame = this.element.closest('.frame');
42
43        while (frame.next('.frame').length) {
44            frame = frame.next('.frame');
45            var list = frame.find(this.options.listSelector);
46            if (list.length) {
47                nextList = list;
48                break;
49            }
50        }
51        this.options.listElement = nextList;
52
53        if (nextList) {
54            nextList.addClass(this.options.listClass);
55            nextList.append(
56                $('<p>', {
57                    class: this.options.noResultClass,
58                    style: 'display: none;',
59                    text: this.options.noResultText
60                })
61            );
62            nextList.append(
63                $('<div>', {
64                    class: this.options.moreClass
65                }).append(
66                    $('<a>', {
67                        text: this.options.showAllText,
68                        href: '#',
69                        'data-listfilter-role': 'more'
70                    })
71                )
72            );
73        }
74    },
75
76    /**
77     * Checks if item list is in a clean state
78     *
79     * @private
80     */
81    _isClean: function () {
82        return this.options.isClean;
83    },
84
85    /**
86     * Sets item list to a clean state
87     *
88     * @private
89     */
90    _setClean: function () {
91        this.options.isClean = true;
92        if (this.options.listElement) {
93            this.options.listElement.addClass(this.options.cleanClass);
94            this.options.listElement.find(this.options.itemSelector).css({display: ''});
95            this.options.listElement.find('.' + this.options.moreClass).show();
96        }
97    },
98
99    /**
100     * Sets item list to an unclean state
101     *
102     * @private
103     */
104    _setUnclean: function () {
105        this.options.isClean = false;
106        if (this.options.listElement) {
107            this.options.listElement.removeClass(this.options.cleanClass);
108            this.options.listElement.find('.' + this.options.moreClass).hide();
109        }
110    },
111
112    /**
113     * Parses multivalue string (which should be escaped Arrays)
114     *
115     * @param {string} value
116     * @returns mixed
117     *
118     * @private
119     */
120    _parseMultivalue: function (value) {
121        return JSON.parse(decodeURI(value));
122    },
123
124    /**
125     * Get possible values for a filter
126     *
127     * @param {string} filter
128     * @param {Object} filterConfig
129     * @return {Object}
130     *
131     * @private
132     */
133    _getPossibleFilterValues: function (filter, filterConfig) {
134        var filterValues = {},
135            sortedFilterValues,
136            elementSelector = '';
137
138        if (this.options.listElement) {
139            if (this._isClean()) {
140                elementSelector = this.options.itemSelector + '[data-listfilter-filter-' + filter + ']';
141            } else {
142                elementSelector = this.options.itemSelector + '[data-listfilter-filter-' + filter + ']:visible';
143            }
144            $(this.options.listElement).find(elementSelector).each(function (i, element) {
145                var value = $(element).attr('data-listfilter-filter-' + filter);
146
147                if (filterConfig.multivalue) {
148                    value = this._parseMultivalue(value);
149                    if (value instanceof Array) {
150                        for (var i = 0; i < value.length; i++) {
151                            if (value[i] !== '') {
152                                filterValues[value[i]] = value[i];
153                            }
154                        }
155                    }
156                } else {
157                    if (value !== '') {
158                        filterValues[value] = value;
159                    }
160                }
161            }.bind(this));
162        }
163        if (filterConfig.sorting && filterConfig.sorting === 'desc') {
164            sortedFilterValues = Object.keys(filterValues).sort(function (a, b) {
165                return filterValues[a].toLowerCase() < filterValues[b].toLowerCase() ? 1 : -1;
166            });
167        } else {
168            sortedFilterValues = Object.keys(filterValues).sort(function (a, b) {
169                return filterValues[a].toLowerCase() > filterValues[b].toLowerCase() ? 1 : -1;
170            });
171        }
172        return sortedFilterValues;
173    },
174
175    /**
176     * Return HTML for select options
177     *
178     * @param {string} filter
179     * @param {Object} filterConfig
180     * @param {string} filterElementPlaceholder
181     * @return {string}
182     *
183     * @private
184     */
185    _getSelectOptionsHtml: function (filter, filterConfig, filterElementPlaceholder) {
186        var filterValues = this._getPossibleFilterValues(filter, filterConfig),
187            selectOptionsHtml = '<option value="" selected="selected">' + filterElementPlaceholder + '</option>';
188
189        $.each(filterValues, function (i) {
190            selectOptionsHtml += '<option value="' + filterValues[i] + '">' + filterValues[i] + '</option>';
191        });
192        return selectOptionsHtml;
193    },
194
195    /**
196     * Find filter input or select element by name
197     *
198     * @param {string} filter
199     * @return {JQuery}
200     *
201     * @private
202     */
203    _findFilterElement: function (filter) {
204        return this.element.find(
205            this.options.filtersWrapperSelector + ' .' + this.options.elementClass + '[data-listfilter-filter="' + filter + '"]'
206        );
207    },
208
209    /**
210     * Adds autocomplete to input fields
211     * Inputs are only valid if an autocomplete option has been chosen
212     *
213     * @param {string} filter
214     * @param {Object} filterConfig
215     *
216     * @private
217     */
218    _addAutocomplete: function (filter, filterConfig) {
219        var filterValues = this._getPossibleFilterValues(filter, filterConfig),
220            filterElement = this._findFilterElement(filter);
221
222        if (filterValues && filterValues instanceof Object) {
223            filterElement.autocomplete({
224                source: filterValues,
225                create: function (event, ui) {
226                    filterElement.attr('data-autocomplete-valid', '1');
227                    this._trigger('autocompletevalidationchange', event, null);
228                }.bind(this),
229                open: function (event, ui) {
230                    filterElement.attr('data-autocomplete-valid', '0');
231                    this._trigger('autocompletevalidationchange', event, null);
232                }.bind(this),
233                select: function (event, ui) {
234                    filterElement.attr('data-autocomplete-valid', '1');
235                    this._trigger('autocompletevalidationchange', event, null);
236                    // We have to do this manually so that the search can use the correct value
237                    if (ui.hasOwnProperty('item') && ui.item.hasOwnProperty('value')) {
238                        filterElement.val(ui.item.value);
239                    }
240                    this._search();
241                }.bind(this)
242            });
243            this._on(filterElement, {
244                // Unfortunately, the change event only fire, when the input is blurred.
245                // We use keypress instead.
246                keyup: function (event) {
247                    if (event.which !== 13) {
248                        if (filterElement.val() === '') {
249                            filterElement.attr('data-autocomplete-valid', '1');
250                            this._search();
251                        } else {
252                            filterElement.attr('data-autocomplete-valid', '0');
253                        }
254                        this._trigger('autocompletevalidationchange', event, null);
255                    }
256                }
257            });
258        }
259    },
260
261    /**
262     * Checks if all autocomplete inputs are valid
263     *
264     * @private
265     */
266    _isAutocompleteValid: function () {
267        return this.element.find('[data-autocomplete-valid=0]').length === 0;
268    },
269
270    /**
271     * Checks if submit button should be disabled for invalid autocomplete inputs
272     *
273     * @private
274     */
275    _autocompleteHandleSubmitButton: function () {
276        var submitButton = this.element.find(this.options.searchSubmitSelector);
277
278        if (this._isAutocompleteValid()) {
279            submitButton.prop('disabled', false);
280        } else {
281            submitButton.prop('disabled', true);
282        }
283    },
284
285    /**
286     * Initialize filters
287     *
288     * @private
289     */
290    _initFilters: function () {
291        var configuration = this.options.configuration,
292            elementClass = this.options.elementClass;
293
294        if (configuration.filters) {
295            $.each(configuration.filters, function (filter, filterConfig) {
296                var filterElement,
297                    filterElementInputType,
298                    filterElementPlaceholder,
299                    filterElementHtml,
300                    filterElementAfterHtml,
301                    filterElementClass;
302
303                filterElementClass = elementClass + ' ' + elementClass + '--' + filterConfig.type;
304                if (filterConfig.placeholder && filterConfig.placeholder[this.options.sysLanguage]) {
305                    filterElementPlaceholder = filterConfig.placeholder[this.options.sysLanguage];
306                }
307                switch (filterConfig.type) {
308                    case 'select':
309                        filterElement = '<select>';
310                        filterElementHtml = this._getSelectOptionsHtml(filter, filterConfig, filterElementPlaceholder);
311                        break;
312
313                    case 'input':
314                    default:
315                        filterElement = '<input>';
316                        filterElementInputType = 'text';
317                        break;
318                }
319                this.element.find(this.options.filtersWrapperSelector).append(
320                    $('<div>', {
321                        class: elementClass + '-wrap'
322                    }).append(
323                        '<label>' + filterElementPlaceholder + '</label>'
324                    ).append(
325                        $(filterElement, {
326                            class: filterElementClass,
327                            type: filterElementInputType,
328                            html: filterElementHtml,
329                            'data-listfilter-filter': filter,
330                            'data-placeholder': filterElementPlaceholder
331                        })
332                    ).append(filterElementAfterHtml)
333                );
334                if (filterConfig.type !== 'select') {
335                    this._addAutocomplete(filter, filterConfig);
336                }
337            }.bind(this));
338        }
339    },
340
341    /**
342     * Update filters
343     *
344     * @private
345     */
346    _updateFilterOptions: function (activeFilters) {
347        if (this.options.configuration.filters) {
348            $.each(this.options.configuration.filters, function (filter, filterConfig) {
349                if (!activeFilters.hasOwnProperty(filter)) {
350                    var filterElement = this._findFilterElement(filter);
351
352                    if (filterConfig.type === 'select') {
353                        filterElement.html(this._getSelectOptionsHtml(filter, filterConfig, filterElement.attr('data-placeholder')));
354                        if (filterElement.find('option').length === 1) {
355                            filterElement.prop('disabled', true);
356                        } else {
357                            filterElement.prop('disabled', false);
358                        }
359                    } else {
360                        var filterValues = this._getPossibleFilterValues(filter, filterConfig);
361
362                        if (filterValues && $.isArray(filterValues) && !$.isEmptyObject(filterValues)) {
363                            filterElement.autocomplete('option', 'source', filterValues);
364                            filterElement.prop('disabled', false);
365                        } else {
366                            filterElement.prop('disabled', true);
367                        }
368                    }
369                }
370            }.bind(this));
371        }
372    },
373
374    /**
375     * Get query parameters for filters
376     *
377     * @return {Object}
378     *
379     * @private
380     */
381    _getQueryParameters: function () {
382        var queryParameters = {},
383            query = window.location.search.substring(1),
384            querySegments = query.split('&');
385
386        for (var i = 0; i < querySegments.length; i++) {
387            var queryParameter = querySegments[i].split('=');
388
389            if (queryParameter[0].lastIndexOf(this.options.queryParameter, 0) === 0) {
390                var queryParameterName = decodeURIComponent(queryParameter[0]).slice(decodeURIComponent(queryParameter[0]).indexOf('[') + 1, -1);
391
392                if (queryParameterName) {
393                    queryParameters[queryParameterName] = decodeURIComponent(queryParameter[1]);
394                }
395            }
396        }
397        return queryParameters;
398    },
399
400    /**
401     * Trigger search via URL if query params are present
402     *
403     * @private
404     */
405    _handleUrlSearch: function () {
406        var queryParameters = this._getQueryParameters(),
407            configuration = this.options.configuration;
408
409        if (!$.isEmptyObject(queryParameters)) {
410            $.each(queryParameters, function (parameterName, parameterValue) {
411                if (configuration.filters.hasOwnProperty(parameterName)) {
412                    var filterElement = this._findFilterElement(parameterName);
413
414                    if (configuration.filters[parameterName].type === 'select') {
415                        filterElement.find('option[value="' + parameterValue + '"]').prop('selected', true);
416                    } else {
417                        filterElement.val(parameterValue);
418                    }
419                } else if (parameterName === 'search') {
420                    this.element.find(this.options.searchInputSelector).val(parameterValue);
421                }
422            }.bind(this));
423            this._search();
424        }
425    },
426
427    /**
428     * Reset the search form
429     *
430     * @private
431     */
432    _reset: function () {
433        var allInputsSelector = '.' + this.options.elementClass + ', ' + this.options.searchInputSelector,
434            allInputs = this.element.find(allInputsSelector)
435
436        allInputs.val('');
437        this._search();
438    },
439
440    /**
441     * Show all search results
442     *
443     * @private
444     */
445    _showMore: function () {
446        this._setUnclean();
447        if (this.options.listElement) {
448            this.options.listElement.find(this.options.itemSelector).show();
449        }
450    },
451
452    /**
453     * Main search function
454     *
455     * @private
456     */
457    _search: function () {
458        var configuration = this.options.configuration,
459            activeFilters = {},
460            numberOfResults = 0,
461            searchword = this.element.find(this.options.searchInputSelector).val().toLowerCase(),
462            searchwords = searchword.split(' ');
463
464        if (configuration.filters) {
465            $.each(configuration.filters, function (k, filter) {
466                var filterVal = this.element.find('[data-listfilter-filter="' + k + '
466"]').val();
467                if (filterVal) {
468                    activeFilters[k] = filterVal;
469                }
470            }.bind(this));
471        }
472        if (this.options.listElement) {
473            var noResultInfo = $(this.options.listElement).find('.' + this.options.noResultClass);
474
475            if ($.isEmptyObject(activeFilters) && searchword === '') {
476                this._setClean();
477            } else {
478                this._setUnclean();
479            }
480
481            $(this.options.listElement).find(this.options.itemSelector).each(function (i, element) {
482                var satisfiesText = true,
483                    satisfiesFilters = true,
484                    elementText = $(element).text().replace(/(\&shy;|­|&#173;)/gi, "").toLowerCase();
485
486                if (searchwords.length) {
487                    for (i = 0; i < searchwords.length; i++) {
488                        if (elementText.indexOf(searchwords[i]) < 0) {
489                            satisfiesText = false;
490                            break;
491                        }
492                    }
493                }
494                if (activeFilters) {
495                    $.each(activeFilters, function (filter, val) {
496                        var filterVal = $(element).attr('data-listfilter-filter-' + filter);
497
498                        if (configuration.filters[filter].multivalue) {
499                            filterVal = this._parseMultivalue(filterVal);
500                            if (filterVal.indexOf(val) < 0) {
501                                satisfiesFilters = false;
502                                return false;
503                            }
504                        } else {
505                            if (filterVal !== val) {
506                                satisfiesFilters = false;
507                                return false;
508                            }
509                        }
510                    }.bind(this));
511                }
512                if (satisfiesText && satisfiesFilters) {
513                    $(element).show();
514                    numberOfResults++;
515                } else {
516                    $(element).hide();
517                }
518            }.bind(this));
519
520            if ($.isEmptyObject(activeFilters) && searchword === '') {
521                this._setClean();
522            } else {
523                this._setUnclean();
524            }
525
526            if (numberOfResults === 0) {
527                noResultInfo.show();
528            } else {
529                noResultInfo.hide();
530            }
531        }
532        this._updateFilterOptions(activeFilters);
533    },
534
535    /**
536     * Binds events
537     *
538     * @private
539     */
540    _bindEvents: function () {
541        this._on(this.element.find(this.options.searchSubmitSelector), {
542            click: this._search
543        });
544        this._on(this.element.find('.listfilter__filter--select'), {
545            change: this._search
546        });
547        this._on(this.element.find('input[type=text]'), {
548            keypress: function (event) {
549                if (event.which === 13 && this._isAutocompleteValid()) {
550                    this._search();
551                }
552            }
553        });
554        this._on(this.element, {
555            listfilterautocompletevalidationchange: this._autocompleteHandleSubmitButton
556        });
557        this._on(this.element.find(this.options.searchResetSelector), {
558            click: function (event) {
559                event.preventDefault();
560                this._reset();
561            }
562        });
563        if (this.options.listElement) {
564            this._on(this.options.listElement.find(this.options.moreSelector), {
565                click: function (event) {
566                    event.preventDefault();
567                    this._showMore();
568                }
569            });
570        }
571    }
572});

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.