PageSourceSearch

https://www.siommms.it/wp-content/plugins/wp-store-locator/assets/src/frontend/js/modules/wpsl-filters.js

js siommms.it collected 2026-10-02 07:03:57 UTC 41,888 bytes, 1,081 lines download raw bytes

1import { config } from './wpsl-shared.js';
2import { sharedHelpers } from '../../../common/wpsl-shared-helpers.js';
3import { helpers } from './wpsl-helpers.js';
4import { search } from './wpsl-search.js';
5import { createDropdowns } from '../../../common/wpsl-dropdowns.js';
6import { accessibility } from './wpsl-accessibility.js';
7
8/**
9 * Filter functionality for WPSL frontend
10 * 
11 * @since 3.0.0
12 */
13export const filters = {
14    /**
15     * Used in the 3.x templates.
16     *
17     * @since 3.0.0
18     */
19    advanced: {
20        /**
21         * Initialize advanced filter interactions for v3 templates.
22         *
23         * @since   3.0.0
24         * @returns {void}
25         */
26        init: function () {
27            const $filters = jQuery( '.wpsl-filter' );
28            const dropdownHandler = filters.dropdowns.getHandler();
29
30            // Bind all filter interactions using the shared common module
31            dropdownHandler.bindFilterInteractions({
32                prepareHandlers: filters.advanced.prepare,
33                closeHandlers: filters.advanced.close
34            });
35
36            // Nested ( accordion ) layout: child filter toggles.
37            filters.advanced.initNested();
38
39            jQuery( '#wpsl-filter-options' ).on( 'keydown', '.wpsl-filter input[type="checkbox"]', function ( e ) {
40                const $currentCheckbox = jQuery( this );
41                const $dropdown = $currentCheckbox.closest( '.wpsl-filter' );
42                                
43                accessibility.keyboard.handleFilterCheckboxNavigation( e, $currentCheckbox, $dropdown, $filters, filters.advanced.closeAll );
44            });
45            
46            // Listen for checkbox changes to show/hide filter actions
47            jQuery( '#wpsl-filter-options' ).on( 'change', '.wpsl-filter input[type="checkbox"]', function() {
48                filters.advanced.toggleFilterActions();
49            });
50
51            // Listen for checkbox changes in custom checkbox panels (e.g. country)
52            jQuery( '#wpsl-filter-options' ).on( 'change', '.wpsl-custom-checkboxes input[type="checkbox"]', function() {
53                const $panelDiv = jQuery( this ).closest( '[data-id]' );
54                const $actions  = $panelDiv.find( '.wpsl-filter-actions' );
55                const hasChecked = $panelDiv.find( 'input[type="checkbox"]:checked' ).length > 0;
56
57                filters.advanced.setActionsVisible( $actions, hasChecked );
58            });
59        },
60
61        /**
62         * Filter specific actions to run before
63         * the correct filter content is shown.
64         */
65        prepare: {
66            /**
67             * Prepare the category filter panel before showing.
68             *
69             * @since   3.0.0
70             * @returns {void}
71             */
72            wpslShowFilters: function() {
73                const $filterOptions = jQuery( '#wpsl-filter-options' );
74                const $filterDiv = $filterOptions.find( '[data-id="wpsl-show-filters"]' );
75                
76                // Insert filter actions if they don't exist (append to filter div after the ul)
77                if ( ! $filterDiv.find( '.wpsl-filter-actions' ).length ) {
78                    const applyLabel = wpslLabels.apply;
79                    
80                    $filterDiv.append(
81                        '<div class="wpsl-filter-actions">' +
82                            '<button type="submit" class="wpsl-apply-filters wpsl-styled-btn wpsl-primary-btn">' + applyLabel + '</button>' +
83                        '</div>'
84                    );
85                }
86                
87                // Show/hide filter actions based on checkbox state
88                filters.advanced.toggleFilterActions();
89            },
90            
91            /**
92             * Prepare the radius filter panel before showing.
93             *
94             * Radius has no category checkboxes, so the actions stay hidden.
95             *
96             * @since   3.0.0
97             * @returns {void}
98             */
99            wpslShowRadius: function() {
100                filters.advanced.setActionsVisible( jQuery( '.wpsl-filter-actions' ), false );
101            },
102
103            /**
104             * Prepare the nested ( accordion ) parent panel before showing.
105             *
106             * @since   3.0.0
107             * @returns {void}
108             */
109            wpslShowNested: function() {
110                filters.advanced.toggleFilterActions();
111
112                // Measure once the panel is actually visible.
113                setTimeout( filters.advanced.updateNestedScroll, 0 );
114            },
115
116            /**
117             * Fallback prepare handler for any panel that contains .wpsl-custom-c
117heckboxes.
118             *
119             * @since   3.0.0
120             * @param   {string} id The button id that triggered the panel open
121             * @returns {void}
122             */
123            default: function( id ) {
124                const $panelDiv = jQuery( '#wpsl-filter-options [data-id="' + id + '"]' );
125                if ( ! $panelDiv.find( '.wpsl-custom-checkboxes' ).length ) { return; }
126
127                if ( ! $panelDiv.find( '.wpsl-filter-actions' ).length ) {
128                    const applyLabel = wpslLabels.apply;
129
130                    $panelDiv.append(
131                        '<div class="wpsl-filter-actions">' +
132                            '<button type="submit" class="wpsl-apply-filters wpsl-styled-btn wpsl-primary-btn">' + applyLabel + '</button>' +
133                        '</div>'
134                    );
135                }
136
137                const $actions = $panelDiv.find( '.wpsl-filter-actions' );
138                const hasChecked = $panelDiv.find( 'input[type="checkbox"]:checked' ).length > 0;
139
140                filters.advanced.setActionsVisible( $actions, hasChecked );
141            },
142        },
143
144        /**
145         * Show the filter actions only when a checkbox is checked.
146         *
147         * @since 3.0.0
148         * @returns {void}
149         */
150        toggleFilterActions: function() {
151            const hasChecked = jQuery( '#wpsl-filter-options .wpsl-filter input[type="checkbox"]:checked' ).length > 0;
152
153            filters.advanced.setActionsVisible( jQuery( '.wpsl-filter-actions' ), hasChecked );
154        },
155
156        /**
157         * Show or hide the Apply bar, and mark its panel so the panel only
158         * reserves room for the bar while it is there ( see filters.css ).
159         *
160         * @since   3.0.0
161         * @param   {jQuery}  $actions The .wpsl-filter-actions element(s)
162         * @param   {boolean} visible  Whether to show them
163         * @returns {void}
164         */
165        setActionsVisible: function( $actions, visible ) {
166            $actions.toggle( visible );
167            $actions.closest( '#wpsl-filter-options > div' ).toggleClass( 'wpsl-has-filter-actions', visible );
168        },
169
170        /**
171         * Filter specific actions to run when
172         * the close filter button is clicked.
173         */
174        close: {
175            /**
176             * Default close handler for filter panels.
177             *
178             * @since   3.0.0
179             * @returns {void}
180             */
181            default: function () {
182                jQuery( '#wpsl-result-filters, #wpsl-filter-options, #wpsl-result-list, #wpsl-filter-header' ).toggle();
183                jQuery( '#wpsl-panel' ).removeClass( 'wpsl-filters-open' );
184
185                // Remove expanded class from all filter panels
186                jQuery( '#wpsl-filter-options > div' ).removeClass( 'wpsl-filter-expanded' );
187            },
188
189            /**
190             * Close handler for category filters.
191             *
192             * @since   3.0.0
193             * @returns {void}
194             */
195            wpslShowFilters: function () {
196                this.default();
197
198                accessibility.aria.setExpanded( jQuery( '#wpsl-show-filters' ), false );
199                
200                // Hide filter actions when closing category filters
201                jQuery( '.wpsl-filter-actions' ).hide();
202            },
203
204            /**
205             * Close handler for radius filter.
206             *
207             * @since   3.0.0
208             * @returns {void}
209             */
210            wpslShowRadius: function () {
211                this.default();
212
213                accessibility.aria.setExpanded( jQuery( '#wpsl-show-radius' ), false );
214                jQuery( '#wpsl-filter-options li' ).removeClass( 'wpsl-selected-filter-option' );
215
216                accessibility.aria.resetFilterItems();
217            },
218
219            /**
220             * Close handler for the nested ( accordion ) parent panel.
221             *
222             * Also collapses every child accordion so the panel reopens
223             * in a clean state.
224             *
225             * @since   3.0.0
226             * @returns {void}
227             */
228            wpslShowNested: function () {
229                this.default();
230
231                accessibility.aria.setExpanded( jQuery( '#wpsl-show-nested' ), false );
232                jQuery( '.wpsl-filter-actions' ).hide();
233
234                filters.advanced.collapseNested();
235            }
236        },
237
238        /**
239         * Test if there's an active checkbox within the filter group
240         *
241         * @since   3.0.0
242         * @param   {string}  dataId data id value of the parent div
243         * @returns {boolean}
244         */
245        isAnyCheckboxChecked: function ( dataId ) {
246            return jQuery( '[data-id="' + dataId + '"] input[type="checkbox"]:checked' ).length > 0;
247        },
248
249        /**
250         * Close all filters.
251         * Note: This is for closing the main filter panel, not individual filter items.
252         *
253         * @since   3.0.0
254         * @returns {void}
255         */
256        closeAll: function () {
257            accessibility.aria.resetFilterButtons();
258        },
259
260        /**
261         * Initialise the nested (accordion) filter layout.
262         *
263         * In nested mode the result filters collapse under a single "Filters"
264         * button that reuses the regular filter panel, with each filter inside
265         * it becoming a collapsible child ( .wpsl-nested-toggle ).
266         *
267         * @since   3.0.0
268         * @returns {void}
269         */
270        initNested: function () {
271            const $options = jQuery( '#wpsl-filter-options' );
272            $options.off( 'click.wpslNested keydown.wpslNested' );
273
274            // Opening a child closes the rest, so only one is ever open.
275            $options.on( 'click.wpslNested', '.wpsl-nested-toggle', function ( e ) {
276                e.preventDefault();
277
278                const $filter = jQuery( this ).closest( '.wpsl-nested-filter' );
279                const willOpen = ! $filter.hasClass( 'wpsl-nested-open' );
280
281                if ( willOpen ) {
282                    filters.advanced.collapseNested();
283                }
284
285                $filter.toggleClass( 'wpsl-nested-open', willOpen );
286                accessibility.aria.setExpanded( jQuery( this ), willOpen );
287
288                // The changed panel height can add or remove the scrollbar.
289                filters.advanced.updateNestedScroll();
290
291                return false;
292            });
293
294            jQuery( document ).off( 'click.wpslNestedApply' ).on( 'click.wpslNestedApply', '.wpsl-apply-filters', function () {
295                filters.advanced.updateNestedCounts();
296            });
297
298            filters.advanced.updateNestedCounts();
299        },
300
301        /**
302         * Collapse every nested child accordion back to its closed state.
303         *
304         * @since   3.0.0
305         * @returns {void}
306         */
307        collapseNested: function () {
308            jQuery( '.wpsl-nested-filter' ).removeClass( 'wpsl-nested-open' );
309            accessibility.aria.setExpanded( jQuery( '.wpsl-nested-toggle' ), false );
310        },
311
312        /**
313         * Update the selected-filter count shown on each nested child toggle.
314         *
315         * @since   3.0.0
316         * @returns {void}
317         */
318        updateNestedCounts: function () {
319            jQuery( '#wpsl-filter-options .wpsl-nested-filter' ).each( function () {
320                const $filter = jQuery( this );
321
322                // The radius child reflects its value, not a count.
323                if ( $filter.hasClass( 'wpsl-nested-filter-radius' ) ) {
324                    return;
325                }
326
327                const $label = $filter.find( '.wpsl-nested-toggle > div > span' ).first();
328                const count  = $filter.find( '.wpsl-nested-options input[type="checkbox"]:checked' ).length;
329                const $count = $label.find( '.wpsl-filter-count' );
330
331                if ( count ) {
332                    if ( $count.length ) {
333                        $count.text( '(' + count + ')' );
334                    } else {
335                        $label.append( ' <span class="wpsl-filter-count">(' + count + ')</span>' );
336                    }
337                } else {
338                    $count.remove();
339                }
340            });
341
342            // The shared handler may have counted onto the parent button.
343            jQuery( '#wpsl-show-nested .wpsl-filter-count' ).remove();
344        },
345
346        /**
347         * Toggle the wpsl-has-scroll class on the nested filter panel.
348         *
349         * The class lets the CSS keep the apply actions clear of the
350         * scrollbar an overflowing panel gets.
351         *
352         * @since   3.0.0
353         * @returns {void}
354         */
355        updateNestedScroll: function () {
356            const el = jQuery( '#wpsl-filter-options' )[0];
357
358            if ( ! el ) {
359                return;
360            }
361
362            jQuery( el ).toggleClass( 'wpsl-has-scroll', el.scrollHeight > el.clientHeight );
363        },
364
365        /**
366         * Restore the panel filters back to their page-load state.
367         *
368         * filters.dropdowns.reset() cannot see these: the panel templates
369         * render the radius as a listbox keyed on data-radius and the
370         * categories as checkboxes inside #wpsl-filter-options, not as the
371         * #wpsl-radius / #wpsl-category dropdowns the other templates use.
372         *
373         * @since   3.0.0
374         * @returns {void}
375         */
376        reset: function () {
377            const $filterOptions = jQuery( '#wpsl-filter-options' );
378
379            if ( ! $filterOptions.length ) {
380                return;
381            }
382
383            const dropdownHandler = filters.dropdowns.getHandler();
384            const defaultRadius   = parseInt( config.search.restrictions.radius );
385            const $defaultRadius  = $filterOptions.find( '[data-handler="radius"] li[data-radius="' + defaultRadius + '"]' ).first();
386
387            // Reuse the click handler so the radius ends up in the same state a
388            // user selection leaves behind, button labels included. It clears
389            // aria-selected on every filter item, so it has to run before the
390            // checkboxes below set theirs.
391            if ( $defaultRadius.length ) {
392                dropdownHandler.handlers.radius( $defaultRadius );
393            }
394
395            // Back to the state the markup was rendered in, so a default
396            // category set on the settings page stays selected.
397            $filterOptions.find( 'input[type="checkbox"]' ).each( function () {
398                const $checkbox = jQuery( this );
399                const isDefault = !! $checkbox.prop( 'defaultChecked' );
400
401                $checkbox.prop( 'checked', isDefault );
402
403                accessibility.aria.setSelected( $checkbox.closest( 'li' ), isDefault );
404            } );
405
406            dropdownHandler.updateFilterCounts();
407            dropdownHandler.updateParentActiveState();
408
409            filters.advanced.updateNestedCounts();
410            filters.advanced.toggleFilterActions();
411
412            jQuery( '#wpsl-remove-filters' ).toggle( $filterOptions.find( 'input[type="checkbox"]:checked' ).length > 0 );
413        },
414    },
415
416    // Initialize the shared dropdown handler with dependencies
417    _sharedDropdowns: null,
418
419    dropdowns: {
420        /**
421         * Get or create the shared dropdown handler instance.
422         * 
423         * @since   3.0.0
424         * @returns {Object} The shared dropdown handler
425         */
426        getHandler: function () {
427            if ( ! filters._sharedDropdowns ) {
428                filters._sharedDropdowns = createDropdowns( helpers, config );
429            }
430
431            return filters._sharedDropdowns;
432        },
433
434        /**
435         * Check what kind of dropdowns / filters we are using.
436         *
437         * @since   3.0.0
438         * @returns {void}
439         */
440        checkStyle: function () {
441            // A panel ( flexbox ) template has no native-select path: the
442            // fallback below is skipped for it, so every value collector reads
443            // the styled markup. Neither the filter nor a coarse pointer may
444            // leave it unbuilt, or the values are silently dropped.
445            const hasPanel = jQuery( '#wpsl-panel' ).length > 0;
446
447            if ( jQuery( '.wpsl-dropdown' ).length && ( hasPanel || ( ! helpers.isTouchPrimary() && config.ux.enableStyledDropdowns ) ) ) {
448                filters.dropdowns.create();
449            } else if ( ! hasPanel ) {
450                jQuery( '#wpsl-search-wrap select' ).show();
451
452                if ( helpers.isTouchPrimary() ) {
453                    jQuery( '#wpsl-wrap' ).addClass( 'wpsl-mobile' );
454
455                    config.isMobile = 1;
456                } else {
457                    config.defaultFilters = 1;
458                }
459            }
460
461            // Line the search bar column up ( default/horizontal, v3 )
462            helpers.template.alignSearchColumns();
463
464            // Reveal the search bar now the column is aligned ( it is hidden
465            // from first paint by a visibility rule in styles.css ).
466            jQuery( '#wpsl-wrap' ).addClass( 'wpsl-labels-aligned' );
467
468            if ( document.fonts && document.fonts.ready ) {
469                document.fonts.ready.then( function () {
470                    helpers.template.alignSearchColumns();
471                } );
472            }
473
474            jQuery( window )
475                .off( 'resize.wpslLabels', helpers.template.alignSearchColumns )
476                .on( 'resize.wpslLabels', helpers.template.alignSearchColumns );
477        },
478
479        /**
480         * Create the styled dropdown filters.
481         *
482         * @since   1.2.24
483         * @returns {void}
484         */
485        create: function () {
486            const handler = this.getHandler();
487            handler.create();
488        },
489
490        /**
491         * Reset radius, max results, category, country and any custom
492         * dropdowns back to their default values.
493         *
494         * @since   3.0.0
495         * @returns {void}
496         */
497        reset: function () {
498            const defaults = {
499                'wpsl-radius': {
500                    value: config.search.restrictions.radius,
501                    text: config.search.restrictions.radius + ' ' + config.search.distanceUnit
502                },
503                'wpsl-results': {
504                    value: config.search.restrictions.maxResults,
505                    text: config.search.restrictions.maxResults
506                }
507            };
508
509            // Reset radius and max results
510            Object.keys( defaults ).forEach( id => {
511                const data = defaults[id];
512                const $dropdown = jQuery( '#' + id );
513
514                if ( ! $dropdown.length ) return;
515
516                // Reset native select
517                $dropdown.find( 'select' ).val( parseInt( data.text ) );
518
519                // Reset styled dropdowns
520                $dropdown.find( 'li' ).removeClass( 'wpsl-selected-dropdown' );
521
522                // Find and select the default option
523                $dropdown.find( 'li' ).each(function () {
524                    if ( jQuery( this ).text() === String( data.text ) ) {
525                        jQuery( this ).addClass( 'wpsl-selected-dropdown' );
526
527                        $dropdown.find( '.wpsl-selected-item' ).html( sharedHelpers.escapeHtml( data.text ) ).attr( 'data-value', data.value );
528                    }
529                });
530            });
531
532            // Reset the category and country filters
533            jQuery.each( ['wpsl-category', 'wpsl-country'], function ( index, value ) {
534                if ( jQuery( '#' + value + '' ).length ) {
535                    jQuery( '#' + value + ' select' ).val( 0 );
536                    jQuery( '#' + value + ' li' ).removeClass();
537                    jQuery( '#' + value + ' li:first-child' ).addClass( 'wpsl-selected-dropdown' );
538
539                    const catText = jQuery( '#' + value + ' li:first-child' ).text();
540
541                    jQuery( '#' + value + ' .wpsl-selected-item' ).html( sharedHelpers.escapeHtml( catText ) ).attr( 'data-value', 0 );
542                }
543            });
544
545            // If any custom dropdowns exist, then we reset them as well.
546            if ( jQuery( '.wpsl-custom-dropdown' ).length > 0 ) {
547                jQuery( '.wpsl-custom-dropdown' ).each( function() {
548                    // Check if we are dealing with the styled dropdowns, or the default select dropdowns.
549                    if ( ! config.defaultFilters ) {
550                        const $customDiv = jQuery( this ).siblings( 'div' );
551                        const $customFirstLi = $customDiv.find( 'li:first-child' );
552                        const customSelectedText = $customFirstLi.text();
553                        const customSelectedData = $customFirstLi.attr( 'data-value' );
554
555                        $customDiv.find( 'li' ).removeClass();
556                        $customDiv.prev().html( sharedHelpers.escapeHtml( customSelectedText ) ).attr( 'data-value', customSelectedData );
557                    } else {
558                        jQuery( this ).find( 'option' ).removeAttr( 'selected' );
559                    }
560                });
561            }
562        },
563
564        /**
565         * Get the values from custom dropdowns.
566         *
567         * @since   3.0.0
568         * @param   {object} ajaxData Data used in the AJAX request
569         * @returns {object} ajaxData with the custom dropdown values added
570         */
571        getCustomValues: function( ajaxData ) {
572            jQuery( '.wpsl-custom-dropdown' ).each( function() {
573                const name = jQuery( this ).attr( 'name' );
574                let value;
575
576                // Check if we are dealing with the styled dropdowns, or the
577                // default select dropdowns. Styled dropdowns keep the selected
578                // value on the button in front of the sibling listbox -- the
579                // same element the reset branch writes it back to.
580                if ( ! config.isMobile && ! config.defaultFilters ) {
581                    value = jQuery( this ).siblings( 'div' ).prev().attr( 'data-value' );
582                } else {
583                    value = jQuery( this ).val();
584                }
585
586                if ( name && value ) {
587                    ajaxData[name] = value;
588                }
589            });
590
591            return ajaxData;
592        },
593
594        /**
595         * Get the max results and radius restrictions.
596         *
597         * @since  3.0.0
598         * @param  {boolean} useDefault Whether or not to use the user selected values
599         *                              or the values from the settings page.
600         * @return {object}  params     The values from the max results and search radius dropdowns
601         */
602        getRestrictions: function( useDefault = false ) {
603            const params = {};
604
605            if ( useDefault ) {
606                if ( typeof config.search.restrictions.maxResults !== 'undefined' ) {
607                    params.max_results = config.search.restrictions.maxResults;
608                }
609
610                params.search_radius = config.search.restrictions.radius;
611            } else {
612                if ( config.isMobile || config.defaultFilters ) {
613                    params.max_results = parseInt( jQuery( '#wpsl-results .wpsl-dropdown' ).val() );
614                    params.search_radius = parseInt( jQuery( '#wpsl-radius .wpsl-dropdown' ).val() );
615                } else if ( jQuery( '[data-id="wpsl-show-radius"]' ).length ) {
616                    params.search_radius = jQuery( '[data-id="wpsl-show-radius"]' ).find( 'li[aria-selected="true"]' ).data( 'radius' );
617                } else {
618                    params.max_results = parseInt( jQuery( '#wpsl-results .wpsl-selected-item' ).attr( 'data-value' ) );
619                    params.search_radius = parseInt( jQuery( '#wpsl-radius .wpsl-selected-item' ).attr( 'data-value' ) );
620                }
621
622                // No need to pass the search radius if it's not
623                // a location search and the value is NaN anyway.
624                if ( ! helpers.search.locationSearchActive() && isNaN( parseInt( params.search_radius ) ) ) {
625                    delete params.search_radius;
626                }
627
628                if ( isNaN( parseInt( params.max_results ) ) ) {
629                    params.max_results = config.search.restrictions.maxResults;
630                }
631
632                if ( ! config.search.skipGeocode ) {
633                    if ( isNaN( parseInt( params.search_radius ) ) ) {
634                        params.search_radius = config.search.restrictions.radius;
635                    }
636                } else {
637                    // Remove search_radius for category-only searches (no location = no distance calculation)
638                    delete params.search_radius;
639                }
640            }
641
642            return params;
643        },
644
645        /**
646         * Get the ID of the active category.
647         *
648         * @since  3.0.0
649         * @return {object} The search params holding the active category ID
650         */
651        getSelectedId: function () {
652            let categoryId,
653                params = {};
654
655            if ( config.isMobile || config.defaultFilters ) {
656                categoryId = parseInt( jQuery( '#wpsl-category .wpsl-dropdown' ).val() );
657            } else if ( jQuery( '#wpsl-panel' ).length ) {
658                const checkedValues = [];
659
660                jQuery( '[data-id="wpsl-show-filters"] [data-type="category"] input[type="checkbox"]:checked' ).each( function() {
661                    const val = parseInt( jQuery( this ).val() );
662
663                    if ( ! isNaN( val ) && val !== 0 ) {
664                        checkedValues.push( val );
665                    }
666                } );
667
668                if ( checkedValues.length > 0 ) {
669                    params.filter = checkedValues.join( ',' );
670                }
671
672                return params;
673            } else {
674                categoryId = parseInt( jQuery( '#wpsl-category .wpsl-selected-item' ).attr( 'data-value' ) );
675            }
676
677            if ( ( ! isNaN( categoryId ) && ( categoryId !== 0 ) ) ) {
678                params.filter = categoryId;
679            }
680
681            return params;
682        },
683
684        /**
685         * Get the selected option data from a dropdown.
686         *
687         * @since   3.0.0
688         * @param   {string} id ID of the target select element.
689         * @returns {object} Object containing value and text of the selected option.
690         */
691        getSelectedOption: function( id ) {
692            const selectedData = {
693                value: '',
694                text: ''
695            };
696
697            if ( config.ux.enableStyledDropdowns ) {
698                // For styled dropdowns, get data from the button element
699                const $button = jQuery( '#' + id ).next( 'button' );
700
701                if ( $button.length ) {
702                    selectedData.value = $button.attr( 'data-value' ) || '';
703                    selectedData.text = $button.text() || '';
704                }
705            } else {
706                // For regular select elements, get the selected option
707                const $selected = jQuery( '#' + id + ' option:selected' );
708
709                if ( $selected.length ) {
710                    selectedData.value = $selected.val() || '';
711                    selectedData.text = $selected.text() || '';
712                }
713            }
714
715            return selectedData;
716        },
717
718        /**
719         * Change the default selected dropdown option.
720         *
721         * @since   3.0.0
722         * @param   {string} id    ID of the target element.
723         * @param   {string} value The value of the selected option.
724         * @returns {void}
725         */
726        setSelectedOption: function( id, value ) {
727            let $listbox,
728                $selected = jQuery( '#' + id + ' option[value=' + value + ']' );
729
730            if ( config.ux.enableStyledDropdowns ) {
731                jQuery( '#' + id + '' ).next( 'button' ).text( $selected.text() ).val( $selected.val() ).attr( 'data-value', $selected.val() );
732
733                $listbox = jQuery( '#' + id + '' ).next( 'div' ).find( 'ul' );
734                $listbox.find( 'li' ).removeClass( 'wpsl-selected-dropdown' );
735                $listbox.find( 'li[data-value=' + value + ']' ).addClass( 'wpsl-selected-dropdown' );
736            }
737
738            // Remove other possible selected options
739            jQuery( '#' + id + ' option' ).removeAttr( 'selected' );
740            $selected.attr( 'selected', 'selected' );
741
742            jQuery( '#wpsl-search-wrap input[type=hidden]' ).val( '' );
743        }
744    },
745    checkboxes: {
746        /**
747         * Collect the ids of the checked checkboxes.
748         *
749         * @since  2.2.0
750         * @return {string} catIds The cat ids from the checkboxes.
751         */
752        getSelectedIds: function() {
753            let catIds = jQuery( '#wpsl-checkbox-filter input:checked' ).map( function() {
754                return jQuery( this ).val();
755            } );
756
757            catIds = catIds.get();
758            catIds = catIds.join( ',' );
759
760            return catIds;
761        },
762
763        /**
764         * Get custom checkbox values by data-name group.
765         *
766         * If multiple selection are made, then the returned
767         * values are comma separated.
768         *
769         * @since  2.2.8
770         * @param  {object} ajaxData Data used in the AJAX request
771         * @return {object} ajaxData Data used in the AJAX request with the custom checkbox values
772         */
773        getCustomValues: function( ajaxData ) {
774            jQuery( '.wpsl-custom-checkboxes' ).each( function() {
775                const $list      = jQuery( this );
776                const searchType = $list.attr( 'data-search-type' );
777                const dataName   = $list.attr( 'data-name' );
778
779                // Without one of these we don't know where to map the values to.
780                if ( ! searchType && ! dataName ) {
781                    return;
782                }
783
784                const checkBoxValues = [];
785
786                $list.find( 'input:checked' ).each( function() {
787                    const currentValue = jQuery( this ).val();
788                    if ( currentValue ) {
789                        checkBoxValues.push( currentValue );
790                    }
791                } );
792
793                if ( ! checkBoxValues.length ) {
794                    return;
795                }
796
797                if ( searchType ) {
798
799                    // Reuse the built-in country / state search handlers by
800                    // nesting the values under location[<type>] and adding the
801                    // type to the types= list.
802                    ajaxData = helpers.search.setSearchTypeArgs( ajaxData, searchType, checkBoxValues.join( ',' ) );
803                } else {
804                    ajaxData[dataName] = checkBoxValues.join( ',' );
805                }
806            } );
807
808            return ajaxData;
809        }
810    },
811    hidden: {
812        /**
813         * Get values from hidden input fields.
814         *
815         * @since   3.0.0
816         * @param   {object} ajaxData Data used in the AJAX request
817         * @returns {object} ajaxData with hidden input values added
818         */
819        getCustomValues: function( ajaxData ) {
820            jQuery( '#wpsl-search-wrap input[type="hidden"]' ).each( function() {
821                ajaxData = filters.setCustomInputData( this, ajaxData );
822            } );
823
824            return ajaxData;
825        }
826    },
827    radio: {
828        /**
829         * Get values from custom radio button groups.
830         *
831         * @since   3.0.0
832         * @param   {object} ajaxData Data used in the AJAX request
833         * @returns {object} ajaxData with radio button values added
834         */
835        getCustomValues: function( ajaxData ) {
836            jQuery( '.wpsl-custom-radiobuttons' ).each( function() {
837                jQuery.each( jQuery( this ).find( 'input[type="radio"]' ).filter( ':checked' ), function() {
838                    ajaxData = filters.setCustomInputData( this, ajaxData );
839                } );
840            } );
841
842            return ajaxData;
843        }
844    },
845    
846    input: {
847        /**
848         * Get values from custom input fields.
849         *
850         * @since   3.0.0
851         * @param   {object} ajaxData Data used in the AJAX request
852         * @returns {object} ajaxData with custom input values added
853         */
854        getCustomValues: function( ajaxData ) {
855            jQuery( '.wpsl-custom-input' ).each( function() {
856                jQuery.each( jQuery( this ), function() {
857                    ajaxData = filters.setCustomInputData( this, ajaxData );
858                } );
859            } );
860
861            return ajaxData;
862        }
863    },
864
865    /**
866     * Assign the name / value for custom radio buttons
867     * or hidden input fields to the ajaxData object.
868     *
869     * @since   3.0.0
870     * @param   {HTMLElement} elem     The input element
871     * @param   {object}      ajaxData Data used in the AJAX request
872     * @returns {object}      ajaxData with the element's name/value added
873     */
874    setCustomInputData: function( elem, ajaxData ) {
875        const name = elem.name;
876        const value = elem.value;
877
878        if ( name && value ) {
879            ajaxData[name] = value;
880        }
881
882        return ajaxData;
883    },
884
885    /**
886     * Collect data from all possible custom elements.
887     * So dropdowns, checkboxes, radio buttons and
888     * (hidden) input fields.
889     *
890     * @since   3.0.0
891     * @returns {object} customValues All values from custom elements.
892     */
893    getAllCustomValues: function() {
894        const customValues = {};
895
896        if ( jQuery( '.wpsl-custom-dropdown' ).length > 0 ) {
897            jQuery.extend( customValues, filters.dropdowns.getCustomValues( customValues ) );
898        }
899
900        if ( jQuery( '.wpsl-custom-checkboxes' ).length > 0 ) {
901            jQuery.extend( customValues, filters.checkboxes.getCustomValues( customValues ) );
902        }
903
904        // If the name value is set to 'type', then it can be used to run
905        // custom code that only returns data from specific meta fields.
906        //
907        // /@todo add link to docu
908        if ( jQuery( '#wpsl-search-wrap input[type="hidden"]' ).length > 0 ) {
909            jQuery.extend( customValues, filters.hidden.getCustomValues( customValues ) );
910        }
911
912        if ( jQuery( '.wpsl-custom-radiobuttons' ).length > 0 ) {
913            jQuery.extend( customValues, filters.radio.getCustomValues( customValues ) );
914        }
915
916        if ( jQuery( '.wpsl-custom-input' ).length > 0 ) {
917            jQuery.extend( customValues, filters.input.getCustomValues( customValues ) );
918        }
919
920        return customValues;
921    },
922    /**
923     * Return all possible input values.
924     *
925     * @since   3.0.0
926     * @returns {object} args All input values, so default + custom ones and search args for filters.
927     */
928    grabAllValues: function() {
929        const args = filters.getAllCustomValues();
930
931        if ( jQuery( '#wpsl-results' ).length || jQuery( '#wpsl-radius' ).length || jQuery( '[data-id="wpsl-show-radius"]' ).length ) {
932            jQuery.extend( args, filters.dropdowns.getRestrictions() );
933        }
934
935        if ( helpers.hasCategoryFilter() ) {
936            jQuery.extend( args, filters.dropdowns.getSelectedId() );
937        }
938
939        if ( jQuery( '#wpsl-checkbox-filter' ).length ) {
940            args.filter = filters.checkboxes.getSelectedIds();
941
942            // If no user input exist, or the input field isn't present, then
943            // make sure to return all location from the selected category.
944            if ( args.filter.length > 0 ) {
945                if ( ! jQuery( '#wpsl-search-input' ).val() || ! jQuery( '#wpsl-search-input' ).length ) {
946                    args.types = 'category';
947                }
948            } else {
949                delete args.filter;
950            }
951        }
952
953        return args;
954    },
955    /**
956     * Listen for changes to custom
957     * input fields like radio and checkboxes.
958     *
959     * @since 3.0.0
960     */
961    customInputFieldListeners: function() {
962        const fields = wp.hooks.applyFilters( 'wpslCustomAutoSubmitInputFields', [
963            { '$elem': jQuery( '.wpsl-custom-radiobuttons' ), 'inputType': 'radio' },
964            { '$elem': jQuery( '.wpsl-custom-checkboxes' ), 'inputType': 'checkbox' }
965        ] );
966
967        let args = {};
968
969        jQuery.each( fields, function( index ) {
970            if ( typeof fields[index].$elem !== 'undefined' && typeof fields[index].inputType !== 'undefined' ) {
971                jQuery.each( fields[index].$elem, function() {
972                    jQuery( this ).find( 'input[type="' + fields[index].inputType + '"]' ).on( 'change', function() {
973                        args = filters.grabAllValues();
974                        search.autoSubmit( args );
975                    } );
976                } );
977            }
978        } );
979    },
980
981    /**
982     * Reset all possible input / filter fields
983     * back to the default state.
984     *
985     * @since   3.0.0
986     * @returns {void}
987     */
988    resetAll: function() {
989        filters.dropdowns.reset();
990        filters.advanced.reset();
991
992        jQuery( '#wpsl-search-input' ).val( '' );
993        jQuery( '.wpsl-search-wrap' ).removeClass( 'wpsl-error' );
994        jQuery( '#wpsl-search-wrap input[type=text]' ).val( '' );
995
996        jQuery.each( jQuery( '#wpsl-search-wrap input[type=checkbox], #wpsl-search-wrap input[type=radio]' ), function() {
997            if ( ! jQuery( this ).attr( 'checked' ) ) {
998                jQuery( this ).prop( 'checked', false );
999            } else {
1000                jQuery( this ).prop( 'checked', true );
1001            }
1002        } );
1003    },
1004
1005    /**
1006     * Set specific selections for dropdowns and checkboxes
1007     *
1008     * @since 3.0.0
1009     * @param {Object} selections Object containing the selections to set
1010     *                           Format: { dropdowns: { 'dropdown-id': 'value' }, checkboxes: { 'checkbox-id': true/false } }
1011     */
1012    setSelection: function( selections ) {
1013        let catText, $customDiv, $customLi, customSelectedText, customSelectedData;
1014
1015        // Set dropdown selections
1016        if ( selections.dropdowns ) {
1017            jQuery.each( selections.dropdowns, function( dropdownId, selectedValue ) {
1018                if ( jQuery( '#' + dropdownId ).length ) {
1019                    const $targetLi = jQuery( '#' + dropdownId + ' li[data-value="' + selectedValue + '"]' );
1020
1021                    if ( $targetLi.length ) {
1022                        jQuery( '#' + dropdownId + ' select' ).val( selectedValue );
1023
1024                        // Update the visual dropdown
1025                        jQuery( '#' + dropdownId + ' li' ).removeClass( 'wpsl-selected-dropdown' );
1026                        $targetLi.addClass( 'wpsl-selected-dropdown' );
1027
1028                        catText = $targetLi.text();
1029                        jQuery( '#' + dropdownId + ' .wpsl-selected-item' ).html( sharedHelpers.escapeHtml( catText ) ).attr( 'data-value', selectedValue );
1030                    }
1031                }
1032            } );
1033        }
1034
1035        // Set custom dropdown selections
1036        if ( selections.customDropdowns ) {
1037            jQuery.each( selections.customDropdowns, function( dropdownClass, selectedValue ) {
1038                const $customDropdown = jQuery( '.' + dropdownClass );
1039
1040                if ( $customDropdown.length ) {
1041                    // Check if we are dealing with the styled dropdowns, or the default select dropdowns
1042                    if ( ! config.defaultFilters ) {
1043                        $customDiv = $customDropdown.siblings( 'div' );
1044                        $customLi = $customDiv.find( 'li[data-value="' + selectedValue + '"]' );
1045
1046                        if ( $customLi.length ) {
1047                            customSelectedText = $customLi.text();
1048                            customSelectedData = $customLi.attr( 'data-value' );
1049
1050                            $customDiv.find( 'li' ).removeClass( 'wpsl-selected-dropdown' );
1051                            $customLi.addClass( 'wpsl-selected-dropdown' );
1052                            $customDiv.prev().html( sharedHelpers.escapeHtml( customSelectedText ) ).attr( 'data-value', customSelectedData );
1053                        }
1054                    } else {
1055                        $customDropdown.find( 'option' ).removeAttr( 'selected' );
1056                        $customDropdown.find( 'option[value="' + selectedValue + '"]' ).attr( 'selected', 'selected' );
1057                    }
1058                }
1059            } );
1060        }
1061
1062        // Set checkbox/radio selections
1063        if ( selections.checkboxes ) {
1064            jQuery.each( selections.checkboxes, function( checkboxId, isChecked ) {
1065                const $checkbox = jQuery( '#' + checkboxId );
1066
1067                if ( $checkbox.length ) {
1068                    $checkbox.prop( 'checked', isChecked );
1069                }
1070            } );
1071        }
1072    },
1073
1074    /**
1075     * Map focus handling - delegated to accessibility module.
1076     * 
1077     * @since 3.0.0
1078     * @deprecated Use accessibility.map instead
1079     */
1080    mapFocus: accessibility.map
1081};

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.