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"> · </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"> · </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.