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.