PageSourceSearch

https://mountaincabinsutah.com/wp-content/plugins/homerunner/assets/js/explorer.js?ver=2.6.7

js mountaincabinsutah.com collected 2026-10-10 02:01:44 UTC 102,194 bytes, 2,991 lines download raw bytes

1(function ($, settings) {
2  $.fn.homelocalExplorer = function (options) {
3    const $container = $(this);
4
5    options = $.extend({}, options);
6
7    let page = 1;
8    let cachedProperties = [];
9    let favorites = localStorage.getItem("homelocal_favorites")
10      ? localStorage.getItem("homelocal_favorites").split(",")
11      : [];
12
13    let filterChangeTimer = null;
14
15    let fullPriceQueued = [];
16    let fullPriceProcessing = false;
17
18    let selector = {
19      rangeCalendar: `#hfse-form-daterange-${options.id}`,
20      priceSliderContainer: ".hfse-form-price-slider-container",
21      resetFormBtn: `#hfse-form-reset-btn-${options.id}`,
22      whereModal: `#hfse-form-where-modal-${options.id}`,
23      zoneModal: `#hfse-form-zone-modal-${options.id}`,
24      dateModal: `#hfse-form-date-modal-${options.id}`,
25      guestsModal: `#hfse-form-guests-modal-${options.id}`,
26      additionalFilterModalBtn: ".hfse-form-additional-filters-btn",
27      additionalFiltersModal: `#hfse-form-additional-filters-modal-${options.id}`,
28      additionalFiltersResetBtn: ".hfse-clear-additional-filters-btn",
29    };
30    let mapInstance = null;
31    let mapSearchActive = false;
32
33    let showingRecommendations = false;
34
35    const daterangepickerEl = document.getElementById(
36      `hfse-form-daterange-${options.id}`,
37    );
38
39    const priceSliderEl = document.getElementById(
40      `hfse-form-price-slider-${options.id}`,
41    );
42
43    const trigger_ga4 = settings.analytics_trigger_ga4 === "1";
44
45    const debounce = (func, timeout = 300) => {
46      let timer;
47      return (...args) => {
48        clearTimeout(timer);
49        timer = setTimeout(() => {
50          func.apply(this, args);
51        }, timeout);
52      };
53    };
54
55    const isGtagAvailable = () => {
56      return typeof gtag === "function";
57    };
58
59    const app = this;
60
61    // Filter values pushed in from an external filter panel (Searchbar Pro etc.)
62    // via window.hrExplorer.applyParams(). Overlaid last in getParams().
63    app.externalParams = {};
64
65    app.isDateQuery = () => {
66      if (showingRecommendations) {
67        return false;
68      }
69
70      const $checking = $(selector.dateModal).find('input[name="checkin"]');
71      const $checkout = $(selector.dateModal).find('input[name="checkout"]');
72
73      if (
74        $checking.length &&
75        $checkout.length &&
76        "" !== $checking.val() &&
77        "" !== $checkout.val()
78      ) {
79        return true;
80      }
81
82      return false;
83    };
84
85    app.isMobile = () => {
86      let agent = navigator.userAgent || navigator.vendor || window.opera;
87      return (
88        /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
89          agent,
90        ) ||
91        /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|
91sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
92          agent.substr(0, 4),
93        )
94      );
95    };
96
97    app.inFavorite = (id) => {
98      return favorites.indexOf(id.toString()) > -1;
99    };
100
101    app.addFavorite = (id) => {
102      favorites.push(id.toString());
103      localStorage.setItem("homelocal_favorites", favorites.join(","));
104    };
105
106    app.removeFavorite = (id) => {
107      var index = favorites.indexOf(id.toString());
108      if (index !== -1) {
109        favorites.splice(index, 1);
110        localStorage.setItem("homelocal_favorites", favorites.join(","));
111      }
112    };
113
114    app.clearFilterChangeTimer = () => {
115      clearTimeout(filterChangeTimer);
116    };
117
118    app.modalActions = (fieldModal, fieldContainer) => {
119      const $modal = $(fieldModal);
120      const $nextField = $container.find(fieldContainer).next();
121      const nextModal = app.getFieldModal($nextField);
122
123      if (options.cta === "1") {
124        $modal.find(".hfse-modal-next-button").removeClass("hk-hidden");
125
126        if (!nextModal) {
127          $modal.find(".hk-modal-body").append(`
128            <div class="hk-margin-xlarge">
129              <button class="uk-button uk-button-primary hk-button hk-width-1-1 hk-button-primary hfse-modal-next-button" type="button">${"Search"}</button>
130            </div>
131          `);
132          $modal
133            .find(".hk-modal-footer .hfse-modal-next-button")
134            .addClass("hk-hidden");
135        }
136      }
137
138      $modal.on("modified", function () {
139        $modal.addClass("hfse-modified");
140        $modal.find(".hfse-modal-reset-button").removeClass("hk-hidden");
141        $modal.find(".hk-modal-footer").removeClass("hk-hidden");
142
143        return false;
144      });
145
146      $(document).on("beforehide", fieldModal, function () {
147        if ($modal.hasClass("hfse-modified")) {
148          $modal.removeClass("hfse-modified");
149
150          clearTimeout(filterChangeTimer);
151
152          filterChangeTimer = setTimeout(() => {
153            app.performFilter();
154          }, 5);
155        }
156      });
157
158      $(document).on("shown", fieldModal, function () {
159        if (
160          $modal.find(".hfse-modal-next-button").is(":hidden") &&
161          $modal.find(".hfse-modal-reset-button").is(":hidden")
162        ) {
163          $modal.find(".hk-modal-footer").addClass("hk-hidden");
164        }
165      });
166
167      $modal.on("click", ".hfse-modal-reset-button", function () {
168        $modal.trigger("reset");
169        $modal.addClass("hfse-modified");
170
171        // Clear corresponding query_params when modal filter is cleared
172        // This ensures URL params don't override the cleared form values
173        if (fieldModal === selector.dateModal) {
174          delete options.query_params.checkin;
175          delete options.query_params.checkout;
176        } else if (fieldModal === selector.whereModal) {
177          delete options.query_params.location;
178          delete options.query_params.state;
179          delete options.query_params.city;
180          delete options.query_params.group;
181        } else if (fieldModal === selector.zoneModal) {
182          delete options.query_params.location;
183          delete options.query_params.city;
184        } else if (fieldModal === selector.guestsModal) {
185          delete options.query_params.adults;
186          delete options.query_params.children;
187          delete options.query_params.pets;
188        }
189
190        if (
191          !$modal.find(".hfse-modal-reset-button").hasClass("hfse-show-always")
192        ) {
193          $modal.find(".hfse-modal-reset-button").addClass("hk-hidden");
194
195          if ($modal.find(".hfse-modal-next-button").is(":hidden")) {
196            $modal.find(".hk-modal-footer").addClass("hk-hidden");
197          }
198        }
199
200        // hkUIkit.modal(fieldModal).hide();
201
202        return false;
203      });
204
205      $modal.on("click", ".hfse-modal-next-button", function () {
206        hkUIkit.modal(fieldModal).hide();
207
208        if (options.cta === "1") {
209          if (nextModal) {
210            hkUIkit.modal(nextModal).show();
211          } else {
212            app.onFormSubmit();
213          }
214        }
215
216        return false;
217      });
218    };
219
220    app.propertiesLoadSuccess = (properties, textStatus, xhr) => {
221      const totalProperties =
222        parseInt(xhr.getResponseHeader("x-wp-total"), 10) || 0;
223
224      $container
225        .find(".hfse-form-properties-count-text")
226        .html(settings.properties_count_text.replace("%d", totalProperties));
227      $container
228        .find(".hfse-form-properties-count-container")
229        .removeClass("hk-hidden");
230
231      let totalPages = xhr.getResponseHeader("x-wp-totalpages");
232      if (totalPages > page) {
233        if (
234          $container.find(".hfse-properties-more-container").length < 1 &&
235          "0" === options.hide_more_btn
236        ) {
237          $container.find(".hfse-properties-container").append(
238            `<div class="hk-margin-top hfse-properties-more-container">
239							<button class="uk-button uk-button-primary hk-button hk-button-primary hk-align-center hfse-properties-more-btn" type="button">${settings.load_more_btn_text}</button>
240						</div>`,
241          );
242        }
243      } else {
244        $container.find(".hfse-properties-more-container").remove();
245      }
246
247      if ($container.find(".hfse-properties-no-items").length > 0) {
248        $container.find(".hfse-properties-no-items").remove();
249      }
250
251      if (totalProperties === 0) {
252        if (
253          ("1" === options.recommendation_show_featured ||
254            options.recommendation_tags.length > 0) &&
255          options.filter &&
256          options.shortcode_params.favorite !== "1"
257        ) {
258          $container
259            .find(".hfse-properties-container")
260            .prepend(
261              `<div class="hfse-properties-no-items">${options.no_items_text}${options.recommendation_text}</div>`,
262            );
263
264          const onAlways = () => {
265            $container
266              .find(".hfse-properties-more-btn")
267              .prop("disabled", false);
268            $container.removeClass("loading");
269            $container.find(selector.resetFormBtn).removeClass("hk-hidden");
270          };
271
272          app.loadRecommendedProperties(onAlways);
273        } else {
274          $container
275            .find(".hfse-properties-container")
276            .prepend(
277              `<div class="hfse-properties-no-items">${options.no_items_text}</div>`,
278            );
279        }
280      }
281    };
282
283    app.updatePriceOrderFields = () => {
284      if (app.priceToDisplay() === "none") {
285        $container
286          .find('select[name="sort"] option[value="price__asc"]')
287          .hide();
288        $container
289          .find('select[name="sort"] option[value="price__desc"]')
290          .hide();
291      } else {
292        $container
293          .find('select[name="sort"] option[value="price__asc"]')
294          .show();
295        $container
296          .find('select[name="sort"] option[value="price__desc"]')
297          .show();
298      }
299    };
300
301    app.setExplorerParams = (params) => {
302      const $guestsModal = $(selector.guestsModal);
303
304      $guestsModal.find('input[name="adults"]').val(params.adults || 1);
305      $guestsModal.find('input[name="children"]').val(params.children || 0);
306
307      if (params.pets) {
308        $guestsModal
309          .find('input[name="pets"][value="yes"]')
310          .prop("checked", true);
311      } else {
312        $guestsModal
313          .find('input[name="pets"][value="no"]')
314          .prop("checked", true);
315      }
316
317      app.updateGuestsInput();
318
319      $additionalFiltersModal = $(selector.additionalFiltersModal);
320
321      if (params.amenities) {
322        $additionalFiltersModal
323          .find(`input[name="amenity[]"]`)
324          .each(function (i, item) {
325            if (params.amenities.indexOf($(item).val()) !== -1) {
326              $(item).prop("checked", true);
327            } else {
328              $(item).prop("checked", false);
329            }
330          });
331      }
332
333      if (params.types) {
334        $additionalFiltersModal
335          .find(`input[name="type[]"]`)
336          .each(function (i, item) {
337            if (params.types.indexOf($(item).val()) !== -1) {
338              $(item).prop("checked", true);
339            } else {
340              $(item).prop("checked", false);
341            }
342          });
343      }
344
345      if (params.title__like) {
346        $additionalFiltersModal
347          .find(`input[name="title__like"]`)
348          .val(params.title__like);
349      }
350
351      const $zoneModal = $(selector.zoneModal);
352
353      if ($container.find(".hfse-form-zone-options-trigger").length > 0) {
354        // locations.
355        if (params.locations) {
356          let location_slugs = [];
357          $zoneModal.find(".hfse-form-zone-location").each(function (i, item) {
358            if (params.locations.indexOf($(item).data("slug")) !== -1) {
359              location_slugs.push($(item).data("slug"));
360              $(item).addClass("hfse-selected");
361            } else {
362              $(item).removeClass("hfse-selected");
363            }
364          });
365
366          $zoneModal
367            .find('input[name="location"]')
368            .val(location_slugs.join(","));
369        }
370
371        // cities
372        if (params.cities) {
373          let city_slugs = [];
374          $zoneModal.find(".hfse-form-zone-city").each(function (i, item) {
375            if (params.cities.indexOf($(item).data("slug")) !== -1) {
376              city_slugs.push($(item).data("slug"));
377              $(item).addClass("hfse-selected");
378            } else {
379              $(item).removeClass("hfse-selected");
380            }
381          });
382
383          $zoneModal.find('input[name="city"]').val(city_slugs.join(","));
384        }
385      }
386
387      // hfse-form-where-modal
388      if ($container.find(".hfse-form-where-input").length > 0) {
389        let whereValues = [];
390
391        if (params.groups) {
392          whereValues = params.groups;
393        } else if (params.cities) {
394          whereValues = params.cities;
395        } else if (params.locations) {
396          whereValues = params.locations;
397        }
398
399        if (whereValues.length > 0) {
400          $(selector.whereModal)
401            .find(".hfse-form-where-item")
402            .each(function (i, item) {
403              if (whereValues.indexOf($(item).data("slug")) !== -1) {
404                $(item).find('input[type="checkbox"]').prop("checked", true);
405              } else {
406                $(item).find('input[type="checkbox"]').prop("checked", false);
407              }
408            });
409        }
410      }
411
412      app.updateZoneLabels();
413      app.updateWhereInput();
414
415      $additionalFiltersModal
416        .find('input[name="bedrooms"]')
417        .val(params.bedrooms || 0);
418
419      $additionalFiltersModal
420        .find('input[name="bathrooms"]')
421        .val(params.bathrooms || 0);
422
423      app.changeAdditionalFiltersModalBtnState();
424
425      if (params.checkin && params.checkout) {
426        $(selector.dateModal).find('input[name="checkin"]').val(params.checkin);
427        $(document).trigger(
428          "homelocal_range_calendar_set_checkin_" + `explorer-${options.id}`,
429          [params.checkin, true],
430        );
431        $(selector.dateModal)
432          .find('input[name="checkout"]')
433          .val(params.checkout);
434
435        $(document).trigger(
436          "homelocal_range_calendar_set_checkout_" + `explorer-${options.id}`,
437          [params.checkout, true],
438        );
439      } else {
440        $(selector.dateModal)
441          .find('input[name="checkin"],input[name="checkout"]')
442          .val("");
443        $(document).trigger(
444          "homelocal_range_calendar_reset_selection_" +
445            `explorer-${options.id}`,
446          [params.checkin, true],
447        );
448      }
449
450      app.updateDatesLabel();
451      app.updateNightsCount();
452      app.priceSliderReset();
453
454      // remaining - types, cities & destinations.
455    };
456
457    app.onFormSubmit = () => {
458      if (options.grid === "0") {
459        let redirectUrl = $container.find(".hfse-filters").attr("action");
460
461        if ("" !== app.getRedirectUrlParams()) {
462          redirectUrl += "?" + app.getRedirectUrlParams();
463        }
464
465        window.location.href = redirectUrl;
466        return false;
467      }
468
469      clearTimeout(filterChangeTimer);
470      app.performFilter();
471      app.gtmPropertySearch("submit");
472
473      return false;
474    };
475
476    app.onFormReset = (e) => {
477      e.preventDefault();
478
479      $container.trigger("reset");
480
481      options.query_params = {};
482
483      if ($container.has(".with-map")) {
484        $container.removeClass("map-hidden").addClass("map-visible");
485      }
486
487      clearTimeout(filterChangeTimer);
488
489      filterChangeTimer = setTimeout(() => {
490        app.performFilter();
491        app.gtmPropertySearch("reset");
492        app.hideResetFormBtn();
493      }, 5);
494
495      return false;
496    };
497
498    app.initFilters = () => {
499      app.initZones();
500      app.initWhere();
501      app.initGuests();
502      app.initCalendar();
503      app.initAdditionalFilters();
504      app.initSort();
505
506      app.updateNightsCount();
507      app.numberControlsInit();
508
509      // submission
510      $container.on("submit", ".hfse-filters", app.onFormSubmit);
511
512      // reset
513      $container.on("click", selector.resetFormBtn, app.onFormReset);
514    };
515
516    app.initAiSearch = () => {
517      $container.homelocalExplorerAiSearch(app, options, settings).init();
518    };
519
520    app.initRelevanceSearch = () => {
521      $container
522        .homelocalExplorerRelevanceSearch(app, options, settings)
523        .init();
524    };
525
526    app.initGrid = () => {
527      $container.append(`
528				<div class="hfse-loader"></div>
529			`);
530
531      if ("1" === options.infinite_scroll) {
532        $container.homelocalExplorerScroll().init();
533      }
534
535      $container.on("click", ".hfse-property-favorite-toggle-btn", function () {
536        const propertyId = $(this).parent().data("id");
537
538        if (app.inFavorite(propertyId)) {
539          app.removeFavorite(propertyId);
540          $(this).removeClass("favorite");
541        } else {
542          app.addFavorite(propertyId);
543          $(this).addClass("favorite");
544        }
545
546        return false;
547      });
548
549      $container.on("click", ".hfse-properties-more-btn", function (e) {
550        e.preventDefault();
551
552        const $btn = $(this);
553
554        $btn.prop("disabled", true);
555        $btn.addClass("loading");
556
557        const onAlways = () => {
558          $btn.prop("disabled", false);
559          $container.removeClass("loading");
560        };
561
562        page += 1;
563        app.loadProperties(
564          page,
565          app.getParams(),
566          app.propertiesLoadSuccess,
567          onAlways,
568        );
569
570        return false;
571      });
572
573      $container.addClass("loading");
574
575      app.loadProperties(1, app.getParams(), app.propertiesLoadSuccess, () => {
576        $container.removeClass("loading");
577      });
578
579      if (options.template == "slider") {
580        $container
581          .find(".hfse-body")
582          .addClass("hk-slider hk-visible-toggle hk-position-relative");
583        $container
584          .find(".hfse-properties-container")
585          .addClass("hk-slider-container hk-visible-toggle");
586        if (options.slider_nav_outside) {
587          $container.find(".hfse-properties-container").after(
588            `
589        	<a hk-slider-item="previous" hk-slidenav-previous class="el-slidenav hk-hidden-hover hk-position-center-left-out hk-icon hk-slidenav hk-sli
589denav-previous hfse-properties-slider-prev-btn" href="#"></a>
590        	<a hk-slider-item="next" hk-slidenav-next class="el-slidenav hk-hidden-hover hk-position-center-right-out hk-icon hk-slidenav hk-slidenav-next hfse-properties-slider-next-btn" href="#"></a>
591        	`,
592          );
593        } else {
594          $container.find(".hfse-properties-container").after(
595            `
596        	<a hk-slider-item="previous" hk-slidenav-previous class="el-slidenav hk-hidden-hover hk-position-center-left hk-icon hk-slidenav hk-slidenav-previous hfse-properties-slider-prev-btn" href="#"></a>
597        	<a hk-slider-item="next" hk-slidenav-next class="el-slidenav hk-hidden-hover hk-position-center-right hk-icon hk-slidenav hk-slidenav-next hfse-properties-slider-next-btn" href="#"></a>
598        	`,
599          );
600        }
601        $container.find(".hfse-properties").addClass("hk-slider-items");
602        hkUIkit.slider(".hfse-body", {
603          showPrevNextButton: false,
604          finite: "0" === options.infinite_slider,
605          autoplay: options.autoplay,
606          autoplayInterval: options.autoplay_interval,
607        });
608      }
609
610      $container.on("click", ".hfse-property-card", function () {
611        app.gtmSelectItem($(this).data("id"));
612      });
613    };
614
615    app.initMap = () => {
616      const onMapSearchStatusChange = (status) => {
617        mapSearchActive = status;
618
619        debounce(app.performFilter, 500)();
620      };
621
622      const onMapPositionChange = (event) => {
623        if (!mapSearchActive) {
624          return;
625        }
626
627        debounce(app.performFilter, 500)();
628      };
629
630      const mapOptions = $.extend({}, options, {
631        map_search_label: settings.map_search_label,
632        onMapSearchStatusChange: onMapSearchStatusChange,
633        onMapPositionChange: onMapPositionChange,
634      });
635
636      mapInstance = $container.homelocalExplorerMap(mapOptions, settings);
637      mapInstance.init();
638
639      $container.find(".hfse-form-map-toggle-btn").removeClass("hk-hidden");
640
641      // toggle map
642      $container.on("click", ".hfse-form-map-toggle-btn", function (e) {
643        e.preventDefault();
644
645        if ($container.hasClass("map-visible")) {
646          $(this)
647            .find(".hfse-form-map-toggle-btn-text")
648            .text(settings.show_map);
649
650          $container.removeClass("map-visible").addClass("map-hidden");
651          $container.find("[name=map_hidden]").val("1");
652
653          hkUIkit.update(document.body, "update");
654
655          mapInstance.closeTooltip();
656          mapInstance.resetMapMarkers();
657
658          app.updateUrl();
659        } else {
660          $(this)
661            .find(".hfse-form-map-toggle-btn-text")
662            .text(settings.hide_map);
663
664          $container.addClass("map-visible").removeClass("map-hidden");
665          $container.find("[name=map_hidden]").val("0");
666
667          hkUIkit.update(document.body, "update");
668
669          mapInstance.updateMarkers();
670
671          app.updateUrl();
672        }
673      });
674
675      $container.on("mouseenter", ".hfse-property-container", function () {
676        if (mapSearchActive) {
677          return;
678        }
679
680        const propId = $(this).data("id");
681        mapInstance.focusMarker(propId);
682      });
683
684      $container.on("mouseleave", ".hfse-property-container", function () {
685        if (mapSearchActive) {
686          return;
687        }
688
689        const propId = $(this).data("id");
690        mapInstance.blurMarker(propId);
691      });
692    };
693
694    app.updateGuestsInput = () => {
695      const $modal = $(selector.guestsModal);
696
697      let adults = $modal.find("input[name=adults]").val() || 1;
698      let children = $modal.find("input[name=children]").val() || 1;
699      let guests = parseInt(adults, 10) + parseInt(children, 10);
700
701      let value = guests + "";
702      if ($modal.find("input[name=pets]").length > 0) {
703        if ("yes" == $modal.find("input[name=pets]:checked").val()) {
704          value += ", Pets";
705        }
706      }
707
708      $container.find(".hfse-form-guests-value").val(value);
709    };
710
711    app.initGuests = () => {
712      const $modal = $(selector.guestsModal);
713
714      const reset = () => {
715        $modal.find("input[name=adults]").val("1");
716        $modal.find("input[name=children]").val("0");
717        $modal.find("input[name=pets]").first().prop("checked", true);
718        app.updateGuestsInput();
719      };
720
721      const modified = () => {
722        $(selector.guestsModal).trigger("modified");
723        app.updateGuestsInput();
724      };
725
726      $container.on("reset", reset);
727      $(selector.guestsModal).on("reset", reset);
728
729      $modal.on(
730        "change",
731        "input[name=adults],input[name=children],input[name=pets]",
732        modified,
733      );
734
735      app.updateGuestsInput();
736
737      app.modalActions(
738        selector.guestsModal,
739        ".hfse-form-guests-field-container",
740      );
741
742      if (app.isMobile()) {
743        $modal.find(".number-input").attr("readonly", true);
744      }
745    };
746
747    app.initCalendar = () => {
748      if (!daterangepickerEl) {
749        return false;
750      }
751
752      const $modal = $(selector.dateModal);
753
754      $(selector.rangeCalendar).homelocalRangeCalendar({
755        id: `explorer-${options.id}`,
756        selector: selector.rangeCalendar,
757        max_num_days: settings.max_num_days,
758        min_stay: options.min_stay,
759        max_stay: settings.max_stay,
760        start_date: $modal.find('input[name="checkin"]').val() || false,
761        end_date: $modal.find('input[name="checkout"]').val() || false,
762        modal: selector.dateModal,
763        setDates: function (checkin, checkout) {
764          $modal.find('input[name="checkin"]').val(checkin);
765          $modal.find('input[name="checkout"]').val(checkout);
766
767          app.updateDatesLabel();
768
769          $modal.trigger("modified");
770          $modal.trigger("dateSelected");
771
772          hkUIkit.modal(selector.dateModal).hide(0);
773
774          app.updateNightsCount();
775          app.priceSliderReset();
776        },
777      });
778
779      app.updateDatesLabel();
780
781      const onReset = () => {
782        $(document).trigger(
783          `homelocal_range_calendar_reset_selection_explorer-${options.id}`,
784        );
785
786        $modal.find('input[name="checkin"],input[name="checkout"]').val("");
787        $container.find(".hfse-form-date-input").val("");
788
789        app.updateNightsCount();
790      };
791
792      $container.on("reset", onReset);
793      $modal.on("reset", onReset);
794      $modal.on("reset", app.priceSliderReset);
795      $modal.on("reset", app.updatePriceOrderFields);
796
797      app.modalActions(selector.dateModal, ".hfse-form-date-field-container");
798    };
799
800    app.updateDatesLabel = () => {
801      const checkin = $(selector.dateModal).find('input[name="checkin"]').val();
802      const checkout = $(selector.dateModal)
803        .find('input[name="checkout"]')
804        .val();
805      if (checkin && checkout) {
806        const { DateTime } = luxon;
807        const fromDate = DateTime.fromISO(checkin, { zone: settings.timezone });
808        const toDate = DateTime.fromISO(checkout, { zone: settings.timezone });
809
810        $container
811          .find(".hfse-form-date-input")
812          .val(
813            `${fromDate.toFormat(options.date_format)} - ${toDate.toFormat(
814              options.date_format,
815            )}`,
816          );
817      } else {
818        $container.find(".hfse-form-date-input").val("");
819      }
820    };
821
822    app.getDateZero = (dateStr = null) => {
823      const date = dateStr ? new Date(dateStr) : new Date();
824
825      // remove offset.
826      date.setTime(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
827
828      // set hour to zero.
829      date.setHours(0, 0, 0, 0);
830
831      return date;
832    };
833
834    app.initZones = () => {
835      if ($container.find(".hfse-form-zone-options-trigger").length === 0) {
836        return;
837      }
838
839      const $trigger = $container.find(".hfse-form-zone-options-trigger");
840      const $modal = $(selector.zoneModal);
841      const $selections = $modal.find(".hfse-form-zone-selections");
842      const $keyword = $modal.find(".hfse-form-zone-keyword");
843
844      let selectionChanged = false;
845      let keywordChanged = false;
846      const zoneMultiselect = options.zone_multiselect === "1";
847
848      let location_slugs = [];
849
850      if ($modal.find('input[name="location"]').length > 0) {
851        let location_slug = $modal
852          .find('input[name="location"]')
853          .val()
854          .toString();
855
856        location_slugs = location_slug ? location_slug.split(",") : [];
857      }
858
859      let city_slugs = [];
860
861      if ($modal.find('input[name="city"]').length > 0) {
862        let city_slug = $modal.find('input[name="city"]').val().toString();
863        city_slugs = city_slug ? city_slug.split(",") : [];
864      }
865
866      const updateItemsVisibility = (keyword = "") => {
867        let visibleLocCount = 0;
868        $modal.find(".hfse-form-zone-location").each(function () {
869          const name = $(this).find(".name").text();
870          if (name.toLowerCase().indexOf(keyword.toLowerCase()) !== -1) {
871            visibleLocCount++;
872            $(this).removeClass("hk-hidden");
873          } else {
874            $(this).addClass("hk-hidden");
875          }
876        });
877
878        if (visibleLocCount === 0) {
879          $modal
880            .find(
881              ".hfse-form-zone-label-locations,.hfse-form-zone-subfield-locations",
882            )
883            .addClass("hk-hidden");
884        } else {
885          $modal
886            .find(
887              ".hfse-form-zone-label-locations,.hfse-form-zone-subfield-locations",
888            )
889            .removeClass("hk-hidden");
890        }
891
892        let visibleCityCount = 0;
893        $modal.find(".hfse-form-zone-city").each(function (i, el) {
894          const name =
895            $(this).find(".name").text() +
896            " " +
897            $(this).find(".subheading").text();
898          if (name.toLowerCase().indexOf(keyword.toLowerCase()) !== -1) {
899            visibleCityCount++;
900            $(this).removeClass("hk-hidden");
901          } else {
902            $(this).addClass("hk-hidden");
903          }
904        });
905
906        if (visibleCityCount === 0) {
907          $modal
908            .find(
909              ".hfse-form-zone-label-cities,.hfse-form-zone-subfield-cities",
910            )
911            .addClass("hk-hidden");
912        } else {
913          $modal
914            .find(
915              ".hfse-form-zone-label-cities,.hfse-form-zone-subfield-cities",
916            )
917            .removeClass("hk-hidden");
918        }
919
920        if (visibleLocCount === 0 && visibleCityCount === 0) {
921          $modal.find(".hfse-form-zone-no-results").removeClass("hk-hidden");
922        } else {
923          $modal.find(".hfse-form-zone-no-results").addClass("hk-hidden");
924        }
925      };
926
927      const reset = () => {
928        location_slugs = [];
929        city_slugs = [];
930
931        $selections.empty();
932        $trigger.val("");
933
934        if ($keyword.length > 0) {
935          $keyword.val("").show();
936        }
937
938        $modal.find('input[name="location"]').val("");
939        $modal.find('input[name="city"]').val("");
940        $modal.find(".hfse-form-zone-btn").removeClass("hfse-selected");
941      };
942
943      const onSelectionChange = (e) => {
944        const $item = $(e.currentTarget);
945
946        // add location id to the input
947        const value = $item.data("slug").toString();
948
949        if ($keyword.length > 0) {
950          if (zoneMultiselect) {
951            $keyword.show();
952          } else {
953            $keyword.hide();
954          }
955        }
956
957        if ($item.hasClass("hfse-form-zone-location")) {
958          if (!zoneMultiselect) {
959            location_slugs = [value];
960            city_slugs = [];
961
962            $modal.find(".hfse-form-zone-btn").removeClass("hfse-selected");
963            $item.addClass("hfse-selected");
964          } else if (location_slugs.indexOf(value) === -1) {
965            location_slugs.push(value);
966
967            $item.addClass("hfse-selected");
968          } else {
969            location_slugs = location_slugs.filter((id) => id !== value);
970
971            $item.removeClass("hfse-selected");
972
973            if ($keyword.length > 0) {
974              $keyword.show();
975            }
976          }
977        } else if ($item.hasClass("hfse-form-zone-city")) {
978          if (!zoneMultiselect) {
979            location_slugs = [];
980            city_slugs = [value];
981
982            $modal.find(".hfse-form-zone-btn").removeClass("hfse-selected");
983            $item.addClass("hfse-selected");
984          } else if (city_slugs.indexOf(value) === -1) {
985            city_slugs.push(value);
986
987            $item.addClass("hfse-selected");
988          } else {
989            city_slugs = city_slugs.filter((id) => id !== value);
990
991            $item.removeClass("hfse-selected");
992
993            if ($keyword.length > 0) {
994              $keyword.show();
995            }
996          }
997        }
998
999        $modal.find('input[name="location"]').val(location_slugs.join(","));
1000        $modal.find('input[name="city"]').val(city_slugs.join(","));
1001
1002        app.updateZoneLabels();
1003        updateItemsVisibility("");
1004
1005        selectionChanged = true;
1006        $modal.trigger("modified");
1007
1008        if (!zoneMultiselect) {
1009          hkUIkit.modal(selector.zoneModal).hide();
1010        }
1011      };
1012
1013      $modal.on("click", ".hfse-form-zone-btn", onSelectionChange);
1014
1015      $modal.on("click", ".hfse-form-zone-selection", function () {
1016        const value = $(this).data("slug").toString();
1017        const type = $(this).data("type");
1018
1019        if ("location" === type && location_slugs.indexOf(value) !== -1) {
1020          location_slugs = location_slugs.filter((val) => val !== value);
1021          $modal
1022            .find(".hfse-form-zone-btn[data-slug='" + value + "']")
1023            .removeClass("hfse-selected");
1024        }
1025
1026        if ("city" === type && city_slugs.indexOf(value) !== -1) {
1027          city_slugs = city_slugs.filter((val) => val !== value);
1028
1029          $modal
1030            .find(".hfse-form-zone-btn[data-slug='" + value + "']")
1031            .removeClass("hfse-selected");
1032        }
1033
1034        $modal.find('input[name="location"]').val(location_slugs.join(","));
1035        $modal.find('input[name="city"]').val(city_slugs.join(","));
1036
1037        if ($keyword.length > 0) {
1038          $keyword.show();
1039        }
1040
1041        app.updateZoneLabels();
1042        updateItemsVisibility("");
1043
1044        selectionChanged = true;
1045        $modal.trigger("modified");
1046
1047        if (!zoneMultiselect) {
1048          hkUIkit.modal(selector.zoneModal).hide();
1049        }
1050      });
1051
1052      if ($keyword.length > 0) {
1053        $modal.on("keyup keydown", ".hfse-form-zone-keyword", function () {
1054          keywordChanged = true;
1055          updateItemsVisibility($(this).val());
1056        });
1057
1058        let hideTimeout = null;
1059        $modal.on("blur", ".hfse-form-zone-keyword", function () {
1060          clearTimeout(hideTimeout);
1061          hideTimeout = setTimeout(() => {
1062            $keyword.val("");
1063            updateItemsVisibility("");
1064          }, 100);
1065        });
1066      }
1067
1068      $(document).on("beforeshow", selector.zoneModal, function () {
1069        updateItemsVisibility("");
1070      });
1071
1072      $container.on("reset", reset);
1073      $modal.on("reset", reset);
1074
1075      app.modalActions(selector.zoneModal, ".hfse-form-zone-field-container");
1076    };
1077
1078    app.updateZoneLabels = () => {
1079      const $trigger = $container.find(".hfse-form-zone-options-trigger");
1080      const $modal = $(selector.zoneModal);
1081      const $selections = $modal.find(".hfse-form-zone-selections");
1082      const $keyword = $modal.find(".hfse-form-zone-keyword");
1083
1084      let labels = [];
1085      let names = [];
1086
1087      let location_slug = "";
1088      if ($modal.find('input[name="location"]').length > 0) {
1089        location_slug = $modal.find('input[name="location"]').val().toString();
1090      }
1091
1092      let location_slugs = location_slug ? location_slug.split(",") : [];
1093
1094      let city_slug = "";
1095      if ($modal.find('input[name="city"]').length > 0) {
1096        city_slug = $modal.find('input[name="city"]').val().toString();
1097      }
1098
1099      let city_slugs = city_slug ? city_slug.split(",") : [];
1100
1101      if (location_slugs.length > 0) {
1102        labels = labels.concat(
1103          location_slugs.map((slug) => {
1104            return (
1105              `<span class="hfse-form-zone-selection" data-type="location" data-slug="${slug}">` +
1106              $modal
1107                .find(`.hfse-form-zone-location[data-slug="${slug}"]`)
1108                .find(".name")
1109                .text() +
1110              ` x</span>`
1111            );
1112          }),
1113        );
1114
1115        names = names.concat(
1116          location_slugs.map((slug) => {
1117            return $modal
1118              .find(`.hfse-form-zone-location[data-slug="${slug}"]`)
1119              .find(".name")
1120              .text();
1121          }),
1122        );
1123      }
1124
1125      if (city_slugs.length > 0) {
1126        labels = labels.concat(
1127          city_slugs.map((slug) => {
1128            return (
1129              `<span class="hfse-form-zone-selection" data-type="city" data-slug="${slug}">` +
1130              $modal
1131                .find(`.hfse-form-zone-city[data-slug="${slug}"]`)
1132                .find(".name")
1133                .text() +
1134              ` x</span>`
1135            );
1136          }),
1137        );
1138
1139        names = names.concat(
1140          city_slugs.map((slug) => {
1141            return $modal
1142              .find(`.hfse-form-zone-city[data-slug="${slug}"]`)
1143              .find(".name")
1144              .text();
1145          }),
1146        );
1147      }
1148
1149      $selections.html(labels.join(""));
1150      $trigger.val(names.join(", "));
1151
1152      if ($keyword.length > 0) {
1153        $keyword.val("");
1154      }
1155    };
1156
1157    app.updateWhereInput = () => {
1158      let $selectedTerms = [];
1159      $(selector.whereModal)
1160        .find("input[type=checkbox]")
1161        .each(function () {
1162          this.checked ? $selectedTerms.push($(this).data("name")) : "";
1163        });
1164
1165      $container
1166        .find(".hfse-form-where-input")
1167        .val($selectedTerms.length > 0 ? $selectedTerms.join(", ") : "");
1168    };
1169
1170    app.initWhere = () => {
1171      const $modal = $(selector.whereModal);
1172
1173      app.updateWhereInput();
1174
1175      const reset = () => {
1176        $modal.find("input[type=checkbox]").prop("checked", false);
1177        app.updateWhereInput();
1178      };
1179
1180      const modified = () => {
1181        $modal.trigger("modified");
1182        app.updateWhereInput();
1183      };
1184
1185      $container.on("reset", reset);
1186      $modal.on("reset", reset);
1187
1188      $modal.on("change", "input[type=checkbox]", modified);
1189
1190      app.modalActions(selector.whereModal, ".hfse-form-term-field-container");
1191    };
1192
1193    app.initCta = () => {
1194      const updateVisibility = () => {
1195        if (app.isMobile()) {
1196          $container.find(".hfse-form-cta-fields").show();
1197          $container
1198            .find(".hfse-form-primary-fields, .hfse-form-secondary-fields")
1199            .hide();
1200        } else {
1201          $container.find(".hfse-form-cta-fields").hide();
1202          $container
1203            .find(".hfse-form-primary-fields, .hfse-form-secondary-fields")
1204            .show();
1205        }
1206      };
1207
1208      $(window).on("resize", updateVisibility);
1209
1210      updateVisibility();
1211
1212      $container.on("click", ".hfse-form-cta-input", function (e) {
1213        const $firstField = $container.find(
1214          ".hfse-form-primary-fields>div:first",
1215        );
1216        const firstModal = app.getFieldModal($firstField);
1217
1218        if (
1219          firstModal &&
1220          $(firstModal).length > 0 &&
1221          $(firstModal).is(":hidden")
1222        ) {
1223          hkUIkit.modal(firstModal).show();
1224        }
1225      });
1226
1227      if (options.zone_multiselect !== "1") {
1228        $(selector.zoneModal).on("modified", function () {
1229          const $nextField = $container
1230            .find(".hfse-form-zone-field-container")
1231            .next();
1232          const nextModal = app.getFieldModal($nextField);
1233
1234          if (
1235            nextModal &&
1236            $(nextModal).length > 0 &&
1237            $(nextModal).is(":hidden")
1238          ) {
1239            hkUIkit.modal(nextModal).show();
1240          }
1241        });
1242      }
1243
1244      $(selector.dateModal).on("dateSelected", function () {
1245        const $nextField = $container
1246          .find(".hfse-form-date-field-container")
1247          .next();
1248        const nextModal = app.getFieldModal($nextField);
1249
1250        if (
1251          nextModal &&
1252          $(nextModal).length > 0 &&
1253          $(nextModal).is(":hidden")
1254        ) {
1255          hkUIkit.modal(nextModal).show();
1256        }
1257      });
1258    };
1259
1260    app.initSort = () => {
1261      // other filters.
1262      $container.on("change", 'select[name="sort"]', function () {
1263        clearTimeout(filterChangeTimer);
1264        filterChangeTimer = setTimeout(() => {
1265          app.performFilter();
1266          app.gtmPropertySearch("sort");
1267        }, 5);
1268      });
1269
1270      $container.on("reset", function () {
1271        $container.find('select[name="sort"]').val(options.default_params.sort);
1272      });
1273
1274      app.updatePriceOrderFields();
1275    };
1276
1277    app.initAdditionalFilters = () => {
1278      const $modal = $(selector.additionalFiltersModal);
1279
1280      const applyFilters = (e) => {
1281        e.preventDefault();
1282        app.performFilter();
1283        app.gtmPropertySearch("additional_filter");
1284        app.changeAdditionalFiltersModalBtnState();
1285        hkUIkit.modal(selector.additionalFiltersModal).hide(0);
1286        return false;
1287      };
1288
1289      const resetFilters = () => {
1290        $modal
1291          .find(`input[name="bedrooms"]`)
1292          .val(options.default_params.bedrooms);
1293
1294        $modal
1295          .find(`input[name="bathrooms"]`)
1296          .val(options.default_params.bathrooms);
1297
1298        $modal.find(`input[name="type[]"]`).each(function (i, item) {
1299          $(item).prop(
1300            "checked",
1301            options.default_params.type.indexOf(item) > -1,
1302          );
1303        });
1304
1305        $modal.find(`input[name="amenity[]"]`).each(function (i, item) {
1306          $(item).prop(
1307            "checked",
1308            options.default_params.amenity.indexOf(item) > -1,
1309          );
1310        });
1311
1312        $modal
1313          .find(`input[name="featured"]`)
1314          .prop("checked", options.default_params.featured === "1");
1315
1316        $modal
1317          .find(`input[name="favorite"]`)
1318          .prop("checked", options.default_params.favorite === "1");
1319
1320        $modal.find(`input[name="title__like"]`).val("");
1321
1322        app.priceSliderReset();
1323        app.updatePriceOrderFields();
1324
1325        $modal.find(selector.additionalFiltersResetBtn).addClass("hk-hidden");
1326
1327        app.changeAdditionalFiltersModalBtnState();
1328
1329        delete options.query_params.amenities;
1330        delete options.query_params.title__like;
1331        delete options.query_params.types;
1332        delete options.query_params.bedrooms;
1333        delete options.query_params.bathrooms;
1334        delete options.query_params.favorite;
1335        delete options.query_params.featured;
1336      };
1337
1338      $modal.on(
1339        "change",
1340        ":input",
1341        app.changeAdditionalFiltersClearBtnVisibility,
1342      );
1343
1344      $(document).on(
1345        "beforeshow",
1346        selector.additionalFiltersModal,
1347        app.changeAdditionalFiltersClearBtnVisibility,
1348      );
1349
1350      app.changeAdditionalFiltersModalBtnState();
1351
1352      $modal.on("click", ".hfse-apply-additional-filters-btn", applyFilters);
1353
1354      $modal.on("click", selector.additionalFiltersResetBtn, function (e) {
1355        e.preventDefault();
1356        resetFilters();
1357        return false;
1358      });
1359
1360      $container.on("reset", resetFilters);
1361
1362      const $amenities = $modal.find(".filter-feature");
1363
1364      if ($amenities.length > options.primary_features_number) {
1365        $amenities.slice(options.primary_features_number).hide();
1366        $modal
1367          .find(".filter-feature")
1368          .last()
1369          .after(
1370            `<div class="hk-width-1-1 hfse-all-amenities-btn-container">
1371        		<button type="button" class="uk-button uk-button-link hk-button hk-button-link hfse-all-amenities-btn">${settings.more_amenity_btn_text}</button>
1372        	</div>`,
1373          );
1374      }
1375
1376      $modal.on("click", ".hfse-all-amenities-btn", function (e) {
1377        e.preventDefault();
1378        $(this).parent("div").remove();
1379        $amenities.show();
1380        return false;
1381      });
1382
1383      // apply filter on title__like input enter.
1384      $modal.on("keypress", 'input[name="title__like"]', function (e) {
1385        if (e.key === "Enter" && e.currentTarget.value) {
1386          applyFilters(e);
1387        }
1388      });
1389
1390      app.initPriceSlider();
1391    };
1392
1393    app.priceToDisplay = () => {
1394      return app.isDateQuery()
1395        ? options.with_dates_price
1396        : options.no_dates_price;
1397    };
1398
1399    app.initPriceSlider = () => {
1400      if (!priceSliderEl) {
1401        return;
1402      }
1403
1404      $modal = $(selector.additionalFiltersModal);
1405
1406      const range = app.getPriceRange(app.priceToDisplay());
1407
1408      let price_min = parseFloat($modal.find('input[name="price_min"]').val());
1409      let price_max = parseFloat($modal.find('input[name="price_max"]').val());
1410
1411      if (!price_min || price_min < range.min) {
1412        price_min = parseFloat(range.min);
1413      }
1414
1415      if (!price_max || price_max > range.max) {
1416        price_max = parseFloat(range.max);
1417      }
1418
1419      // set default params, convert number to string.
1420      options.default_params.price_min = range.min + "";
1421      options.default_params.price_max = range.max + "";
1422
1423      noUiSlider.create(priceSliderEl, {
1424        range: range,
1425        step: parseInt(options.range_step, 10),
1426        margin: parseInt(options.range_step, 10),
1427        start: [price_min, price_max],
1428        connect: true,
1429        behaviour: "tap-drag",
1430        format: {
1431          to: function (value) {
1432            return Math.round(value);
1433          },
1434          from: function (value) {
1435            return Math.round(value);
1436          },
1437        },
1438      });
1439
1440      app.priceSliderUpdateNotation([price_min, price_max]);
1441
1442      // if (app.priceToDisplay() === "none") {
1443      //   $modal.find(selector.priceSliderContainer).addClass("hk-hidden");
1444      // } else {
1445      //   $modal.find(selector.priceSliderContainer).removeClass("hk-hidden");
1446      // }
1447
1448      // debounce drag / slide.
1449      priceSliderEl.noUiSlider.on(
1450        "slide",
1451        debounce(app.priceSliderUpdateNotation, 20),
1452      );
1453
1454      priceSliderEl.noUiSlider.on("set", app.priceSliderUpdateValues);
1455    };
1456
1457    app.priceSliderUpdateValues = (values) => {
1458      $modal = $(selector.additionalFiltersModal);
1459
1460      let changed = false;
1461      if ($modal.find('input[name="price_min"]').val() !== values[0]) {
1462        changed = true;
1463        $modal.find('input[name="price_min"]').val(values[0]);
1464      }
1465
1466      if ($modal.find('input[name="price_max"]').val() !== values[1]) {
1467        changed = true;
1468        $modal.find('input[name="price_max"]').val(values[1]);
1469      }
1470
1471      app.priceSliderUpdateNotation(values);
1472    };
1473
1474    app.priceSliderUpdateNotation = (values) => {
1475      $modal = $(selector.additionalFiltersModal);
1476      $modal
1477        .find(".hfse-form-price-slider-text")
1478        .text(`(${app.price(values[0])} - ${app.price(values[1])})`);
1479    };
1480
1481    app.priceSliderReset = () => {
1482      if (!priceSliderEl) {
1483        return;
1484      }
1485
1486      $modal = $(selector.additionalFiltersModal);
1487
1488      const range = app.getPriceRange(app.priceToDisplay());
1489
1490      // set default params.
1491      options.default_params.price_min = range.min + "";
1492      options.default_params.price_max = range.max + "";
1493
1494      priceSliderEl.noUiSlider.updateOptions({
1495        range: range,
1496        start: [parseFloat(range.min), parseFloat(range.max)],
1497      });
1498
1499      // if (app.priceToDisplay() === "none") {
1500      //   $modal.find(selector.priceSliderContainer).addClass("hk-hidden");
1501      // } else {
1502      //   $modal.find(selector.priceSliderContainer).removeClass("hk-hidden");
1503      // }
1504    };
1505
1506    app.formatPrice = (price) => {
1507      return price.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, "$&,");
1508    };
1509
1510    app.getPriceRange = (priceToDisplay = "none") => {
1511      // if (priceToDisplay === "none") {
1512      //   priceToDisplay = "average_price";
1513      // }
1514
1515      let range = { min: 0, max: 20000 };
1516
1517      if (options.price_range_new) {
1518        range = options.price_range_new;
1519      }
1520
1521      return range;
1522    };
1523
1524    app.getFieldModal = ($field) => {
1525      if ($field.hasClass("hfse-form-zone-field-container")) {
1526        return selector.zoneModal;
1527      } else if ($field.hasClass("hfse-form-term-field-container")) {
1528        return selector.whereModal;
1529      } else if ($field.hasClass("hfse-form-date-field-container")) {
1530        return selector.dateModal;
1531      } else if ($field.hasClass("hfse-form-guests-field-container")) {
1532        return selector.guestsModal;
1533        // } else if ($field.hasClass('hfse-additional-filters-field-container')) {
1534        //   return selector.additionalFiltersModal;
1535      }
1536
1537      return null;
1538    };
1539
1540    app.numberControlsInit = () => {
1541      const reduceBtnClick = function () {
1542        let prev_val = parseInt($(this).next().val(), 10) || 0;
1543        let min_val = parseInt($(this).next().attr("min"), 10) || 0;
1544        if (prev_val > 0 && prev_val > min_val) {
1545          $(this)
1546            .next()
1547            .val(prev_val - 1)
1548            .trigger("change");
1549        }
1550        return false;
1551      };
1552
1553      const increaseBtnClick = function () {
1554        let prev_val = parseInt($(this).prev().val(), 10) || 0;
1555        $(this)
1556          .prev()
1557          .val(prev_val + 1)
1558          .trigger("change");
1559        return false;
1560      };
1561
1562      $(selector.guestsModal).on(
1563        "click",
1564        ".hfse-number-with-control .reduce-btn",
1565        reduceBtnClick,
1566      );
1567
1568      $(selector.guestsModal).on(
1569        "click",
1570        ".hfse-number-with-control .increase-btn",
1571        increaseBtnClick,
1572      );
1573
1574      $(selector.additionalFiltersModal).on(
1575        "click",
1576        ".hfse-number-with-control .reduce-btn",
1577        reduceBtnClick,
1578      );
1579
1580      $(selector.additionalFiltersModal).on(
1581        "click",
1582        ".hfse-number-with-control .increase-btn",
1583        increaseBtnClick,
1584      );
1585    };
1586
1587    app.updateNightsCount = () => {
1588      const nights = app.getNightsCount();
1589
1590      if (nights > 0) {
1591        $container
1592          .find(".hfse-form-nights-count-container")
1593          .removeClass("hk-hidden");
1594        $container
1595          .find(".hfse-form-nights-count-text")
1596          .text(`${nights}${nights > 1 ? " Nights" : " Night"}`);
1597      } else {
1598        $container
1599          .find(".hfse-form-nights-count-container")
1600          .addClass("hk-hidden");
1601      }
1602    };
1603
1604    app.showResetFormBtn = () => {
1605      $container.find(selector.resetFormBtn).removeClass("hk-hidden");
1606    };
1607
1608    app.hideResetFormBtn = () => {
1609      $container.find(selector.resetFormBtn).addClass("hk-hidden");
1610    };
1611
1612    app.changeAdditionalFiltersClearBtnVisibility = () => {
1613      const $modal = $(selector.additionalFiltersModal);
1614
1615      if (app.additionalFiltersChanged()) {
1616        $modal
1617          .find(selector.additionalFiltersResetBtn)
1618          .removeClass("hk-hidden");
1619      } else {
1620        $modal.find(selector.additionalFiltersResetBtn).addClass("hk-hidden");
1621      }
1622    };
1623
1624    app.changeAdditionalFiltersModalBtnState = () => {
1625      if (app.additionalFiltersChanged()) {
1626        $container.find(selector.additionalFilterModalBtn).addClass("modified");
1627      } else {
1628        $container
1629          .find(selector.additionalFilterModalBtn)
1630          .removeClass("modified");
1631      }
1632    };
1633
1634    app.additionalFiltersChanged = () => {
1635      const $modal = $(selector.additionalFiltersModal);
1636
1637      let hasChanges = false;
1638      ["bedrooms", "bathrooms"].forEach(function (tf) {
1639        if (
1640          options.default_params[tf] !==
1641          $modal.find(`input[name="${tf}"]`).val()
1642        ) {
1643          hasChanges = true;
1644        }
1645      });
1646
1647      // check title__like.
1648      if ($modal.find(`input[name="title__like"]`).val()) {
1649        hasChanges = true;
1650      }
1651
1652      ["type[]", "amenity[]", "featured", "favorite"].forEach(function (tf) {
1653        if ($modal.find(`input[name="${tf}"]:checked`).length > 0) {
1654          hasChanges = true;
1655        }
1656      });
1657
1658      return hasChanges;
1659    };
1660
1661    app.price = (price, currency) => {
1662      return $.homelocalPrice(price, currency || settings.currency);
1663    };
1664
1665    app.getPropertyLoadingHtml = function (i) {
1666      let priceHtml = "";
1667
1668      if (
1669        (app.isDateQuery() && options.with_dates_price === "none") ||
1670        (!app.isDateQuery() && options.no_dates_price === "none")
1671      ) {
1672        // nothing..
1673      } else if (
1674        app.isDateQuery() &&
1675        options.with_dates_price === "average_price"
1676      ) {
1677        priceHtml = `${app.getPropertyPriceLoaderHtml()}${app.getPropertyTaxNoteLoaderHtml()}`;
1678      } else {
1679        priceHtml = app.getPropertyPriceLoaderHtml();
1680      }
1681
1682      if (priceHtml) {
1683        priceHtml = `<div class="hk-margin-small-top hfse-property-price-container">${priceHtml}</div>`;
1684      }
1685
1686      let ratioWidth = options.photos_aspect_ratio.split(":")[0] || 16;
1687      let ratioHeight = options.photos_aspect_ratio.split(":")[1] || 9;
1688      let imageLoaderHeight = Math.round(
1689        (options.image_width / ratioWidth) * ratioHeight,
1690      );
1691
1692      return `
1693        <div class="property-item hfse-property-container property-loading" data-ind="${i}">
1694          <div class="hk-card hk-card-default hk-card-hover hk-card-small hk-link-toggle hk-flex hk-flex-column hfse-property-card">
1695            <div class="hk-card-media-top hfse-property-media">
1696              <svg role="img" width="${options.image_width}" height="${imageLoaderHeight}" aria-labelledby="loading-aria" viewBox="0 0 ${options.image_width} ${imageLoaderHeight}"
1697                preserveAspectRatio="none">
1698                <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-thumbnail)" style='fill: url("#fill-thumbnail");'></rect>
1699                <defs>
1700                  <clipPath id="clip-path-thumbnail">
1701                    <rect x="0" y="0" rx="0" ry="0" width="${options.image_width}" height="${imageLoaderHeight}" />
1702                    <rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1703                  </clipPath>
1704                  <linearGradient id="fill-thumbnail">
1705                    <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1706                      <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1707                        repeatCount="indefinite"></animate>
1708                    </stop>
1709                    <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1710                      <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1711                        repeatCount="indefinite"></animate>
1712                    </stop>
1713                    <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1714                      <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1715                        repeatCount="indefinite"></animate>
1716                    </stop>
1717                  </linearGradient>
1718                </defs>
1719              </svg>
1720            </div>
1721            <div class="hk-card-body hk-margin-remove-first-child hfse-property-body">
1722              <h3 class="hk-h5 hfse-property-title">
1723                <svg role="img" width="300" height="24" aria-labelledby="loading-aria" viewBox="0 0 300 24" preserveAspectRatio="none">
1724                <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-title)" style='fill: url("#fill-title");'></rect>
1725                <defs>
1726                  <clipPath id="clip-path-title">
1727                    <rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1728                    <rect x="0" y="0" rx="0" ry="0" width="300" height="24" />
1729                  </clipPath>
1730                  <linearGradient id="fill-title">
1731                    <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1732                      <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1733                        repeatCount="indefinite"></animate>
1734                    </stop>
1735                    <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1736                      <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1737                        repeatCount="indefinite"></animate>
1738                    </stop>
1739                    <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1740                      <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1741                        repeatCount="indefinite"></animate>
1742                    </stop>
1743                  </linearGradient>
1744                </defs>
1745              </svg>
1746              </h3>
1747              ${priceHtml}
1748            </div>
1749
1750            <div class="hfse-property-meta">
1751              <div class="hfse-property-meta-primary">
1752                <div class="terms terms-left">
1753                  <svg role="img" width="60" height="20" aria-labelledby="loading-aria" viewBox="0 0 60 20" preserveAspectRatio="none">
1754                    <title id="loading-aria">Loading...</title>
1755                    <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-term)" style='fill: url("#fill-term");'></rect>
1756                    <defs>
1757                      <clipPath id="clip-path-term">
1758                        <rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1759                        <rect x="0" y="0" rx="0" ry="0" width="60" height="20" />
1760                      </clipPath>
1761                      <linearGradient id="fill-term">
1762                        <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1763                          <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1764                            repeatCount="indefinite"></animate>
1765                        </stop>
1766                        <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1767                          <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1768                            repeatCount="indefinite"></animate>
1769                        </stop>
1770                        <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1771                          <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1772                            repeatCount="indefinite"></animate>
1773                        </stop>
1774                      </linearGradient>
1775                    </defs>
1776                  </svg>
1777                </div>
1778                <div class="terms terms-right">
1779                  <svg role="img" width="60" height="20" aria-labelledby="loading-aria" viewBox="0 0 60 20" preserveAspectRatio="none">
1780                    <title id="loading-aria">Loading...</title>
1781                    <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-term)" style='fill: url("#fill-term");'></rect>
1782                    <defs>
1783                      <clipPath id="clip-path-term">
1784                        <rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1785                        <rect x="0" y="0" rx="0" ry="0" width="60" height="20" />
1786                      </clipPath>
1787                      <linearGradient id="fill-term">
1788                        <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1789                          <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1790                            repeatCount="indefinite"></animate>
1791                        </stop>
1792                        <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1793                          <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1794                            repeatCount="indefinite"></animate>
1795                        </stop>
1796                        <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1797                          <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1798                            repeatCount="indefinite"></animate>
1799                        </stop>
1800                      </linearGradient>
1801                    </defs>
1802                  </svg>
1803                </div>
1804              </div>
1805              <div class="hfse-property-meta-secondary">
1806                <div>
1807                  <svg role="img" width="40" height="20" aria-labelledby="loading-aria" viewBox="0 0 40 20" preserveAspectRatio="none">
1808                    <title id="loading-aria">Loading...</title>
1809                    <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-unit)" style='fill: url("#fill-unit");'></rect>
1810                    <defs>
1811                      <clipPath id="clip-path-unit">
1812                        <rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1813                        <rect x="0" y="0" rx="0" ry="0" width="40" height="20" />
1814                      </clipPath>
1815                      <linearGradient id="fill-unit">
1816                        <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1817                          <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1818                            repeatCount="indefinite"></animate>
1819                        </stop>
1820                        <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1821                          <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1822                            repeatCount="indefinite"></animate>
1823                        </stop>
1824                        <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1825                          <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1826                            repeatCount="indefinite"></animate>
1827                        </stop>
1828                      </linearGradient>
1829                    </defs>
1830                  </svg>
1831                </div>
1832                <div>
1833                  <svg role="img" width="40" height="20" aria-labelledby="loading-aria" viewBox="0 0 40 20" preserveAspectRatio="none">
1834                    <title id="loading-aria">Loading...</title>
1835                    <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-unit)" style='fill: url("#fill-unit");'></rect>
1836                    <defs>
1837                      <clipPath id="clip-path-unit">
1838                        <rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1839                        <rect x="0" y="0" rx="0" ry="0" width="40" height="20" />
1840                      </clipPath>
1841                      <linearGradient id="fill-unit">
1842                        <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1843                          <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1844                            repeatCount="indefinite"></animate>
1845                        </stop>
1846                        <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1847                          <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1848                            repeatCount="indefinite"></animate>
1849                        </stop>
1850                        <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1851                          <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1852                            repeatCount="indefinite"></animate>
1853                        </stop>
1854                      </linearGradient>
1855                    </defs>
1856                  </svg>
1857                </div>
1858                <div>
1859                  <svg role="img" width="40" height="20" aria-labelledby="loading-aria" viewBox="0 0 40 20" preserveAspectRatio="none">
1860                    <title id="loading-aria">Loading...</title>
1861                    <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-unit)" style='fill: url("#fill-unit");'></rect>
1862                    <defs>
1863                      <clipPath id="clip-path-unit">
1864                        <rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1865                        <rect x="0" y="0" rx="0" ry="0" width="40" height="20" />
1866                      </clipPath>
1867                      <linearGradient id="fill-unit">
1868                        <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1869                          <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1870                            repeatCount="indefinite"></animate>
1871                        </stop>
1872                        <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1873                          <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1874                            repeatCount="indefinite"></animate>
1875                        </stop>
1876                        <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1877                          <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1878                            repeatCount="indefinite"></animate>
1879                        </stop>
1880                      </linearGradient>
1881                    </defs>
1882                  </svg>
1883                </div>
1884              </div>
1885            </div>
1886          </div>
1887        </div>`;
1888    };
1889
1890    app.getPropertyPriceLoaderHtml = function () {
1891      return `<span class="hfse-property-price">
1892					<svg role="img" width="200" height="20" aria-labelledby="loading-aria" viewBox="0 0 200 20" preserveAspectRatio="none">
1893						<rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-price-main)" style='fill: url("#fill-price-main");'></rect>
1894						<defs>
1895							<clipPath id="clip-path-price-main">
1896								<rect x="197" y="253" rx="0" ry="0" width="9" height="0" />
1897								<rect x="0" y="0" rx="0" ry="0" width="200" height="20" />
1898							</clipPath>
1899							<linearGradient id="fill-price-main">
1900								<stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1901									<animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1902										repeatCount="indefinite"></animate>
1903								</stop>
1904								<stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1905									<animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1906										repeatCount="indefinite"></animate>
1907								</stop>
1908								<stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1909									<animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1910										repeatCount="indefinite"></animate>
1911								</stop>
1912							</linearGradient>
1913						</defs>
1914					</svg>
1915        </span>`;
1916    };
1917
1918    app.getPropertyTaxNoteLoaderHtml = function () {
1919      return `<div class="hk-text hk-text-small hfse-property-tax-note">
1920          <svg role="img" width="110" height="18" aria-labelledby="loading-aria" viewBox="0 0 110 18" preserveAspectRatio="none">
1921            <title id="loading-aria">Loading...</title>
1922            <rect x="0" y="0" width="100%" height="100%" clip-path="url(#clip-path-price-sub)" style='fill: url("#fill-price-sub");'></rect>
1923            <defs>
1924              <clipPath id="clip-path-price-sub">
1925                <rect x="0" y="0" rx="3" ry="3" width="110" height="18" />
1926              </clipPath>
1927              <linearGradient id="fill-price-sub">
1928                <stop offset="0.599964" stop-color="#f3f3f3" stop-opacity="1">
1929                  <animate attributeName="offset" values="-2; -2; 1" keyTimes="0; 0.25; 1" dur="2s"
1930                    repeatCount="indefinite"></animate>
1931                </stop>
1932                <stop offset="1.59996" stop-color="#ecebeb" stop-opacity="1">
1933                  <animate attributeName="offset" values="-1; -1; 2" keyTimes="0; 0.25; 1" dur="2s"
1934                    repeatCount="indefinite"></animate>
1935                </stop>
1936                <stop offset="2.59996" stop-color="#f3f3f3" stop-opacity="1">
1937                  <animate attributeName="offset" values="0; 0; 3" keyTimes="0; 0.25; 1" dur="2s"
1938                    repeatCount="indefinite"></animate>
1939                </stop>
1940              </linearGradient>
1941            </defs>
1942          </svg>
1943        </div>`;
1944    };
1945
1946    app.getPropertyHtml = function (property) {
1947      const {
1948        id,
1949        title,
1950        subtitle,
1951        url,
1952        image,
1953        total_rent_discounted,
1954        average_rent,
1955        average_rent_discounted,
1956        lowest_rent,
1957        groups,
1958        average_rating,
1959      } = property;
1960      const {
1961        base_daily_rate,
1962        maximum_guests: guests,
1963        bedrooms,
1964        bathrooms,
1965        photos,
1966        currency,
1967      } = property.acf;
1968
1969      let media = "";
1970      let coverImg = "";
1971      let coverImgId = "";
1972      if (image.sizes) {
1973        coverImgId = image.id;
1974        coverImg =
1975          image.sizes["homelocal-medium"] ||
1976          image.sizes["homelocal-thumbnail"] ||
1977          image.sizes["large"];
1978      }
1979      let maxPhotos = parseInt(options.photos_limit, 10);
1980
1981      if ((photos && photos.length > 0) || coverImg) {
1982        media += `<div class="hk-position-relative hk-visible-toggle hk-light hfse-property-slideshow" tabindex="-1" hk-slideshow="animation: ${options.photos_slide_animation}; ratio: ${options.photos_aspect_ratio}">`;
1983        media += `<ul class="hk-slideshow-items">`;
1984      }
1985
1986      if (coverImg) {
1987        media += `<li class="hfse-property-slideshow-item"><img data-src="${coverImg}" hk-img class="hfse-property-slideshow-image" alt="" hk-cover></li>`;
1988        --maxPhotos;
1989      }
1990
1991      if (photos && photos.length > 0 && maxPhotos > 0) {
1992        photos.forEach((photo) => {
1993          if (maxPhotos > 0) {
1994            const src =
1995              photo.sizes["homelocal-medium"] ||
1996              photo.sizes["homelocal-thumbnail"] ||
1997              photo.sizes["large"];
1998
1999            if (!coverImg || photo.id !== coverImgId) {
2000              media += `<li class="hfse-property-slideshow-item"><img data-src="${src}" hk-img class="hfse-property-slideshow-image" alt="" hk-cover></li>`;
2001              --maxPhotos;
2002            }
2003          }
2004        });
2005      }
2006
2007      if ((photos && photos.length > 0) || coverImg) {
2008        media += `</ul>`;
2009
2010        if (
2011          photos &&
2012          photos.length > 0 &&
2013          parseInt(options.photos_limit, 10) > 1
2014        ) {
2015          media += `<span class="hk-position-center-left hk-position-small hk-hidden-hover hfse-property-slideshow-prev-btn" hk-slidenav-previous hk-slideshow-item="previous"></span>`;
2016          media += `<span class="hk-position-center-right hk-position-small hk-hidden-hover hfse-property-slideshow-next-btn" hk-slidenav-next hk-slideshow-item="next"></span>`;
2017          media += `<ul class="hk-slideshow-nav hk-dotnav hk-flex-center hk-position-bottom hk-margin-small-bottom hfse-property-slideshow-dotnav"></ul>`;
2018        }
2019
2020        media += `</div>`;
2021      }
2022
2023      if (media) {
2024        media = `<div class="hk-card-media-top hfse-property-media">${media}</div>`;
2025      }
2026
2027      let favBtn = `<button type="button" class="hfse-property-favorite-toggle-btn ${
2028        app.inFavorite(id) ? "favorite" : ""
2029      }" hk-icon="icon: heart;"></button>`;
2030
2031      let priceHtml = "";
2032
2033      const baseRentHtml = `<span class="hfse-property-price">${settings.base_rent_text.replace(
2034        "%s",
2035        "<strong>" + app.price(base_daily_rate, currency) + "</strong>",
2036      )}</span>`;
2037
2038      const lowestRentHtml = `<span class="hfse-property-price">${settings.lowest_rent_text.replace(
2039        "%s",
2040        "<strong>" + app.price(lowest_rent, currency) + "</strong>",
2041      )}</span>`;
2042
2043      if (app.isDateQuery()) {
2044        if (
2045          options.with_dates_price === "average_price" &&
2046          average_rent !== null
2047        ) {
2048          if (options.full_price === "1") {
2049            priceHtml += `${app.getPropertyPriceLoaderHtml()}${app.getPropertyTaxNoteLoaderHtml()}`;
2050          } else {
2051            priceHtml += `<span class="hfse-property-price">`;
2052            if (average_rent > average_rent_discounted) {
2053              priceHtml += `<del>${app.price(average_rent, currency)}</del> `;
2054            }
2055            priceHtml += settings.per_night_rent.replace(
2056              "%s",
2057              "<strong>" +
2058                app.price(average_rent_discounted, currency) +
2059                "</strong>",
2060            );
2061            priceHtml += `</span>`;
2062
2063            if (total_rent_discounted) {
2064              priceHtml += `<span class="hfse-property-price-sep"> &middot; </span>`;
2065              priceHtml += `<span class="hfse-property-price-total">${settings.total_rent_text.replace(
2066                "%s",
2067                app.price(total_rent_discounted, currency),
2068              )}</span>`;
2069              priceHtml += `<div class="hk-text hk-text-small hfse-property-tax-note">${settings.rent_tax_note}</div>`;
2070            }
2071          }
2072        } else if (
2073          options.with_dates_price === "lowest_price" &&
2074          lowest_rent !== null
2075        ) {
2076          priceHtml += lowestRentHtml;
2077        } else if (options.with_dates_price !== "none") {
2078          priceHtml += baseRentHtml;
2079        }
2080      } else {
2081        if (
2082          options.no_dates_price === "average_price" &&
2083          average_rent !== null
2084        ) {
2085          priceHtml += `<span class="hfse-property-price">${settings.avg_rent_text.replace(
2086            "%s",
2087            "<strong>" + app.price(average_rent, currency) + "</strong>",
2088          )}</span>`;
2089        } else if (
2090          options.no_dates_price === "lowest_price" &&
2091          lowest_rent !== null
2092        ) {
2093          priceHtml += lowestRentHtml;
2094        } else if (options.no_dates_price !== "none") {
2095          priceHtml += baseRentHtml;
2096        }
2097      }
2098
2099      if (priceHtml) {
2100        priceHtml = `<div class="hk-margin-small-top hfse-property-price-container">${priceHtml}</div>`;
2101      }
2102
2103      let groupsHtml = "";
2104      if (options.hide_groups === "0") {
2105        const visibleGroups = groups.filter(
2106          (term) =>
2107            term.meta.hasOwnProperty("show_label") &&
2108            "yes" === term.meta.show_label,
2109        );
2110        if (visibleGroups.length > 0) {
2111          groupsHtml += `<div class="hfse-property-labels hfse-property-groups">`;
2112          visibleGroups.forEach((term) => {
2113            groupsHtml += `<span class="hfse-property-group hfse-property-group-${term.slug}">${term.name}</span>`;
2114          });
2115          groupsHtml += "</div>";
2116        }
2117      }
2118
2119      let leftTermsHtml = "";
2120      if (options.grid_left_taxonomy) {
2121        const visibleItems = property[options.grid_left_taxonomy]
2122          .filter(
2123            (term) =>
2124              term.meta.hasOwnProperty("show_in_card") &&
2125              "yes" === term.meta.show_in_card,
2126          )
2127          .slice(0, parseInt(options.grid_left_limit || "3", 10));
2128
2129        if (visibleItems.length > 0) {
2130          leftTermsHtml = visibleItems.map((c) => c.name).join(" / ");
2131        }
2132      }
2133
2134      let rightTermsHtml = "";
2135      if (options.grid_right_taxonomy) {
2136        const visibleItems = property[options.grid_right_taxonomy]
2137          .filter(
2138            (term) =>
2139              term.meta.hasOwnProperty("show_in_card") &&
2140              "yes" === term.meta.show_in_card,
2141          )
2142          .slice(0, parseInt(options.grid_right_limit || "3", 10));
2143
2144        if (visibleItems.length > 0) {
2145          rightTermsHtml = visibleItems.map((c) => c.name).join(" / ");
2146        }
2147      }
2148
2149      let ratingHtml = "";
2150      if (average_rating > 0) {
2151        ratingHtml = `<span class="hk-flex hk-flex-middle hk-flex-none hfse-property-rating">
2152					<span hk-icon="icon: star" class="hfse-property-rating-icon"></span>
2153					<span class="hfse-property-rating-number">${average_rating}</span>
2154				</span>`;
2155      }
2156
2157      return `
2158        <div class="property-item hfse-property-container" data-id="${id}">
2159          <a class="hk-card hk-card-default hk-card-hover hk-card-small hk-link-toggle hk-flex hk-flex-column hfse-property-card" href="${url}" data-id="${
2160            property.id
2161          }"${options.newtab === "1" ? ` target="_blank"` : ``}>
2162            ${groupsHtml}
2163            ${options.show_favorite_btn === "1" ? favBtn : ""}
2164            ${media}
2165            <div class="hk-card-body hk-margin-remove-first-child hfse-property-body">
2166							<div class="hk-flex hk-flex-between hk-flex-top">
2167								<h3 class="hk-h5 hk-flex-auto hfse-property-title">
2168									${title.rendered}
2169								</h3>
2170								${options.hide_rating === "0" ? ratingHtml : ""}
2171							</div>
2172							${
2173                "0" === options.hide_subtitle && subtitle
2174                  ? `<div class="hfse-property-subtitle">${subtitle}</div>`
2175                  : ""
2176              }
2177              ${priceHtml}
2178            </div>
2179
2180            <div class="hfse-property-meta">
2181              <div class="hfse-property-meta-primary">
2182                <div class="terms terms-left hfse-property-locations">${leftTermsHtml}</div>
2183                <div class="terms terms-right hfse-property-types">${rightTermsHtml}</div>
2184              </div>
2185              <div class="hfse-property-meta-secondary">
2186                <div class="hfse-property-guests">${guests}</div>
2187                <div class="hfse-property-bedrooms">${
2188                  bedrooms || "Studio"
2189                }</div>
2190                <div class="hfse-property-bathrooms">${bathrooms}</div>
2191              </div>
2192            </div>
2193          </a>
2194        </div>
2195        `;
2196    };
2197
2198    app.loadProperties = function (page = 1, data = "", onSuccess, onAlways) {
2199      const perPage =
2200        app.isDateQuery() &&
2201        "1" === options.full_price &&
2202        parseInt(options.per_page_with_full_price, 10) > 0
2203          ? options.per_page_with_full_price
2204          : options.per_page;
2205
2206      if (data.indexOf("favorite=1") !== -1) {
2207        if (favorites.length > 0) {
2208          data += "&include=" + favorites.join(",");
2209        } else {
2210          data += "&include=-1";
2211        }
2212      }
2213
2214      data += "&_filter=" + options.filter;
2215
2216      if (
2217        mapSearchActive &&
2218        options.map &&
2219        !$container.hasClass("map-hidden") &&
2220        mapInstance.getMapBoundBoxString()
2221      ) {
2222        data += "&map_bounds=" + mapInstance.getMapBoundBoxString();
2223      }
2224
2225      if (page === 1) {
2226        $container
2227          .find(".hfse-properties .property-item")
2228          .not(".property-loading")
2229          .remove();
2230      }
2231
2232      // add property loading skeleton.
2233      // we do not need to add more than 8 loading
2234      // skeletons at a time. This is to prevent
2235      // the page from becoming too long.
2236      for (let i = 0; i < Math.min(perPage, 8); i++) {
2237        $container
2238          .find(".hfse-properties")
2239          .append(app.getPropertyLoadingHtml(i));
2240      }
2241
2242      $.ajax({
2243        type: "get",
2244        url: settings.api_url + "wp/v2/properties",
2245        data:
2246          data +
2247          "&_fields=id,title,subtitle,url,image,types,states,cities,locations,groups,base_rent,total_rent,total_rent_discounted,average_rent,average_rent_discounted,lowest_rent,discount_percent,total_reviews,average_rating,acf&acf_format=standard&per_page=" +
2248          perPage +
2249          "&page=" +
2250          page +
2251          "&max_photos=" +
2252          parseInt(options.photos_limit, 10),
2253      })
2254        .done(function (properties, textStatus, xhr) {
2255          if (page === 1) {
2256            $container
2257              .find(".hfse-properties .property-item")
2258              .not(".property-loading")
2259              .remove();
2260            cachedProperties = [];
2261          }
2262
2263          app.gtmViewItemList(properties);
2264          cachedProperties = [...cachedProperties, ...properties];
2265
2266          properties.forEach((property) => {
2267            const $skeleton = $container
2268              .find(".hfse-properties .property-loading")
2269              .first();
2270
2271            if ($skeleton.length > 0) {
2272              $skeleton.replaceWith(app.getPropertyHtml(property));
2273            } else {
2274              $container
2275                .find(".hfse-properties")
2276                .append(app.getPropertyHtml(property));
2277            }
2278          });
2279
2280          // queue properties to load full price.
2281          if (options.full_price === "1") {
2282            app.maybeQueueFullPrice(properties);
2283            app.processFullPriceQueue();
2284          }
2285
2286          // remove any remaining loading skeletons.
2287          $container.find(".hfse-properties .property-loading").remove();
2288
2289          if (mapInstance) {
2290            mapInstance.setProperties(cachedProperties);
2291            mapInstance.updateMarkers();
2292          }
2293          if (onSuccess) {
2294            onSuccess(properties, textStatus, xhr);
2295          }
2296        })
2297        .always(onAlways);
2298    };
2299
2300    app.maybeQueueFullPrice = function (properties) {
2301      if (!app.isDateQuery()) {
2302        fullPriceQueued = [];
2303        return;
2304      }
2305
2306      properties.forEach((currentItem) => {
2307        const property = cachedProperties.find((p) => p.id === currentItem.id);
2308
2309        property.total_rent = null;
2310        property.total_rent_discounted = null;
2311
2312        property.average_rent = null;
2313        property.average_rent_discounted = null;
2314      });
2315
2316      properties.forEach((property) => {
2317        fullPriceQueued.push({
2318          id: property.id,
2319        });
2320      });
2321    };
2322
2323    app.sleep = function (ms) {
2324      return new Promise((resolve) => setTimeout(resolve, ms));
2325    };
2326
2327    app.processFullPriceQueue = async function () {
2328      if (
2329        fullPriceQueued.length === 0 ||
2330        fullPriceProcessing ||
2331        !app.isDateQuery() ||
2332        options.with_dates_price !== "average_price"
2333      ) {
2334        return;
2335      }
2336
2337      const checkin =
2338        $(selector.dateModal).find("input[name=checkin]").val() || "";
2339      const checkout =
2340        $(selector.dateModal).find("input[name=checkout]").val() || "";
2341      const guests =
2342        $(selector.dateModal).find("input[name=guests]").val() || 1;
2343
2344      // get the first property in the queue.
2345      const currentItem = fullPriceQueued.shift();
2346
2347      fullPriceProcessing = true;
2348
2349      const $property = $container.find(
2350        `.hfse-property-container[data-id="${currentItem.id}"]`,
2351      );
2352
2353      const property = cachedProperties.find((p) => p.id === currentItem.id);
2354      const currency = property.acf.currency || settings.currency;
2355
2356      let priceHtml = "";
2357
2358      // get the full price for the property.
2359      $.ajax({
2360        type: "post",
2361        url: settings.api_url + "homelocal/v1/quotes",
2362        data: {
2363          property_id: currentItem.id,
2364          checkin: checkin,
2365          checkout: checkout,
2366          guests: guests,
2367          use_cache: 1,
2368        },
2369      })
2370        .done(function (response) {
2371          if (!response || !response.rateplans) {
2372            return;
2373          }
2374
2375          const rateplan = response.rateplans[0];
2376
2377          const totalPrice =
2378            options.full_price_after_tax === "0"
2379              ? rateplan.total -
2380                rateplan.total_taxes +
2381                Math.abs(rateplan.discount)
2382              : rateplan.total + Math.abs(rateplan.discount);
2383
2384          const totalPriceDiscounted =
2385            options.full_price_after_tax === "0"
2386              ? rateplan.total - rateplan.total_taxes
2387              : rateplan.total;
2388
2389          const average_rent = totalPrice / app.getNightsCount();
2390          const average_rent_discounted =
2391            totalPriceDiscounted / app.getNightsCount();
2392
2393          priceHtml += `<span class="hfse-property-price">`;
2394          if (average_rent > average_rent_discounted) {
2395            priceHtml += `<del>${app.price(average_rent, currency)}</del> `;
2396          }
2397          priceHtml += settings.per_night_rent.replace(
2398            "%s",
2399            "<strong>" +
2400              app.price(average_rent_discounted, currency) +
2401              "</strong>",
2402          );
2403          priceHtml += `</span>`;
2404
2405          priceHtml += `<span class="hfse-property-price-sep"> &middot; </span>`;
2406
2407          // place to edit.
2408          priceHtml += `<span class="hfse-property-price-total">${settings.total_rent_text.replace(
2409            "%s",
2410            app.price(totalPriceDiscounted, currency),
2411          )}</span>`;
2412
2413          if (options.full_price_after_tax === "0") {
2414            priceHtml += `<div class="hk-text hk-text-small hfse-property-tax-note">${settings.total_excluding_tax_note}</div>`;
2415          }
2416
2417          $property.find(".hfse-property-price-container").html(priceHtml);
2418
2419          property.average_rent = average_rent;
2420          property.average_rent_discounted = average_rent_discounted;
2421
2422          property.total_rent = totalPrice;
2423          property.total_rent_discounted = totalPriceDiscounted;
2424
2425          if (mapInstance) {
2426            mapInstance.updateMarkers();
2427          }
2428        })
2429        .fail(function (xhr, textStatus, errorThrown) {
2430          $property
2431            .find(".hfse-property-price-container")
2432            .html(
2433              `<div class="full-price-failed-text">${settings.view_full_price}<div>`,
2434            );
2435        })
2436        .always(() => {
2437          fullPriceProcessing = false;
2438          app.processFullPriceQueue();
2439        });
2440    };
2441
2442    app.loadRecommendedProperties = function (onAlways) {
2443      let page = 1;
2444      let data = "";
2445      let perPage = parseInt(options.recommendations_per_page, 10);
2446      showingRecommendations = true;
2447
2448      if (options.recommendation_show_featured) {
2449        data += "&featured=1";
2450      }
2451      if (options.recommendation_tags) {
2452        data += "&tag_id=" + options.recommendation_tags.join(",");
2453      }
2454
2455      $container
2456        .find(".hfse-properties .property-item")
2457        .not(".property-loading")
2458        .remove();
2459
2460      // add property loading skeleton.
2461      for (let i = 0; i < Math.min(perPage, 8); i++) {
2462        $container
2463          .find(".hfse-properties")
2464          .append(app.getPropertyLoadingHtml());
2465      }
2466
2467      $.ajax({
2468        type: "get",
2469        url: settings.api_url + "wp/v2/properties",
2470        data:
2471          data +
2472          "&_fields=id,title,subtitle,url,image,types,states,cities,locations,groups,base_rent,total_rent,total_rent_discounted,average_rent,average_rent_discounted,lowest_rent,discount_percent,total_reviews,average_rating,acf&acf_format=standard&per_page=" +
2473          perPage +
2474          "&page=" +
2475          page +
2476          "&max_photos=" +
2477          parseInt(options.photos_limit, 10),
2478        beforeSend: function (xhr) {
2479          if (settings.user_nonce) {
2480            xhr.setRequestHeader("X-WP-Nonce", settings.user_nonce);
2481          }
2482        },
2483      })
2484        .done(function (properties, textStatus, xhr) {
2485          $container
2486            .find(".hfse-properties .property-item")
2487            .not(".property-loading")
2488            .remove();
2489          cachedProperties = [];
2490
2491          if (typeof properties === "string") {
2492            $container.find(".hfse-properties").append("<p>Request failed</p>");
2493            return false;
2494          }
2495
2496          cachedProperties = [...cachedProperties, ...properties];
2497
2498          properties.forEach((property) => {
2499            const $skeleton = $container
2500              .find(".hfse-properties .property-loading")
2501              .first();
2502
2503            if ($skeleton) {
2504              $skeleton.replaceWith(app.getPropertyHtml(property));
2505            } else {
2506              $container
2507                .find(".hfse-properties")
2508                .append(app.getPropertyHtml(property));
2509            }
2510          });
2511
2512          $container.find(".hfse-properties .property-loading").remove();
2513
2514          if (mapInstance) {
2515            mapInstance.setProperties(cachedProperties);
2516            mapInstance.updateMarkers();
2517          }
2518        })
2519        .always(onAlways);
2520    };
2521
2522    app.performFilter = (givenPage = 1) => {
2523      if (options.hide_start_over === "0") {
2524        app.showResetFormBtn();
2525      }
2526
2527      if (options.grid === "0") {
2528        return;
2529      }
2530
2531      if (givenPage) {
2532        page = givenPage;
2533      } else {
2534        page = 1;
2535      }
2536
2537      const data = app.getParams();
2538      app.updateUrl();
2539
2540      if ("0" === options.hide_more_btn) {
2541        $container.find(".hfse-properties-more-btn").prop("disabled", true);
2542      }
2543      $container.addClass("loading");
2544
2545      const onAlways = () => {
2546        if ("0" === options.hide_more_btn) {
2547          $container.find(".hfse-properties-more-btn").prop("disabled", false);
2548        }
2549        $container.removeClass("loading");
2550      };
2551
2552      app.loadProperties(page, data, app.propertiesLoadSuccess, onAlways);
2553    };
2554
2555    app.getNightsCount = () => {
2556      const checkin = $(selector.dateModal).find("input[name=checkin]").val();
2557      const checkout = $(selector.dateModal).find("input[name=checkout]").val();
2558
2559      let nights = 0;
2560      // calculate nights from date difference.
2561      if (checkin && checkout) {
2562        nights = Math.ceil(
2563          (new Date(checkout) - new Date(checkin)) / (1000 * 60 * 60 * 24),
2564        );
2565      }
2566
2567      return nights;
2568    };
2569
2570    app.getParams = (raw = false) => {
2571      let params = JSON.parse(JSON.stringify(options.default_params));
2572
2573      Object.keys(options.query_params).forEach((key) => {
2574        if (key in params) {
2575          params[key] = options.query_params[key];
2576        }
2577      });
2578
2579      Object.keys(options.shortcode_params).forEach((key) => {
2580        if (key in params) {
2581          params[key] = options.shortcode_params[key];
2582        }
2583      });
2584
2585      const filterGroups = [];
2586
2587      if ($container.find(".hfse-filters").length > 0) {
2588        filterGroups.push($container.find(".hfse-filters").serializeArray());
2589      }
2590
2591      if ($container.find(".hfse-ai-form").length > 0) {
2592        filterGroups.push($container.find(".hfse-ai-form").serializeArray());
2593      }
2594
2595      if ($container.find(".hfse-relevance-form").length > 0) {
2596        filterGroups.push(
2597          $container.find(".hfse-relevance-form").serializeArray(),
2598        );
2599      }
2600
2601      if ($(selector.zoneModal).length > 0) {
2602        filterGroups.push(
2603          $(selector.zoneModal).find(":input").serializeArray(),
2604        );
2605      }
2606
2607      if ($(selector.whereModal).length > 0) {
2608        filterGroups.push(
2609          $(selector.whereModal).find(":input").serializeArray(),
2610        );
2611      }
2612
2613      if ($(selector.dateModal).length > 0) {
2614        filterGroups.push(
2615          $(selector.dateModal).find(":input").serializeArray(),
2616        );
2617      }
2618
2619      if ($(selector.guestsModal).length > 0) {
2620        filterGroups.push(
2621          $(selector.guestsModal).find(":input").serializeArray(),
2622        );
2623      }
2624
2625      if ($(selector.additionalFiltersModal).length > 0) {
2626        filterGroups.push(
2627          $(selector.additionalFiltersModal).find(":input").serializeArray(),
2628        );
2629      }
2630
2631      filterGroups.forEach((filters) => {
2632        filters.forEach((filter) => {
2633          if ("[]" === filter.name.slice(-2)) {
2634            const name = filter.name.slice(0, -2);
2635            if (filter.value) {
2636              if (params[name]) {
2637                if (!params[name].includes(filter.value)) {
2638                  params[name].push(filter.value);
2639                }
2640              } else {
2641                params[name] = [filter.value];
2642              }
2643            }
2644          } else if (filter.value) {
2645            params[filter.name] = filter.value;
2646          }
2647        });
2648      });
2649
2650      // other filters.
2651      if ($container.find('select[name="sort"]').length > 0) {
2652        params["sort"] = $container.find('select[name="sort"]').val();
2653      }
2654
2655      if ($container.find('input[name="map_hidden"]').length > 0) {
2656        params["map_hidden"] = $container
2657          .find('input[name="map_hidden"]')
2658          .val();
2659      }
2660
2661      // External filter values pushed via window.hrExplorer.applyParams().
2662      // Overlaid last so an external panel wins over in-pane/URL values. Only
2663      // canonical keys (those present in default_params) are accepted.
2664      Object.keys(app.externalParams).forEach((key) => {
2665        if (key in params) {
2666          params[key] = app.externalParams[key];
2667        }
2668      });
2669
2670      // remove price filter if value is equal to default.
2671      if (params["price_min"] === options.default_params["price_min"]) {
2672        delete params["price_min"];
2673      }
2674
2675      if (params["price_max"] === options.default_params["price_max"]) {
2676        delete params["price_max"];
2677      }
2678
2679      // sanitize params
2680      Object.keys(params).forEach((key) => {
2681        // remove empty params
2682        if (params[key] === "") {
2683          delete params[key];
2684        }
2685      });
2686
2687      if (raw) {
2688        return params;
2689      }
2690
2691      return $.param(params);
2692    };
2693
2694    app.getUrlOnlyParams = (raw = false) => {
2695      const rawParams = app.getParams(true);
2696      const appliedParams = {};
2697
2698      Object.keys(rawParams).forEach((key) => {
2699        if (key in options.default_params) {
2700          if (rawParams[key] === options.default_params[key]) {
2701          }
2702          // compare array values for exclusion.
2703          else if (
2704            Array.isArray(rawParams[key]) &&
2705            Array.isArray(options.default_params[key]) &&
2706            rawParams[key].length === options.default_params[key].length &&
2707            rawParams[key].every((v, i) => v === options.default_params[key][i])
2708          ) {
2709          } else {
2710            appliedParams[key] = rawParams[key];
2711          }
2712        }
2713      });
2714
2715      // exclude options.shortcode_params values from url.
2716      Object.keys(options.shortcode_params).forEach((key) => {
2717        if (key in appliedParams) {
2718          if (appliedParams[key] === options.shortcode_params[key]) {
2719            delete appliedParams[key];
2720          }
2721          // compare array values for exclusion.
2722          else if (
2723            Array.isArray(appliedParams[key]) &&
2724            Array.isArray(options.shortcode_params[key]) &&
2725            appliedParams[key].length ===
2726              options.shortcode_params[key].length &&
2727            appliedParams[key].every(
2728              (v, i) => v === options.shortcode_params[key][i],
2729            )
2730          ) {
2731            delete appliedParams[key];
2732          }
2733        }
2734      });
2735
2736      if (raw) {
2737        return appliedParams;
2738      }
2739
2740      return $.param(appliedParams);
2741    };
2742
2743    app.getRedirectUrlParams = (raw = false) => {
2744      const rawParams = app.getParams(true);
2745      const appliedParams = {};
2746
2747      Object.keys(rawParams).forEach((key) => {
2748        if (key in options.default_params) {
2749          if (rawParams[key] === options.default_params[key]) {
2750          }
2751          // compare array values for exclusion.
2752          else if (
2753            Array.isArray(rawParams[key]) &&
2754            Array.isArray(options.default_params[key]) &&
2755            rawParams[key].length === options.default_params[key].length &&
2756            rawParams[key].every((v, i) => v === options.default_params[key][i])
2757          ) {
2758          } else {
2759            appliedParams[key] = rawParams[key];
2760          }
2761        }
2762      });
2763
2764      if (raw) {
2765        return appliedParams;
2766      }
2767
2768      return $.param(appliedParams);
2769    };
2770
2771    app.updateUrl = () => {
2772      const appliedParams = app.getUrlOnlyParams(true);
2773
2774      const params = $.param(appliedParams);
2775      const $url = new URL(window.location.href);
2776      $url.search = new URLSearchParams(params);
2777
2778      window.history.pushState({}, "", $url);
2779    };
2780
2781    app.init = () => {
2782      if (
2783        options.filter === "0" &&
2784        options.ai_search === "0" &&
2785        options.grid === "0" &&
2786        options.map === "0"
2787      ) {
2788        console.warn(settings.no_elements);
2789      }
2790
2791      if (options.map === "1") {
2792        // Sticky map requires the body overflow to be visible.
2793        $("body").css("overflow", "visible");
2794        app.initMap();
2795      }
2796
2797      if (options.filter === "1") {
2798        app.initFilters();
2799      }
2800
2801      if (options.ai_search === "1") {
2802        app.initAiSearch();
2803      }
2804
2805      if (options.relevance_search === "1") {
2806        app.initRelevanceSearch();
2807      }
2808
2809      if (options.cta === "1") {
2810        app.initCta();
2811      }
2812
2813      if (options.grid === "1") {
2814        app.initGrid();
2815      }
2816    };
2817
2818    app.gtmViewItemList = (properties) => {
2819      const items = properties.map((property) => {
2820        return {
2821          item_id: property.acf.central_source_id,
2822          item_name: property.title.rendered.toLowerCase(),
2823          item_brand: homelocal_common_config.sitename.toLowerCase(),
2824          item_category:
2825            property.types.length > 0
2826              ? property.types[0].name.toLowerCase()
2827              : "",
2828          index: properties.indexOf(property),
2829          price: property.average_rent,
2830          currency: property.acf.currency,
2831        };
2832      });
2833
2834      const nights = app.getNightsCount();
2835
2836      if (trigger_ga4 && isGtagAvailable()) {
2837        gtag("event", "view_item_list", {
2838          nights_selected: nights,
2839          item_list_id: "explorer_search_results",
2840          item_list_name: "Explorer Search Results",
2841          items: items,
2842        });
2843      } else {
2844        window.dataLayer = window.dataLayer || [];
2845        window.dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object.
2846        window.dataLayer.push({
2847          event: "view_item_list",
2848          nights_selected: nights,
2849          ecommerce: {
2850            item_list_id: "explorer_search_results",
2851            item_list_name: "Explorer Search Results",
2852            items: items,
2853          },
2854        });
2855      }
2856    };
2857
2858    app.gtmPropertySearch = (trigger = "other") => {
2859      const nights = app.getNightsCount();
2860
2861      if (trigger_ga4 && isGtagAvailable()) {
2862        gtag("event", "property_search", {
2863          nights_selected: nights,
2864          trigger: trigger,
2865        });
2866      } else {
2867        window.dataLayer = window.dataLayer || [];
2868        window.dataLayer.push({
2869          event: "property_search",
2870          nights_selected: nights,
2871          trigger: trigger,
2872        });
2873      }
2874
2875      if (
2876        "keyword" === trigger &&
2877        "" !== $container.find(".hfse-form-search-input").val()
2878      ) {
2879        if (trigger_ga4 && isGtagAvailable()) {
2880          gtag("event", "search", {
2881            nights_selected: nights,
2882            search_term: $container.find(".hfse-form-search-input").val(),
2883          });
2884        } else {
2885          window.dataLayer.push({
2886            event: "search",
2887            nights_selected: nights,
2888            search_term: $container.find(".hfse-form-search-input").val(),
2889          });
2890        }
2891      }
2892    };
2893
2894    app.gtmSelectItem = (id) => {
2895      const nights = app.getNightsCount();
2896      const property = cachedProperties.find((p) => p.id === id);
2897
2898      if (property) {
2899        if (trigger_ga4 && isGtagAvailable()) {
2900          gtag("event", "select_item", {
2901            nights_selected: nights,
2902            items: [
2903              {
2904                item_id: property.acf.central_source_id,
2905                item_name: property.title.rendered.toLowerCase(),
2906                item_brand: homelocal_common_config.sitename.toLowerCase(),
2907                item_category:
2908                  property.types.length > 0
2909                    ? property.types[0].name.toLowerCase()
2910                    : "",
2911                index: cachedProperties.indexOf(property),
2912                price: property.average_rent,
2913                currency: property.acf.currency,
2914              },
2915            ],
2916          });
2917        } else {
2918          window.dataLayer = window.dataLayer || [];
2919          window.dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object.
2920          window.dataLayer.push({
2921            event: "select_item",
2922            nights_selected: nights,
2923            ecommerce: {
2924              items: [
2925                {
2926                  item_id: property.acf.central_source_id,
2927                  item_name: property.title.rendered.toLowerCase(),
2928                  item_brand: homelocal_common_config.sitename.toLowerCase(),
2929                  item_category:
2930                    property.types.length > 0
2931                      ? property.types[0].name.toLowerCase()
2932                      : "",
2933                  index: cachedProperties.indexOf(property),
2934                  price: property.average_rent,
2935                  currency: property.acf.currency,
2936                },
2937              ],
2938            },
2939          });
2940        }
2941      }
2942    };
2943
2944    // Register this instance and expose the public external-control API so an
2945    // external filter panel can drive the grid+map without a page reload.
2946    window.hrExplorer = window.hrExplorer || { instances: {} };
2947    window.hrExplorer.instances[options.id] = app;
2948
2949    app.applyExternalParams = (params) => {
2950      // Full replace of the external layer so stale values don't accumulate.
2951      app.externalParams = $.extend({}, params);
2952      // To make the external panel the sole source of truth (ignore the values
2953      // the landing URL arrived with), also uncomment the next line:
2954      // options.query_params = {};
2955      app.performFilter();
2956    };
2957
2958    const resolveTargets = (instanceId) =>
2959      instanceId != null
2960        ? [window.hrExplorer.instances[instanceId]].filter(Boolean)
2961        : Object.values(window.hrExplorer.instances);
2962
2963    // Push captured filter values, then refetch. Primary integration surface.
2964    window.hrExplorer.applyParams = (params, instanceId) => {
2965      resolveTargets(instanceId).forEach((inst) =>
2966        inst.applyExternalParams(params || {}),
2967      );
2968    };
2969
2970    // Refetch with the current params (no value change). Fallback API.
2971    window.hrExplorer.refresh = (instanceId) => {
2972      resolveTargets(instanceId).forEach((inst) => inst.performFilter());
2973    };
2974
2975    // Event-driven contract: dispatch with detail.params to apply values, or
2976    // without to just refetch. Bound once across all instances.
2977    if (!window.hrExplorer._eventsBound) {
2978      window.hrExplorer._eventsBound = true;
2979      window.addEventListener("homerunner_explorer_url_changed", (e) => {
2980        const detail = e.detail || {};
2981        if (detail.params) {
2982          window.hrExplorer.applyParams(detail.params, detail.instanceId);
2983        } else {
2984          window.hrExplorer.refresh(detail.instanceId);
2985        }
2986      });
2987    }
2988
2989    app.init();
2990  };
2991})(jQuery, homelocal_explorer_settings);

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.