1/** 2 * Houzez Custom JavaScript 3 * Consolidated version of all Houzez JavaScript modules 4 * 5 * This file combines the following modules: 6 * - Core: Basic functionality and utilities 7 * - Login: Authentication functionality 8 * - Favorites: Property favorites management 9 * - Compare: Property comparison features 10 * - Properties: Property-specific functionality 11 * - Pagination: AJAX pagination and infinite loading 12 * - Elementor: Integration with Elementor page builder 13 */ 14(function ($) { 15 'use strict'; 16 17 // Main Houzez object that will contain all modules 18 var houzez = window.houzez || {}; 19 20 /** 21 * Core Module 22 * Core functionality and utility functions 23 */ 24 houzez.Core = (function () { 25 // Cache DOM selectors 26 const $document = $(document); 27 const $window = $(window); 28 29 // Global variables 30 const $win = $(window); 31 const $body = $('body'); 32 33 // Centralized config from global vars 34 const config = { 35 // Core config 36 ajaxurl: houzez_vars.admin_url + 'admin-ajax.php', 37 userID: houzez_vars.user_id, 38 primary_color: houzez_vars.primary_color, 39 houzez_rtl: houzez_vars.houzez_rtl === 'yes', 40 wp_is_mobile: houzez_vars.wp_is_mobile, 41 currency_position: houzez_vars.currency_position, 42 currency_symbol: houzez_vars.currency_symbol, 43 decimals: houzez_vars.decimals, 44 decimal_point_separator: houzez_vars.decimal_point_separator, 45 thousands_separator: houzez_vars.thousands_separator, 46 is_singular_property: houzez_vars.is_singular_property, 47 property_detail_nav: houzez_vars.prop_detail_nav, 48 processing_text: houzez_vars.processing_text, 49 houzez_is_splash: houzez_vars.houzez_is_splash, 50 wpadminbar_height: $('#wpadminbar').outerHeight() || 0, 51 // Login config 52 login_loading: houzez_vars.login_loading, 53 login_redirect: houzez_vars.login_redirect, 54 redirect_type: houzez_vars.redirect_type, 55 houzez_reCaptcha: parseInt(houzez_vars.houzez_reCaptcha), 56 g_recaptha_version: houzez_vars.g_recaptha_version, 57 58 // Favorites config 59 favorite_url: houzez_vars.favorite_url || '', 60 add_to_favorite_login_required: parseInt( 61 houzez_vars.add_to_favorite_login_required || 0 62 ), 63 64 // Compare config 65 compare_url: houzez_vars.compare_url || '', 66 compare_add_icon: houzez_vars.compare_add_icon || '', 67 compare_remove_icon: houzez_vars.compare_remove_icon || '', 68 add_compare_text: houzez_vars.add_compare_text || '', 69 remove_compare_text: houzez_vars.remove_compare_text || '', 70 compare_limit: houzez_vars.compare_limit || '', 71 compare_page_not_found: houzez_vars.compare_page_not_found || '', 72 73 // Sliders config 74 prev_text: houzez_vars.prev_text || 'Prev', 75 next_text: houzez_vars.next_text || 'Next', 76 disable_property_gallery: houzez_vars.disable_property_gallery || 0, 77 grid_gallery_behaviour: houzez_vars.grid_gallery_behaviour || '', 78 79 // Properties config 80 // ... 81 82 // Pagination config 83 // ... 84 85 // New config for half map and fullscreen sections 86 is_halfmap: parseInt(houzez_vars.is_halfmap), 87 }; 88 89 // Utility functions 90 const util = { 91 /** 92 * Debounce function to limit rapid function calls 93 * @param {Function} func - Function to debounce 94 * @param {number} delay - Delay in milliseconds 95 * @return {Function} - Debounced function 96 */ 97 debounce: function (func, delay) { 98 let debounceTimer; 99 return function () { 100 const context = this; 101 const args = arguments; 102 clearTimeout(debounceTimer); 103 debounceTimer = setTimeout( 104 () =>
104 func.apply(context, args), 105 delay 106 ); 107 }; 108 }, 109 110 /** 111 * Parse a string to boolean 112 * @param {string} str - String to parse 113 * @return {boolean} - Parsed boolean 114 */ 115 parseBool: function (str) { 116 if (typeof str === 'string') { 117 switch (str.toLowerCase()) { 118 case 'true': 119 case '1': 120 case 'yes': 121 case 'y': 122 return true; 123 case 'false': 124 case '0': 125 case 'no': 126 case 'n': 127 return false; 128 default: 129 return Boolean(str); 130 } 131 } else { 132 return Boolean(str); 133 } 134 }, 135 136 /** 137 * Format number with thousands separator 138 * @param {number} number - Number to format 139 * @return {string} - Formatted number 140 */ 141 thousandSeparator: function (n) { 142 if (typeof n === 'number') { 143 n += ''; 144 const x = n.split('.'); 145 const x1 = x[0]; 146 const x2 = x.length > 1 ? '.' + x[1] : ''; 147 const rgx = /(\d+)(\d{3})/; 148 149 let result = x1; 150 while (rgx.test(result)) { 151 result = result.replace( 152 rgx, 153 '$1' + config.thousands_separator + '$2' 154 ); 155 } 156 return result + x2; 157 } 158 return n; 159 }, 160 161 /** 162 * Format currency values 163 * @param {string|number} price_value - Price to format 164 * @return {string} - Formatted price with currency symbol 165 */ 166 currencyFormat: function (price_value) { 167 if (!config.currency_position || !config.currency_symbol) { 168 return price_value; 169 } 170 171 return config.currency_position === 'after' 172 ? price_value + config.currency_symbol 173 : config.currency_symbol + price_value; 174 }, 175 176 /** 177 * Format numbers with proper separators 178 * @param {number} number - Number to format 179 * @return {string} - Formatted number 180 */ 181 numberFormat: function (number, custom = false) { 182 const dec_point = config.decimal_point_separator; 183 const thousands_sep = config.thousands_separator; 184 185 let i, j, kw, kd, km; 186 let decimals = config.decimals; 187 188 // Input sanitation & defaults 189 if (isNaN(decimals) || custom) { 190 decimals = 2; 191 } 192 193 i = parseInt((number = (+number || 0).toFixed(decimals))) + ''; 194 j = (j = i.length) > 3 ? j % 3 : 0; 195 196 km = j ? i.substr(0, j) + thousands_sep : ''; 197 kw = i 198 .substr(j) 199 .replace(/(\d{3})(?=\d)/g, '$1' + thousands_sep); 200 201 kd = decimals 202 ? dec_point + 203 Math.abs(number - i) 204 .toFixed(decimals) 205 .replace(/-/, 0) 206 .slice(2) 207 : ''; 208 209 return km + kw + kd; 210 }, 211 212 /** 213 * Get cookie by name 214 * @param {string} name - Cookie name 215 * @return {string} - Cookie value 216 */ 217 getCookie: function (name) { 218 const nameEQ = name + '='; 219 const ca = document.cookie.split(';'); 220 for (let i = 0; i < ca.length; i++) { 221 let c = ca[i]; 222 while (c.charAt(0) === ' ') c = c.substring(1, c.length); 223 if (c.indexOf(nameEQ) === 0) { 224 return c.substring(nameEQ.length, c.length); 225 } 226 } 227 return null; 228 }, 229 230 /** 231 * Set cookie 232 * @param {string} name - Cookie name 233 * @param {string} value - Cookie value 234 * @param {number} days - Days until cookie expires 235 */
236 setCookie: function (name, value, days) { 237 let expires = ''; 238 if (days) { 239 const date = new Date(); 240 date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); 241 expires = '; expires=' + date.toUTCString(); 242 } 243 document.cookie = name + '=' + value + expires + '; path=/'; 244 }, 245 246 /** 247 * Delete cookie 248 * @param {string} name - Cookie name 249 */ 250 deleteCookie: function (name) { 251 this.setCookie(name, '', -1); 252 }, 253 254 /** 255 * Add commas to number for display 256 * @param {string|number} nStr - Number to format 257 * @returns {string} Formatted number 258 */ 259 addCommas: function (nStr) { 260 nStr += ''; 261 const x = nStr.split('.'); 262 let x1 = x[0]; 263 const x2 = x.length > 1 ? '.' + x[1] : ''; 264 const rgx = /(\d+)(\d{3})/; 265 while (rgx.test(x1)) { 266 x1 = x1.replace(rgx, '$1' + ',' + '$2'); 267 } 268 return x1 + x2; 269 }, 270 271 /** 272 * Format number with commas 273 * @param {number} number - Number to format 274 * @returns {string} Formatted number 275 */ 276 formatNumber: function (number) { 277 const parts = number.toString().split('.'); 278 parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); 279 return parts.join('.'); 280 }, 281 282 /** 283 * Show the processing modal 284 * @param {string} msg - Message to display 285 */ 286 processingModal: function (msg) { 287 var process_modal = 288 '<div class="modal fade" id="fave_modal" tabindex="-1" role="dialog" aria-labelledby="faveModalLabel" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-body houzez_messages_modal">' + 289 msg + 290 '</div></div></div></div>'; 291 292 jQuery('body').append(process_modal); 293 jQuery('#fave_modal').modal('show'); 294 }, 295 296 /** 297 * Close the processing modal 298 */ 299 processingModalClose: function () { 300 jQuery('#fave_modal').modal('hide'); 301 }, 302 303 /** 304 * Generate loader HTML for AJAX requests 305 * @returns {string} Loader HTML markup 306 */ 307 getLoaderHtml: function () { 308 return '<div class="houzez-overlay-loading"><div class="overlay-placeholder"><div class="loader-ripple spinner"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div></div></div>'; 309 }, 310 311 /** 312 * Generate success alert HTML 313 * @param {string} message - Message to display 314 * @returns {string} Success alert HTML 315 */ 316 getSuccessHtml: function (message) { 317 return ( 318 '<div class="alert alert-success alert-dismissible fade show" role="alert">' + 319 '<i class="houzez-icon icon-check-circle-1 me-1"></i> ' + 320 message + 321 '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' + 322 '</div>' 323 ); 324 }, 325 326 /** 327 * Generate error alert HTML 328 * @param {string} message - Message to display 329 * @returns {string} Error alert HTML 330 */ 331 getErrorHtml: function (message) { 332 return ( 333 '<div class="alert alert-danger alert-dismissible fade show" role="alert">' + 334 '<i class="houzez-icon icon-remove-circle me-1"></i> ' + 335 message + 336 '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' + 337 '</div>' 338 ); 339 }, 340 341 /**
342 * Display success message in target element 343 * @param {jQuery|string} target - Target element or selector 344 * @param {string} message - Message to display 345 */ 346 showSuccess: function (target, message) { 347 const $target = typeof target === 'string' ? $(target) : target; 348 $target.empty().append(this.getSuccessHtml(message)); 349 }, 350 351 /** 352 * Display error message in target element 353 * @param {jQuery|string} target - Target element or selector 354 * @param {string} message - Message to display 355 */ 356 showError: function (target, message) { 357 const $target = typeof target === 'string' ? $(target) : target; 358 $target.empty().append(this.getErrorHtml(message)); 359 }, 360 361 /** 362 * Calculate monthly mortgage payment 363 * @param {number} principal - Principal loan amount 364 * @param {number} annualInterestRate - Annual interest rate (in percentage) 365 * @param {number} loanTermInYears - Loan term in years 366 * @return {number} - Monthly payment amount 367 */ 368 calculateMonthlyPayment: function ( 369 principal, 370 annualInterestRate, 371 loanTermInYears 372 ) { 373 const monthlyInterestRate = annualInterestRate / 12 / 100; 374 const numberOfMonths = loanTermInYears * 12; 375 376 if (monthlyInterestRate === 0) { 377 return principal / numberOfMonths; 378 } 379 380 return ( 381 (principal * 382 (monthlyInterestRate * 383 Math.pow( 384 1 + monthlyInterestRate, 385 numberOfMonths 386 ))) / 387 (Math.pow(1 + monthlyInterestRate, numberOfMonths) - 1) 388 ); 389 }, 390 391 /** 392 * Parse and validate input, removing non-numeric characters 393 * @param {string} selector - Input selector 394 * @return {number} - Parsed value or 0 if invalid/empty 395 */ 396 parseNumberInput: function (selector) { 397 const rawValue = $(selector).val(); 398 399 if (!rawValue) return 0; // if value is empty or undefined, return 0 400 401 // Remove any non-digit character except the decimal point 402 const cleanedValue = rawValue.replace(/[^0-9.]/g, ''); 403 404 // Parsing to number 405 const parsedValue = parseFloat(cleanedValue); 406 407 // if parsedValue is NaN, return 0, otherwise return the parsed number 408 return isNaN(parsedValue) ? 0 : parsedValue; 409 }, 410 411 /** 412 * Update URL with query parameter and return the new URL string 413 * @param {string} key - Parameter key 414 * @param {string} value - Parameter value 415 * @returns {string} The new URL with the updated parameter 416 */ 417 updateUrlParam: function (key, value) { 418 var pathname = window.location.pathname; 419 var urlParams = new URLSearchParams(window.location.search); 420 urlParams.set(key, value); 421 422 // If sorting is being changed, remove pagination to start from page 1 423 if (key === 'sortby') { 424 // Remove WordPress permalink-based pagination (e.g., /page/2/) 425 pathname = pathname.replace(/\/page\/\d+\/?/, '/'); 426 427 // Also remove query-based pagination parameters 428 urlParams.delete('paged'); 429 urlParams.delete('page'); 430 } 431 432 var baseUrl = window.location.origin + pathname; 433 // Preserve other existing query parameters 434 var newUrl = baseUrl + '?' + urlParams.toString(); 435 return newUrl; 436 }, 437 438 /** 439 * Calculate heights for half map and fullscreen sections 440 */ 441 calculateSectionHeight: function () { 442 const windowHeight = $window.height(); 443 const headerDesktop = $('.header-desktop').outerHeight() || 0; 444 const headerMobile = $('.header-mobile').outerHeight() || 0; 445 const topBar = $('.top-bar-wrap').outerHeight() || 0; 446 const headerElementor = 447 $('#header-hz-elementor').outerHeight() || 0; 448 let adminBarHeight = houzez.Core.config.wpadminbar_height; 449 let halfMapWrapHeight = 0; 450 451 const mobileSearchNav = 452 $('.mobile-search-nav').outerHeight() || 0; 453 const desktopSearchNav = 454 $('.desktop-search-nav').outerHeight() || 0; 455 456 // Use headerElementor as the only header height if present 457 let headerHeight; 458 if (headerElementor > 0) { 459 headerHeight = 460 headerElementor + 461 (window.innerWidth > 991 462 ? desktopSearchNav + topBar 463 : mobileSearchNav); 464 } else { 465 headerHeight = 466 window.innerWidth > 991 467 ? headerDesktop + desktopSearchNav + topBar 468 : headerMobile + mobileSearchNav; 469 } 470 471 let halfMapHeight = 472 windowHeight - headerHeight - adminBarHeight; 473 halfMapWrapHeight = 474 windowHeight + headerHeight + adminBarHeight; 475 476 if (halfMapHeight < 0) { 477 halfMapHeight = 0; 478 } 479 480 if (window.innerWidth > 768) { 481 $('.half-map-wrap').height(halfMapHeight); 482 $('.main-half-map-wrap').css({ 483 'max-height': halfMapWrapHeight + 'px', 484 height: 'calc(100vh - ' + adminBarHeight + 'px)', 485 }); 486 } else { 487 $('.half-map-left-wrap').height(halfMapHeight); 488 } 489 490 $('.top-banner-wrap-fullscreen').height( 491 windowHeight - headerHeight 492 ); 493 $('.top-banner-wrap-fullscreen .property-slider').height( 494 windowHeight - headerHeight 495 ); 496 $( 497 '.top-banner-wrap-fullscreen .property-slider-item-wrap' 498 ).height(windowHeight - headerHeight); 499 }, 500 501 /** 502 * Reset captcha (supports both Google reCaptcha and Cloudflare Turnstile) 503 * @param {jQuery} $form - Form element containing captcha 504 */ 505 resetCaptcha: function($form) { 506 // Remove any existing captcha responses 507 $form.find('.g-recaptcha-response').remove(); 508 $form.find('.cf-turnstile-response').remove(); 509 510 // Check if reCaptcha is active 511 if (config.houzez_reCaptcha == 1) { 512 if (config.g_recaptha_version == 'v3') { 513 // reCaptcha v3 - regenerate token 514 if (typeof houzezReCaptchaLoad === 'function') { 515 houzezReCaptchaLoad(); 516 } 517 } else { 518 // reCaptcha v2 - reset widget 519 if (typeof houzezReCaptchaReset === 'function') { 520 houzezReCaptchaReset(); 521 } 522 } 523 }
524 // Check if Turnstile is active (check for widget on page) 525 else if ($form.find('.houzez-turnstile').length > 0) { 526 if (typeof houzezTurnstileReset === 'function') { 527 houzezTurnstileReset(); 528 } else if (typeof turnstile !== 'undefined') { 529 $form.find('.houzez-turnstile').each(function() { 530 turnstile.reset(this); 531 }); 532 } 533 } 534 }, 535 }; 536 537 /** 538 * Check if the device is mobile 539 */ 540 const is_mobile = () => { 541 // 1. matchMedia (layout) 542 if (window.matchMedia('(max-width: 991px)').matches) { 543 return true; 544 } 545 546 // 2. modern UAData 547 if (navigator.userAgentData?.mobile === true) { 548 return true; 549 } 550 551 // 3. UA regex fallback 552 return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 553 navigator.userAgent 554 ); 555 }; 556 557 const sortby_mobile = () => { 558 if (is_mobile()) { 559 $('#ajax_sort_properties').addClass('mobile-sortby'); 560 $('#mobile-search-form').addClass('apply-mobile-pagination'); 561 } else { 562 $('#ajax_sort_properties').removeClass('mobile-sortby'); 563 $('#mobile-search-form').removeClass('apply-mobile-pagination'); 564 } 565 }; 566 567 /** 568 * Initialize tooltips 569 */ 570 const initTooltips = function () { 571 const tooltipTriggerList = [].slice.call( 572 document.querySelectorAll('[data-bs-toggle="tooltip"]') 573 ); 574 tooltipTriggerList.map(function (tooltipTriggerEl) { 575 // Check if tooltip already exists to prevent duplicates 576 const existingTooltip = 577 bootstrap.Tooltip.getInstance(tooltipTriggerEl); 578 if (!existingTooltip) { 579 return new bootstrap.Tooltip(tooltipTriggerEl); 580 } 581 return existingTooltip; 582 }); 583 }; 584 585 /** 586 * Hide all tooltips 587 */ 588 const hideAllTooltips = function () { 589 const tooltips = document.querySelectorAll( 590 '[data-bs-toggle="tooltip"]' 591 ); 592 tooltips.forEach(function (element) { 593 const tooltipInstance = bootstrap.Tooltip.getInstance(element); 594 if (tooltipInstance) { 595 tooltipInstance.hide(); 596 } 597 }); 598 }; 599 600 /** 601 * Initialize reviews tab from all reviews link 602 */ 603 const initReviewsTab = function () { 604 $('.all-reviews').on('click', function (e) { 605 e.preventDefault(); 606 $('.hz-review-tab').trigger('click'); 607 }); 608 }; 609 610 /** 611 * Initialize property sorting 612 */ 613 const initPropertySorting = function () { 614 $('#sort_properties').on('change', function () { 615 let key = 'sortby'; 616 let value = $(this).val(); 617 // Use the new Core utility function and reload 618 window.location.href = houzez.Core.util.updateUrlParam( 619 key, 620 value 621 ); 622 }); 623 }; 624 625 /** 626 * Initialize modals with dynamic content 627 */ 628 // const initDynamicModals = function () { 629 // $('.schedule-contact-btn').on('click', function (e) { 630 // e.preventDefault(); 631 // $('#schedule-contact-modal').modal('show'); 632 // }); 633 634 // // Membership selection modals 635 // $('.houzez-open-membership-modal').on('click', function (e) { 636 // e.preventDefault(); 637 // $('#modal-membership').modal('show'); 638 // }); 639 // }; 640 641 /** 642 * Toggle mobile menus and panels 643 */ 644 const mobileToggleMenu = function () { 645 // Main navigation 646 $('.toggle-mobile-nav').on('click', function (e) { 647 e.preventDefault(); 648 $('#mobile-main-nav').toggleClass('show'); 649 $('.toggle-mobile-nav').toggleClass('active'); 650 }); 651 652 // Account navigation 653 $('.user-icon').on('click', function (e) { 654 e.preventDefault(); 655 if ($('#user-login-dropdown').is(':visible')) {
656 $('#user-login-dropdown').slideUp(); 657 } else { 658 $('#user-login-dropdown').slideDown(); 659 } 660 }); 661 }; 662 663 /** 664 * Handle call and email popup modals from grid items 665 */ 666 const gridCallToAction = function () { 667 // Handle call popup clicks 668 $('.hz-call-popup-js').on('click', function () { 669 var call_model_id = $(this).data('model-id'); 670 $('#' + call_model_id).appendTo('body'); 671 $('#' + call_model_id).modal('show'); 672 }); 673 674 // Handle email popup clicks 675 $('.hz-email-popup-js').on('click', function () { 676 var email_model_id = $(this).data('model-id'); 677 $('#' + email_model_id).appendTo('body'); 678 $('#' + email_model_id).modal('show'); 679 }); 680 }; 681 682 $('.agent-show-onClick').on('click', function (e) { 683 $(this).toggleClass('agent-phone-hidden'); 684 }); 685 686 /** 687 * Handle scroll to top functionality 688 */ 689 const scrollToTop = function () { 690 const scroll_anchor = $('#scroll-top'); 691 692 $(window).on('scroll', function () { 693 if ($(this).scrollTop() > 500) { 694 scroll_anchor.fadeIn('fast').css('display', 'block'); 695 return; 696 } 697 scroll_anchor.fadeOut('fast'); 698 }); 699 700 scroll_anchor.on('click', function (event) { 701 event.preventDefault(); 702 $('html, body').animate({ scrollTop: 0 }, 'slow'); 703 }); 704 }; 705 706 /** 707 * Reinitialize necessary functions after new content is loaded via AJAX 708 * Common utility for all modules that load content dynamically 709 */ 710 const reinitializeAjaxFunctions = function () { 711 // Reinitialize favorites 712 if (houzez.Favorites) { 713 houzez.Favorites.initAddFavorite(); 714 } 715 716 if (houzez.Compare) { 717 houzez.Compare.compare_for_ajax(); 718 } 719 720 // Reinitialize grid image gallery 721 if (houzez.Sliders) { 722 houzez.Sliders.gridImageGallery(); 723 } 724 725 if (houzez.PropertyPreview) { 726 houzez.PropertyPreview.init(); 727 } 728 729 // Reinitialize tooltips 730 initTooltips(); 731 732 // Reinitialize lazy loading 733 houzezLazyload(); 734 735 // Reinitialize grid call to action 736 gridCallToAction(); 737 }; 738 739 /** 740 * Handle property view switching (list/grid) 741 */ 742 const listingViewSwitch = function () { 743 // Handle view switching with URL update 744 $('.listing-switch-view .switch-btn, .switch-btn').on( 745 'click', 746 function (e) { 747 e.preventDefault(); 748 var view = $(this).hasClass('btn-grid') ? 'grid' : 'list'; 749 750 // Update URL and reload page 751 window.location.href = util.updateUrlParam( 752 'listing-view', 753 view 754 ); 755 } 756 ); 757 758 // Set initial active state for buttons based on data-view attribute 759 var currentView = $('.listing-view').attr('data-view'); 760 if (currentView) { 761 var $btnGrid = $('.btn-grid'); 762 var $btnList = $('.btn-list'); 763 764 if (currentView === 'grid') { 765 $btnGrid.addClass('active').attr('aria-pressed', 'true'); 766 $btnList 767 .removeClass('active') 768 .attr('aria-pressed', 'false'); 769 } else { 770 $btnList.addClass('active').attr('aria-pressed', 'true'); 771 $btnGrid 772 .removeClass('active') 773 .attr('aria-pressed', 'false'); 774 } 775 } 776 }; 777 778 /** 779 * Handle lazy loading of images with .houzez-lazyload class 780 */ 781 const houzezLazyload = function () { 782 // Check if any .houzez-lazyload elements exist before initializing 783 if (document.querySelectorAll('.houzez-lazyload').length === 0) { 784 return; 785 } 786 787 // core logic: find all .houzez-lazyload images and observe them
788 function init() { 789 var lazyImages = [].slice.call( 790 document.querySelectorAll('.houzez-lazyload') 791 ); 792 if (!lazyImages.length || !('IntersectionObserver' in window)) 793 return; 794 795 var observer = new IntersectionObserver(function (entries) { 796 entries.forEach(function (entry) { 797 if (!entry.isIntersecting) return; 798 var img = entry.target; 799 img.src = img.dataset.src; 800 if (img.dataset.srcset) { 801 img.srcset = img.dataset.srcset; 802 } 803 img.classList.remove('houzez-lazyload'); 804 observer.unobserve(img); 805 }); 806 }); 807 808 lazyImages.forEach(function (img) { 809 observer.observe(img); 810 }); 811 } 812 813 // if DOM not ready, wait; otherwise run now 814 if (document.readyState === 'loading') { 815 document.addEventListener('DOMContentLoaded', init); 816 } else { 817 init(); 818 } 819 }; 820 821 /** 822 * Handle login and register trigger 823 */ 824 const LoginRegisterTrigger = function () { 825 // Common function to toggle between login and register forms 826 const toggleForms = function (showLogin) { 827 // Toggle modal buttons 828 $('.modal-toggle-1').toggleClass('active', showLogin); 829 $('.modal-toggle-2').toggleClass('active', !showLogin); 830 831 // Toggle form visibility 832 $('.login-form-tab').toggleClass('active show', showLogin); 833 $('.register-form-tab').toggleClass('active show', !showLogin); 834 }; 835 836 // Event handlers 837 $('.login-link a').on('click', function (e) { 838 e.preventDefault(); 839 toggleForms(true); 840 }); 841 842 $('.register-link a').on('click', function (e) { 843 e.preventDefault(); 844 toggleForms(false); 845 }); 846 }; 847 848 /** 849 * Handle sidebar menu toggle 850 */ 851 const sidebarMenuToggle = function () { 852 $('.menu-btn').on('click', function () { 853 $('body').toggleClass('sidebar-collapsed'); 854 }); 855 856 $('.crose-btn').on('click', function () { 857 $('body').removeClass('sidebar-collapsed'); 858 }); 859 }; 860 861 /** 862 * Initialize sticky sidebar functionality 863 * Calculates appropriate top margin based on various header elements 864 */ 865 const stickySidebar = function () { 866 // Only proceed if sticky elements exist 867 if (!$('.houzez_sticky').length) return; 868 869 // Initialize variables for margin calculation 870 let houzezStickyTop = 0; 871 let adminBarHeight = houzez.Core.config.wpadminbar_height; 872 873 // Get advanced search nav height if sticky 874 const advanced_search_nav = $('.advanced-search-nav'); 875 let advanced_search_nav_height = 0; 876 877 if (advanced_search_nav.data('sticky') === 1) { 878 advanced_search_nav_height = 879 advanced_search_nav.innerHeight() || 0; 880 } 881 882 // Get header nav height and sticky status 883 const header_nav = $('#header-section'); 884 const houzez_nav_sticky_height = header_nav.innerHeight() || 0; 885 let only_nav_sticky = header_nav.data('sticky'); 886 887 // Disable sticky nav on property detail pages with property navigation 888 if ( 889 houzez.Core.config.property_detail_nav === 'yes' && 890 houzez.Core.config.is_singular_property 891 ) { 892 only_nav_sticky = 0; 893 } 894 895 // Add header height to sticky top if header is sticky 896 if (only_nav_sticky === 1) { 897 houzezStickyTop = houzez_nav_sticky_height; 898 } 899 900 // Add admin bar height if present 901 if (adminBarHeight) { 902 houzezStickyTop += adminBarHeight; 903 } 904 905 // Get property navigation height if present 906 const listing_nav_area_height = 907 $('.property-navigation-wrap').innerHeight() || 0; 908 909 // Initialize theiaStickySidebar with calculated margins 910 $('.houzez_sticky').theiaStickySidebar({ 911 additionalMarginTop: 912 houzezStickyTop + 913 advanced_search_nav_height + 914 listing_nav_area_height, 915 minWidth: 768, 916 updateSidebarHeight: false, 917 }); 918 }; 919 920 const checkCollapse = function () { 921 var el = document.getElementById('search-expandable-collapse'); 922 923 if (!el) return; 924 925 if (window.innerWidth < 992) { 926 // Mobile: chiuso 927 el.classList.remove('show'); 928 } else {
929 // Desktop: aperto 930 el.classList.add('show'); 931 } 932 }; 933 934 /** 935 * One Page push state 936 */ 937 const onePagePushState = function () { 938 $( 939 '.houzez-onepage-mode .header-main-wrap .main-nav li.nav-item a.nav-link' 940 ).on('click', function (e) { 941 var currentUrl = $(this).attr('href'); 942 window.history.pushState({ houzezTheme: true }, '', currentUrl); 943 }); 944 }; 945 946 const houzezBannerSearchAutocomplete = function () { 947 const input = $('.search-icon input.form-control'); 948 const dropdown = $('.auto-complete-banner'); 949 950 // Only execute if auto-complete-banner exists 951 if (dropdown.length > 0 && input.length > 0) { 952 const rect = input[0].getBoundingClientRect(); 953 const offset = 8; // distanza verticale extra 954 955 dropdown.css({ 956 position: 'absolute', 957 top: `${window.scrollY + rect.bottom + offset}px`, 958 left: `${window.scrollX + rect.left + rect.width / 2}px`, 959 transform: 'translateX(-50%)', 960 width: `${rect.width}px`, 961 zIndex: '9999', 962 }); 963 } 964 }; 965 966 // Initialize core module 967 const init = function () { 968 $(document).ready(function () { 969 LoginRegisterTrigger(); 970 listingViewSwitch(); 971 initTooltips(); 972 initPropertySorting(); 973 initReviewsTab(); 974 mobileToggleMenu(); 975 //handlePageSpecificFunctions(); 976 //initDynamicModals(); 977 //is_mobile(); 978 sortby_mobile(); 979 gridCallToAction(); 980 sidebarMenuToggle(); 981 scrollToTop(); // Initialize scroll-to-top 982 stickySidebar(); 983 houzezBannerSearchAutocomplete(); 984 }); 985 986 houzezLazyload(); 987 onePagePushState(); 988 989 document.addEventListener('DOMContentLoaded', checkCollapse); 990 // window.addEventListener("resize", checkCollapse); 991 992 // Initialize section height calculations 993 util.calculateSectionHeight(); 994 $window.on('load', util.calculateSectionHeight); 995 $window.on('resize', util.calculateSectionHeight); 996 $window.on('scroll', houzezBannerSearchAutocomplete); 997 $window.on('resize', houzezBannerSearchAutocomplete); 998 $window.on('resize', checkCollapse); 999 }; 1000 1001 // Public API 1002 return { 1003 init: init, 1004 config: config, 1005 util: util, 1006 initTooltips: initTooltips, 1007 hideAllTooltips: hideAllTooltips, 1008 initReviewsTab: initReviewsTab, 1009 listingViewSwitch: listingViewSwitch, 1010 is_mobile: is_mobile, 1011 //initDynamicModals: initDynamicModals, 1012 mobileToggleMenu: mobileToggleMenu, 1013 //handlePageSpecificFunctions: handlePageSpecificFunctions, 1014 gridCallToAction: gridCallToAction, 1015 scrollToTop: scrollToTop, // Expose scrollToTop 1016 stickySidebar: stickySidebar, 1017 reinitializeAjaxFunctions: reinitializeAjaxFunctions, 1018 }; 1019 })(); 1020 1021 houzez.PropertiesTabs = (function () { 1022 // Flag to track if tabs have been initialized 1023 let tabsInitialized = false; 1024 1025 /** 1026 * Initialize products tabs for Elementor 1027 */ 1028 const propertiesTabsInit = function () { 1029 // Exit if already initialized to prevent duplicate event handlers 1030 if (tabsInitialized) { 1031 return; 1032 } 1033 1034 tabsInitialized = true; 1035 var alreadyProcessed = false; 1036 1037 $('.houzez-properties-tabs-js').each(function () { 1038 var $this = $(this); 1039 var $html_container = $this.find('.houzez-tab-content'); 1040 var $products_cache = []; 1041 $this.find('ul.property-nav-tabs li').on('click', function (e) { 1042 e.preventDefault(); 1043 var $this = $(this); 1044 var settings = $this.data('json'); 1045 var data_index = $this.index(); 1046 1047 // Only check if processing, not if active 1048 if (alreadyProcessed) { 1049 return; 1050 } 1051 1052 // Remove active class from all tabs and add to the clicked tab 1053 $this.siblings().find('a').removeClass('active'); 1054 $this.find('a').addClass('active'); 1055 1056 alreadyProcessed = true; 1057 1058 if ($products_cache[data_index]) { 1059 setTimeout(function () { 1060 $html_container.html( 1061 $products_cache[data_index].html 1062 ); 1063 1064 houzez.Core.reinitializeAjaxFunctions(); 1065 1066 alreadyProcessed = false;
1067 }, 300); 1068 return; 1069 } 1070 1071 $.ajax({ 1072 url: houzez.Core.config.ajaxurl, 1073 data: { 1074 action: 'houzez_get_properties_tab_content', 1075 settings: settings, 1076 }, 1077 dataType: 'json', 1078 method: 'POST', 1079 beforeSend: function () { 1080 $html_container 1081 .empty() 1082 .append( 1083 '<div id="houzez-map-loading">' + 1084 '<div class="mapPlaceholder">' + 1085 '<div class="loader-ripple spinner">' + 1086 '<div class="bounce1"></div>' + 1087 '<div class="bounce2"></div>' + 1088 '<div class="bounce3"></div>' + 1089 '</div>' + 1090 '</div>' + 1091 '</div>' 1092 ); 1093 }, 1094 success: function (data) { 1095 $products_cache[data_index] = data; 1096 $html_container.html(data.html); 1097 1098 houzez.Core.reinitializeAjaxFunctions(); 1099 }, 1100 error: function (xhr, status, error) { 1101 var err = eval('(' + xhr.responseText + ')'); 1102 console.log(err.Message); 1103 }, 1104 complete: function () { 1105 alreadyProcessed = false; 1106 }, 1107 }); 1108 }); 1109 1110 // Set first tab as active by default if none are active 1111 var $firstTab = $this.find( 1112 'ul.property-nav-tabs li:first-child' 1113 ); 1114 if (!$this.find('ul.property-nav-tabs li a.active').length) { 1115 $firstTab.find('a').addClass('active'); 1116 } 1117 }); 1118 }; 1119 1120 /** 1121 * Initialize the module 1122 */ 1123 const init = function () { 1124 propertiesTabsInit(); 1125 }; 1126 1127 // Public API 1128 return { 1129 init: init, 1130 propertiesTabsInit: propertiesTabsInit, 1131 }; 1132 })(); 1133 1134 /** 1135 * Login Module 1136 * Handles authentication functions 1137 */ 1138 houzez.Login = (function () { 1139 /** 1140 * Process login form submission 1141 */ 1142 const processLogin = function (e) { 1143 // Prevent default form submission 1144 e.preventDefault(); 1145 1146 // Get form data 1147 const $form = $(this); 1148 const $messages = $('#hz-login-messages'); 1149 const config = houzez.Core.config; 1150 1151 // Check if already processing 1152 if ($form.hasClass('form-submitted')) { 1153 return; 1154 } 1155 1156 // Mark form as submitted 1157 $form.addClass('form-submitted'); 1158 1159 // Show processing message 1160 houzez.Core.util.showSuccess($messages, config.login_loading); 1161 1162 // Send AJAX request 1163 $.ajax({ 1164 type: 'POST', 1165 dataType: 'json', 1166 url: config.ajaxurl, 1167 data: $form.serialize(), 1168 beforeSend: function () { 1169 $form.find('.btn-login').attr('disabled', true); 1170 $form 1171 .find('.btn-login .houzez-loader-js') 1172 .addClass('loader-show'); 1173 }, 1174 success: function (response) { 1175 if (response.success) { 1176 houzez.Core.util.showSuccess($messages, response.msg); 1177 1178 if (config.redirect_type === 'same_page') { 1179 window.location.reload(); 1180 } else { 1181 window.location.href = config.login_redirect; 1182 } 1183 } else { 1184 houzez.Core.util.showError($messages, response.msg); 1185 } 1186 }, 1187 error: function (xhr, status, error) { 1188 console.error('Login error: ' + error); 1189 houzez.Core.util.showError( 1190 $messages, 1191 'Error processing login' 1192 ); 1193 }, 1194 complete: function () { 1195 $form.removeClass('form-submitted'); 1196 $form.find('.btn-login').attr('disabled', false); 1197 $form 1198 .find('.btn-login .houzez-loader-js') 1199 .removeClass('loader-show'); 1200 }, 1201 }); 1202 1203 return false;
1204 }; 1205 1206 /** 1207 * Process registration form submission 1208 */ 1209 const processRegister = function (e) { 1210 // Prevent default form submission 1211 e.preventDefault(); 1212 1213 // Get form data 1214 const $form = $(this); 1215 const $messages = $('#hz-register-messages'); 1216 const config = houzez.Core.config; 1217 1218 // Check if already processing 1219 if ($form.hasClass('form-submitted')) { 1220 return; 1221 } 1222 1223 // Mark form as submitted 1224 $form.addClass('form-submitted'); 1225 1226 // Show processing message 1227 houzez.Core.util.showSuccess($messages, config.login_loading); 1228 1229 submitRegistrationForm(); 1230 1231 function submitRegistrationForm() { 1232 // Send AJAX request 1233 $.ajax({ 1234 type: 'POST', 1235 dataType: 'json', 1236 url: config.ajaxurl, 1237 data: $form.serialize(), 1238 beforeSend: function () { 1239 $form.find('.btn-register').attr('disabled', true); 1240 $form 1241 .find('.btn-register .houzez-loader-js') 1242 .addClass('loader-show'); 1243 }, 1244 success: function (response) { 1245 if (response.success) { 1246 houzez.Core.util.showSuccess( 1247 $messages, 1248 response.msg 1249 ); 1250 1251 setTimeout(function () { 1252 $('#login-tab').tab('show'); 1253 }, 2000); 1254 } else { 1255 houzez.Core.util.showError($messages, response.msg); 1256 } 1257 // Reset captcha (supports both reCaptcha and Turnstile) 1258 houzez.Core.util.resetCaptcha($form); 1259 }, 1260 error: function (xhr, status, error) { 1261 console.error('Registration error: ' + error); 1262 houzez.Core.util.showError( 1263 $messages, 1264 'Error processing registration' 1265 ); 1266 }, 1267 complete: function () { 1268 $form.removeClass('form-submitted'); 1269 $form.find('.btn-register').attr('disabled', false); 1270 $form 1271 .find('.btn-register .houzez-loader-js') 1272 .removeClass('loader-show'); 1273 }, 1274 }); 1275 } 1276 }; 1277 1278 /** 1279 * Process password reset form 1280 */ 1281 const processForgotPassword = function (e) { 1282 // Prevent default form submission 1283 e.preventDefault(); 1284 1285 // Get form data 1286 const $form = $(this); 1287 const user_login = $('#user_login').val(); 1288 const security = $('#fave_resetpassword_security').val(); 1289 const $messages = $('#reset_pass_msg'); 1290 const config = houzez.Core.config; 1291 1292 // Check if already processing 1293 if ($form.hasClass('form-submitted')) { 1294 return; 1295 } 1296 1297 // Mark form as submitted 1298 $form.addClass('form-submitted'); 1299 1300 // Show processing message 1301 houzez.Core.util.showSuccess($messages, config.login_loading); 1302 1303 // Send AJAX request 1304 $.ajax({ 1305 type: 'POST', 1306 dataType: 'json', 1307 url: config.ajaxurl, 1308 data: { 1309 action: 'houzez_forgot_password', 1310 user_login: user_login, 1311 security: security, 1312 }, 1313 beforeSend: function () { 1314 $form.find('.btn-reset-password').attr('disabled', true); 1315 $form 1316 .find('.btn-reset-password .houzez-loader-js') 1317 .addClass('loader-show'); 1318 }, 1319 success: function (response) { 1320 if (response.success) { 1321 houzez.Core.util.showSuccess($messages, response.msg); 1322 } else { 1323 houzez.Core.util.showError($messages, response.msg); 1324 } 1325 }, 1326 error: function (xhr, status, error) { 1327 console.error('Reset password error: ' + error); 1328 houzez.Core.util.showError( 1329 $messages, 1330 'Error processing request' 1331 ); 1332 }, 1333 complete: function () { 1334 $form.removeClass('form-submitted'); 1335 $form.find('.btn-reset-password').attr('disabled', false); 1336 $form 1337 .find('.btn-reset-password .houzez-loader-js') 1338 .removeClass('loader-show'); 1339 }, 1340 }); 1341 1342 return false;
1343 }; 1344 1345 /** 1346 * Process reset password from reset password page 1347 */ 1348 const processResetPassword = function () { 1349 if ($('#houzez_reset_password').length > 0) { 1350 $('#houzez_reset_password_form').on('submit', function (e) { 1351 e.preventDefault(); 1352 var $this = $(this); 1353 var rg_login = $('input[name="rp_login"]').val(); 1354 var rp_key = $('input[name="rp_key"]').val(); 1355 var pass1 = $('input[name="pass1"]').val(); 1356 var pass2 = $('input[name="pass2"]').val(); 1357 var security = $( 1358 'input[name="resetpassword_security"]' 1359 ).val(); 1360 var $messages = $('#reset_pass_msg_2'); 1361 1362 $.ajax({ 1363 type: 'post', 1364 url: houzez.Core.config.ajaxurl, 1365 dataType: 'json', 1366 data: { 1367 action: 'houzez_reset_password', 1368 rq_login: rg_login, 1369 password: pass1, 1370 confirm_pass: pass2, 1371 rp_key: rp_key, 1372 security: security, 1373 }, 1374 beforeSend: function () { 1375 $this 1376 .find('.houzez-loader-js') 1377 .addClass('loader-show'); 1378 }, 1379 complete: function () { 1380 $this 1381 .find('.houzez-loader-js') 1382 .removeClass('loader-show'); 1383 }, 1384 success: function (response) { 1385 if (response.success) { 1386 houzez.Core.util.showSuccess( 1387 $messages, 1388 response.msg 1389 ); 1390 jQuery('#oldpass, #newpass, #confirmpass').val( 1391 '' 1392 ); 1393 } else { 1394 houzez.Core.util.showError( 1395 $messages, 1396 response.msg 1397 ); 1398 } 1399 }, 1400 error: function (errorThrown) {}, 1401 }); 1402 }); 1403 } 1404 }; 1405 1406 // Initialize login module 1407 const init = function () { 1408 // Attach event handlers 1409 $('#houzez_login_form').on('submit', processLogin); 1410 $('#houzez_register_form').on('submit', processRegister); 1411 $('#houzez_forgot_password_form').on( 1412 'submit', 1413 processForgotPassword 1414 ); 1415 1416 processResetPassword(); 1417 // Handle popup login 1418 $('.hz-popup-login').on('click', function (e) { 1419 e.preventDefault(); 1420 $('#login-register-form').modal('show'); 1421 $('.login-form-tab').show(); 1422 $('.register-form-tab').hide(); 1423 }); 1424 1425 // Handle popup register 1426 $('.hz-popup-register').on('click', function (e) { 1427 e.preventDefault(); 1428 $('#login-register-form').modal('show'); 1429 $('.register-form-tab').show(); 1430 $('.login-form-tab').hide(); 1431 }); 1432 }; 1433 1434 // Public API 1435 return { 1436 init: init, 1437 processLogin: processLogin, 1438 processRegister: processRegister, 1439 processForgotPassword: processForgotPassword, 1440 processResetPassword: processResetPassword, 1441 }; 1442 })(); 1443 1444 /** 1445 * Favorites Module 1446 * Handles property favorites functionality 1447 */ 1448 houzez.Favorites = (function () { 1449 // Cache DOM selectors for better performance 1450 const $document = $(document); 1451 const $favoriteBtn = $('a.favorite-btn'); 1452 const $favCount = $('span.frvt-count'); 1453 1454 // Common selectors 1455 const selectors = { 1456 addFavorite: '.add-favorite-js', 1457 removeFavorite: '.remove_fav', 1458 loginRegisterForm: '#login-register-form', 1459 loginFormTab: '.login-form-tab', 1460 modalToggle1: '.modal-toggle-1.nav-link', 1461 registerFormTab: '.register-form-tab', 1462 modalToggle2: '.modal-toggle-2.nav-link', 1463 }; 1464 1465 /** 1466 * Initialize adding a property to favorites 1467 */ 1468 const initAddFavorite = function () { 1469 // Remove any existing handlers first to prevent duplicates 1470 $document.off('click.addFavorite', selectors.addFavorite); 1471 1472 // Add the handler with namespace for better management 1473 $document.on( 1474 'click.addFavorite', 1475 selectors.addFavorite, 1476 function (e) { 1477 e.preventDefault(); 1478 e.stopPropagation(); 1479 1480 const $this = $(this); 1481 const listID = $this.data('listid'); 1482 1483 add_to_favorite( 1484 houzez.Core.config.ajaxurl, 1485 listID, 1486 $this, 1487 houzez.Core.config.userID 1488 ); 1489 return false;
1490 } 1491 ); 1492 }; 1493 1494 /** 1495 * Initialize removing a property from favorites 1496 */ 1497 const initRemoveFavorite = function () { 1498 // Remove any existing handlers first to prevent duplicates 1499 $document.off('click.removeFavorite', selectors.removeFavorite); 1500 1501 // Add the handler with namespace for better management 1502 $document.on( 1503 'click.removeFavorite', 1504 selectors.removeFavorite, 1505 function (e) { 1506 e.preventDefault(); 1507 e.stopPropagation(); 1508 1509 const $this = $(this); 1510 const listID = $this.data('listid'); 1511 1512 add_to_favorite( 1513 houzez.Core.config.ajaxurl, 1514 listID, 1515 $this, 1516 houzez.Core.config.userID 1517 ); 1518 1519 // If on the favorites page, remove the row 1520 if ($this.parents('tr').length > 0) { 1521 $this.parents('tr').remove(); 1522 } 1523 1524 return false; 1525 } 1526 ); 1527 }; 1528 1529 /** 1530 * Initialize favorite dropdown link navigation 1531 * Fixes the issue where clicking the favorite link in the dropdown menu 1532 * doesn't navigate because the parent button has a modal trigger 1533 */ 1534 const initFavoriteDropdownLink = function () { 1535 // Remove any existing handlers first to prevent duplicates 1536 $document.off('click.favoriteDropdown', '.dropdown-menu-favorites .favorite-btn.dropdown-item'); 1537 1538 // Add the handler with namespace for better management 1539 $document.on( 1540 'click.favoriteDropdown', 1541 '.dropdown-menu-favorites .favorite-btn.dropdown-item', 1542 function (e) { 1543 e.preventDefault(); 1544 e.stopPropagation(); 1545 1546 const $this = $(this); 1547 const href = $this.attr('href'); 1548 1549 // Navigate to favorites page if href is valid 1550 if (href && href !== '#' && href !== '') { 1551 window.location.href = href; 1552 } 1553 1554 return false; 1555 } 1556 ); 1557 }; 1558 1559 /** 1560 * Show the login modal with login tab active 1561 */ 1562 const showLoginModal = function () { 1563 $(selectors.loginRegisterForm).modal('show'); 1564 $(selectors.loginFormTab).addClass('active show'); 1565 $(selectors.modalToggle1).addClass('active'); 1566 $(selectors.registerFormTab).removeClass('active show'); 1567 $(selectors.modalToggle2).removeClass('active'); 1568 }; 1569 1570 /** 1571 * Update favorite button UI 1572 * @param {object} $element - jQuery object of the element 1573 * @param {boolean} isActive - Whether the favorite is active or not 1574 */ 1575 const updateFavoriteUI = function ($element, isActive) { 1576 if (isActive) { 1577 $element.children('i').addClass('text-danger'); 1578 if ($element.hasClass('add-favorite-js')) { 1579 $element 1580 .removeClass('add-favorite-js') 1581 .addClass('remove_fav'); 1582 } 1583 $element.html( 1584 '<i class="houzez-icon icon-love-it text-danger"></i>' 1585 ); 1586 } else { 1587 $element.children('i').removeClass('text-danger'); 1588 if ($element.hasClass('remove_fav')) { 1589 $element 1590 .removeClass('remove_fav') 1591 .addClass('add-favorite-js'); 1592 } 1593 $element.html('<i class="houzez-icon icon-love-it"></i>'); 1594 } 1595 }; 1596 1597 /** 1598 * Add a property to favorites (works for both adding and removing) 1599 * @param {string} ajaxurl - Ajax URL 1600 * @param {number} listID - Property ID 1601 * @param {object} curnt - jQuery object of the clicked element 1602 * @param {number} userID - User ID 1603 */ 1604 const add_to_favorite = function (ajaxurl, listID, curnt, userID) { 1605 const config = houzez.Core.config; 1606 1607 // Handle non-logged in users 1608 if (parseInt(userID, 10) === 0 || userID === undefined) { 1609 if (config.add_to_favorite_login_required != 0) { 1610 // If login is required, show login modal 1611 showLoginModal(); 1612 } else { 1613 // If login is not required, handle with cookies 1614 add_to_favorite_with_cookies(curnt, listID); 1615 } 1616 return; 1617 } 1618 1619 // Handle logged in users with AJAX 1620 const $parents = curnt.parents('.item-wrap'); 1621 const preview_loader = $parents.find('.preview_loader'); 1622 1623 $.ajax({ 1624 type: 'post', 1625 url: houzez.Core.config.ajaxurl, 1626 dataType: 'json', 1627 data: { 1628 action: 'houzez_add_to_favorite', 1629 listing_id: listID, 1630 }, 1631 beforeSend: function () { 1632 if (preview_loader.length) { 1633 preview_loader 1634 .empty() 1635 .append(houzez.Core.util.getLoaderHtml()); 1636 } else { 1637 curnt.html('<i class="houzez-loader-js"></i>'); 1638 curnt.find('.houzez-loader-js').addClass('loader-show'); 1639 } 1640 }, 1641 complete: function () { 1642 if (preview_loader.length) { 1643 preview_loader.empty(); 1644 } 1645 }, 1646 success: function (response) { 1647 if (response.success) { 1648 const isAdded = response.data.added; 1649 updateFavoriteUI(curnt, isAdded); 1650 } 1651 }, 1652 error: function (xhr, status, error) { 1653 console.log(error); 1654 }, 1655 }); 1656 }; 1657 1658 /** 1659 * Update favorite count and URL 1660 * @param {Array} listings_favorite - Array of favorite listing IDs 1661 */ 1662 const updateFavoriteCountAndUrl = function (listings_favorite) { 1663 $favCount.html(''); 1664 $favCount.html(listings_favorite.length); 1665 1666 $favoriteBtn.attr( 1667 'href', 1668 houzez.Core.config.favorite_url + 1669 '?ids=' + 1670 listings_favorite.join(',') 1671 ); 1672 }; 1673 1674 /** 1675 * Add to favorite without login using cookies 1676 * @param {object} curnt - jQuery object of the clicked element 1677 * @param {number} listing_id - Listing ID 1678 */ 1679 const add_to_favorite_with_cookies = function (curnt, listing_id) { 1680 let listings_favorite = houzez.Core.util.getCookie( 1681 'houzez_favorite_listings' 1682 ); 1683 1684 if (listings_favorite && listings_favorite.length) { 1685 listings_favorite = listings_favorite.split(','); 1686 } else { 1687 listings_favorite = []; 1688 } 1689 1690 const index = listings_favorite.indexOf(listing_id.toString()); 1691 const isAdding = index === -1; 1692 1693 if (isAdding) { 1694 // Add to favorites 1695 listings_favorite.push(listing_id.toString()); 1696 } else { 1697 // Remove from favorites 1698 listings_favorite.splice(index, 1); 1699 } 1700 1701 // Save cookie 1702 houzez.Core.util.setCookie( 1703 'houzez_favorite_listings', 1704 listings_favorite.join(','), 1705 30 1706 ); 1707 1708 // Update UI 1709 updateFavoriteCountAndUrl(listings_favorite); 1710 updateFavoriteUI(curnt, isAdding); 1711 }; 1712 1713 /** 1714 * Check favorites for user (matches original implementation) 1715 * @param {number} userID - User ID 1716 */ 1717 const check_favorites = function (userID) { 1718 if (parseInt(userID, 10) === 0 || userID === undefined) { 1719 let listings_favorite = houzez.Core.util.getCookie( 1720 'houzez_favorite_listings' 1721 ); 1722 1723 $favoriteBtn.attr( 1724 'href', 1725 houzez.Core.config.favorite_url + 1726 '?ids=' + 1727 listings_favorite 1728 ); 1729 1730 if (listings_favorite && listings_favorite.length) { 1731 listings_favorite = listings_favorite.split(','); 1732 1733 if (listings_favorite.length) { 1734 for (let i = 0; i < listings_favorite.length; i++) { 1735 $( 1736 `.add-favorite-js[data-listid="${listings_favorite[i]}"] i` 1737 ).addClass('text-danger'); 1738 $( 1739 `.add-favorite-js[data-listid="${listings_favorite[i]}"]` 1740 ).addClass('remove-favorite'); 1741 } 1742 1743 $favCount.html(''); 1744 $favCount.html(listings_favorite.length); 1745 } 1746 } else { 1747 listings_favorite = []; 1748 } 1749 } 1750 // No else block - no AJAX call for logged-in users in original co
1750de 1751 }; 1752 1753 // Initialize favorites module 1754 const init = function () { 1755 initAddFavorite(); 1756 initRemoveFavorite(); 1757 initFavoriteDropdownLink(); 1758 check_favorites(houzez.Core.config.userID); 1759 }; 1760 1761 // Public API 1762 return { 1763 init: init, 1764 initAddFavorite: initAddFavorite, 1765 initRemoveFavorite: initRemoveFavorite, 1766 initFavoriteDropdownLink: initFavoriteDropdownLink, 1767 add_to_favorite: add_to_favorite, 1768 remove_from_favorite: add_to_favorite, // Use the same function for consistency 1769 check_favorites: check_favorites, 1770 showLoginModal: showLoginModal, 1771 }; 1772 })(); 1773 1774 /** 1775 * Compare Module 1776 * Handles property comparison functionality 1777 */ 1778 houzez.Compare = (function () { 1779 // Limit for comparison items 1780 const limit_item_compare = 4; 1781 1782 /** 1783 * Add property to compare 1784 */ 1785 function add_to_compare( 1786 compare_url, 1787 compare_add_icon, 1788 compare_remove_icon, 1789 add_compare_text, 1790 remove_compare_text, 1791 compare_limit, 1792 listings_compare, 1793 limit_item_compare 1794 ) { 1795 var storedData = localStorage.getItem('houzez_compare_listings'); 1796 var listings_compare = storedData ? JSON.parse(storedData) : []; 1797 1798 $('a.compare-btn').attr( 1799 'href', 1800 compare_url + 1801 '?ids=' + 1802 listings_compare.map((item) => item.id).join(',') 1803 ); 1804 1805 if (listings_compare.length > 0) { 1806 $('.compare-property-label').fadeIn(1000); 1807 } 1808 1809 if (listings_compare.length) { 1810 for (var i = 0; i < listings_compare.length; i++) { 1811 $( 1812 '.houzez_compare[data-listing_id="' + 1813 listings_compare[i].id + 1814 '"] i' 1815 ) 1816 .removeClass('icon-add-circle') 1817 .addClass('icon-subtract-circle'); 1818 $( 1819 '.houzez_compare[data-listing_id="' + 1820 listings_compare[i].id + 1821 '"]' 1822 ).attr('title', remove_compare_text); 1823 $( 1824 '.houzez_compare[data-listing_id="' + 1825 listings_compare[i].id + 1826 '"]' 1827 ) 1828 .tooltip('hide') 1829 .attr('data-original-title', remove_compare_text); 1830 } 1831 $('.compare-property-label') 1832 .find('.compare-count') 1833 .html(listings_compare.length); 1834 } 1835 } 1836 1837 /** 1838 * Handle compare button click 1839 */ 1840 const handleCompareButtonClick = function ( 1841 compare_url, 1842 compare_add_icon, 1843 compare_remove_icon, 1844 add_compare_text, 1845 remove_compare_text, 1846 compare_limit, 1847 limit_item_compare 1848 ) { 1849 $('.houzez_compare') 1850 .off('click') 1851 .on('click', function (e) { 1852 e.preventDefault(); 1853 1854 var storedData = localStorage.getItem( 1855 'houzez_compare_listings' 1856 ); 1857 var listings_compare = storedData 1858 ? JSON.parse(storedData) 1859 : []; 1860 1861 var listing_id = $(this).data('listing_id'); 1862 var index = listings_compare.findIndex( 1863 (item) => item.id === listing_id.toString() 1864 ); 1865 var image_div = $(this).parents('.item-wrap'); 1866 var thumb_url = image_div.find('img').attr('src'); 1867 1868 if (index == -1) { 1869 if (listings_compare.length >= limit_item_compare) { 1870 alert(compare_limit); 1871 } else { 1872 $('.compare-wrap').append( 1873 '<div class="compare-item remove-' + 1874 listing_id + 1875 '"><a href="" class="remove-compare remove-icon" data-listing_id="' + 1876 listing_id + 1877 '"><i class="houzez-icon icon-remove-circle"></i></a><img class="img-fluid" src="' + 1878 thumb_url + 1879 '" width="200" height="150" alt="Thumb"></div>' 1880 ); 1881 1882 listings_compare.push({ 1883 id: listing_id.toString(), 1884 image: thumb_url, 1885 }); 1886 localStorage.setItem( 1887 'houzez_compare_listings', 1888 JSON.stringify(listings_compare) 1889 ); 1890 $(this).attr('title', remove_compare_text); 1891 $(this) 1892 .find('i') 1893 .removeClass('icon-add-circle') 1894 .addClass('icon-subtract-circle'); 1895 $('.compare-property-label') 1896 .find('.compare-count') 1897 .html(listings_compare.length); 1898 $('a.compare-btn').attr( 1899 'href', 1900 compare_url + 1901 '?ids=' + 1902 listings_compare 1903 .map((item) => item.id) 1904 .join(',') 1905 ); 1906 $('.compare-property-label').fadeIn(1000); 1907 $(this).toggleClass('active'); 1908 $('.compare-property-active').addClass( 1909 'compare-property-active-push-toleft' 1910 ); 1911 $('#compare-property-panel').addClass( 1912 'compare-property-panel-open' 1913 ); 1914 $(this).tooltip('dispose').tooltip('show'); 1915 remove_from_compare( 1916 listings_compare, 1917 compare_add_icon,
1918 compare_remove_icon, 1919 add_compare_text, 1920 remove_compare_text 1921 ); 1922 } 1923 } else { 1924 $('div.remove-' + listing_id).remove(); 1925 $(this).attr('title', add_compare_text); 1926 $(this) 1927 .find('i') 1928 .removeClass('icon-subtract-circle') 1929 .addClass('icon-add-circle'); 1930 listings_compare.splice(index, 1); 1931 localStorage.setItem( 1932 'houzez_compare_listings', 1933 JSON.stringify(listings_compare) 1934 ); 1935 $('.compare-property-label') 1936 .find('.compare-count') 1937 .html(listings_compare.length); 1938 $('a.compare-btn').attr( 1939 'href', 1940 compare_url + 1941 '?ids=' + 1942 listings_compare 1943 .map((item) => item.id) 1944 .join(',') 1945 ); 1946 $(this).tooltip('dispose').tooltip('show'); 1947 1948 if (listings_compare.length > 0) { 1949 $('.compare-property-label').fadeIn(1000); 1950 $(this).toggleClass('active'); 1951 $('.compare-property-active').addClass( 1952 'compare-property-active-push-toleft' 1953 ); 1954 $('#compare-property-panel').addClass( 1955 'compare-property-panel-open' 1956 ); 1957 } else { 1958 $('.compare-property-label').fadeOut(1000); 1959 } 1960 } 1961 return false; 1962 }); 1963 }; 1964 1965 /** 1966 * Remove property from compare 1967 */ 1968 function remove_from_compare( 1969 listings_compare, 1970 compare_add_icon, 1971 compare_remove_icon, 1972 add_compare_text, 1973 remove_compare_text 1974 ) { 1975 $('.remove-compare') 1976 .off('click') 1977 .on('click', function (e) { 1978 e.preventDefault(); 1979 const config = houzez.Core.config; 1980 var compare_url = config.compare_url; 1981 var storedData = localStorage.getItem( 1982 'houzez_compare_listings' 1983 ); 1984 listings_compare = storedData ? JSON.parse(storedData) : []; 1985 1986 var listing_id = $(this).data('listing_id'); 1987 var index = listings_compare.findIndex( 1988 (item) => item.id === listing_id.toString() 1989 ); 1990 1991 if (index !== -1) { 1992 // Only proceed if the item was found 1993 listings_compare.splice(index, 1); 1994 localStorage.setItem( 1995 'houzez_compare_listings', 1996 JSON.stringify(listings_compare) 1997 ); 1998 1999 $('.compare-property-label') 2000 .find('.compare-count') 2001 .html(listings_compare.length); 2002 2003 // Update UI elements if they exist 2004 var compareElement = $('.compare-' + listing_id); 2005 if (compareElement.length) { 2006 compareElement.attr('title', add_compare_text); 2007 compareElement 2008 .tooltip('hide') 2009 .attr('data-original-title', add_compare_text); 2010 compareElement 2011 .find('i') 2012 .removeClass('icon-subtract-circle') 2013 .addClass('icon-add-circle'); 2014 } 2015 2016 $(this).parents('.compare-item').remove(); 2017 2018 // Update the compare URL 2019 $('a.compare-btn').attr( 2020 'href', 2021 compare_url + 2022 '?ids=' + 2023 listings_compare 2024 .map((item) => item.id) 2025 .join(',') 2026 ); 2027 } 2028 }); 2029 } 2030 2031 /** 2032 * Initialize the compare panel toggle functionality 2033 */
2034 const initComparePanel = function () { 2035 $('.show-compare-panel').on('click', function () { 2036 $(this).toggleClass('active'); 2037 $('.compare-property-active').addClass( 2038 'compare-property-active-push-toleft' 2039 ); 2040 $('#compare-property-panel').addClass( 2041 'compare-property-panel-open' 2042 ); 2043 }); 2044 2045 $('.close-compare-panel').on('click', function () { 2046 $(this).toggleClass('active'); 2047 $('.compare-property-active').removeClass( 2048 'compare-property-active-push-toleft' 2049 ); 2050 $('#compare-property-panel').removeClass( 2051 'compare-property-panel-open' 2052 ); 2053 }); 2054 }; 2055 2056 /** 2057 * Update compare properties after AJAX operations 2058 * Used for refreshing comparison after content loads via AJAX 2059 */ 2060 const compare_for_ajax = function () { 2061 const config = houzez.Core.config; 2062 var listings_compare = houzez.Core.util.getCookie( 2063 'houzez_compare_listings' 2064 ); 2065 2066 add_to_compare( 2067 config.compare_url, 2068 config.compare_add_icon, 2069 config.compare_remove_icon, 2070 config.add_compare_text, 2071 config.remove_compare_text, 2072 config.compare_limit, 2073 listings_compare, 2074 limit_item_compare 2075 ); 2076 2077 // Set up event handlers for compare buttons 2078 handleCompareButtonClick( 2079 config.compare_url, 2080 config.compare_add_icon, 2081 config.compare_remove_icon, 2082 config.add_compare_text, 2083 config.remove_compare_text, 2084 config.compare_limit, 2085 limit_item_compare 2086 ); 2087 2088 remove_from_compare( 2089 listings_compare, 2090 config.compare_add_icon, 2091 config.compare_remove_icon, 2092 config.add_compare_text, 2093 config.remove_compare_text 2094 ); 2095 }; 2096 2097 /** 2098 * Render stored comparison properties in the compare panel 2099 */ 2100 const renderComparePropertiesData = function () { 2101 let compare_listings = JSON.parse( 2102 localStorage.getItem('houzez_compare_listings') 2103 ); 2104 2105 // Check if 'compare_listings' is not null and not empty before processing 2106 if (compare_listings && compare_listings.length > 0) { 2107 let properties_array = ''; 2108 compare_listings.forEach(function (item) { 2109 let img = item.image; // Image URL from the local storage array 2110 let listingId = item.id; // Listing ID from the local storage array 2111 2112 properties_array += 2113 '<div class="compare-item remove-' + 2114 listingId + 2115 '">' + 2116 '<a href="#" class="remove-compare remove-icon" data-listing_id="' + 2117 listingId + 2118 '">' + 2119 '<i class="houzez-icon icon-remove-circle"></i></a>' + 2120 '<img class="img-fluid" src="' + 2121 img + 2122 '" width="200" height="150" alt="Thumb">' + 2123 '</div>'; 2124 }); 2125 2126 $('.compare-wrap').html(properties_array); 2127 } 2128 }; 2129 2130 /** 2131 * Initialize the compare module 2132 */ 2133 const init = function () { 2134 initComparePanel(); 2135 compare_for_ajax(); 2136 renderComparePropertiesData(); 2137 2138 // Call remove_from_compare again after items are rendered to ensure event handlers are attached 2139 const compare_listings = 2140 JSON.parse(localStorage.getItem('houzez_compare_listings')) || 2141 []; 2142 remove_from_compare( 2143 compare_listings, 2144 houzez.Core.config.compare_add_icon,
2145 houzez.Core.config.compare_remove_icon, 2146 houzez.Core.config.add_compare_text, 2147 houzez.Core.config.remove_compare_text 2148 ); 2149 }; 2150 2151 // Public API 2152 return { 2153 init: init, 2154 add_to_compare: add_to_compare, 2155 remove_from_compare: remove_from_compare, 2156 compare_for_ajax: compare_for_ajax, 2157 }; 2158 })(); 2159 2160 /** 2161 * Sliders Module 2162 * Handles all slider and carousel functionality across the theme 2163 */ 2164 houzez.Sliders = (function () { 2165 // Cache DOM selectors for better performance 2166 const $document = $(document); 2167 const $window = $(window); 2168 2169 // Common Slick slider settings 2170 const commonSettings = { 2171 rtl: houzez.Core.config.houzez_rtl, 2172 lazyLoad: 'ondemand', 2173 adaptiveHeight: true, 2174 speed: 500, 2175 arrows: true, 2176 }; 2177 2178 const commonResponsiveSettings = [ 2179 { 2180 breakpoint: 992, 2181 settings: { 2182 slidesToShow: 2, 2183 slidesToScroll: 1, 2184 }, 2185 }, 2186 { 2187 breakpoint: 768, 2188 settings: { 2189 slidesToShow: 1, 2190 slidesToScroll: 1, 2191 }, 2192 }, 2193 ]; 2194 2195 const commonArrowButtons = { 2196 prevArrow: `<button type="button" class="slick-prev btn-primary-outlined">${houzez.Core.config.prev_text}</button>`, 2197 nextArrow: `<button type="button" class="slick-next btn-primary-outlined">${houzez.Core.config.next_text}</button>`, 2198 }; 2199 2200 /** 2201 * Initialize slider with error handling 2202 * @param {jQuery} $element - jQuery element to initialize slider on 2203 * @param {Object} settings - Slick slider settings 2204 */ 2205 const initSlider = function ($element, settings) { 2206 try { 2207 if ( 2208 $element && 2209 $element.length > 0 && 2210 typeof $.fn.slick === 'function' 2211 ) { 2212 // Check if slick is already initialized 2213 if ($element.hasClass('slick-initialized')) { 2214 $element.slick('unslick'); 2215 } 2216 2217 $element.slick({ 2218 ...commonSettings, 2219 ...settings, 2220 }); 2221 } else if (!$element || $element.length === 0) { 2222 console.warn('Slider element not found or empty'); 2223 } else if (typeof $.fn.slick !== 'function') { 2224 console.warn('Slick slider library not loaded'); 2225 } 2226 } catch (error) { 2227 console.error('Error initializing slider:', error); 2228 } 2229 }; 2230 2231 /** 2232 * Initialize custom carousel 2233 */ 2234 const customCarousel = function () { 2235 const $customCarousel = $('.custom-carousel'); 2236 if ($customCarousel.length > 0) { 2237 $customCarousel.each(function () { 2238 const $this = $(this); 2239 const token = $this.data('token'); 2240 const carouselSettings = $this.data('carousel'); 2241 2242 initSlider($('.custom-carousel-js-' + token), { 2243 autoplay: carouselSettings.slide_auto === 'true', 2244 autoplaySpeed: 2245 parseInt(carouselSettings.auto_speed) || 3000, 2246 infinite: carouselSettings.slide_infinite === 'true', 2247 speed: 500, 2248 slidesToShow: 2249 parseInt(carouselSettings.slides_to_show) || 3, 2250 slidesToScroll: 2251 parseInt(carouselSettings.slides_to_scroll) || 1, 2252 arrows: carouselSettings.navigation === 'true', 2253 dots: carouselSettings.slide_dots === 'true', 2254 appendArrows: '.custom-carousel-js-wrap-' + token, 2255 prevArrow: $('.slick-prev-js-' + token), 2256 nextArrow: $('.slick-next-js-' + token), 2257 responsive: commonResponsiveSettings, 2258 }); 2259 }); 2260 } 2261 }; 2262 2263 /** 2264 * Initialize property detail gallery with optimized loading experience 2265 * Improved for faster display and better mobile performance 2266 */ 2267 const propertyDetailGallery = function () { 2268 var property_detail_gallery = $('#property-gallery-js'); 2269 2270 if (property_detail_gallery.length > 0) { 2271 // Check if already has a lightSlider instance 2272 // var existingInstance = property_detail_gallery.data('lightSlider'); 2273 // if (existingInstance && typeof existingInstance.refresh === 'function') { 2274 // // Refresh existing instance instead of recreating 2275 // existingInstance.refresh(); 2276 // return; 2277 // } 2278 2279 if (!property_detail_gallery.hasClass('gallery-initialized')) { 2280 try { 2281 // Detect mobile for optimized timing
2282 var isMobile = houzez.Core.is_mobile(); 2283 2284 // Get all images 2285 var $images = property_detail_gallery.find( 2286 '.houzez-gallery-img' 2287 ); 2288 var totalImages = $images.length; 2289 var initialLoadCount = Math.min(3, totalImages); // Load first 3 or less 2290 var loadedCount = 0; 2291 var galleryInitialized = false; 2292 var sliderInitialized = false; 2293 2294 // Function to initialize the slider and show gallery 2295 var initializeSliderAndGallery = function () { 2296 if (sliderInitialized) return; 2297 sliderInitialized = true; 2298 2299 // Small delay to let browser paint the visible element 2300 setTimeout(function () { 2301 // Show slider div before initializing (lightSlider needs it visible to measure) 2302 property_detail_gallery.css('display', 'block'); 2303 2304 // Initialize slider on visible element 2305 var slider = 2306 property_detail_gallery.lightSlider({ 2307 rtl: houzez.Core.config.houzez_rtl, 2308 gallery: true, 2309 item: 1, 2310 thumbItem: 8, 2311 slideMargin: 0, 2312 speed: 500, 2313 adaptiveHeight: false, 2314 auto: false, 2315 loop: false, 2316 prevHtml: 2317 '<button type="button" class="slick-prev slick-arrow"></button>', 2318 nextHtml: 2319 '<button type="button" class="slick-next slick-arrow"></button>', 2320 onSliderLoad: function (el) { 2321 var _this = this; 2322 2323 // Store instance for later reference 2324 property_detail_gallery.data( 2325 'lightSlider', 2326 _this 2327 ); 2328 2329 // Add class to parent to show slider and hide placeholder 2330 $(el) 2331 .closest('.top-gallery-section') 2332 .addClass('slider-loaded'); 2333 2334 // Hide featured image placeholder now that gallery is ready 2335 // Delay slightly to ensure smooth LCP transition 2336 setTimeout(function () { 2337 var $placeholder = $( 2338 '#gallery-featured-placeholder' 2339 ); 2340 if ($placeholder.length > 0) { 2341 $placeholder.addClass( 2342 'hide-placeholder' 2343 ); 2344 } 2345 }, 100); 2346 2347 // Mark gallery as initialized 2348 property_detail_gallery.addClass( 2349 'gallery-initialized' 2350 ); 2351 galleryInitialized = true; 2352 2353 // Load remaining images after gallery is visible 2354 if ( 2355 totalImages > initialLoadCount 2356 ) { 2357 $images 2358 .slice(initialLoadCount) 2359 .each(function (index) { 2360 var $img = $(this); 2361 var lazySrc = 2362 $img.data('lazy'); 2363 if (lazySrc) { 2364 // Stagger loading to avoid blocking 2365 setTimeout( 2366 function () { 2367 var remainingImg = 2368 new Image(); 2369 remainingImg.onload = 2370 function () { 2371 $img.attr( 2372 'src', 2373 lazySrc 2374 ); 2375 }; 2376 remainingImg.onerror = 2377 function () { 2378 // Set src anyway so browser can handle broken image 2379 $img.attr( 2380 'src', 2381 lazySrc 2382 ); 2383 }; 2384 remainingImg.src = 2385 lazySrc; 2386 }, 2387 index * 50 2388 ); 2389 } 2390 }); 2391 } 2392 }, 2393 }); 2394 2395 // Store instance reference 2396 property_detail_gallery.data( 2397 'lightSlider', 2398 slider 2399 ); 2400 }, 100); // 10ms delay for browser paint 2401 }; 2402 initializeSliderAndGallery(); 2403 } catch (error) { 2404 // Error handling: Show gallery even if initialization fails 2405 console.error('Gallery initialization error:', error); 2406 property_detail_gallery.removeClass( 2407 'gallery-preparing' 2408 ); 2409 property_detail_gallery.addClass('gallery-initialized'); 2410 } 2411 } 2412 } 2413 }; 2414 2415 /** 2416 * Initialize lightbox slider 2417 */ 2418 const lightboxSlider = function () { 2419 const $lightboxSlider = $('#lightbox-slider-js'); 2420 if ($lightboxSlider.length > 0) { 2421 initSlider($lightboxSlider, { 2422 infinite: true, 2423 slidesToShow: 1, 2424 slidesToScroll: 1, 2425 autoplay: false, 2426 autoplaySpeed: 3000, 2427 arrows: true, 2428 dots: false, 2429 responsive: [], 2430 }); 2431 2432 $('.houzez-trigger-popup-slider-js').on('click', function (e) { 2433 e.preventDefault(); 2434 2435 let slider_num = parseInt($(this).data('slider-no')); 2436 setTimeout(function () { 2437 $lightboxSlider.slick('slickGoTo', slider_num - 1); 2438 }, 200); 2439 }); 2440 } 2441 }; 2442 2443 /** 2444 * Initialize variable width slider 2445 */ 2446 const variableWidthSlider = function () { 2447 const $variableWidthSlider = $('.listing-slider-variable-width'); 2448 if ($variableWidthSlider.length > 0) { 2449 initSlider($variableWidthSlider, { 2450 infinite: true, 2451 speed: 500, 2452 slidesToShow: 1, 2453 centerMode: true, 2454 variableWidth: true, 2455 arrows: true, 2456 adaptiveHeight: true, 2457 }); 2458 2459 // $('.property-detail-v5 #pills-gallery-tab').on('click', function () { 2460 // if (!listing_slider_variable_width.hasClass('hz-slick-refreshed')) { 2461 // setTimeout(function () { 2462 // listing_slider_variable_width.slick('setPosition'); 2463 // listing_slider_variable_width.slick('refresh'); 2464 // listing_slider_variable_width.addClass( 2465 // 'hz-slick-refreshed' 2466 // ); 2467 // }, 0); 2468 // } 2469 // }); 2470 } 2471 }; 2472 2473 /** 2474 * Initialize testimonials sliders 2475 */ 2476 const testimonialsSliderV1 = function () { 2477 const $testimonialsSliderV1 = $('.testimonials-slider-wrap-v1'); 2478 if ($testimonialsSliderV1.length > 0) { 2479 initSlider($testimonialsSliderV1, {
2480 slidesToShow: 1, 2481 slidesToScroll: 1, 2482 autoplay: true, 2483 autoplaySpeed: 4000, 2484 dots: false, 2485 arrows: true, 2486 infinite: true, 2487 speed: 500, 2488 fade: false, 2489 cssEase: 'ease', 2490 slide: 'div', 2491 appendArrows: '.testimonials-slider-wrap-v1-arrows', 2492 prevArrow: 2493 '<button type="button" class="slick-prev testimonials-slider-v1-prev"></button>', 2494 nextArrow: 2495 '<button type="button" class="slick-next testimonials-slider-v1-next"></button>', 2496 }); 2497 } 2498 }; 2499 2500 const testimonialsSliderV2 = function () { 2501 const $testimonialsSliderV2 = $('.testimonials-slider-wrap-v2'); 2502 if ($testimonialsSliderV2.length > 0) { 2503 initSlider($testimonialsSliderV2, { 2504 infinite: true, 2505 speed: 500, 2506 slidesToShow: 3, 2507 arrows: true, 2508 dots: true, 2509 appendArrows: '.testimonials-slider-wrap-v2-arrows', 2510 prevArrow: 2511 '<button type="button" class="slick-prev testimonials-slider-v2-prev"></button>', 2512 nextArrow: 2513 '<button type="button" class="slick-next testimonials-slider-v2-next"></button>', 2514 responsive: commonResponsiveSettings, 2515 }); 2516 } 2517 }; 2518 2519 const testimonialsSliderV3 = function () { 2520 const $testimonialsSliderV3 = $('.testimonials-slider-wrap-v3'); 2521 if ($testimonialsSliderV3.length > 0) { 2522 initSlider($testimonialsSliderV3, { 2523 infinite: true, 2524 speed: 500, 2525 slidesToShow: 1, 2526 adaptiveHeight: true, 2527 arrows: true, 2528 dots: false, 2529 appendArrows: '.testimonials-slider-wrap-v3-arrows', 2530 prevArrow: 2531 '<button type="button" class="slick-prev btn-primary testimonials-slider-v3-prev"></button>', 2532 nextArrow: 2533 '<button type="button" class="slick-next btn-primary testimonials-slider-v3-next"></button>', 2534 }); 2535 } 2536 }; 2537 2538 /** 2539 * Initialize agents carousel 2540 */ 2541 const agentsCarousel = function () { 2542 const $agentsCarousel = $('.agents-carousel'); 2543 if ($agentsCarousel.length > 0) { 2544 initSlider($agentsCarousel, { 2545 slidesToShow: 4, 2546 slidesToScroll: 1, 2547 autoplay: true, 2548 speed: 500, 2549 autoplaySpeed: 3000, 2550 arrows: true, 2551 dots: false, 2552 responsive: commonResponsiveSettings, 2553 }); 2554 } 2555 }; 2556 2557 /** 2558 * Initialize partners carousel 2559 */ 2560 const partnersCarousel = function () { 2561 const $partnersCarousel = $('.partners-slider-wrap'); 2562 if ($partnersCarousel.length > 0) { 2563 initSlider($partnersCarousel, { 2564 slidesToShow: 4, 2565 slidesToScroll: 1, 2566 autoplay: true, 2567 autoplaySpeed: 3000, 2568 arrows: true, 2569 dots: true, 2570 appendArrows: '.partners-module-slider', 2571 prevArrow: $('.partner-prev-js'), 2572 nextArrow: $('.partner-next-js'), 2573 responsive: commonResponsiveSettings, 2574 }); 2575 } 2576 }; 2577 2578 /** 2579 * Initialize partners carousel 2580 */ 2581 const splashSlider = function () { 2582 const $splashSlider = $('.splash-slider-wrap'); 2583 if ($splashSlider.length > 0) { 2584 initSlider($splashSlider, { 2585 lazyLoad: 'ondemand', 2586 slidesToShow: 1, 2587 slidesToScroll: 1, 2588 autoplay: true, 2589 infinite: true, 2590 autoplaySpeed: 3000,
2591 arrows: false, 2592 dots: false, 2593 }); 2594 } 2595 }; 2596 2597 const propertyScheduleTourDayFormSlide = function () { 2598 const $propertyScheduleTourDayFormSlide = $( 2599 '.property-schedule-tour-day-form-slide' 2600 ); 2601 if ($propertyScheduleTourDayFormSlide.length > 0) { 2602 initSlider($propertyScheduleTourDayFormSlide, { 2603 infinite: true, 2604 lazyLoad: 'ondemand', 2605 speed: 500, 2606 slidesToShow: 4, 2607 slidesToScroll: 4, 2608 arrows: true, 2609 adaptiveHeight: false, 2610 dots: false, 2611 prevArrow: 2612 '<div class="tour-day-form-slide-arrow prev" role="button" aria-label="Previous dates"><i class="houzez-icon icon-arrow-left-1" aria-hidden="true"></i></div>', 2613 nextArrow: 2614 '<div class="tour-day-form-slide-arrow next" role="button" aria-label="Next dates"><i class="houzez-icon icon-arrow-right-1" aria-hidden="true"></i></div>', 2615 responsive: [ 2616 { 2617 breakpoint: 992, 2618 settings: { 2619 slidesToShow: 4, 2620 slidesToScroll: 4, 2621 }, 2622 }, 2623 { 2624 breakpoint: 769, 2625 settings: { 2626 slidesToShow: 3, 2627 slidesToScroll: 3, 2628 }, 2629 }, 2630 { 2631 breakpoint: 480, 2632 settings: { 2633 slidesToShow: 2, 2634 slidesToScroll: 2, 2635 }, 2636 }, 2637 ], 2638 }); 2639 } 2640 }; 2641 2642 /** 2643 * Initialize featured property widget 2644 */ 2645 const featuredPropertyWidget = function () { 2646 const $featuredPropertyWidget = $( 2647 '.widget-featured-property-slider' 2648 ); 2649 if ($featuredPropertyWidget.length > 0) { 2650 initSlider($featuredPropertyWidget, { 2651 slidesToShow: 1, 2652 slidesToScroll: 1, 2653 autoplay: true, 2654 autoplaySpeed: 3000, 2655 arrows: true, 2656 dots: false, 2657 responsive: [], 2658 }); 2659 } 2660 }; 2661 2662 /** 2663 * Initialize property banner slider 2664 */ 2665 const propertyBannerSlider = function () { 2666 const $propertyBannerSlider = $('.property-banner-slider'); 2667 if ($propertyBannerSlider.length > 0) { 2668 initSlider($propertyBannerSlider, { 2669 slidesToShow: 1, 2670 slidesToScroll: 1, 2671 autoplay: true, 2672 autoplaySpeed: 4000, 2673 arrows: true, 2674 dots: false, 2675 responsive: [], 2676 }); 2677 } 2678 }; 2679 2680 /** 2681 * Initialize splash page slider 2682 */ 2683 const splashPageSlider = function () { 2684 const $splashPageSlider = $('.splash-slider'); 2685 if ($splashPageSlider.length > 0) { 2686 initSlider($splashPageSlider, { 2687 slidesToShow: 1, 2688 slidesToScroll: 1, 2689 autoplay: true, 2690 autoplaySpeed: 3000, 2691 arrows: false, 2692 dots: true, 2693 responsive: [], 2694 }); 2695 } 2696 }; 2697 2698 /** 2699 * Initialize grid image gallery for property listings 2700 */ 2701 const gridImageGallery = function () { 2702 var is_listing_gallery_slider = false;
2703 if (houzez_vars.disable_property_gallery == 1) { 2704 let gallery_behaviour = houzez_vars.grid_gallery_behaviour; 2705 2706 $('.hz-item-gallery-js').each(function () { 2707 let $this = $(this); 2708 2709 if ( 2710 !$this.hasClass('houzez-gallery-loaded') && 2711 $this.data('images') 2712 ) { 2713 // First check if URL was previously stored 2714 var href = $this.data('property-url'); 2715 if (!href) { 2716 // Try to find the link 2717 href = $this 2718 .find('a.listing-featured-thumb') 2719 .attr('href'); 2720 // Store it for future re-initializations 2721 if (href) { 2722 $this.data('property-url', href); 2723 } 2724 } 2725 2726 // Similarly for link_target 2727 var link_target = $this.data('property-target'); 2728 if (!link_target) { 2729 link_target = $this 2730 .find('a.listing-featured-thumb') 2731 .attr('target'); 2732 if (link_target) { 2733 $this.data('property-target', link_target); 2734 } 2735 } 2736 var images = $this.data('images'); 2737 2738 // Create document fragment for better performance 2739 var fragment = document.createDocumentFragment(); 2740 var galleryWrap = document.createElement('div'); 2741 galleryWrap.className = 2742 'listing-gallery-wrap ' + gallery_behaviour; 2743 2744 var carouselDiv = document.createElement('div'); 2745 carouselDiv.className = 'houzez-listing-carousel'; 2746 2747 // Clear existing content before adding new elements 2748 carouselDiv.innerHTML = ''; 2749 2750 images.forEach((image) => { 2751 const itemDiv = document.createElement('div'); 2752 itemDiv.className = 'slide-img'; 2753 2754 const anchor = document.createElement('a'); 2755 anchor.className = 'hover-effect'; // Added wf class from your edit 2756 anchor.href = href; 2757 anchor.target = link_target; 2758 anchor.setAttribute('role', 'link'); 2759 2760 const img = document.createElement('img'); 2761 img.src = image.image; 2762 img.alt = image.alt || ''; // Ensure alt is not undefined 2763 img.width = image.width; 2764 img.height = image.height; 2765 img.className = 'img-fluid'; 2766 2767 // Add srcset and sizes attributes if available 2768 if (image.srcset) { 2769 img.srcset = image.srcset; 2770 } 2771 if (image.sizes) { 2772 img.sizes = image.sizes; 2773 } 2774 2775 anchor.appendChild(img); 2776 itemDiv.appendChild(anchor); 2777 carouselDiv.appendChild(itemDiv); 2778 }); 2779 2780 galleryWrap.appendChild(carouselDiv); 2781 fragment.appendChild(galleryWrap); 2782 2783 $this.find('.listing-image-wrap').html(fragment); 2784 2785 $('.item-wrap-v6 a').removeClass('hover-effect'); 2786 2787 var listing_slider = $this.find( 2788 '.houzez-listing-carousel' 2789 ); 2790 2791 listing_slider.slick({ 2792 rtl: houzez.Core.config.houzez_rtl, 2793 autoplay: false, 2794 lazyLoad: 'ondemand', 2795 infinite: false, 2796 speed: 500, 2797 slidesToShow: 1, 2798 arrows: true, 2799 prevArrow: 2800 '<button type="button" class="slick-prev slick-arrow"></button>', 2801 nextArrow: 2802 '<button type="button" class="slick-next slick-arrow"></button>', 2803 adaptiveHeight: true, 2804 }); 2805 2806 is_listing_gallery_slider = true; 2807 $this.addClass('houzez-gallery-loaded'); 2808 } 2809 }); 2810 } 2811 }; 2812 2813 /** 2814 * Initialize the module 2815 */ 2816 const init = function () { 2817 // Initialize functions that need DOM ready 2818 $(document).ready(function () { 2819 // Initialize grid gallery on document ready for better reliability 2820 gridImageGallery(); 2821 2822 // Initialize other components that work well with DOM ready 2823 customCarousel();
2824 partnersCarousel(); 2825 featuredPropertyWidget(); 2826 }); 2827 2828 // Initialize functions that need all resources loaded 2829 $(window).on('load', function () { 2830 propertyDetailGallery(); 2831 lightboxSlider(); 2832 variableWidthSlider(); 2833 testimonialsSliderV1(); 2834 testimonialsSliderV2(); 2835 testimonialsSliderV3(); 2836 agentsCarousel(); 2837 customCarousel(); 2838 partnersCarousel(); 2839 propertyBannerSlider(); 2840 featuredPropertyWidget(); 2841 splashPageSlider(); 2842 gridImageGallery(); // Keep here too as fallback 2843 splashSlider(); 2844 propertyScheduleTourDayFormSlide(); 2845 }); 2846 2847 // Fallback initialization for cached pages 2848 setTimeout(function () { 2849 // Check if galleries exist but aren't initialized 2850 if ( 2851 $('.hz-item-gallery-js').length > 0 && 2852 $('.hz-item-gallery-js:not(.houzez-gallery-loaded)') 2853 .length > 0 2854 ) { 2855 gridImageGallery(); 2856 } 2857 }, 500); 2858 }; 2859 2860 // Public API 2861 return { 2862 init: init, 2863 initSlider: initSlider, 2864 customCarousel: customCarousel, 2865 propertyBannerSlider: propertyBannerSlider, 2866 propertyDetailGallery: propertyDetailGallery, 2867 lightboxSlider: lightboxSlider, 2868 variableWidthSlider: variableWidthSlider, 2869 testimonialsSliderV1: testimonialsSliderV1, 2870 agentsCarousel: agentsCarousel, 2871 partnersCarousel: partnersCarousel, 2872 //partnersSlider: partnersSlider, 2873 featuredPropertyWidget: featuredPropertyWidget, 2874 splashPageSlider: splashPageSlider, 2875 gridImageGallery: gridImageGallery, 2876 testimonialsSliderV2: testimonialsSliderV2, 2877 testimonialsSliderV3: testimonialsSliderV3, 2878 propertyScheduleTourDayFormSlide: propertyScheduleTourDayFormSlide, 2879 }; 2880 })(); 2881 2882 /** 2883 * Pagination Module 2884 * Handles AJAX pagination and infinite loading for property listings 2885 */ 2886 houzez.Pagination = (function () { 2887 // Variables for tracking pagination state 2888 let moduleLoading = false; 2889 let listingsLoading = false; 2890 let moduleObserver = null; 2891 let listingsObserver = null; 2892 let loadMoreButton = $('#properties_module_section .fave-load-more a'); 2893 let paginationType = 2894 loadMoreButton.data('pagination_type') || 'loadmore'; 2895 2896 /** 2897 * Load more properties with AJAX 2898 * Used in property modules section with load more button 2899 * @param {Event} e - Click event 2900 */
2901 const loadMoreModuleProperties = function (e) { 2902 if (e) e.preventDefault(); 2903 if (moduleLoading) { 2904 return; 2905 } 2906 2907 const $this = $(this); 2908 const $wrap = $this 2909 .closest('#properties_module_section') 2910 .find('#module_properties'); 2911 2912 // Initialize the data object with the action 2913 let data = { 2914 action: 'houzez_loadmore_properties', 2915 }; 2916 2917 // Dynamically collect all data attributes 2918 const allData = $this.data(); 2919 2920 // Merge all data attributes into the data object 2921 $.each(allData, function (key, value) { 2922 data[key] = value; 2923 }); 2924 2925 moduleLoading = true; 2926 $.ajax({ 2927 type: 'POST', 2928 url: houzez.Core.config.ajaxurl, 2929 dataType: 'json', 2930 data: data, 2931 beforeSend: function () { 2932 $this.find('.houzez-loader-js').addClass('loader-show'); 2933 }, 2934 complete: function () { 2935 $this.find('.houzez-loader-js').removeClass('loader-show'); 2936 moduleLoading = false; 2937 }, 2938 success: function (data) { 2939 if (data.html == 'no_result') { 2940 $this 2941 .closest('#properties_module_section') 2942 .find('.fave-load-more') 2943 .fadeOut('fast') 2944 .remove(); 2945 return; 2946 } 2947 2948 $wrap.append(data.html); 2949 $this.data('paged', parseInt($this.data('paged')) + 1); 2950 $this.find('i').remove(); 2951 2952 if (!data.has_more_posts) { 2953 $this 2954 .closest('#properties_module_section') 2955 .find('.fave-load-more') 2956 .fadeOut('fast') 2957 .remove(); 2958 } else if (paginationType === 'infinite_scroll') { 2959 // Re-observe the button after new content is loaded 2960 observeModuleLoadMoreButton(); 2961 } 2962 2963 // Use the Core function instead of local one 2964 houzez.Core.reinitializeAjaxFunctions(); 2965 }, 2966 error: function (xhr, status, error) { 2967 moduleLoading = false; 2968 console.error('AJAX error:', error); 2969 }, 2970 }); 2971 }; 2972 2973 const observeModuleLoadMoreButton = function () { 2974 if (moduleObserver) { 2975 moduleObserver.disconnect(); 2976 } 2977
2978 const loadMoreButton = document.querySelector( 2979 '#properties_module_section .fave-load-more a' 2980 ); 2981 2982 if (!loadMoreButton) return; 2983 2984 moduleObserver = new IntersectionObserver( 2985 (entries) => { 2986 entries.forEach((entry) => { 2987 if (entry.isIntersecting && !moduleLoading) { 2988 loadMoreModuleProperties.call(entry.target); 2989 } 2990 }); 2991 }, 2992 { 2993 root: null, 2994 rootMargin: '0px 0px 300px 0px', // Increased bottom margin for earlier detection 2995 threshold: 0.1, 2996 } 2997 ); 2998 2999 moduleObserver.observe(loadMoreButton); 3000 }; 3001 3002 /** 3003 * Load more listings for main listings view 3004 */ 3005 const loadMoreListings = function () { 3006 if (listingsLoading || this.noMoreListings) { 3007 return; 3008 } 3009 3010 listingsLoading = true; 3011 const $loadMoreBtn = $('.houzez-infinite-load'); 3012 const nextPageUrl = $loadMoreBtn.attr('href'); 3013 3014 $.ajax({ 3015 url: nextPageUrl, 3016 type: 'GET', 3017 dataType: 'html', 3018 beforeSend: function () { 3019 $loadMoreBtn 3020 .find('.houzez-loader-js') 3021 .addClass('loader-show'); 3022 }, 3023 complete: function () { 3024 $loadMoreBtn 3025 .find('.houzez-loader-js') 3026 .removeClass('loader-show'); 3027 }, 3028 success: function (response) { 3029 const $html = $(response); 3030 const $listingView = $('.listing-view'); 3031 const $paginationContainer = $('#fave-pagination-loadmore'); 3032 const $newListings = $html.find( 3033 '.listing-view .item-listing-wrap' 3034 ); 3035 3036 if ($newListings.length > 0) { 3037 $listingView.append($newListings); 3038 this.currentPage++; 3039 3040 const nextPageLink = $html 3041 .find('.houzez-infinite-load') 3042 .attr('href'); 3043 3044 if ( 3045 nextPageLink && 3046 this.currentPage * this.perPage < this.totalListings 3047 ) { 3048 $loadMoreBtn.attr('href', nextPageLink); 3049 $loadMoreBtn.attr( 3050 'data-page', 3051 this.currentPage + 1 3052 ); 3053 updateBrowserUrl(nextPageUrl); 3054 $loadMoreBtn.show(); // Ensure button is visible 3055 } else { 3056 this.noMoreListings = true; 3057 $paginationContainer.html( 3058 '<p class="no-more-listings">' + 3059 houzez_vars.listings_not_found + 3060 '</p>' 3061 ); 3062 $loadMoreBtn.hide(); // Hide button when no more listings 3063 } 3064 3065 // Use the Core function instead of local one 3066 houzez.Core.reinitializeAjaxFunctions(); 3067 if ($('.houzez-parallax').length > 0) { 3068 houzez.Parallax.init(); 3069 } 3070 } else { 3071 this.noMoreListings = true; 3072 $paginationContainer.html( 3073 '<p class="no-more-listings">' + 3074 houzez_vars.listings_not_found + 3075 '</p>' 3076 ); 3077 $loadMoreBtn.hide(); // Hide button when no more listings 3078 } 3079 3080 listingsLoading = false;
3081 }.bind(this), 3082 error: function (xhr, status, error) { 3083 listingsLoading = false; 3084 }, 3085 }); 3086 }; 3087 3088 /** 3089 * Update browser URL for history state 3090 */ 3091 const updateBrowserUrl = function (url) { 3092 if (history.pushState) { 3093 const newUrl = new URL(url); 3094 window.history.pushState( 3095 { path: newUrl.href }, 3096 '', 3097 newUrl.href 3098 ); 3099 } 3100 }; 3101 3102 /** 3103 * Initialize the module pagination functionality 3104 */ 3105 const initPropertiesModulePagination = function () { 3106 const properties_module = 3107 $('#properties_module_section').length > 0; 3108 3109 if (!properties_module) { 3110 return; 3111 } 3112 3113 // Load More button click event 3114 $('body').on( 3115 'click', 3116 '#properties_module_section .fave-load-more a', 3117 loadMoreModuleProperties 3118 ); 3119 3120 // Initialize Infinite Scroll if that's the pagination type 3121 if (paginationType === 'infinite_scroll') { 3122 observeModuleLoadMoreButton(); 3123 } 3124 }; 3125 3126 /** 3127 * Handle main listings infinite load pagination 3128 */ 3129 const initListingsInfiniteScroll = function () { 3130 const $listingView = $('.listing-view'); 3131 const $loadMoreBtn = $('.houzez-infinite-load'); 3132 3133 // Skip if we're in properties module context or there's no listing view 3134 const inModuleContext = $('#properties_module_section').length > 0; 3135 3136 if (inModuleContext || !$listingView.length || !$loadMoreBtn.length) 3137 return; 3138 3139 // Set up context with pagination state 3140 const context = { 3141 currentPage: 1, 3142 noMoreListings: false, 3143 totalListings: parseInt($loadMoreBtn.data('total'), 10) || 0, 3144 perPage: parseInt($loadMoreBtn.data('per-page'), 10) || 10, 3145 }; 3146 3147 const pagi_type = $loadMoreBtn.data('pagi-type') || '_number'; 3148 3149 // Bind loadMoreListings to the context 3150 const boundLoadMoreListings = loadMoreListings.bind(context); 3151 3152 // Check if we need to show the Load More button initially 3153 if (context.totalListings <= context.perPage) { 3154 $loadMoreBtn.hide(); 3155 } 3156 3157 // Set up infinite scroll 3158 if (pagi_type == '_infinite' && $loadMoreBtn.length > 0) { 3159 if (listingsObserver) { 3160 listingsObserver.disconnect(); 3161 } 3162 3163 listingsObserver = new IntersectionObserver( 3164 (entries) => { 3165 entries.forEach((entry) => { 3166 if ( 3167 entry.isIntersecting && 3168 !listingsLoading && 3169 !context.noMoreListings 3170 ) { 3171 boundLoadMoreListings(); 3172 } 3173 }); 3174 }, 3175 { 3176 root: null, 3177 rootMargin: '0px 0px 300px 0px', // Increased bottom margin 3178 threshold: 0.1, 3179 } 3180 ); 3181 3182 // Start observing the load more button 3183 listingsObserver.observe($loadMoreBtn[0]); 3184 } 3185 3186 // Keep the click event for browsers that don't support IntersectionObserver 3187 $loadMoreBtn.on('click', function (e) { 3188 e.preventDefault(); 3189 if (!listingsLoading && !context.noMoreListings) { 3190 boundLoadMoreListings(); 3191 } 3192 }); 3193 3194 // Handle browser back/forward buttons 3195 $(window).on('popstate', function (e) { 3196 if (e.originalEvent.state !== null) { 3197 window.location.reload(); 3198 } 3199 }); 3200 }; 3201 3202 /** 3203 * Initialize the module 3204 */ 3205 const init = function () { 3206 initPropertiesModulePagination(); 3207 initListingsInfiniteScroll(); 3208 }; 3209 3210 // Public API 3211 return { 3212 init: init,
3213 loadMoreModuleProperties: loadMoreModuleProperties, 3214 loadMoreListings: loadMoreListings, 3215 }; 3216 })(); 3217 3218 /** 3219 * Mortgage Module 3220 * Handles mortgage calculator functionality 3221 */ 3222 houzez.Mortgage = (function () { 3223 /** 3224 * Calculation handler for mortgage calculator section 3225 */ 3226 const mortgageCalculation = function () { 3227 const homePrice = houzez.Core.util.parseNumberInput('#homePrice'); 3228 const downPaymentPercentage = houzez.Core.util.parseNumberInput( 3229 '#downPaymentPercentage' 3230 ); 3231 const annualInterestRate = houzez.Core.util.parseNumberInput( 3232 '#annualInterestRate' 3233 ); 3234 const loanTermInYears = 3235 houzez.Core.util.parseNumberInput('#loanTermInYears'); 3236 const annualPropertyTaxRate = houzez.Core.util.parseNumberInput( 3237 '#annualPropertyTaxRate' 3238 ); 3239 const annualHomeInsurance = houzez.Core.util.parseNumberInput( 3240 '#annualHomeInsurance' 3241 ); 3242 const monthlyHOAFees = 3243 houzez.Core.util.parseNumberInput('#monthlyHOAFees'); 3244 const pmi = houzez.Core.util.parseNumberInput('#pmi'); 3245 3246 const downPayment = homePrice * (downPaymentPercentage / 100); 3247 const principal = homePrice - downPayment; 3248 const monthlyPayment = houzez.Core.util.calculateMonthlyPayment( 3249 principal, 3250 annualInterestRate, 3251 loanTermInYears 3252 ); 3253 const monthlyPropertyTax = 3254 (homePrice * (annualPropertyTaxRate / 100)) / 12; 3255 const monthlyHomeInsurance = annualHomeInsurance / 12; 3256 3257 const pmiRequired = downPayment / homePrice < 0.2; 3258 const monthlyPMI = pmiRequired ? (principal * (pmi / 100)) / 12 : 0; 3259 3260 const totalMonthlyPayment = 3261 monthlyPayment + 3262 monthlyPropertyTax + 3263 monthlyHomeInsurance + 3264 monthlyHOAFees + 3265 monthlyPMI; 3266 3267 const loanAmount = homePrice - downPayment; 3268 3269 // Use numberFormat from Core utility 3270 const numberFormat = houzez.Core.util.numberFormat; 3271 3272 const formattedDownPayment = numberFormat(downPayment, true); 3273 const formattedLoanAmount = numberFormat(loanAmount, true); 3274 const formattedMonthlyPayment = numberFormat(monthlyPayment, true); 3275 const formattedPropertyTax = numberFormat(monthlyPropertyTax, true); 3276 const formattedHomeInsurance = numberFormat( 3277 monthlyHomeInsurance, 3278 true 3279 ); 3280 const formattedPMI = pmiRequired 3281 ? numberFormat(monthlyPMI, true) 3282 : ''; 3283 const formattedHOAFees = numberFormat(monthlyHOAFees, true); 3284 const formattedTotalMonthlyPayment = numberFormat( 3285 totalMonthlyPayment, 3286 true 3287 ); 3288 3289 // Update UI elements with results 3290 $('#downPaymentResult').html( 3291 houzez.Core.util.currencyFormat(formattedDownPayment) 3292 ); 3293 $('#loadAmountResult').html( 3294 houzez.Core.util.currencyFormat(formattedLoanAmount) 3295 ); 3296 $('#monthlyMortgagePaymentResult').html( 3297 houzez.Core.util.currencyFormat(formattedMonthlyPayment) 3298 ); 3299 $('#monthlyPropertyTaxResult').html( 3300 houzez.Core.util.currencyFormat(formattedPropertyTax) 3301 ); 3302 $('#monthlyHomeInsuranceResult').html( 3303 houzez.Core.util.currencyFormat(formattedHomeInsurance) 3304 ); 3305 3306 if (pmiRequired) { 3307 $('.rslt-pmi').show(); 3308 $('#monthlyPMIResult').html( 3309 houzez.Core.util.currencyFormat(formattedPMI) 3310 ); 3311 } else { 3312 $('.rslt-pmi').hide(); 3313 } 3314 3315 $('#monthlyHOAResult').html( 3316 houzez.Core.util.currencyFormat(formattedHOAFees) 3317 ); 3318 $('#m_monthly_val').html( 3319 houzez.Core.util.currencyFormat(formattedTotalMonthlyPayment) 3320 ); 3321 3322 // Update chart data for visualization 3323 const chartData = [ 3324 { 3325 label: 'Monthly Mortgage Payment', 3326 value: monthlyPayment, 3327 color: '#ff6384', 3328 }, 3329 { 3330 label: 'Property Tax', 3331 value: monthlyPropertyTax, 3332 color: '#36a2eb', 3333 }, 3334 { 3335 label: 'Home Insurance', 3336 value: monthlyHomeInsurance, 3337 color: '#ffce56', 3338 }, 3339 { 3340 label: 'HOA', 3341 value: monthlyHOAFees, 3342 color: '#c2d500', 3343 }, 3344 ]; 3345 3346 if (pmiRequired) { 3347 chartData.push({ 3348 label: 'PMI', 3349 value: monthlyPMI, 3350 color: '#4bc0c0', 3351 }); 3352 } 3353 3354 updateChart(chartData); 3355 }; 3356 3357 /** 3358 * Update the mortgage donut chart 3359 * @param {Array} chartData - Chart data for visualization 3360 */ 3361 const updateChart = function (chartData) { 3362 const ctx = $('#mortgage-calculator-chart')[0].getContext('2d'); 3363 3364 if (window.myChart) { 3365 window.myChart.destroy(); 3366 } 3367 3368 window.myChart = new Chart(ctx, { 3369 type: 'doughnut', 3370 data: { 3371 datasets: [ 3372 { 3373 data: chartData.map((item) => item.value), 3374 backgroundColor: chartData.map( 3375 (item) => item.color 3376 ), 3377 }, 3378 ], 3379 }, 3380 options: { 3381 cutoutPercentage: 85, 3382 responsive: false, 3383 tooltips: false, 3384 }, 3385 }); 3386 }; 3387 3388 /** 3389 * Initialize the mortgage calculator 3390 */ 3391 const initMortgageCalculator = function () { 3392 // If the calculator form exists, set up event handlers 3393 if ($('#houzez-calculator-form').length > 0) { 3394 $('#houzez-calculator-form input').on( 3395 'input', 3396 mortgageCalculation 3397 ); 3398 3399 // Calculate on page load 3400 mortgageCalculation(); 3401 } 3402 }; 3403 3404 /** 3405 * Initialize the mortgage module 3406 */ 3407 const init = function () { 3408 initMortgageCalculator(); 3409 }; 3410 3411 // Public API 3412 return { 3413 init: init, 3414 mortgageCalculation: mortgageCalculation, 3415 updateChart: updateChart, 3416 }; 3417 })(); 3418 3419 /** 3420 * Property Contact Forms Module 3421 * Handles property contact forms functionality 3422 */ 3423 houzez.ContactForms = (function () { 3424 /** 3425 * Handle agent contact form submittal 3426 */ 3427 const propertyAgentContactForm = function () { 3428 $('.houzez_agent_property_form').on('click', function (e) { 3429 e.preventDefault(); 3430 3431 var $this = $(this); 3432 var $form = $this.parents('form'); 3433 var $form_wrap = $this.parents('.property-form-wrap'); 3434 var $result = $form_wrap.find('.form_messages'); 3435 var $is_bottom = $('.is_bottom').val(); 3436 3437 if ($is_bottom == 'bottom') { 3438 $result = $form.find('.form_messages'); 3439 } 3440 $result.empty(); 3441 3442 $.ajax({ 3443 type: 'post', 3444 url: houzez.Core.config.ajaxurl, 3445 data: $form.serialize(), 3446 dataType: 'JSON', 3447 beforeSend: function () { 3448 $this.attr('disabled', true); 3449 $this.find('.houzez-loader-js').addClass('loader-show'); 3450 }, 3451 success: function (response) { 3452 if (response.success) { 3453 // Clear form fields 3454 $form 3455 .find( 3456 'input[name="name"], input[name="mobile"], input[name="email"]' 3457 ) 3458 .val(''); 3459 $form.find('textarea').val(''); 3460 3461 // Show success message 3462 houzez.Core.util.showSuccess($result, response.msg); 3463 } else { 3464 // Show error message 3465 houzez.Core.util.showError($result, response.msg); 3466 } 3467 3468 // Reset captcha (supports both reCaptcha and Turnstile) 3469 houzez.Core.util.resetCaptcha($form); 3470 3471 // Handle redirection if needed 3472 if ( 3473 houzez_vars.agent_redirection != '' && 3474 response.success 3475 ) { 3476 setTimeout(function () { 3477 window.location.replace( 3478 houzez_vars.agent_redirection 3479 ); 3480 }, 500); 3481 } 3482 }, 3483 error: function (xhr, status, error) { 3484 var err = eval('(' + xhr.responseText + ')'); 3485 console.log(err.Message); 3486 }, 3487 complete: function () { 3488 $this.attr('disabled', false); 3489 $this 3490 .find('.houzez-loader-js') 3491 .removeClass('loader-show'); 3492 }, 3493 }); 3494 }); 3495 }; 3496 3497 /** 3498 * Handle property schedule tour form submission 3499 */ 3500 const scheduleTour = function () { 3501 $('.schedule_contact_form').on('click', function (e) { 3502 e.preventDefault(); 3503 3504 const $this = $(this); 3505 const $form = $this.parents('form'); 3506 const $messages = $form.find('.form_messages'); 3507 3508 $.ajax({ 3509 method: $form.attr('method'), 3510 dataType: 'JSON', 3511 url: houzez.Core.config.ajaxurl, 3512 data: $form.serialize(), 3513 beforeSend: function () { 3514 $this.attr('disabled', true); 3515 $this.find('.houzez-loader-js').addClass('loader-show'); 3516 }, 3517 success: function (response) { 3518 if (response.success) { 3519 houzez.Core.util.showSuccess( 3520 $messages, 3521 response.msg 3522 ); 3523 } else { 3524 houzez.Core.util.showError($messages, response.msg); 3525 } 3526 }, 3527 error: function (xhr, status, error) { 3528 var err = eval('(' + xhr.responseText + ')'); 3529 console.log(err.Message); 3530 }, 3531 complete: function () { 3532 $this.attr('disabled', false); 3533 $this 3534 .find('.houzez-loader-js') 3535 .removeClass('loader-show'); 3536 }, 3537 }); 3538 }); 3539 3540 /* ------------------------------------------------------------------------ */ 3541 /* Schedule tour v2 3542 /* ------------------------------------------------------------------------ */ 3543 3544 // $('.tour-day-form-slide-arrow.next').click(function (e) { 3545 // $('.property-schedule-tour-day-form-slide').addClass('end'); 3546 // $('.property-schedule-tour-day-form-slide').removeClass( 3547 // 'start' 3548 // ); 3549 // }); 3550 // $('.tour-day-form-slide-arrow.prev').click(function (e) { 3551 // $('.property-schedule-tour-day-form-slide').addClass('start'); 3552 // $('.property-schedule-tour-day-form-slide').removeClass('end'); 3553 // }); 3554 }; 3555 3556 /** 3557 * Handle contact agent form submission 3558 */ 3559 const contactRealtor = function () { 3560 $(document).on('click', '.contact-realtor-btn', function (e) { 3561 e.preventDefault(); 3562 var current_element = $(this); 3563 var $this = $(this); 3564 var $form = $this.parents('form'); 3565 var $result = $form.find('.form_messages'); 3566 3567 jQuery.ajax({ 3568 type: 'post', 3569 url: houzez.Core.config.ajaxurl, 3570 data: $form.serialize(), 3571 method: $form.attr('method'), 3572 dataType: 'JSON', 3573 3574 beforeSend: function () { 3575 $this.find('.houzez-loader-js').addClass('loader-show'); 3576 }, 3577 success: function (res) { 3578 if (res.success) { 3579 houzez.Core.util.showSuccess($result, res.msg); 3580 } else { 3581 // Show error message 3582 houzez.Core.util.showError($result, res.msg); 3583 } 3584 3585 $this 3586 .find('.houzez-loader-js') 3587 .removeClass('loader-show'); 3588 // Reset captcha (supports both reCaptcha and Turnstile) 3589 houzez.Core.util.resetCaptcha($form); 3590 }, 3591 error: function (xhr, status, error) { 3592 var err = eval('(' + xhr.responseText + ')'); 3593 houzez.Core.util.showError($result, err.Message); 3594 }, 3595 complete: function () { 3596 $this 3597 .find('.houzez-loader-js') 3598 .removeClass('loader-show'); 3599 }, 3600 }); 3601 }); 3602 }; 3603 3604 /** 3605 * Initialize the module 3606 */ 3607 const init = function () { 3608 propertyAgentContactForm(); 3609 contactRealtor(); 3610 if (houzez.Core.config.is_singular_property) { 3611 scheduleTour(); 3612 } 3613 }; 3614 3615 // Public API 3616 return { 3617 init: init, 3618 propertyAgentContactForm: propertyAgentContactForm, 3619 scheduleTour: scheduleTour, 3620 contactRealtor: contactRealtor, 3621 }; 3622 })(); 3623 3624 /** 3625 * Single Property Module 3626 * Handles single property functionality 3627 */ 3628 houzez.SingleProperty = (function () { 3629 /** 3630 * Refresh the media tabs 3631 */ 3632 const refreshMediaTabs = () => { 3633 // Track if tour has been loaded 3634 let tourLoaded = false;
3635 3636 // Use class selector instead of ID 3637 let tourTabs = document.querySelectorAll( 3638 '.houzez-360-virtual-media-tab' 3639 ); 3640 3641 // Check if elements exist before adding event listeners 3642 if (tourTabs.length > 0) { 3643 tourTabs.forEach((tourTab) => { 3644 tourTab.addEventListener('shown.bs.tab', () => { 3645 // Only load if not already loaded 3646 if (!tourLoaded) { 3647 // Show loading indicator 3648 $('.loader-360').show(); 3649 3650 // Properly reload the content 3651 $('#pills-360tour').load( 3652 window.location.href + 3653 ' #virtual-tour-iframe-container', 3654 function () { 3655 // Hide loading indicator when done 3656 $('.loader-360').hide(); 3657 // Mark as loaded 3658 tourLoaded = true; 3659 } 3660 ); 3661 } 3662 }); 3663 }); 3664 } 3665 3666 // Handle gallery tab switch - refresh slider and load images 3667 var galleryTab = document.getElementById('pills-gallery-tab'); 3668 if (galleryTab) { 3669 galleryTab.addEventListener('shown.bs.tab', function () { 3670 // Handle LightSlider for standard gallery 3671 var slider = $('#property-gallery-js'); 3672 if (slider.length && slider.data('lightSlider')) { 3673 // Force refresh the slider 3674 slider.data('lightSlider').refresh(); 3675 3676 // Trigger lazy image loading for visible slides 3677 slider 3678 .find('.houzez-gallery-img[data-lazy]') 3679 .each(function () { 3680 var $img = $(this); 3681 var lazySrc = $img.data('lazy'); 3682 if ( 3683 lazySrc && 3684 (!$img.attr('src') || 3685 !$img.attr('src').includes(lazySrc)) 3686 ) { 3687 $img.attr('src', lazySrc); 3688 } 3689 }); 3690 } 3691 3692 // Also handle Slick slider for v5 layouts 3693 var slickSlider = $('.listing-slider-variable-width'); 3694 if ( 3695 slickSlider.length && 3696 slickSlider.hasClass('slick-initialized') 3697 ) { 3698 slickSlider.slick('setPosition'); 3699 slickSlider.slick('refresh'); 3700 } 3701 }); 3702 } 3703 }; 3704 3705 /** 3706 * Initialize the sticky property navigation 3707 */ 3708 const propertyDetailNav = function () { 3709 let houzez_listing_nav = $('.property-navigation-wrap'); 3710 if ( 3711 houzez.Core.config.property_detail_nav === 'yes' && 3712 houzez_listing_nav.length > 0 3713 ) { 3714 let header_area = $('.header-main-wrap'); 3715 3716 if (!header_area.length) { 3717 header_area = $('#header-hz-elementor'); 3718 } 3719 3720 const header_area_height = header_area.innerHeight(); 3721 const get_header_search = $('.advanced-search-header'); 3722 const get_header_search_height = 3723 get_header_search.innerHeight(); 3724 let scroll_nav_height; 3725 3726 if (get_header_search.length) { 3727 scroll_nav_height = 3728 header_area_height + get_header_search_height; 3729 } else { 3730 scroll_nav_height = header_area_height; 3731 } 3732 3733 if ($(window).width() >= 992) { 3734 const contentTop = $('section.property-wrap').offset().top; 3735 3736 $(window).scroll(function () { 3737 const scroll = $(window).scrollTop(); 3738 const admin_nav = houzez.Core.config.wpadminbar_height; 3739 3740 let top_position = 0; 3741 if (admin_nav > 0) { 3742 top_position = admin_nav; 3743 } 3744 3745 if (scroll > contentTop) { 3746 $('.property-navigation-wrap').css({ 3747 top: top_position, 3748 }); 3749 $('.property-navigation-wrap').fadeIn(); 3750 $('.property-navigation-wrap').addClass( 3751 'nav-fixed' 3752 ); 3753 } else if (scroll == 0) { 3754 $('.property-navigation-wrap').removeAttr('style'); 3755 $('.property-navigation-wrap').removeClass( 3756 'nav-fixed' 3757 ); 3758 $('.property-navigation-wrap').fadeOut(); 3759 } 3760 }); 3761 3762 $(window).on('scroll', function () { 3763 $('.property-section-wrap').each(function () { 3764 if ( 3765 $(window).scrollTop() >= 3766 $(this).offset().top - 86 3767 ) { 3768 var id = $(this).attr('id'); 3769 $('.target').removeClass('active'); 3770 $('.target[href="#' + id + '"]').addClass( 3771 'active' 3772 ); 3773 } else if ($(window).scrollTop() <= 0) { 3774 $('.target').removeClass('active'); 3775 } 3776 }); 3777 }); 3778 3779 $('.property-navigation-item a.target').on( 3780 'click', 3781 function (e) { 3782 e.preventDefault(); // Prevent the default jump-to-anchor behavior 3783 3784 // Grab the href (e.g. "#section1") and find its offset from the top 3785 let targetId = $(this).attr('href'); 3786 let $target = $(targetId); 3787 if ($target.length) { 3788 let offsetTop = 3789 $target.offset().top - header_area_height; 3790 3791 // Animate the scroll to that position (600ms duration) 3792 $('html, body').animate( 3793 { scrollTop: offsetTop }, 3794 100 3795 ); 3796 } 3797 } 3798 ); 3799 } 3800 } 3801 }; 3802 3803 const propertyFancyboxGallery = function () { 3804 Fancybox.bind( 3805 'a[data-fancybox][data-houzez-fancybox], .listing-slider-variable-width .slick-slide:not(.slick-cloned) a[data-fancybox="gallery-variable-width"]', 3806 { 3807 Hash: false, 3808 Thumbs: { 3809 type: 'modern', // or 'classic' 3810 }, 3811 } 3812 ); 3813 }; 3814 3815 const propertyDetailLightbox = function () { 3816 $('.btn-expand').on('click', function () { 3817 $('.lightbox-gallery-wrap').toggleClass( 3818 'lightbox-gallery-full-wrap' 3819 ); 3820 $('.lightbox-slider').slick('setPosition'); 3821 }); 3822 3823 $('.btn-email').on('click', function () { 3824 $('.lightbox-form-wrap').toggleClass('lightbox-form-wrap-show'); 3825 }); 3826 }; 3827 3828 /** 3829 * Handle print property feature 3830 */ 3831 const printProperty = function () { 3832 $('.houzez-print').on('click', function (e) { 3833 e.preventDefault(); 3834 const propID = $(this).attr('data-propid'); 3835 const printWindow = window.open( 3836 '', 3837 'Print Me', 3838 'width=800, height=842' 3839 ); 3840 3841 $.ajax({ 3842 type: 'post', 3843 url: houzez.Core.config.ajaxurl, 3844 data: { 3845 action: 'houzez_create_print', 3846 propid: propID, 3847 }, 3848 success: function (data) { 3849 printWindow.document.write(data); 3850 printWindow.document.close(); 3851 printWindow.focus(); 3852 }, 3853 error: function (xhr, status, error) { 3854 var err = eval('(' + xhr.responseText + ')'); 3855 console.log(err.Message); 3856 }, 3857 }); 3858 return false;
3859 }); 3860 }; 3861 3862 const adjustTabContentHeight = function (forceAdjust = false) { 3863 // Get the tab content element 3864 const $tabContent = $('.hs-property-gallery-wrap .tab-content'); 3865 if (!$tabContent.length) return; 3866 3867 $('.mobile-property-tools a.nav-link').on('click', function () { 3868 const tabHeight = $('.tab-content').outerHeight(true); 3869 // Now set the fixed height 3870 $tabContent.css({ 3871 height: tabHeight + 'px', 3872 transition: 'height 0.3s ease', 3873 }); 3874 }); 3875 }; 3876 3877 /** 3878 * Initialize the module 3879 */ 3880 const init = function () { 3881 if (houzez.Core.config.is_singular_property) { 3882 propertyDetailNav(); 3883 propertyFancyboxGallery(); 3884 propertyDetailLightbox(); 3885 printProperty(); 3886 refreshMediaTabs(); 3887 3888 // Initialize Bootstrap tooltips for media tab buttons 3889 var tooltipTriggerList = [].slice.call( 3890 document.querySelectorAll( 3891 '#pills-tab [data-bs-tooltip="tooltip"]' 3892 ) 3893 ); 3894 tooltipTriggerList.forEach(function (tooltipTriggerEl) { 3895 new bootstrap.Tooltip(tooltipTriggerEl, { 3896 placement: 'bottom', 3897 trigger: 'hover', 3898 }); 3899 }); 3900 3901 if (houzez.Core.is_mobile) { 3902 adjustTabContentHeight(); 3903 } 3904 } 3905 }; 3906 3907 // Public API 3908 return { 3909 init: init, 3910 propertyDetailNav: propertyDetailNav, 3911 propertyFancyboxGallery: propertyFancyboxGallery, 3912 propertyDetailLightbox: propertyDetailLightbox, 3913 printProperty: printProperty, 3914 refreshMediaTabs: refreshMediaTabs, 3915 adjustTabContentHeight: adjustTabContentHeight, 3916 }; 3917 })(); 3918 3919 /** 3920 * Realtor Stats Module 3921 * Handles realtor stats functionality 3922 */ 3923 houzez.RealtorStats = (function () { 3924 /* ------------------------------------------------------------------------ */ 3925 /* Types chart for agent and agency 3926 /* ------------------------------------------------------------------------ */ 3927 const init = function () { 3928 $('.houzez-realtor-stats-js[id^="stats-property-"]').each( 3929 function () { 3930 var $div = $(this); 3931 var token = $div.data('token'); 3932 3933 var statsID = 'stats-property-' + token; 3934 if ($('#' + statsID).length > 0) { 3935 var chartData = $('#' + statsID).data('chart'); 3936 var ctx = document 3937 .getElementById(statsID) 3938 .getContext('2d'); 3939 var myDoughnutChart = new Chart(ctx, { 3940 type: 'doughnut', 3941 data: { 3942 datasets: [ 3943 { 3944 data: chartData, 3945 backgroundColor: [ 3946 'rgba(255, 99, 132, 0.5)', 3947 'rgba(54, 162, 235, 0.5)', 3948 'rgba(255, 206, 86, 0.5)', 3949 'rgba(75, 192, 192, 0.5)', 3950 ], 3951 borderColor: [ 3952 'rgba(255, 99, 132, 1)', 3953 'rgba(54, 162, 235, 1)', 3954 'rgba(255, 206, 86, 1)', 3955 'rgba(75, 192, 192, 1)', 3956 ], 3957 borderWidth: 1, 3958 }, 3959 ], 3960 }, 3961 options: { 3962 cutoutPercentage: 60, 3963 responsive: false, 3964 tooltips: false, 3965 }, 3966 }); 3967 } 3968 } 3969 ); 3970 }; 3971 3972 return { 3973 init: init, 3974 }; 3975 })(); 3976 3977 /** 3978 * Logo Module 3979 * Handles logo functionality 3980 */ 3981 houzez.Logo = (function () { 3982 /** 3983 * Handle retina logo display 3984 */ 3985 const retinaLogo = function () { 3986 // Get retina logo paths from global vars 3987 const retina_logo = houzez_vars.retina_logo || ''; 3988 const retina_logo_splash = houzez_vars.retina_logo_splash || ''; 3989 const retina_logo_mobile = houzez_vars.retina_logo_mobile || ''; 3990 const retina_logo_mobile_splash = 3991 houzez_vars.retina_logo_mobile_splash || ''; 3992 const transparent_logo = houzez_vars.transparent_logo || false; 3993 3994 if ( 3995 retina_logo !== '' || 3996 retina_logo_splash != '' || 3997 retina_logo_mobile != '' || 3998 retina_logo_mobile_splash != '' 3999 ) { 4000 if (window.devicePixelRatio == 2) { 4001 if (retina_logo != '') { 4002 $('.logo-desktop img').attr('src', retina_logo); 4003 } 4004 4005 if (retina_logo_splash != '') { 4006 $('.logo-splash img').attr('src', retina_logo_splash); 4007 } 4008 4009 if (retina_logo_mobile != '') { 4010 $('.logo-mobile img').attr('src', retina_logo_mobile); 4011 } 4012 } 4013 } 4014 4015 // Special handling for transparent logo in retina screens 4016 if (window.devicePixelRatio == 2 && transparent_logo) { 4017 if (retina_logo_splash != '') { 4018 houzez_vars.custom_logo_splash = retina_logo_splash; 4019 } 4020 if (retina_logo != '') { 4021 houzez_vars.simple_logo = retina_logo; 4022 } 4023 } 4024 }; 4025 4026 const init = function () { 4027 retinaLogo(); 4028 }; 4029 4030 return { 4031 init: init, 4032 retinaLogo: retinaLogo, 4033 }; 4034 })(); 4035 4036 /** 4037 * Sticky Module 4038 * Handles sticky functionality 4039 */
4040 houzez.Sticky = (function () { 4041 // Cache DOM elements 4042 const $win = $(window); 4043 const $header_area = $('.header-main-wrap'); 4044 const $header_nav = $('#header-section'); 4045 const $header_mobile = $('#header-mobile'); 4046 const $desktop_search_nav = $('.desktop-search-nav'); 4047 const $mobile_search_nav = $('.mobile-search-nav'); 4048 const $top_banner_wrap = $('.top-banner-wrap'); 4049 4050 // Configuration variables 4051 let houzezStickyTop = 0; 4052 let adminBarHeight = houzez.Core.config.wpadminbar_height; 4053 let header_area_height = $header_area.innerHeight() || 0; 4054 let desktop_search_nav_height = $desktop_search_nav.innerHeight() || 0; 4055 let mobile_search_nav_height = $mobile_search_nav.innerHeight() || 0; 4056 let houzez_nav_sticky_height = $header_nav.innerHeight() || 0; 4057 let top_banner_wrap_height = 0; 4058 4059 let only_nav_sticky = $header_nav.data('sticky') || 0; 4060 let mobile_menu_sticky = $header_mobile.data('sticky') || 0; 4061 4062 if ( 4063 houzez.Core.config.property_detail_nav == 'yes' && 4064 houzez.Core.config.is_singular_property 4065 ) { 4066 only_nav_sticky = 0; 4067 } 4068 4069 /** 4070 * Calculate sticky heights and positions 4071 */ 4072 const calculateStickyHeights = function () { 4073 houzezStickyTop = houzez_nav_sticky_height; 4074 4075 if (adminBarHeight > 0) { 4076 houzezStickyTop += adminBarHeight; 4077 } 4078 4079 // Update top banner height 4080 top_banner_wrap_height = $top_banner_wrap.innerHeight() || 0; 4081 4082 // Search position calculations 4083 let make_search_sticky_position = 4084 header_area_height + desktop_search_nav_height; 4085 let searchStickyPlus = 100; 4086 let search_under_navigation = true; 4087 4088 if (houzez.Core.is_mobile()) { 4089 make_search_sticky_position += 40; 4090 } 4091 4092 if (houzez_vars.search_position === 'under_banner') { 4093 make_search_sticky_position = 4094 header_area_height + top_banner_wrap_height; 4095 searchStickyPlus = 0; 4096 4097 if (houzez_vars.is_transparent) { 4098 searchStickyPlus = 60; 4099 } 4100 4101 search_under_navigation = false; 4102 } 4103 4104 return { 4105 make_search_sticky_position, 4106 searchStickyPlus, 4107 search_under_navigation, 4108 }; 4109 }; 4110 4111 /** 4112 * Handle sticky navigation 4113 */ 4114 const stickyNav = function () { 4115 $win.scroll(function () { 4116 const scroll = $win.scrollTop(); 4117 let top_banner_margin = 0; 4118 const thisHeight = $header_nav.outerHeight(); 4119 top_banner_margin = thisHeight; 4120 if (houzez_vars.is_top_header != 0) { 4121 const top_bar_wrap = $('.top-bar-wrap').height(); 4122 top_banner_margin = top_banner_margin + top_bar_wrap; 4123 } 4124 4125 // Check if sticky is disabled 4126 if (only_nav_sticky === 0) { 4127 return; 4128 } 4129 // Update logo if transparent 4130 if (houzez_vars.transparent_logo) { 4131 $('.logo-splash img').attr('src', houzez_vars.simple_logo); 4132 } 4133 4134 if (scroll > header_area_height) { 4135 $header_nav.addClass('sticky-nav-area'); 4136 $header_nav.css('top', adminBarHeight); 4137 4138 if (houzez_vars.transparent_logo) { 4139 $header_area.removeClass('header-transparent-wrap'); 4140 $('.top-banner-wrap').css( 4141 'margin-top', 4142 '-' + top_banner_margin + 'px' 4143 ); 4144 $('body.houzez-header-elementor .content-wrap').css( 4145 'margin-top', 4146 '-' + top_banner_margin + 'px' 4147 ); 4148 $('#main-wrap > div.elementor').css( 4149 'margin-top',
4150 '-' + top_banner_margin + 'px' 4151 ); 4152 } 4153 4154 if (scroll >= header_area_height + 20) { 4155 $header_nav.addClass('houzez-in-view'); 4156 $('#main-wrap').css('margin-top', thisHeight); 4157 } 4158 } else { 4159 $header_nav.removeClass('sticky-nav-area'); 4160 $header_nav.removeAttr('style'); 4161 4162 if (houzez_vars.transparent_logo) { 4163 $header_area.addClass('header-transparent-wrap'); 4164 $('.top-banner-wrap').css('margin-top', 0); 4165 $('body.houzez-header-elementor .content-wrap').css( 4166 'margin-top', 4167 0 4168 ); 4169 $('#main-wrap > div.elementor').css('margin-top', 0); 4170 $('.logo-splash img').attr( 4171 'src', 4172 houzez_vars.custom_logo_splash 4173 ); 4174 } 4175 4176 if (scroll <= header_area_height + 20) { 4177 $header_nav.removeClass('houzez-in-view'); 4178 } 4179 4180 $('#main-wrap').css('margin-top', 0); 4181 } 4182 }); 4183 }; 4184 4185 const stickyMobileNav = function () { 4186 let thisHeight = $header_mobile.outerHeight(); 4187 $win.scroll(function () { 4188 let scroll = $win.scrollTop(); 4189 4190 if (mobile_menu_sticky === 0 || !houzez.Core.is_mobile()) { 4191 return; 4192 } 4193 4194 if (scroll > header_area_height + 50) { 4195 $header_mobile.addClass('sticky-nav-area'); 4196 //$header_mobile.css('top', adminBarHeight); 4197 4198 if (scroll >= header_area_height + 60) { 4199 $header_mobile.addClass('houzez-in-view'); 4200 $('#main-wrap').css('margin-top', thisHeight); 4201 } 4202 } else { 4203 $header_mobile.removeClass('sticky-nav-area'); 4204 $header_mobile.removeAttr('style'); 4205 4206 if (scroll <= header_area_height + 60) { 4207 $header_mobile.removeClass('houzez-in-view'); 4208 } 4209 4210 $('#main-wrap').css('margin-top', 0); 4211 } 4212 }); 4213 }; 4214 4215 /** 4216 * Handle sticky search 4217 */ 4218 const stickySearch = function () { 4219 const { make_search_sticky_position, searchStickyPlus } = 4220 calculateStickyHeights(); 4221 4222 $win.scroll(function () { 4223 const scroll = $win.scrollTop(); 4224 const thisHeight = $desktop_search_nav.outerHeight(); 4225 const hidden_data = $desktop_search_nav.data('hidden'); 4226 4227 if (scroll >= make_search_sticky_position) { 4228 $desktop_search_nav.addClass('sticky-search-area'); 4229 4230 if (hidden_data) { 4231 $desktop_search_nav.removeClass('search-hidden'); 4232 } 4233 4234 if (!houzez.Core.is_mobile()) { 4235 $desktop_search_nav.css('top', adminBarHeight); 4236 } 4237 4238 if ( 4239 scroll >= 4240 make_search_sticky_position + searchStickyPlus 4241 ) { 4242 $desktop_search_nav.addClass('houzez-in-view'); 4243 4244 if (!hidden_data) { 4245 $('#main-wrap').css('padding-top', thisHeight); 4246 } 4247 } 4248 } else { 4249 $desktop_search_nav.removeClass('sticky-search-area'); 4250 $desktop_search_nav.removeAttr('style'); 4251 4252 if (scroll <= make_search_sticky_position + 20) { 4253 $desktop_search_nav.removeClass('houzez-in-view'); 4254 } 4255 4256 if (hidden_data) { 4257 $desktop_search_nav.addClass('search-hidden'); 4258 } 4259 4260 if (!hidden_data) { 4261 $('#main-wrap').css('padding-top', 0); 4262 } 4263 } 4264 }); 4265 }; 4266 4267 const stickyMobileSearch = function () { 4268 let thisHeight = $mobile_search_nav.outerHeight(); 4269 let { make_search_sticky_position, searchStickyPlus } = 4270 calculateStickyHeights(); 4271 4272 let make_mobile_search_sticky_position = thisHeight; 4273 let searchInView = thisHeight; 4274 if (adminBarHeight > 0) { 4275 make_mobile_search_sticky_position += adminBarHeight; 4276 searchInView += adminBarHeight + 46; 4277 } 4278 4279 $win.scroll(function () { 4280 const scroll = $win.scrollTop(); 4281 4282 if (scroll >= make_mobile_search_sticky_position) { 4283 $mobile_search_nav.addClass('sticky-search-area'); 4284 $mobile_search_nav.css('top', adminBarHeight); 4285 4286 if (scroll >= searchInView) { 4287 $mobile_search_nav.addClass('houzez-in-view'); 4288 4289 $('#main-wrap').css('padding-top', thisHeight); 4290 } 4291 } else { 4292 $mobile_search_nav.removeClass('sticky-search-area'); 4293 $mobile_search_nav.removeAttr('style'); 4294 4295 if (scroll <= searchInView + 20) { 4296 $mobile_search_nav.removeClass('houzez-in-view'); 4297 } 4298 4299 $('#main-wrap').css('padding-top', 0); 4300 } 4301 }); 4302 }; 4303 4304 /** 4305 * Initialize all functionality 4306 */ 4307 const init = function () { 4308 // Calculate heights and initialize variables 4309 calculateStickyHeights(); 4310 4311 // Initialize functionality based on configuration 4312 const only_nav_sticky = $header_nav.data('sticky'); 4313 const desktop_header_search_enabled = 4314 $desktop_search_nav.data('sticky'); 4315 const mobile_header_search_enabled = 4316 $mobile_search_nav.data('sticky'); 4317 4318 // Initialize sticky navigation if enabled
4319 if (only_nav_sticky && !houzez.Core.is_mobile()) { 4320 // apply new mobile 4321 stickyNav(); 4322 4323 // Special handling for compare table if needed 4324 if ($('.compare-table').length > 0) { 4325 var compare_top_margin = $desktop_search_nav.outerHeight(); 4326 $('.compare-table thead th').css( 4327 'top', 4328 compare_top_margin + 25 4329 ); 4330 } 4331 } 4332 4333 stickyMobileNav(); 4334 4335 if (mobile_header_search_enabled) { 4336 stickyMobileSearch(); 4337 } 4338 4339 // Initialize sticky search if enabled 4340 if (desktop_header_search_enabled && !houzez.Core.is_mobile()) { 4341 stickySearch(); 4342 } 4343 }; 4344 4345 // Public API 4346 return { 4347 init: init, 4348 stickyNav: stickyNav, 4349 stickySearch: stickySearch, 4350 }; 4351 })(); 4352 4353 /** 4354 * Search Module 4355 * Handles search functionality 4356 */ 4357 houzez.Search = (function () { 4358 /** 4359 * Initialize mobile search overlay 4360 */ 4361 const mobileSearchOverlay = function () { 4362 // Calculate admin bar height and set overlay height 4363 const setOverlayHeight = function () { 4364 const $adminBar = houzez.Core.config.wpadminbar_height; 4365 const $searchOverlay = $('#overlay-search-advanced-module'); 4366 const windowHeight = $(window).height(); 4367 4368 if ($adminBar > 0 && $searchOverlay.length) { 4369 const adminBarHeight = $adminBar; 4370 $searchOverlay.css({ 4371 height: 'calc(100vh)', 4372 zIndex: '999', 4373 top: adminBarHeight, 4374 }); 4375 } else if ($searchOverlay.length) { 4376 $searchOverlay.css({ 4377 height: '100vh', 4378 top: '0', 4379 }); 4380 } 4381 }; 4382 4383 // Set initial height 4384 setOverlayHeight(); 4385 4386 // Update height on window resize 4387 $(window).on('resize', function () { 4388 setOverlayHeight(); 4389 }); 4390 4391 // Toggle overlay 4392 $( 4393 '.mobile-search-nav, .overlay-search-module-close, .overly_is_halfmap .half-map-search-js-btn' 4394 ).click(function () { 4395 $('#overlay-search-advanced-module').toggleClass('open'); 4396 }); 4397 }; 4398 4399 /** 4400 * Initialize search status tabs 4401 */ 4402 const initSearchTabs = function () { 4403 $('.houzez-status-tabs li a').on('click', function (e) { 4404 e.preventDefault(); 4405 let $this = $(this); 4406 let status = $this.data('val'); 4407 4408 $('#search-tabs').val(status); 4409 4410 const $form = $('.houzez-search-form-js'); 4411 statusChangeHandler(status, $form); 4412 }); 4413 }; 4414 4415 /** 4416 * Changes price dropdown options based on property status 4417 * @param {string} propertyStatus - Selected property status 4418 * @param {object} $form - jQuery form object 4419 * @returns {boolean} - Returns true if successful, false otherwise 4420 */ 4421 const statusChangeHandler = function (propertyStatus, $form) { 4422 // Validate parameters 4423 if (!propertyStatus || !$form || !$form.length) { 4424 return false; 4425 } 4426 4427 const isForRent = propertyStatus == houzez_vars.for_rent; 4428 const rentSelector = '.prices-only-for-rent'; 4429 const allSelector = '.prices-for-all'; 4430 4431 // Toggle rent-specific price options 4432 if (isForRent) { 4433 $form.find(allSelector).addClass('hide'); 4434 $form 4435 .find(`${allSelector} select`) 4436 .attr('disabled', 'disabled'); 4437 $form.find(rentSelector).removeClass('hide'); 4438 $form.find(`${rentSelector} select`).removeAttr('disabled'); 4439 $form.find(`${rentSelector} select`).selectpicker('refresh'); 4440 } else { 4441 $form.find(rentSelector).addClass('hide'); 4442 $form 4443 .find(`${rentSelector} select`) 4444 .attr('disabled', 'disabled'); 4445 $form.find(allSelector).removeClass('hide'); 4446 $form.find(`${allSelector} select`).removeAttr('disabled'); 4447 $form.find(`${allSelector} select`).selectpicker('refresh'); 4448 } 4449 4450 return true; 4451 }; 4452 4453 /** 4454 * Initialize property status change event handlers 4455 * Sets up event listeners for status changes and initializes the UI 4456 */ 4457 const initStatusChangeHandlers = function () { 4458 // Status dropdown change event 4459 $('select.status-js').on('change', function (e) { 4460 // Prevent event from bubbling up to the wrapper div 4461 e.stopPropagation(); 4462 4463 const selected_status = $(this).val(); 4464 const $form = $(this).parents('form'); 4465 statusChangeHandler(selected_status, $form); 4466 }); 4467 4468 // Status tab click event 4469 $('.status-tab-js').on('click', function () { 4470 const tab_selected_status = $(this).data('val'); 4471 const $form = $(this).parents('form'); 4472 statusChangeHandler(tab_selected_status, $form); 4473 }); 4474 4475 // Initialize on page load based on current status 4476 const $searchForm = $('.houzez-search-form-js'); 4477 const $builderForm = $('.houzez-search-builder-form-js'); 4478 4479 /* On page load*/ 4480 const selected_status = $('select.status-js').val(); 4481 if (selected_status == houzez_vars.for_rent) { 4482 statusChangeHandler(selected_status, $searchForm); 4483 } else { 4484 statusChangeHandler('dummy', $searchForm); 4485 } 4486 4487 /* On page load status tab */ 4488 const selected_status_tab = $('.status-tab-js').val(); 4489 if (selected_status_tab == houzez_vars.for_rent) { 4490 statusChangeHandler(selected_status_tab, $builderForm); 4491 } else { 4492 statusChangeHandler('dummy', $builderForm); 4493 } 4494 }; 4495 4496 /** 4497 * Fill the distance range slider with gradient background 4498 */ 4499 const fillDistanceRangeSlider = function (slider) { 4500 if (!slider) return; 4501 4502 const value = 4503 ((slider.value - slider.min) / (slider.max - slider.min)) * 100; 4504 const isRTL = document.documentElement.dir === 'rtl'; 4505 const direction = isRTL ? 'to left' : 'to right'; 4506 slider.style.background = `linear-gradient( 4507 ${direction}, 4508 ${houzez_vars.primary_color} 0%, 4509 ${houzez_vars.primary_color} ${value}%, 4510 #dce0e0 ${value}%, 4511 #dce0e0 100% 4512 )`; 4513 }; 4514 4515 /** 4516 * Initialize the distance range slider functionality 4517 */ 4518 const initDistanceRangeSlider = function () { 4519 const rangeInput = document.getElementById('radius-range-text'); 4520 const rangeSlider = document.getElementById('radius-range-slider'); 4521 const radiusValue = document.getElementById('radius-range-value'); 4522 const radiusCheckbox = document.getElementById('use_radius'); 4523 const distanceRange = document.querySelector('.distance-range'); 4524 4525 if (!rangeInput || !rangeSlider || !radiusValue) return; 4526 4527 // Sync input and slider 4528 rangeInput.addEventListener('input', (e) => { 4529 const value = e.target.value; 4530 rangeSlider.value = value; 4531 radiusValue.value = value; 4532 if (distanceRange) { 4533 distanceRange.value = value; 4534 } 4535 fillDistanceRangeSlider(rangeSlider); 4536 }); 4537 4538 rangeSlider.addEventListener('input', (e) => { 4539 const value = e.target.value; 4540 rangeInput.value = value; 4541 radiusValue.value = value; 4542 if (distanceRange) { 4543 distanceRange.value = value; 4544 } 4545 fillDistanceRangeSlider(rangeSlider); 4546 }); 4547 4548 // Set initial values 4549 const defaultRadius = rangeSlider.value; 4550 rangeInput.value = defaultRadius; 4551 radiusValue.value = defaultRadius; 4552 if (distanceRange) { 4553 distanceRange.value = defaultRadius; 4554 } 4555 4556 // Initial fill 4557 fillDistanceRangeSlider(rangeSlider); 4558 4559 // Enable/disable based on checkbox 4560 if (radiusCheckbox) { 4561 radiusCheckbox.addEventListener('change', (e) => { 4562 const isEnabled = e.target.checked; 4563 rangeInput.disabled = !isEnabled; 4564 rangeSlider.disabled = !isEnabled; 4565 if (isEnabled) { 4566 radiusValue.value = rangeSlider.value; 4567 if (distanceRange) { 4568 distanceRange.value = rangeSlider.value; 4569 } 4570 } else { 4571 radiusValue.value = ''; 4572 if (distanceRange) { 4573 distanceRange.value = ''; 4574 } 4575 } 4576 fillDistanceRangeSlider(rangeSlider); 4577 }); 4578 4579 // Set initial state
4580 const isChecked = radiusCheckbox.checked; 4581 rangeInput.disabled = !isChecked; 4582 rangeSlider.disabled = !isChecked; 4583 if (isChecked) { 4584 radiusValue.value = defaultRadius; 4585 if (distanceRange) { 4586 distanceRange.value = defaultRadius; 4587 } 4588 } else { 4589 radiusValue.value = ''; 4590 if (distanceRange) { 4591 distanceRange.value = ''; 4592 } 4593 } 4594 } 4595 }; 4596 4597 /** 4598 * Initialize the price range slider 4599 * @param {number|string} min_price - Minimum price 4600 * @param {number|string} max_price - Maximum price 4601 * @param {string} unique_id - Unique identifier for this price range slider 4602 * @param {number|null} selectedMinPrice - Selected minimum price 4603 * @param {number|null} selectedMaxPrice - Selected maximum price 4604 */ 4605 const initPriceRangeSlider = function ( 4606 min_price, 4607 max_price, 4608 unique_id = 'default', 4609 selectedMinPrice = null, 4610 selectedMaxPrice = null 4611 ) { 4612 const fromSlider = document.getElementById( 4613 'fromSlider_' + unique_id 4614 ); 4615 const toSlider = document.getElementById('toSlider_' + unique_id); 4616 const rangeWrap = document.querySelector( 4617 `.range-wrap[data-price-range-id="${unique_id}"]` 4618 ); 4619 4620 if (!rangeWrap) { 4621 return; 4622 } 4623 4624 const minPriceDisplay = rangeWrap.querySelector('.min-price-range'); 4625 const maxPriceDisplay = rangeWrap.querySelector('.max-price-range'); 4626 4627 if ( 4628 !fromSlider || 4629 !toSlider || 4630 !minPriceDisplay || 4631 !maxPriceDisplay 4632 ) { 4633 return; 4634 } 4635 4636 // Ensure min_price and max_price are numbers 4637 min_price = parseInt(min_price, 10); 4638 max_price = parseInt(max_price, 10); 4639 4640 // Set min and max attributes for both sliders 4641 fromSlider.min = min_price; 4642 fromSlider.max = max_price; 4643 toSlider.min = min_price; 4644 toSlider.max = max_price; 4645 4646 // Set initial values based on selected values or defaults 4647 if (selectedMinPrice !== null && selectedMaxPrice !== null) { 4648 fromSlider.value = selectedMinPrice; 4649 toSlider.value = selectedMaxPrice; 4650 } else { 4651 fromSlider.value = min_price; 4652 toSlider.value = max_price; 4653 } 4654 4655 const formatCurrency = (value) => { 4656 // Get separators from theme options 4657 const thousandSeparator = 4658 houzez_vars.thousands_separator || ','; 4659 const decimalSeparator = 4660 houzez_vars.decimal_point_separator || '.'; 4661 4662 // Format number with proper separators 4663 let formattedValue = value.toString(); 4664 4665 // Split number at decimal point 4666 const parts = formattedValue.split('.'); 4667 4668 // Format the whole number part with thousand separators 4669 parts[0] = parts[0].replace( 4670 /\B(?=(\d{3})+(?!\d))/g, 4671 thousandSeparator 4672 ); 4673 4674 // Join back with decimal part if exists 4675 formattedValue = parts.join(decimalSeparator); 4676 4677 // Add currency symbol based on position 4678 if (houzez_vars.currency_position === 'after') { 4679 return formattedValue + houzez_vars.currency_symbol; 4680 } else { 4681 return houzez_vars.currency_symbol + formattedValue; 4682 } 4683 }; 4684 4685 // Creates the gradient background effect for the slider 4686 const fillSlider = ( 4687 from, 4688 to, 4689 sliderColor, 4690 rangeColor, 4691 controlSlider 4692 ) => { 4693 const rangeDistance = to.max - to.min; 4694 const fromPosition = from.value - to.min; 4695 const toPosition = to.value - to.min; 4696 const isRTL = document.documentElement.dir === 'rtl'; 4697 const direction = isRTL ? 'to left' : 'to right'; 4698 controlSlider.style.background = `linear-gradient( 4699 ${direction}, 4700 ${sliderColor} 0%, 4701 ${sliderColor} ${(fromPosition / rangeDistance) * 100}%, 4702 ${rangeColor} ${(fromPosition / rangeDistance) * 100}%, 4703 ${rangeColor} ${(toPosition / rangeDistance) * 100}%, 4704 ${sliderColor} ${(toPosition / rangeDistance) * 100}%, 4705 ${sliderColor} 100%)`; 4706 }; 4707 4708 // Track which slider was last interacted with 4709 let lastActiveSlider = null; 4710 4711 // Adjusts z-index of sliders for better handle overlap behavior 4712 const setToggleAccessible = (activeSlider = null) => { 4713 const from = parseInt(fromSlider.value, 10); 4714 const to = parseInt(toSlider.value, 10); 4715 const range = 4716 parseInt(toSlider.max, 10) - parseInt(toSlider.min, 10); 4717 const threshold = range * 0.05; // 5% of range as overlap threshold 4718 4719 // Check if sliders are overlapping or very close 4720 const isOverlapping = Math.abs(to - from) <= threshold; 4721 4722 if (isOverlapping && activeSlider) { 4723 // When overlapping, the active slider gets higher z-index 4724 if (activeSlider === 'from') { 4725 fromSlider.style.zIndex = 2; 4726 toSlider.style.zIndex = 0; 4727 } else { 4728 fromSlider.style.zIndex = 0; 4729 toSlider.style.zIndex = 2; 4730 } 4731 } else { 4732 // Default behavior when not overlapping 4733 // Min slider has higher z-index when at same position 4734 if (from === to) { 4735 fromSlider.style.zIndex = 2; 4736 toSlider.style.zIndex = 0; 4737 } else { 4738 fromSlider.style.zIndex = 1; 4739 toSlider.style.zIndex = 0; 4740 } 4741 } 4742 }; 4743 4744 const controlFromSlider = () => { 4745 const from = parseInt(fromSlider.value, 10); 4746 const to = parseInt(toSlider.value, 10); 4747 4748 fillSlider( 4749 fromSlider, 4750 toSlider, 4751 '#dce0e0', 4752 houzez_vars.primary_color, 4753 toSlider 4754 ); 4755 4756 if (from > to) { 4757 fromSlider.value = to; 4758 } else { 4759 fromSlider.value = from; 4760 } 4761 4762 // Set this as the active slider 4763 lastActiveSlider = 'from'; 4764 setToggleAccessible('from'); 4765 4766 // Update display values 4767 minPriceDisplay.textContent = formatCurrency(fromSlider.value); 4768 maxPriceDisplay.textContent = formatCurrency(toSlider.value); 4769 }; 4770 4771 const controlToSlider = () => { 4772 const from = parseInt(fromSlider.value, 10); 4773 const to = parseInt(toSlider.value, 10); 4774 4775 fillSlider( 4776 fromSlider, 4777 toSlider, 4778 '#dce0e0', 4779 houzez_vars.primary_color, 4780 toSlider 4781 ); 4782 4783 if (from <= to) { 4784 toSlider.value = to; 4785 } else { 4786 toSlider.value = from; 4787 } 4788 4789 // Set this as the active slider 4790 lastActiveSlider = 'to'; 4791 setToggleAccessible('to'); 4792 4793 // Update display values 4794 minPriceDisplay.textContent = formatCurrency(fromSlider.value); 4795 maxPriceDisplay.textContent = formatCurrency(toSlider.value); 4796 }; 4797 4798 // Add event listeners 4799 fromSlider.addEventListener('input', controlFromSlider); 4800 toSlider.addEventListener('input', controlToSlider); 4801 4802 // Initialize slider appearance 4803 fillSlider( 4804 fromSlider, 4805 toSlider, 4806 '#dce0e0', 4807 houzez_vars.primary_color, 4808 toSlider 4809 ); 4810 setToggleAccessible(); 4811 4812 // Initialize display values 4813 minPriceDisplay.textContent = formatCurrency(fromSlider.value); 4814 maxPriceDisplay.textContent = formatCurrency(toSlider.value); 4815 }; 4816 4817 /** 4818 * Initialize price range sliders with appropriate values based on property status 4819 */ 4820 const initPriceRangeSliders = function () { 4821 // Check if any price range sliders exist 4822 if (!$('.range-wrap[data-price-range-id]').length) { 4823 return; 4824 } 4825 4826 // Get min and max values from theme options 4827 const min_price_rent = 4828 parseInt(houzez_vars.search_min_price_range_for_rent, 10) || 0; 4829 const max_price_rent = 4830 parseInt(houzez_vars.search_max_price_range_for_rent, 10) || 4831 1000000; 4832 const min_price_sale = 4833 parseInt(houzez_vars.search_min_price_range, 10) || 0; 4834 const max_price_sale = 4835 parseInt(houzez_vars.search_max_price_range, 10) || 1000000; 4836 4837 // Check if we have values from search results 4838 let selectedMinPrice = null; 4839 let selectedMaxPrice = null; 4840 4841 if (houzez_vars.get_min_price && houzez_vars.get_max_price) { 4842 selectedMinPrice = parseInt(houzez_vars.get_min_price, 10); 4843 selectedMaxPrice = parseInt(houzez_vars.get_max_price, 10); 4844 } 4845 4846 // Initialize all price range sliders 4847 $('.range-wrap[data-price-range-id]').each(function () { 4848 const unique_id = $(this).data('price-range-id'); 4849 const selected_status_adv_search = $('select.status-js').val(); 4850 4851 // Update slider min/max values based on property status 4852 if ( 4853 selected_status_adv_search == 4854 houzez_vars.for_rent_price_slider 4855 ) { 4856 initPriceRangeSlider( 4857 min_price_rent, 4858 max_price_rent, 4859 unique_id, 4860 selectedMinPrice, 4861 selectedMaxPrice 4862 ); 4863 } else { 4864 initPriceRangeSlider( 4865 min_price_sale, 4866 max_price_sale, 4867 unique_id, 4868 selectedMinPrice, 4869 selectedMaxPrice 4870 ); 4871 } 4872 }); 4873 4874 // Handle price slider changes based on status 4875 $('select.status-js').on('change', function () { 4876 let search_status = $(this).val(); 4877 4878 // Update all price range sliders 4879 $('.range-wrap[data-price-range-id]').each(function () { 4880 const unique_id = $(this).data('price-range-id'); 4881 4882 if (search_status == houzez_vars.for_rent_price_slider) { 4883 initPriceRangeSlider( 4884 min_price_rent, 4885 max_price_rent, 4886 unique_id 4887 ); 4888 } else { 4889 initPriceRangeSlider( 4890 min_price_sale, 4891 max_price_sale, 4892 unique_id 4893 ); 4894 } 4895 }); 4896 }); 4897 4898 // Handle price slider changes based on status tab 4899 $('.status-tab-js').on('click', function () { 4900 let tab_status = $(this).data('val'); 4901 4902 // Update all price range sliders 4903 $('.range-wrap[data-price-range-id]').each(function () { 4904 const unique_id = $(this).data('price-range-id'); 4905 4906 if (tab_status == houzez_vars.for_rent_price_slider) { 4907 initPriceRangeSlider( 4908 min_price_rent, 4909 max_price_rent, 4910 unique_id 4911 ); 4912 } else { 4913 initPriceRangeSlider( 4914 min_price_sale, 4915 max_price_sale, 4916 unique_id 4917 ); 4918 } 4919 }); 4920 }); 4921 }; 4922 4923 /** 4924 * Initialize auto-complete for keyword search 4925 */ 4926 const initKeywordAutocomplete = function () { 4927 if (houzez_vars.keyword_autocomplete != 0) { 4928 let ajaxCount = 0; 4929 let $dataType = ''; 4930 4931 $('body').on( 4932 'keyup', 4933 '.houzez-keyword-autocomplete', 4934 houzez.Core.util.debounce(function () {
4935 let $this = $(this); 4936 $dataType = $this.data('type'); 4937 let $form = $this.parents('form'); 4938 4939 let auto_complete_container = 4940 $form.find('.auto-complete'); 4941 4942 if ($dataType == 'banner') { 4943 auto_complete_container = $( 4944 '.auto-complete-banner' 4945 ); 4946 } 4947 4948 let keyword = $this.val(); 4949 4950 keyword = $.trim(keyword); 4951 const currentLength = keyword.length; 4952 4953 if (currentLength < 2) { 4954 auto_complete_container.hide(); 4955 return; 4956 } 4957 4958 auto_complete_container.fadeIn(); 4959 4960 $.ajax({ 4961 type: 'POST', 4962 url: houzez.Core.config.ajaxurl, 4963 data: { 4964 action: 'houzez_get_auto_complete_search', 4965 key: keyword, 4966 }, 4967 beforeSend: function () { 4968 ajaxCount++; 4969 if (ajaxCount == 1) { 4970 auto_complete_container.html( 4971 '<ul class="list-group"><li class="list-group-item"><i class="fa fa-spinner fa-spin fa-fw"></i> ' + 4972 houzez_vars.autosearch_text + 4973 '</li></ul>' 4974 ); 4975 } 4976 }, 4977 success: function (data) { 4978 ajaxCount--; 4979 if (ajaxCount == 0) { 4980 auto_complete_container.show(); 4981 if (data != '') { 4982 auto_complete_container 4983 .empty() 4984 .html(data); 4985 } 4986 } 4987 }, 4988 error: function (xhr, status, error) { 4989 ajaxCount--; 4990 if (ajaxCount == 0) { 4991 auto_complete_container.html( 4992 '<ul class="list-group"><li class="list-group-item"><i class="fa fa-spinner fa-spin fa-fw"></i> ' + 4993 error + 4994 '</li></ul>' 4995 ); 4996 } 4997 }, 4998 }); 4999 }, 300) 5000 ); 5001 5002 // Handle mousedown on autocomplete items to update input value 5003 // before click events fire (fixes half-map search reading old value) 5004 $(document).on('mousedown', '.auto-complete li', function () { 5005 let $this = $(this); 5006 let $input; 5007 let $container = $this.closest('.auto-complete'); 5008 5009 $input = $container.siblings( 5010 '.houzez-keyword-autocomplete' 5011 ); 5012 5013 if ($dataType == 'banner') { 5014 $input = $('.houzez-keyword-autocomplete.is-banner'); 5015 } 5016 5017 $input.val($this.data('text')); 5018 }); 5019 5020 // Handle click on autocomplete items to hide dropdown 5021 $(document).on('click', '.auto-complete li', function () { 5022 let $this = $(this); 5023 let $container = $this.closest('.auto-complete'); 5024 $container.fadeOut(); 5025 }); 5026
5027 // Handle click on "View All Results" link 5028 $(document).on('click', '.search-result-view', function () { 5029 $('.auto-complete').fadeOut(); 5030 }); 5031 } 5032 }; 5033 5034 /** 5035 * Handle reset button click to clear all search fields 5036 */ 5037 const handleResetSearch = function () { 5038 $('.reset-search-btn').on('click', function (e) { 5039 e.preventDefault(); 5040 5041 const $form = $(this).closest('form'); 5042 5043 // Reset text inputs and selects 5044 $form 5045 .find('input[type="text"], input[type="number"], select') 5046 .each(function () { 5047 $(this).val(''); 5048 }); 5049 5050 // Reset selects with Select2 5051 $form.find('select.selectpicker').each(function () { 5052 $(this).val('').selectpicker('refresh'); 5053 }); 5054 5055 // Reset checkboxes and radio buttons, except use_radius 5056 $form 5057 .find('input[type="checkbox"], input[type="radio"]') 5058 .not('[name="use_radius"]') 5059 .prop('checked', false); 5060 5061 // Ensure use_radius remains checked 5062 $form.find('input[name="use_radius"]').prop('checked', true); 5063 5064 // Reset price range sliders 5065 $form.find('.range-slider').each(function () { 5066 const $rangeWrap = $(this).closest('.range-wrap'); 5067 const fromSlider = $rangeWrap.find( 5068 '.min-price-range-slider' 5069 )[0]; 5070 const toSlider = $rangeWrap.find( 5071 '.max-price-range-slider' 5072 )[0]; 5073 5074 if (fromSlider && toSlider) { 5075 fromSlider.value = fromSlider.min; 5076 toSlider.value = toSlider.max; 5077 5078 // Trigger input event to update UI 5079 $(fromSlider).trigger('input'); 5080 $(toSlider).trigger('input'); 5081 } 5082 }); 5083 5084 // Reset distance slider 5085 const distanceSlider = $form.find('#radius-range-slider')[0]; 5086 const distanceInput = $form.find('#radius-range-value')[0]; 5087 const distanceText = $form.find('#radius-range-text')[0]; 5088 5089 if (distanceSlider && distanceInput) { 5090 // Get default radius from radius-range-value input which has the data-default attribute 5091 const defaultRadius = 5092 $(distanceInput).data('default') || 50; 5093 5094 // Reset the slider value 5095 distanceSlider.value = defaultRadius; 5096 5097 // Reset the hidden input value (radius-range-value) 5098 distanceInput.value = defaultRadius; 5099 5100 // Also reset any direct input field with name="radius" 5101 $form.find('input[name="radius"]').val(defaultRadius); 5102 5103 // Reset the text display if it exists 5104 if (distanceText) { 5105 distanceText.value = defaultRadius; 5106 } 5107 5108 // Update fill 5109 if (typeof fillDistanceRangeSlider === 'function') { 5110 fillDistanceRangeSlider(distanceSlider); 5111 } else { 5112 // If the function is not directly accessible, use it from the current context 5113 const rangeDistance = 5114 distanceSlider.max - distanceSlider.min; 5115 const position = defaultRadius - distanceSlider.min; 5116 5117 // Apply the gradient fill manually 5118 const isRTL = document.documentElement.dir === 'rtl'; 5119 const direction = isRTL ? 'to left' : 'to right'; 5120 distanceSlider.style.background = `linear-gradient( 5121 ${direction}, 5122 #dce0e0 0%, 5123 #dce0e0 ${(position / rangeDistance) * 100}%, 5124 ${houzez_vars.primary_color} ${ 5125 (position / rangeDistance) * 100 5126 }%, 5127 ${houzez_vars.primary_color} 100% 5128 )`; 5129 } 5130 } else { 5131 // If slider elements aren't found, still try to reset radius input field 5132 const defaultRadius = 50; 5133 $form.find('input[name="radius"]').val(defaultRadius); 5134 } 5135 5136 // Reset hidden fields 5137 $form 5138 .find('input[type="hidden"]') 5139 .not( 5140 '[name="_wp_http_referer"], [name="search-location"], [name="radius"]' 5141 ) 5142 .val(''); 5143 5144 // Clear location fields 5145 $form.find('.location-search input[type="text"]').val(''); 5146 5147 // Clear keyword fields 5148 $form.find('.keyword-search input').val(''); 5149 5150 // Hide any open dropdowns 5151 $('.search-dropdown-wrap').removeClass('show'); 5152 5153 // If using any custom sliders, reset them here 5154 5155 // If this is a half map search, submit the form after reset 5156 const isHalfMapSearch = $('.main-half-map-wrap').length > 0; 5157 5158 if (isHalfMapSearch) { 5159 // Submit the form after a small delay to ensure all values are properly reset 5160 setTimeout(function () { 5161 $form.find('.btn-search').trigger('click'); 5162 }, 100); 5163 return false;
5164 } 5165 5166 // Don't submit the form - let user click search if they want to 5167 return false; 5168 }); 5169 }; 5170 5171 /** 5172 * Initialize beds and baths counter functionality 5173 */ 5174 const initBedsAndBaths = function () { 5175 /* ------------------------------------------------------------------------ */ 5176 /* Beds and baths 5177 /* ------------------------------------------------------------------------ */ 5178 const beds_baths = function (btn_action, btn_count, btn_val) { 5179 $('.' + btn_action).on('click', function (e) { 5180 e.preventDefault(); 5181 e.stopPropagation(); 5182 var current_val = parseInt($('.' + btn_val).val()) || 0; 5183 5184 if ( 5185 btn_action == 'btn_count_plus' || 5186 btn_action == 'btn_beds_plus' 5187 ) { 5188 current_val++; 5189 } else { 5190 if (current_val == 0) return; 5191 current_val--; 5192 } 5193 5194 $('.' + btn_count).text(current_val); 5195 $('.' + btn_val).val(current_val); 5196 }); 5197 }; 5198 5199 // Initialize beds and baths buttons 5200 beds_baths('btn_count_plus', 'baths_count', 'bathrooms'); 5201 beds_baths('btn_count_minus', 'baths_count', 'bathrooms'); 5202 beds_baths('btn_beds_plus', 'beds_count', 'bedrooms'); 5203 beds_baths('btn_beds_minus', 'beds_count', 'bedrooms'); 5204 beds_baths('btn_rooms_plus', 'rooms_count', 'rooms'); 5205 beds_baths('btn_rooms_minus', 'rooms_count', 'rooms'); 5206 5207 // Apply button handler 5208 $('.btn-apply').on('click', function (e) { 5209 e.preventDefault(); 5210 $('.advanced-search-v3 .btn-group .dropdown-menu').removeClass( 5211 'show' 5212 ); 5213 }); 5214 5215 // Clear buttons handlers 5216 $('.clear-baths').on('click', function (e) { 5217 e.preventDefault(); 5218 e.stopPropagation(); 5219 $('.baths_count').text('0'); 5220 $('.bathrooms').val(''); 5221 }); 5222 5223 $('.clear-beds').on('click', function (e) { 5224 e.preventDefault(); 5225 e.stopPropagation(); 5226 $('.beds_count').text('0'); 5227 $('.bedrooms').val(''); 5228 }); 5229 5230 $('.clear-rooms').on('click', function (e) { 5231 e.preventDefault(); 5232 e.stopPropagation(); 5233 $('.rooms_count').text('0'); 5234 $('.rooms').val(''); 5235 }); 5236 5237 $('.clear-checkboxes').on('click', function (e) { 5238 e.preventDefault(); 5239 e.stopPropagation(); 5240 $(this) 5241 .parents('.btn-group') 5242 .find('input[type="checkbox"]') 5243 .prop('checked', false) 5244 .attr('checked', false); 5245 }); 5246 }; 5247 5248 /** 5249 * Initialize the save search functionality 5250 */ 5251 const initSaveSearch = function () { 5252 $('.save_search_click').on('click', function (e) { 5253 e.preventDefault(); 5254 5255 var $this = $(this); 5256 let $parent = $this.parents('.save-search-form-wrap'); 5257 5258 var search_args = $parent 5259 .find('input[name="search_args"]') 5260 .val(); 5261 var security = $parent 5262 .find('input[name="houzez_save_search_ajax"]') 5263 .val(); 5264 var search_URI = $parent.find('input[name="search_URI"]').val(); 5265 5266 if (parseInt(houzez.Core.config.userID, 10) === 0) { 5267 houzez.Favorites.showLoginModal(); 5268 } else { 5269 $.ajax({ 5270 url: houzez.Core.config.ajaxurl, 5271 data: { 5272 action: 'houzez_save_search', 5273 search_args: search_args, 5274 search_URI: search_URI, 5275 houzez_save_search_ajax: security, 5276 }, 5277 method: 'POST', 5278 dataType: 'JSON', 5279 5280 beforeSend: function () { 5281 $this 5282 .find('.houzez-loader-js') 5283 .addClass('loader-show'); 5284 }, 5285 success: function (response) { 5286 if (response.success) { 5287 $('.save_search_click').attr('disabled', true); 5288 } 5289 }, 5290 error: function (xhr, status, error) { 5291 var err = eval('(' + xhr.responseText + ')'); 5292 console.log(err.Message); 5293 }, 5294 complete: function () { 5295 $this 5296 .find('.houzez-loader-js') 5297 .removeClass('loader-show'); 5298 }, 5299 }); 5300 } 5301 }); 5302 }; 5303 5304 /** 5305 * Initialize the module 5306 */ 5307 const init = function () { 5308 mobileSearchOverlay(); 5309 initSearchTabs(); 5310 initStatusChangeHandlers(); 5311 initPriceRangeSliders(); 5312 initKeywordAutocomplete(); 5313 handleResetSearch();
5314 initBedsAndBaths(); 5315 initDistanceRangeSlider(); 5316 initSaveSearch(); 5317 }; 5318 5319 // Public API 5320 return { 5321 init: init, 5322 mobileSearchOverlay: mobileSearchOverlay, 5323 statusChangeHandler: statusChangeHandler, 5324 initPriceRangeSlider: initPriceRangeSlider, 5325 initSearchTabs: initSearchTabs, 5326 initKeywordAutocomplete: initKeywordAutocomplete, 5327 handleResetSearch: handleResetSearch, 5328 initBedsAndBaths: initBedsAndBaths, 5329 initDistanceRangeSlider: initDistanceRangeSlider, 5330 initSaveSearch: initSaveSearch, 5331 }; 5332 })(); 5333 5334 /* ------------------------------------------------------------------------ */ 5335 /* BLOG 5336 /* ------------------------------------------------------------------------ */ 5337 houzez.Blog = (function () { 5338 const resizeMasonryItem = function (item) { 5339 /* Get the grid object, its row-gap, and the size of its implicit rows */ 5340 var grid = document.getElementsByClassName('masonry')[0], 5341 rowGap = parseInt( 5342 window 5343 .getComputedStyle(grid) 5344 .getPropertyValue('grid-row-gap') 5345 ), 5346 rowHeight = parseInt( 5347 window 5348 .getComputedStyle(grid) 5349 .getPropertyValue('grid-auto-rows') 5350 ); 5351 5352 /* 5353 * Spanning for any brick = S 5354 * Grid's row-gap = G 5355 * Size of grid's implicitly create row-track = R 5356 * Height of item content = H 5357 * Net height of the item = H1 = H + G 5358 * Net height of the implicit row-track = T = G + R 5359 * S = H1 / T 5360 */ 5361 var rowSpan = Math.ceil( 5362 (item.querySelector('.masonry-content').getBoundingClientRect() 5363 .height + 5364 rowGap) / 5365 (rowHeight + rowGap) 5366 ); 5367 5368 /* Set the spanning as calculated above (S) */ 5369 item.style.gridRowEnd = 'span ' + rowSpan; 5370 }; 5371 5372 const resizeAllMasonryItems = function () { 5373 // Get all item class objects in one list 5374 var allItems = document.getElementsByClassName('masonry-brick'); 5375 5376 /* 5377 * Loop through the above list and execute the spanning function to 5378 * each list-item (i.e. each masonry item) 5379 */ 5380 for (var i = 0; i < allItems.length; i++) { 5381 resizeMasonryItem(allItems[i]); 5382 } 5383 }; 5384 5385 const waitForImages = function () { 5386 var allItems = document.getElementsByClassName('masonry-brick'); 5387 for (var i = 0; i < allItems.length; i++) { 5388 var images = allItems[i].getElementsByTagName('img'); 5389 for (var j = 0; j < images.length; j++) { 5390 images[j].addEventListener('load', function () { 5391 resizeMasonryItem(this.closest('.masonry-brick')); 5392 }); 5393 if (images[j].complete) { 5394 resizeMasonryItem(images[j].closest('.masonry-brick')); 5395 } 5396 } 5397 } 5398 }; 5399 5400 const initMasonry = function () { 5401 /* Resize all the grid items on the load and resize events */ 5402 var masonryEvents = ['load', 'resize']; 5403 masonryEvents.forEach(function (event) { 5404 window.addEventListener(event, resizeAllMasonryItems); 5405 }); 5406 5407 /* Do a resize once more when all the images finish loading */ 5408 waitForImages(); 5409 }; 5410 5411 const init = function () { 5412 initMasonry(); 5413 }; 5414 5415 return { 5416 init: init, 5417 initMasonry: initMasonry, 5418 }; 5419 })(); 5420 5421 /** 5422 * LocationFilter Module 5423 * Handles location filtering functionality 5424 */ 5425 houzez.LocationFilter = (function () { 5426 /** 5427 * Handle select filter functionality 5428 * @param {jQuery} $this - jQuery element 5429 * @param {string} dataRef - Data reference 5430 * @param {string} list_num - List number 5431 * @param {string} is_on_load - Whether function is called on load 5432 */ 5433 const houzezSelectFilter = function ( 5434 $this, 5435 dataRef, 5436 list_num = '', 5437 is_on_load = '' 5438 ) { 5439 let e = $this.data('target'); 5440 let i = $this.find(':selected').data('ref'); 5441 5442 if (is_on_load == 'yes') { 5443 e = list_num; 5444 i = dataRef; 5445 } 5446 5447 $('select.' + e).val(''); 5448 5449 if (i == null) { 5450 $('select.' + e) 5451 .find('option') 5452 .each(function () { 5453 $(this).removeAttr('disabled hidden'); 5454 }); 5455 } else { 5456 $('select.' + e) 5457 .find('option') 5458 .each(function () { 5459 const e = $(this).data('belong'); 5460 const t = $(this).val(); 5461 5462 if (i != e && t != '') { 5463 $(this).prop('disabled', true); 5464 $(this).prop('hidden', true); 5465 } else { 5466 $(this).prop('disabled', false); 5467 $(this).prop('hidden', false); 5468 } 5469 }); 5470 } 5471 $('select.' + e).selectpicker('refresh'); 5472 }; 5473 5474 /** 5475 * Initialize location filters on page load 5476 */ 5477 const initLocationFilters = function () { 5478 if ( 5479 $('.houzez-search-form-js').length > 0 || 5480 $('#location').length > 0 5481 ) { 5482 let countryRef, stateRef, cityRef, areasRef; 5483 5484 if ( 5485 typeof is_edit_property !== 'undefined' && 5486 is_edit_property 5487 ) { 5488 countryRef = $('#country').data('country'); 5489 stateRef = $('#countyState').data('state'); 5490 cityRef = $('#city').data('city'); 5491 areasRef = $('#neighborhood').data('area'); 5492 } else { 5493 countryRef = houzez_vars.s_country; 5494 stateRef = houzez_vars.s_state; 5495 cityRef = houzez_vars.s_city; 5496 areasRef = houzez_vars.s_areas; 5497 } 5498 5499 // Handle country filter 5500 if ( 5501 ($('.houzez-country-js').length > 0 || 5502 $('#country').length > 0) && 5503 countryRef != '' 5504 ) { 5505 const countryFilter = $('.houzezCountryFilter'); 5506 houzezSelectFilter( 5507 countryFilter, 5508 countryRef, 5509 'houzezSecondList', 5510 'yes' 5511 ); 5512 5513 $(window).on('load', function () { 5514 $('.houzezSecondList').val(stateRef); 5515 $('select.houzezSecondList').selectpicker('refresh'); 5516 }); 5517 } 5518 5519 // Handle state filter 5520 if ( 5521 ($('.houzez-state-js').length > 0 || 5522 $('#countyState').length > 0) && 5523 stateRef != '' 5524 ) { 5525 const stateFilter = $('.houzezStateFilter'); 5526 houzezSelectFilter( 5527 stateFilter, 5528 stateRef, 5529 'houzezThirdList', 5530 'yes' 5531 ); 5532 5533 $(window).on('load', function () { 5534 $('.houzezThirdList').val(cityRef); 5535 $('select.houzezThirdList').selectpicker('refresh'); 5536 }); 5537 } 5538
5539 // Handle city filter 5540 if ( 5541 ($('.houzez-city-js').length > 0 || 5542 $('#city').length > 0) && 5543 cityRef != '' 5544 ) { 5545 const cityFilter = $('.houzezCityFilter'); 5546 houzezSelectFilter( 5547 cityFilter, 5548 cityRef, 5549 'houzezFourthList', 5550 'yes' 5551 ); 5552 5553 $(window).on('load', function () { 5554 $('.houzezFourthList').val(areasRef); 5555 $('select.houzezFourthList').selectpicker('refresh'); 5556 }); 5557 } 5558 } 5559 }; 5560 5561 /** 5562 * Initialize the module 5563 */ 5564 const init = function () { 5565 // Attach change event handler 5566 $('.houzezSelectFilter').on('change', function () { 5567 houzezSelectFilter($(this)); 5568 }); 5569 5570 // Initialize location filters 5571 initLocationFilters(); 5572 }; 5573 5574 // Public API 5575 return { 5576 init: init, 5577 houzezSelectFilter: houzezSelectFilter, 5578 }; 5579 })(); 5580 5581 /** 5582 * CurrencySwitcher Module 5583 * Handles currency switching functionality 5584 */ 5585 houzez.CurrencySwitcher = (function () { 5586 /** 5587 * Process currency switch 5588 * @param {string} selectedCurrencyCode - The selected currency code 5589 */ 5590 const processCurrencySwitch = function (selectedCurrencyCode) { 5591 if (!selectedCurrencyCode) { 5592 console.warn('No currency code provided'); 5593 return; 5594 } 5595 5596 // Update hidden input value 5597 $('#houzez-switch-to-currency').val(selectedCurrencyCode); 5598 5599 // Show processing modal 5600 houzez.Core.util.processingModal( 5601 houzez.Core.config.processing_text 5602 ); 5603 5604 // Make AJAX request 5605 $.ajax({ 5606 url: houzez.Core.config.ajaxurl, 5607 dataType: 'JSON', 5608 method: 'POST', 5609 data: { 5610 action: 'houzez_currency_converter', 5611 currency_converter: selectedCurrencyCode, 5612 }, 5613 success: function (response) { 5614 if (response.success) { 5615 window.location.reload(true); 5616 } else { 5617 houzez.Core.util.processingModalClose(); 5618 console.warn('Currency conversion failed:', response); 5619 5620 // Show error message if provided 5621 if (response.msg) { 5622 const $messages = $( 5623 '.hz-currency-switcher-messages' 5624 ); 5625 houzez.Core.util.showError($messages, response.msg); 5626 } 5627 } 5628 }, 5629 error: function (xhr, status, error) { 5630 houzez.Core.util.processingModalClose(); 5631 console.error('Currency switch error:', error); 5632 5633 // Show generic error message 5634 const $messages = $('.hz-currency-switcher-messages'); 5635 houzez.Core.util.showError( 5636 $messages, 5637 'Error processing currency switch' 5638 ); 5639 }, 5640 }); 5641 }; 5642 5643 /** 5644 * Initialize currency switcher functionality 5645 */ 5646 const initCurrencySwitcher = function () { 5647 const $currencySwitcherList = $('#hz-currency-switcher-list'); 5648 5649 if ($currencySwitcherList.length === 0) { 5650 return; 5651 } 5652 5653 // Handle currency selection 5654 $currencySwitcherList.on('click', 'li', function (e) { 5655 e.preventDefault(); 5656 const selectedCurrencyCode = $(this).data('currency-code'); 5657 5658 if (selectedCurrencyCode) { 5659 processCurrencySwitch(selectedCurrencyCode); 5660 } 5661 }); 5662 }; 5663 5664 /** 5665 * Initialize the module 5666 */ 5667 const init = function () { 5668 initCurrencySwitcher(); 5669 }; 5670 5671 // Public API 5672 return { 5673 init: init, 5674 processCurrencySwitch: processCurrencySwitch, 5675 }; 5676 })(); 5677 5678 /** 5679 * AreaSwitcher Module 5680 * Handles area unit switching functionality 5681 */ 5682 houzez.AreaSwitcher = (function () { 5683 /** 5684 * Process area unit switch 5685 * @param {string} selectedAreaCode - The selected area unit code 5686 */ 5687 const processAreaSwitch = function (selectedAreaCode) { 5688 if (!selectedAreaCode) { 5689 console.warn('No area code provided'); 5690 return; 5691 } 5692 5693 // Update hidden input value 5694 $('#houzez-switch-to-area').val(selectedAreaCode); 5695 5696 // Show processing modal 5697 houzez.Core.util.processingModal( 5698 houzez.Core.config.processing_text 5699 ); 5700 5701 // Make AJAX request 5702 $.ajax({ 5703 url: houzez.Core.config.ajaxurl, 5704 dataType: 'JSON', 5705 method: 'POST', 5706 data: { 5707 action: 'houzez_switch_area', 5708 switch_to_area: selectedAreaCode, 5709 }, 5710 success: function (response) { 5711 if (response.success) { 5712 window.location.reload(true); 5713 } else { 5714 houzez.Core.util.processingModalClose(); 5715 console.warn('Area unit conversion failed:', response); 5716 5717 // Show error message if provided 5718 if (response.msg) { 5719 const $messages = $('.hz-area-switcher-messages'); 5720 houzez.Core.util.showError($messages, response.msg); 5721 } 5722 } 5723 }, 5724 error: function (xhr, status, error) { 5725 houzez.Core.util.processingModalClose(); 5726 console.error('Area unit switch error:', error); 5727 5728 // Show generic error message 5729 const $messages = $('.hz-area-switcher-messages'); 5730 houzez.Core.util.showError( 5731 $messages, 5732 'Error processing area unit switch' 5733 ); 5734 }, 5735 }); 5736 }; 5737 5738 /** 5739 * Initialize area switcher functionality 5740 */ 5741 const initAreaSwitcher = function () { 5742 const $areaSwitcherList = $('#area-switcher-list-js'); 5743 5744 if ($areaSwitcherList.length === 0) { 5745 return; 5746 } 5747 5748 // Handle area unit selection 5749 $areaSwitcherList.on('click', 'li', function (e) { 5750 e.preventDefault(); 5751 const selectedAreaCode = $(this).data('area-code'); 5752 5753 if (selectedAreaCode) { 5754 processAreaSwitch(selectedAreaCode); 5755 } 5756 }); 5757 }; 5758 5759 /** 5760 * Initialize the module 5761 */ 5762 const init = function () { 5763 initAreaSwitcher(); 5764 }; 5765 5766 // Public API 5767 return { 5768 init: init, 5769 processAreaSwitch: processAreaSwitch, 5770 }; 5771 })(); 5772 5773 /** 5774 * Houzez Elementor Mobile Menu Module 5775 * Handles Elementor mobile menu functionality 5776 */ 5777 houzez.ElementorMobileMenu = (function () { 5778 const $win = $(window); 5779 5780 /* ------------------------------------------------------------------------ */ 5781 /* Elementor Mobile menu trigger 5782 /* ------------------------------------------------------------------------ */ 5783 const mobileMenuTrigger = function () { 5784 $('.houzez-nav-menu-main-mobile-wrap .houzez-menu-toggle').on( 5785 'click', 5786 function (e) { 5787 $( 5788 '.houzez-nav-menu-main-mobile-wrap .navbar-nav, .houzez-nav-menu-main-mobile-wrap .houzez-menu-toggle' 5789 ).toggleClass('houzez-nav-menu-active'); 5790 } 5791 ); 5792 }; 5793 /** 5794 * Adjust mega menu positioning 5795 */ 5796 const adjustMegaMenu = function ( 5797 megaMenuSelector, 5798 containerSelector, 5799 fullwidthClass, 5800 customWidthClass, 5801 customWidthPx 5802 ) { 5803 jQuery(megaMenuSelector).each(function () { 5804 var $megamenu = jQuery(this); 5805 var windowWidth = jQuery(window).width(); 5806 var isRTL = jQuery('html').attr('dir') === 'rtl'; 5807 5808 // Cache common selections 5809 var $fullwidthContainer = $megamenu.closest(fullwidthClass); 5810 var $customWidthContainer = $megamenu.closest(customWidthClass); 5811 var $container = $megamenu.closest(containerSelector);
5812 var $navItem = $megamenu.closest('.nav-item'); 5813 5814 // Reset inline styles 5815 $megamenu.css({ left: '', right: '', width: '' }); 5816 5817 // Case 1: Full viewport width 5818 if ($fullwidthContainer.length > 0) { 5819 $megamenu.css('width', '100vw'); 5820 5821 // Store current position 5822 var originalPosition = $megamenu.css('position'); 5823 5824 // Temporarily set to absolute to get correct offset 5825 if (originalPosition === 'fixed') { 5826 $megamenu.css('position', 'absolute'); 5827 } 5828 5829 var offset = $megamenu.offset(); 5830 5831 // Remove inline position style to let CSS take over 5832 if (originalPosition === 'fixed') { 5833 $megamenu.css('position', ''); 5834 } 5835 5836 if (offset && typeof offset.left === 'number') { 5837 var position = offset.left * -1; 5838 if (isRTL) { 5839 $megamenu.css('right', -position + 'px'); 5840 } else { 5841 $megamenu.css('left', position + 'px'); 5842 } 5843 } 5844 } 5845 // Case 2: Custom width 5846 else if ($customWidthContainer.length > 0) { 5847 $megamenu.css('width', customWidthPx + 'px'); 5848 if ($navItem.length > 0) { 5849 var menuItemOffset = $navItem.offset().left; 5850 var parentOffset = $megamenu.parent().offset(); 5851 parentOffset = parentOffset ? parentOffset.left : 0; 5852 var position = menuItemOffset - parentOffset; 5853 5854 // Adjust if the dropdown goes outside the window 5855 if (menuItemOffset + customWidthPx > windowWidth) { 5856 position -= customWidthPx - $navItem.width(); 5857 } 5858 5859 if (isRTL) { 5860 $megamenu.css('right', -position + 'px'); 5861 } else { 5862 $megamenu.css('left', position + 'px'); 5863 } 5864 } 5865 } 5866 // Case 3: Use the nearest container's width 5867 else if ($container.length > 0) { 5868 var containerWidth = $container.width(); 5869 $megamenu.css('width', containerWidth); 5870 5871 var containerOffsetObj = $container.offset(); 5872 var navItemOffsetObj = $navItem.offset(); 5873 5874 if (containerOffsetObj && navItemOffsetObj) { 5875 var containerOffset = containerOffsetObj.left; 5876 var megamenuOffset = navItemOffsetObj.left; 5877 var position = containerOffset - megamenuOffset; 5878 5879 if (isRTL) { 5880 $megamenu.css('right', -position + 'px'); 5881 } else { 5882 $megamenu.css('left', position + 'px'); 5883 } 5884 } 5885 } 5886 }); 5887 }; 5888 5889 /** 5890 * Set full width menu element 5891 */ 5892 const setMenuFullWidthEle = function () { 5893 if ($('#houzez_toggle').length > 0) { 5894 var leftDistance = $('#houzez_toggle').offset().left; 5895 $('.houzez-nav-mobile-menu-fullwidth .main-mobile-nav').css( 5896 'left', 5897 'calc(50vw - ' + leftDistance + 'px)' 5898 ); 5899 } 5900 }; 5901 5902 /** 5903 * Initialize all UI-related functionality 5904 */ 5905 const init = function () { 5906 // Initialize menu adjustments 5907 $win.on('load', function () { 5908 adjustMegaMenu( 5909 '.houzez-elementor-menu .dropdown-menu.megamenu', 5910 '.e-con-inner', 5911 '.menu-item-design-full-width', 5912 '.menu-item-design-custom-size', 5913 200 5914 ); 5915 setTimeout(setMenuFullWidthEle, 500); 5916 }); 5917
5918 // Window resize events 5919 $win.resize(function () { 5920 adjustMegaMenu( 5921 '.houzez-elementor-menu .dropdown-menu.megamenu', 5922 '.e-con-inner', 5923 '.menu-item-design-full-width', 5924 '.menu-item-design-custom-size', 5925 200 5926 ); 5927 setMenuFullWidthEle(); 5928 }); 5929 5930 mobileMenuTrigger(); 5931 }; 5932 5933 // Public API 5934 return { 5935 init: init, 5936 adjustMegaMenu: adjustMegaMenu, 5937 mobileMenuTrigger: mobileMenuTrigger, 5938 }; 5939 })(); 5940 5941 /** 5942 * Property Preview Module 5943 * Handles property preview functionality 5944 */ 5945 houzez.PropertyPreview = (function () { 5946 const init = function () { 5947 // Remove any existing handlers first to prevent duplicates 5948 $(document).off('click.houzezPreview', '.hz-show-lightbox-js'); 5949 5950 // Use event delegation with namespaced event for proper cleanup 5951 $(document).on( 5952 'click.houzezPreview', 5953 '.hz-show-lightbox-js', 5954 function (e) { 5955 e.preventDefault(); 5956 e.stopPropagation(); 5957 5958 // Hide all tooltips when preview is clicked 5959 houzez.Core.hideAllTooltips(); 5960 5961 var listing_id = $(this).data('listid'); 5962 var $parents = $(this).parents('.item-wrap'); 5963 var preview_loader = $parents.find('.preview_loader'); 5964 5965 $.ajax({ 5966 type: 'post', 5967 url: houzez.Core.config.ajaxurl, 5968 data: { 5969 action: 'load_lightbox_content', 5970 listing_id: listing_id, 5971 }, 5972 beforeSend: function () { 5973 preview_loader 5974 .empty() 5975 .append( 5976 '' + 5977 '<div class="houzez-overlay-loading">' + 5978 '<div class="overlay-placeholder">' + 5979 '<div class="loader-ripple spinner">' + 5980 '<div class="bounce1"></div>' + 5981 '<div class="bounce2"></div>' + 5982 '<div class="bounce3"></div>' + 5983 '</div>' + 5984 '</div>' + 5985 '</div>' 5986 ); 5987 }, 5988 complete: function () { 5989 preview_loader.empty(); 5990 }, 5991 success: function (response) { 5992 $('#hz-listing-model-content').html(response); 5993 5994 // Bootstrap 5 modal initialization 5995 var modalElement = document.getElementById( 5996 'houzez-listing-lightbox' 5997 ); 5998 if (modalElement) { 5999 // Check if modal instance already exists 6000 var existingModal = 6001 bootstrap.Modal.getInstance(modalElement); 6002 if (existingModal) { 6003 existingModal.show(); 6004 } else { 6005 // Create new modal instance with proper options 6006 var myModal = new bootstrap.Modal( 6007 modalElement, 6008 { 6009 backdrop: true, 6010 keyboard: true, 6011 focus: true, 6012 } 6013 ); 6014 myModal.show(); 6015 } 6016 } 6017 6018 //$('.lightbox-slider').slick('unslick'); 6019 6020 $('.lightbox-slider') 6021 .not('.slick-initialized') 6022 .slick({ 6023 rtl: houzez.Core.config.houzez_rtl, 6024 lazyLoad: 'ondemand', 6025 infinite: true, 6026 speed: 300, 6027 slidesToShow: 1, 6028 arrows: true, 6029 adaptiveHeight: true, 6030 }); 6031 6032 $('#houzez-listing-lightbox').on( 6033 'shown.bs.modal', 6034 function (e) { 6035 $('.lightbox-slider').slick('setPosition'); 6036 } 6037 ); 6038 6039 $('#houzez-listing-lightbox').on( 6040 'hide.bs.modal', 6041 function (e) { 6042 $('.lightbox-slider').slick('unslick'); 6043 } 6044 ); 6045 6046 $('.btn-expand').on('click', function () { 6047 $('.lightbox-gallery-wrap').toggleClass( 6048 'lightbox-gallery-full-wrap' 6049 ); 6050 $('.lightbox-slider').slick('setPosition'); 6051 }); 6052 6053 $('.btn-email').on('click', function () { 6054 $('.lightbox-form-wrap').toggleClass( 6055 'lightbox-form-wrap-show' 6056 ); 6057 }); 6058 6059 houzez.Core.reinitializeAjaxFunctions(); 6060 }, 6061 error: function (xhr, status, error) { 6062 console.log(error); 6063 }, 6064 }); 6065 } 6066 ); 6067 }; 6068 6069 // Public API 6070 return { 6071 init: init, 6072 }; 6073 })(); 6074 6075 /* ------------------------------------------------------------------------ */ 6076 /* Mobile Navigation 6077 /* ------------------------------------------------------------------------ */ 6078 houzez.MobileNav = (function () { 6079 const initMobileNav = function () {}; 6080 6081 const setupToggleButtons = function () {}; 6082 6083 const init = function () { 6084 initMobileNav(); 6085 setupToggleButtons(); 6086 }; 6087 6088 return { 6089 init: init, 6090 }; 6091 })(); 6092 6093 /* ------------------------------------------------------------------------ */ 6094 /* Top Banner Full Screen 6095 /* ------------------------------------------------------------------------ */ 6096 houzez.TopBannerFullScreen = (function () { 6097 const getWindowHeight = function () { 6098 return $(window).innerHeight(); 6099 }; 6100 6101 const setTopBannerFullScreen = function () { 6102 var totalTopBarsHeight = 0; 6103 var searchH = 0; 6104 var topBarH = 0; 6105 var totalBannerHeight = 0; 6106 var window_height = $(window).innerHeight(); 6107 var admin_bar_height = houzez.Core.config.wpadminbar_height; 6108 var topBarB = $('.top-bar-wrap'); 6109 var header_area = $('.header-main-wrap'); 6110 if (!header_area.length) { 6111 header_area = $('#header-hz-elementor'); 6112 } 6113 var header_area_height = header_area.outerHeight(); 6114 var advanced_search_nav = $('.advanced-search-nav'); 6115 var advanced_search_nav_height = advanced_search_nav.outerHeight(); 6116 searchH = window_height - header_area_height; 6117 6118 if (header_area.hasClass('header-transparent-wrap')) { 6119 if (topBarB.length) { 6120 topBarH = topBarB.outerHeight(); 6121 } 6122 totalBannerHeight = 6123 getWindowHeight() - (topBarH + admin_bar_height); 6124 } else { 6125 if ( 6126 header_area.length && 6127 advanced_search_nav.length && 6128 !advanced_search_nav.hasClass('search-hidden') 6129 ) { 6130 totalTopBarsHeight = 6131 parseInt(header_area_height) + 6132 parseInt(advanced_search_nav_height); 6133 } else if (header_area.length) { 6134 totalTopBarsHeight = parseInt(header_area_height); 6135 } 6136 totalBannerHeight = 6137 getWindowHeight() - (totalTopBarsHeight + admin_bar_height); 6138 } 6139 6140 $('.top-banner-wrap-fullscreen').css('height', totalBannerHeight); 6141 }; 6142 6143 const init = function () { 6144 if ( 6145 !houzez.Core.config.houzez_is_splash && 6146 $('.top-banner-wrap-fullscreen').length > 0 6147 ) { 6148 setTopBannerFullScreen(); 6149 6150 $(window).on('resize', function () { 6151 setTopBannerFullScreen(); 6152 }); 6153 } 6154 }; 6155 6156 return { 6157 init: init, 6158 }; 6159 })(); 6160 6161 /** 6162 * Reviews Module 6163 * Handles property reviews functionality 6164 */ 6165 houzez.Reviews = (function () { 6166 // Like/dislike handler for reviews 6167 const review_likes = function () { 6168 $(document).on('click', '.hz-like-dislike-js', function (e) { 6169 e.preventDefault(); 6170 var $this = $(this); 6171 var $parent = $this.parents('.likes-container-js'); 6172 6173 if ($this.hasClass('already-voted')) { 6174 $parent.find('.vote-msg').text($this.data('msg')).show(); 6175 setTimeout(function () { 6176 $parent.find('.vote-msg').hide(); 6177 }, 3000); 6178 } else { 6179 var review_id = $this.data('id'); 6180 var type = $this.data('type'); 6181 6182 $.ajax({ 6183 type: 'post', 6184 url: houzez.Core.config.ajaxurl, 6185 dataType: 'JSON', 6186 data: { 6187 action: 'reviews_likes_dislikes', 6188 type: type, 6189 review_id: review_id, 6190 }, 6191 beforeSend: function () { 6192 $parent.find('.vote-msg').empty(); 6193 $parent 6194 .find('.houzez-loader-js') 6195 .addClass('loader-show'); 6196 if (type == 'likes') { 6197 $('.review-dislike-button a').removeClass( 6198 'already-voted' 6199 ); 6200 } else if (type == 'dislikes') { 6201 $('.review-like-button a').removeClass( 6202 'already-voted' 6203 ); 6204 } 6205 }, 6206 success: function (response) { 6207 if (response.success) { 6208 $parent 6209 .find('.likes-count') 6210 .text(response.data.likes); 6211 $parent 6212 .find('.dislikes-count') 6213 .text(response.data.dislikes); 6214 $parent 6215 .find('.vote-msg') 6216 .text(response.data.message) 6217 .show(); 6218 } else { 6219 $parent 6220 .find('.vote-msg') 6221 .text(response.data.message) 6222 .show(); 6223 } 6224 setTimeout(function () { 6225 $parent.find('.vote-msg').hide(); 6226 }, 3000); 6227 $this.addClass('already-voted'); 6228 }, 6229 error: function (xhr, status, error) { 6230 var err = eval('(' + xhr.responseText + ')'); 6231 console.log(err.Message); 6232 }, 6233 complete: function () { 6234 $parent 6235 .find('.houzez-loader-js') 6236 .removeClass('loader-show'); 6237 }, 6238 }); 6239 } 6240 }); 6241 }; 6242 6243 // AJAX load reviews 6244 const listing_review_ajax = function (sortby, listing_id, paged) { 6245 var review_container = $('#houzez_reviews_container'); 6246 var review_post_type = $('input[name="review_post_type"]').val(); 6247 6248 $.ajax({ 6249 type: 'post', 6250 url: houzez.Core.config.ajaxurl, 6251 data: { 6252 action: 'houzez_ajax_review', 6253 sortby: sortby, 6254 listing_id: listing_id, 6255 review_post_type: review_post_type, 6256 paged: paged, 6257 }, 6258 beforeSend: function () { 6259 review_container 6260 .empty() 6261 .append( 6262 '<div id="houzez-map-loading">' + 6263 '<div class="mapPlaceholder">' + 6264 '<div class="loader-ripple spinner">' + 6265 '<div class="bounce1"></div>' + 6266 '<div class="bounce2"></div>' + 6267 '<div class="bounce3"></div>' + 6268 '</div>' + 6269 '</div>' + 6270 '</div>' 6271 ); 6272 $('html, body').animate( 6273 { 6274 scrollTop: 6275 $('#property-review-wrap').offset().top - 50, 6276 }, 6277 'slow' 6278 ); 6279 }, 6280 success: function (data) { 6281 review_container.empty(); 6282 review_container.html(data); 6283 review_likes(); 6284 }, 6285 error: function (xhr, status, error) { 6286 var err = eval('(' + xhr.responseText + ')'); 6287 console.log(err.Message); 6288 }, 6289 complete: function () {}, 6290 }); 6291 }; 6292 6293 // Submit review handler 6294 const submitReview = function () { 6295 $(document).on('click', '#submit-review', function (e) { 6296 e.preventDefault(); 6297 var $this = $(this); 6298 var $form = $this.parents('form'); 6299 var $messages = $form.find('.form_messages'); 6300 var $is_bottom = $('.is_bottom').val(); 6301 6302 if ($is_bottom == 'bottom') { 6303 $messages = $form.find('.form_messages'); 6304 } 6305 $messages.empty(); 6306 6307 $.ajax({ 6308 type: 'post', 6309 url: houzez.Core.config.ajaxurl, 6310 data: $form.serialize(), 6311 dataType: 'JSON', 6312 beforeSend: function () { 6313 $this.attr('disabled', true); 6314 $this.find('.houzez-loader-js').addClass('loader-show'); 6315 }, 6316 success: function (response) { 6317 if (response.success) { 6318
6318 // Clear form fields 6319 $form 6320 .find( 6321 'input[name="name"], input[name="mobile"], input[name="email"]' 6322 ) 6323 .val(''); 6324 $form.find('textarea').val(''); 6325 6326 // Show success message 6327 houzez.Core.util.showSuccess( 6328 $messages, 6329 response.data.message 6330 ); 6331 } else { 6332 // Show error message 6333 houzez.Core.util.showError( 6334 $messages, 6335 response.data.message 6336 ); 6337 } 6338 6339 // Reset captcha (supports both reCaptcha and Turnstile) 6340 houzez.Core.util.resetCaptcha($form); 6341 6342 // Handle redirection if needed 6343 if ( 6344 houzez_vars.agent_redirection != '' && 6345 response.success 6346 ) { 6347 setTimeout(function () { 6348 window.location.replace( 6349 houzez_vars.agent_redirection 6350 ); 6351 }, 500); 6352 } 6353 }, 6354 error: function (xhr, status, error) { 6355 var err = eval('(' + xhr.responseText + ')'); 6356 console.log(err.Message); 6357 }, 6358 complete: function () { 6359 $this.attr('disabled', false); 6360 $this 6361 .find('.houzez-loader-js') 6362 .removeClass('loader-show'); 6363 }, 6364 }); 6365 }); 6366 }; 6367 6368 // Sort and pagination handlers 6369 const sortAndPaginate = function () { 6370 $(document).on('change', '#sort_review', function () { 6371 var sortby = $(this).val(); 6372 var listing_id = $('#review_listing_id').val(); 6373 var paged = 1; 6374 $('#review_paged').val(paged); 6375 $('#review_prev').attr('disabled', true); 6376 $('#review_next').attr('disabled', false); 6377 listing_review_ajax(sortby, listing_id, paged); 6378 }); 6379 6380 $(document).on('click', '#review_next', function (e) { 6381 e.preventDefault(); 6382 $('#review_prev').removeAttr('disabled'); 6383 var sortby = $('#sort_review').val(); 6384 var total_pages = $('#total_pages').val(); 6385 var listing_id = $('#review_listing_id').val(); 6386 var paged = $('#review_paged').val(); 6387 paged = Number(paged) + 1; 6388 $('#review_paged').val(paged); 6389 if (paged == total_pages) { 6390 $(this).attr('disabled', true); 6391 } 6392 listing_review_ajax(sortby, listing_id, paged); 6393 }); 6394 6395 $(document).on('click', '#review_prev', function (e) { 6396 e.preventDefault(); 6397 $('#review_next').removeAttr('disabled'); 6398 var sortby = $('#sort_review').val(); 6399 var listing_id = $('#review_listing_id').val(); 6400 var paged = $('#review_paged').val(); 6401 paged = Number(paged) - 1; 6402 $('#review_paged').val(paged); 6403 if (paged <= 1) { 6404 $(this).attr('disabled', true); 6405 } 6406 listing_review_ajax(sortby, listing_id, paged); 6407 }); 6408 }; 6409 6410 // Initialize reviews module 6411 const init = function () { 6412 review_likes(); 6413 submitReview(); 6414 sortAndPaginate(); 6415 }; 6416 6417 // Public API 6418 return { 6419 init: init, 6420 review_likes: review_likes, 6421 listing_review_ajax: listing_review_ajax, 6422 }; 6423 })(); 6424 6425 houzez.Parallax = (function () { 6426 // closure vars 6427 let $els; 6428 const speedDefault = 0.5; 6429 let ticking = false;
6430 6431 const init = function () { 6432 $els = $('.houzez-parallax'); 6433 if ($els.length === 0) { 6434 return; 6435 } 6436 6437 _setBackgrounds(); 6438 _bindEvents(); 6439 _update(); // position backgrounds on load 6440 }; 6441 6442 const _setBackgrounds = function () { 6443 $els.each(function () { 6444 const $el = $(this); 6445 const src = $el.data('parallax-bg-image'); 6446 if (src) { 6447 $el.css({ 6448 'background-image': `url("${src}")`, 6449 'background-size': 'cover', 6450 'background-position': 'center center', 6451 'background-repeat': 'no-repeat', 6452 }); 6453 } 6454 }); 6455 }; 6456 6457 const _bindEvents = function () { 6458 $(window).on('scroll', _onScroll); 6459 }; 6460 6461 const _onScroll = function () { 6462 if (!ticking) { 6463 window.requestAnimationFrame(function () { 6464 _update(); 6465 ticking = false; 6466 }); 6467 ticking = true; 6468 } 6469 }; 6470 6471 const _update = function () { 6472 const scrollY = $(window).scrollTop(); 6473 6474 $els.each(function () { 6475 const $el = $(this); 6476 const speed = 6477 parseFloat($el.data('parallax-speed')) || speedDefault; 6478 const top = $el.offset().top; 6479 const offset = scrollY - top; 6480 6481 $el.css('background-position', `center ${offset * speed}px`); 6482 }); 6483 }; 6484 6485 // Public API 6486 return { 6487 init: init, 6488 }; 6489 })(); 6490 6491 // Social login module 6492 houzez.SocialLogin = (function () { 6493 const initFacebookLogin = function () { 6494 $('.hz-facebook-login').on('click', function () { 6495 var current = $(this); 6496 loginViaFacebook(current); 6497 }); 6498 }; 6499 6500 const loginViaFacebook = function (current) { 6501 var $messages = $('.hz-social-messages'); 6502 const config = houzez.Core.config; 6503 6504 $.ajax({ 6505 type: 'POST', 6506 url: config.ajaxurl, 6507 dataType: 'json', 6508 data: { 6509 action: 'houzez_facebook_login_oauth', 6510 }, 6511 beforeSend: function () { 6512 houzez.Core.util.showSuccess( 6513 $messages, 6514 config.login_loading 6515 ); 6516 current.find('.houzez-loader-js').addClass('loader-show'); 6517 }, 6518 complete: function () { 6519 current 6520 .find('.houzez-loader-js') 6521 .removeClass('loader-show'); 6522 }, 6523 success: function (response) { 6524 if (response.success) { 6525 window.location.replace(response.url); 6526 } else { 6527 houzez.Core.util.showError($messages, response.message); 6528 } 6529 }, 6530 error: function (xhr, status, error) { 6531 console.error('Facebook login error: ' + error); 6532 houzez.Core.util.showError( 6533 $messages, 6534 'Error processing login' 6535 ); 6536 }, 6537 }); 6538 }; 6539 6540 const initGoogleLogin = function () { 6541 $('.hz-google-login').on('click', function () { 6542 var current = $(this); 6543 loginViaGoogle(current); 6544 }); 6545 }; 6546 6547 const loginViaGoogle = function (current) { 6548 var $messages = $('#hz-login-messages'); 6549 const config = houzez.Core.config; 6550 6551 $.ajax({ 6552 type: 'POST', 6553 url: config.ajaxurl, 6554 data: { 6555 action: 'houzez_google_login_oauth', 6556 }, 6557 beforeSend: function () { 6558 houzez.Core.util.showSuccess( 6559 $messages, 6560 config.login_loading 6561 ); 6562 current.find('.houzez-loader-js').addClass('loader-show'); 6563 }, 6564 complete: function () { 6565 current 6566 .find('.houzez-loader-js') 6567 .removeClass('loader-show'); 6568 }, 6569 success: function (data) { 6570 window.location.replace(data); 6571 }, 6572 error: function (xhr, status, error) { 6573 console.error('Google login error: ' + error); 6574 houzez.Core.util.showError($messages, error); 6575 }, 6576 }); 6577 }; 6578 6579 const initLinkAccount = function () { 6580 $('#houzez-link-account').on('click', function (e) { 6581 e.preventDefault(); 6582 var current = $(this); 6583 linkAccount(current); 6584 }); 6585 }; 6586 6587 const linkAccount = function (current) { 6588 var $form = current.parents('form'); 6589 var $messages = $('#hz-link-messages'); 6590 const config = houzez.Core.config; 6591 6592 $.ajax({ 6593 type: 'post', 6594 url: config.ajaxurl, 6595 dataType: 'json', 6596 data: $form.serialize(), 6597 beforeSend: function () { 6598 current.find('.houzez-loader-js').addClass('loader-show'); 6599 }, 6600 complete: function () { 6601 current 6602 .find('.houzez-loader-js') 6603 .removeClass('loader-show'); 6604 }, 6605 success: function (response) { 6606 if (response.success) { 6607 houzez.Core.util.showSuccess($messages, response.msg); 6608 window.location.replace(response.redirect_to); 6609 } else { 6610 houzez.Core.util.showError($messages, response.msg); 6611 } 6612 6613 // Reset captcha (supports both reCaptcha and Turnstile) 6614 houzez.Core.util.resetCaptcha($form); 6615 }, 6616 error: function (xhr, status, error) { 6617 console.error('Link account error: ' + error); 6618 houzez.Core.util.showError( 6619 $messages, 6620 'Error processing account link' 6621 ); 6622 }, 6623 }); 6624 }; 6625
6626 const initCreateAccount = function () { 6627 $('#houzez-create-account-btn').on('click', function (e) { 6628 e.preventDefault(); 6629 var current = $(this); 6630 createAccount(current); 6631 }); 6632 }; 6633 6634 const createAccount = function (current) { 6635 var $form = current.parents('form'); 6636 var $messages = $('#hz-create-messages'); 6637 const config = houzez.Core.config; 6638 6639 $.ajax({ 6640 type: 'post', 6641 url: config.ajaxurl, 6642 dataType: 'json', 6643 data: $form.serialize(), 6644 beforeSend: function () { 6645 current.find('.houzez-loader-js').addClass('loader-show'); 6646 }, 6647 complete: function () { 6648 current 6649 .find('.houzez-loader-js') 6650 .removeClass('loader-show'); 6651 }, 6652 success: function (response) { 6653 if (response.success) { 6654 houzez.Core.util.showSuccess($messages, response.msg); 6655 window.location.replace(response.redirect_to); 6656 } else { 6657 houzez.Core.util.showError($messages, response.msg); 6658 } 6659 }, 6660 error: function (xhr, status, error) { 6661 console.error('Create account error: ' + error); 6662 houzez.Core.util.showError( 6663 $messages, 6664 'Error creating account' 6665 ); 6666 }, 6667 }); 6668 }; 6669 6670 const initSocialLoginPanel = function () { 6671 const $mainStepWrap = $('.main-step-wrap'); 6672 const $newAccountWrap = $('.new-account-wrap'); 6673 const $linkAccountWrap = $('.link-account-wrap'); 6674 6675 const toggleVisibility = function (hide1, hide2, show) { 6676 hide1.hide(); 6677 hide2.hide(); 6678 show.show(); 6679 }; 6680 6681 $(document).on('click', '.btn-link-account', function (event) { 6682 event.preventDefault(); 6683 toggleVisibility( 6684 $mainStepWrap, 6685 $newAccountWrap, 6686 $linkAccountWrap 6687 ); 6688 }); 6689 6690 $(document).on('click', '.btn-create-account', function (event) { 6691 event.preventDefault(); 6692 toggleVisibility( 6693 $mainStepWrap, 6694 $linkAccountWrap, 6695 $newAccountWrap 6696 ); 6697 }); 6698 6699 $(document).on('click', '.hz-fb-cancel', function (event) { 6700 event.preventDefault(); 6701 toggleVisibility( 6702 $linkAccountWrap, 6703 $newAccountWrap, 6704 $mainStepWrap 6705 ); 6706 }); 6707 }; 6708 6709 const init = function () { 6710 initFacebookLogin(); 6711 initGoogleLogin(); 6712 initLinkAccount(); 6713 initCreateAccount(); 6714 initSocialLoginPanel(); 6715 }; 6716 6717 return { 6718 init: init, 6719 loginViaFacebook: loginViaFacebook, 6720 loginViaGoogle: loginViaGoogle, 6721 linkAccount: linkAccount, 6722 createAccount: createAccount, 6723 }; 6724 })(); 6725 6726 // Payments module 6727 houzez.Payments = (function () { 6728 /** 6729 * Handle changing listing fee for featured properties 6730 */ 6731 const changeListingFeeForFeatured = function () { 6732 $('.prop_featured').on('change', function () { 6733 var currency_symbol = houzez_vars.currency_symbol; 6734 var currency_position = houzez_vars.currency_position; 6735 var total_price, 6736 total_price_with_currency, 6737 price_regular_with_currency, 6738 price_regular_with_tax_currency; 6739 6740 var price_regular = parseFloat($('#submission_price').text()); 6741 var price_featured = parseFloat( 6742 $('#submission_featured_price').text() 6743 ); 6744 6745 // Get tax information 6746 var submission_tax_percent = 6747 parseFloat($('#submission_tax_percent').text()) || 0; 6748 var featured_tax_percent = 6749 parseFloat($('#featured_tax_percent').text()) || 0; 6750 var submission_tax_amount = 6751 parseFloat($('#submission_tax_amount').text()) || 0; 6752 var featured_tax_amount = 6753 parseFloat($('#featured_tax_amount').text()) || 0; 6754 6755 // Calculate total with taxes 6756 var price_regular_with_tax = 6757 price_regular + submission_tax_amount; 6758 var price_featured_with_tax = 6759 price_featured + featured_tax_amount; 6760 6761 total_price = price_regular_with_tax + price_featured_with_tax; 6762 6763 if (currency_position === 'after') { 6764 price_regular_with_currency = 6765 price_regular + '' + currency_symbol; 6766 price_regular_with_tax_currency = 6767 price_regular_with_tax + '' + currency_symbol; 6768 total_price_with_currency = 6769 total_price + '' + currency_symbol; 6770 } else { 6771 price_regular_with_currency = 6772 currency_symbol + '' + price_regular; 6773 price_regular_with_tax_currency = 6774 currency_symbol + '' + price_regular_with_tax; 6775 total_price_with_currency = 6776 currency_symbol + '' + total_price; 6777 } 6778 6779 if ($(this).is(':checked')) { 6780 $('#submission_total_price').text( 6781 total_price_with_currency 6782 ); 6783 $('#featured_pay').val(1); 6784 $('#houzez_listing_price').val(total_price); 6785 $('input[name="pay_ammout"]').val(total_price * 100); 6786 } else { 6787 $('#submission_total_price').text( 6788 price_regular_with_tax_currency 6789 ); 6790 $('#featured_pay').val(0); 6791 $('input[name="pay_ammout"]').val( 6792 price_regular_with_tax * 100 6793 ); 6794 $('#houzez_listing_price').val(price_regular_with_tax); 6795 } 6796 return false;
6797 }); 6798 }; 6799 6800 /** 6801 * Handle Paypal payment for per listing 6802 * @param {number} property_id - Property ID 6803 * @param {number} is_prop_featured - Is property featured 6804 * @param {number} is_prop_upgrade - Is property upgrade 6805 * @param {string} relist_mode - Relist mode 6806 */ 6807 const paypalPerListingPayment = function ( 6808 property_id, 6809 is_prop_featured, 6810 is_prop_upgrade, 6811 relist_mode 6812 ) { 6813 $.ajax({ 6814 type: 'post', 6815 url: houzez.Core.config.ajaxurl, 6816 data: { 6817 action: 'houzez_property_paypal_payment', 6818 prop_id: property_id, 6819 is_prop_featured: is_prop_featured, 6820 is_prop_upgrade: is_prop_upgrade, 6821 relist_mode: relist_mode, 6822 }, 6823 success: function (response) { 6824 window.location.href = response; 6825 }, 6826 error: function (xhr, status, error) { 6827 var err = eval('(' + xhr.responseText + ')'); 6828 console.log(err.Message); 6829 }, 6830 }); 6831 }; 6832 6833 /** 6834 * Handle Stripe payment for per listing 6835 * @param {number} property_id - Property ID 6836 * @param {number} is_prop_featured - Is property featured 6837 * @param {number} is_prop_upgrade - Is property upgrade 6838 * @param {string} relist_mode - Relist mode 6839 */ 6840 const stripePerListingPayment = function ( 6841 property_id, 6842 is_prop_featured, 6843 is_prop_upgrade, 6844 relist_mode 6845 ) { 6846 $.ajax({ 6847 type: 'post', 6848 url: houzez.Core.config.ajaxurl, 6849 dataType: 'JSON', 6850 data: { 6851 action: 'houzez_property_stripe_payment', 6852 prop_id: property_id, 6853 is_prop_featured: is_prop_featured, 6854 is_prop_upgrade: is_prop_upgrade, 6855 relist_mode: relist_mode, 6856 }, 6857 success: function (response) { 6858 if (response.status) { 6859 window.location.href = response.paymeny_link; 6860 } else { 6861 alert(response.message); 6862 } 6863 }, 6864 error: function (xhr, status, error) { 6865 var err = eval('(' + xhr.responseText + ')'); 6866 console.log(err.Message); 6867 }, 6868 }); 6869 }; 6870 6871 /** 6872 * Handle bank transfer per listing payment 6873 * @param {number} prop_id - Property ID 6874 * @param {number} listing_price - Listing price 6875 */ 6876 const bankTransferPerListing = function (prop_id, listing_price) { 6877 var is_featured = $('input[name="featured_pay"]').val(); 6878 var is_upgrade = $('input[name="is_upgrade"]').val(); 6879 6880 jQuery.ajax({ 6881 type: 'POST', 6882 url: houzez.Core.config.ajaxurl, 6883 data: { 6884 action: 'houzez_direct_pay_per_listing', 6885 prop_id: prop_id, 6886 is_featured: is_featured, 6887 is_upgrade: is_upgrade, 6888 }, 6889 success: function (data) { 6890 window.location.href = data; 6891 }, 6892 error: function (errorThrown) {}, 6893 }); 6894 }; 6895 6896 /** 6897 * Handle WooCommerce payment for per listing 6898 * @param {number} listID - Listing ID 6899 * @param {number} is_featured - Is property featured 6900 */ 6901 const wooCommercePerListingPayment = function (listID, is_featured) { 6902 $.ajax({ 6903 type: 'POST', 6904 url: houzez.Core.config.ajaxurl, 6905 data: { 6906 action: 'houzez_perlist_woo_pay', 6907 listing_id: listID, 6908 is_featured: is_featured, 6909 }, 6910 success: function (data) { 6911 if (data.success != false) { 6912 window.location.href = houzez_vars.woo_checkout_url; 6913 } else { 6914 houzez.Core.util.processingModalClose(); 6915 } 6916 }, 6917 error: function (errorThrown) { 6918 console.log(errorThrown); 6919 houzez.Core.util.processingModalClose(); 6920 }, 6921 }); 6922 }; 6923 6924 /** 6925 * Handle WooCommerce payment for package 6926 * @param {number} packId - Package ID 6927 */ 6928 const wooCommercePackagePayment = function (packId) { 6929 $.ajax({ 6930 type: 'POST', 6931 url: houzez.Core.config.ajaxurl, 6932 data: { 6933 action: 'houzez_woo_pay_package', 6934 package_id: packId, 6935 }, 6936 success: function (data) { 6937 if (data.success != false) { 6938 window.location.href = houzez_vars.woo_checkout_url; 6939 } else { 6940 houzez.Core.util.processingModalClose(); 6941 } 6942 }, 6943 error: function (errorThrown) { 6944 console.log(errorThrown); 6945 houzez.Core.util.processingModalClose(); 6946 }, 6947 }); 6948 }; 6949 6950 /** 6951 * Initialize payment event listeners 6952 */ 6953 const initPaymentEvents = function () { 6954 $('#houzez_complete_order').on('click', function (e) { 6955 e.preventDefault(); 6956 var payment_gateway, 6957 is_prop_featured, 6958 is_prop_upgrade, 6959 relist_mode; 6960 var property_id, houzez_listing_price; 6961 6962 payment_gateway = $( 6963 "input[name='houzez_payment_type']:checked" 6964 ).val(); 6965 is_prop_featured = $("input[name='featured_pay']").val(); 6966 is_prop_upgrade = $("input[name='is_upgrade']").val(); 6967 relist_mode = $("input[name='relist_mode']").val(); 6968 6969 property_id = $('#houzez_property_id').val(); 6970 houzez_listing_price = $('#houzez_listing_price').val(); 6971 6972 if (payment_gateway == 'paypal') { 6973 houzez.Core.util.processingModal( 6974 houzez_vars.paypal_connecting 6975 ); 6976 paypalPerListingPayment( 6977 property_id, 6978 is_prop_featured, 6979 is_prop_upgrade, 6980 relist_mode 6981 ); 6982 } else if (payment_gateway == 'stripe') { 6983 houzez.Core.util.processingModal( 6984 houzez.Core.config.processing_text 6985 );
6986 stripePerListingPayment( 6987 property_id, 6988 is_prop_featured, 6989 is_prop_upgrade, 6990 relist_mode 6991 ); 6992 } else if (payment_gateway == 'direct_pay') { 6993 houzez.Core.util.processingModal( 6994 houzez.Core.config.processing_text 6995 ); 6996 bankTransferPerListing(property_id, houzez_listing_price); 6997 } 6998 return; 6999 }); 7000 7001 // WooCommerce Per Listing Payment 7002 $('.houzez-woocommerce-pay').on('click', function (e) { 7003 e.preventDefault(); 7004 7005 const listID = $(this).data('listid'); 7006 const is_featured = $(this).data('featured'); 7007 7008 houzez.Core.util.processingModal( 7009 houzez.Core.config.processing_text 7010 ); 7011 wooCommercePerListingPayment(listID, is_featured); 7012 }); 7013 7014 // WooCommerce Package Payment 7015 $('.houzez-woocommerce-package').on('click', function (e) { 7016 e.preventDefault(); 7017 7018 if ( 7019 parseInt(houzez.Core.config.userID, 10) === 0 || 7020 houzez.Core.config.userID === undefined 7021 ) { 7022 $('#login-register-form').modal('show'); 7023 $('.login-form-tab').addClass('active show'); 7024 $('.modal-toggle-1.nav-link').addClass('active'); 7025 } else { 7026 const packId = $(this).data('packid'); 7027 7028 houzez.Core.util.processingModal( 7029 houzez.Core.config.processing_text 7030 ); 7031 wooCommercePackagePayment(packId); 7032 } 7033 }); 7034 }; 7035 7036 /** 7037 * Initialize payment method options and UI 7038 */ 7039 const initPaymentOptions = function () { 7040 // Get payment method selector 7041 const $paymentMethodSelect = $('.payment-method-select'); 7042 7043 if ($paymentMethodSelect.length === 0) { 7044 return; // Exit if element doesn't exist on the page 7045 } 7046 7047 // Initialize UI based on current payment type 7048 const initPaymentUI = function (paymentType) { 7049 // Hide all payment sections first 7050 $( 7051 '.recurring-payment-paypal, .recurring-payment-stripe' 7052 ).hide(); 7053 7054 // Show appropriate section based on payment type 7055 if (paymentType === 'paypal') { 7056 $('.recurring-payment-stripe').hide(); 7057 $('.recurring-payment-paypal').fadeIn(300); 7058 } else if (paymentType === 'stripe') { 7059 $('.recurring-payment-paypal').hide(); 7060 $('.recurring-payment-stripe').fadeIn(300); 7061 } 7062 }; 7063 7064 // Set initial state based on default selected payment method 7065 let initialPaymentType = $paymentMethodSelect.val(); 7066 initPaymentUI(initialPaymentType); 7067 7068 // Handle payment method change event 7069 $paymentMethodSelect.on('change', function () { 7070 const paymentType = $(this).val(); 7071 initPaymentUI(paymentType); 7072 }); 7073 7074 // Handle recurring payment checkbox for Stripe 7075 $('#stripe_package_recurring').on('change', function () { 7076 if ($(this).is(':checked')) { 7077 if ($('#houzez_stripe_recurring').length === 0) { 7078 $('.houzez_payment_form').append( 7079 '<input type="hidden" name="houzez_stripe_recurring" id="houzez_stripe_recurring" value="1">' 7080 ); 7081 } 7082 } else { 7083 $('#houzez_stripe_recurring').remove(); 7084 } 7085 }); 7086 7087 // Initialize recurring checkbox to match its current state 7088 if ( 7089 $('#stripe_package_recurring').is(':checked') && 7090 $('#houzez_stripe_recurring').length === 0 7091 ) { 7092 $('.houzez_payment_form').append( 7093 '<input type="hidden" name="houzez_stripe_recurring" id="houzez_stripe_recurring" value="1">' 7094 ); 7095 } 7096 }; 7097 7098 /** 7099 * Initialize membership payment functionality 7100 */ 7101 const initMembershipPayment = function () { 7102 /** 7103 * Handle Stripe package payment 7104 * @param {number} houzez_package_id - Package ID 7105 * @param {boolean} is_stripe_recurring - Whether the payment is recurring 7106 */ 7107 const stripePackagePayment = function ( 7108 houzez_package_id, 7109 is_stripe_recurring 7110 ) { 7111 $.ajax({ 7112 type: 'POST', 7113 url: houzez.Core.config.ajaxurl, 7114 dataType: 'JSON', 7115 data: { 7116 action: 'houzez_stripe_package_payment', 7117 package_id: houzez_package_id, 7118 is_stripe_recurring: is_stripe_recurring, 7119 }, 7120 success: function (response) { 7121 if (response.status) { 7122 window.location.href = response.paymeny_link; 7123 } else { 7124 alert(response.message); 7125 } 7126 }, 7127 error: function (xhr, status, error) { 7128 var err = eval('(' + xhr.responseText + ')'); 7129 console.log(err.Message); 7130 }, 7131 }); 7132 }; 7133 7134 /** 7135 * Handle PayPal package payment 7136 * @param {number} houzez_package_price - Package price 7137 * @param {string} houzez_package_name - Package name 7138 * @param {number} houzez_package_id - Package ID 7139 */ 7140 const paypalPackagePayment = function ( 7141 houzez_package_price, 7142 houzez_package_name, 7143 houzez_package_id 7144 ) { 7145 $.ajax({ 7146 type: 'POST', 7147 url: houzez.Core.config.ajaxurl, 7148 data: { 7149 action: 'houzez_paypal_package_payment', 7150 houzez_package_price: houzez_package_price, 7151 houzez_package_name: houzez_package_name, 7152 houzez_package_id: houzez_package_id, 7153 }, 7154 success: function (data) { 7155 window.location.href = data; 7156 }, 7157 error: function (xhr, status, error) { 7158 var err = eval('(' + xhr.responseText + ')'); 7159 console.log(err.Message); 7160 }, 7161 }); 7162 }; 7163 7164 /** 7165 * Handle recurring PayPal package payment 7166 * @param {number} houzez_package_price - Package price 7167 * @param {string} houzez_package_name - Package name 7168 * @param {number} houzez_package_id - Package ID 7169 */ 7170 const recuringPaypalPackagePayment = function ( 7171 houzez_package_price, 7172 houzez_package_name, 7173 houzez_package_id 7174 ) { 7175 jQuery.ajax({ 7176 type: 'POST', 7177 url: houzez.Core.config.ajaxurl, 7178 data: { 7179 action: 'houzez_recuring_paypal_package_payment', 7180 houzez_package_name: houzez_package_name, 7181 houzez_package_id: houzez_package_id, 7182 houzez_package_price: houzez_package_price, 7183 }, 7184 success: function (data) { 7185 window.location.href = data; 7186 }, 7187 error: function (xhr, status, error) { 7188 var err = eval('(' + xhr.responseText + ')'); 7189 console.log(err.Message); 7190 }, 7191 }); 7192 }; 7193 7194 /** 7195 * Handle direct bank transfer package payment 7196 * @param {number} houzez_package_id - Package ID 7197 * @param {number} houzez_package_price - Package price 7198 * @param {string} houzez_package_name - Package name 7199 */ 7200 const directBankTransferPackage = function ( 7201 houzez_package_id, 7202 houzez_package_price, 7203 houzez_package_name 7204 ) { 7205 jQuery.ajax({ 7206 type: 'POST', 7207 url: houzez.Core.config.ajaxurl, 7208 data: { 7209 action: 'houzez_direct_pay_package', 7210 selected_package: houzez_package_id, 7211 }, 7212 success: function (data) { 7213 window.location.href = data; 7214 }, 7215 error: function (errorThrown) {}, 7216 }); 7217 }; 7218 7219 /** 7220 * Handle free membership package 7221 * @param {number} houzez_package_id - Package ID 7222 */ 7223 const freeMembershipPackage = function (houzez_package_id) { 7224 jQuery.ajax({ 7225 type: 'POST', 7226 url: houzez.Core.config.ajaxurl, 7227 data: { 7228 action: 'houzez_free_membership_package', 7229 selected_package: houzez_package_id, 7230 }, 7231 success: function (data) { 7232 window.location.href = data; 7233 }, 7234 error: function (errorThrown) {}, 7235 }); 7236 }; 7237 7238 /** 7239 * Process membership data based on payment method 7240 * @param {jQuery} currnt - Current jQuery element 7241 * @returns {boolean} - Always returns false 7242 */ 7243 const membershipData = function (currnt) { 7244 var payment_gateway = $( 7245 "input[name='houzez_payment_type']:checked" 7246 ).val(); 7247 var houzez_package_price = $( 7248 "input[name='houzez_package_price']" 7249 ).val(); 7250 var houzez_package_id = $( 7251 "input[name='houzez_package_id']" 7252 ).val(); 7253 var houzez_package_name = $('#houzez_package_name').text(); 7254 7255 if (payment_gateway == 'paypal') { 7256 houzez.Core.util.processingModal( 7257 houzez_vars.paypal_connecting 7258 ); 7259 if ($('#paypal_package_recurring').is(':checked')) { 7260 recuringPaypalPackagePayment( 7261 houzez_package_price, 7262 houzez_package_name, 7263 houzez_package_id 7264 ); 7265 } else { 7266 paypalPackagePayment( 7267 houzez_package_price, 7268 houzez_package_name, 7269 houzez_package_id 7270 ); 7271 } 7272 } else if (payment_gateway == 'stripe') { 7273 houzez.Core.util.processingModal( 7274 houzez.Core.config.processing_text 7275 ); 7276 var is_stripe_recurring = $('#houzez_stripe_recurring').is( 7277 ':checked' 7278 );
7279 stripePackagePayment( 7280 houzez_package_id, 7281 is_stripe_recurring 7282 ); 7283 } else if (payment_gateway == 'direct_pay') { 7284 houzez.Core.util.processingModal( 7285 houzez.Core.config.processing_text 7286 ); 7287 directBankTransferPackage( 7288 houzez_package_id, 7289 houzez_package_price, 7290 houzez_package_name 7291 ); 7292 } else { 7293 houzez.Core.util.processingModal( 7294 houzez.Core.config.processing_text 7295 ); 7296 freeMembershipPackage(houzez_package_id); 7297 } 7298 7299 return false; 7300 }; 7301 7302 /** 7303 * Register user with membership 7304 * @param {jQuery} currnt - Current jQuery element 7305 */ 7306 const registerUserWithMembership = function (currnt) { 7307 var $form = currnt.parents('form'); 7308 var $messages = $('#packmem-msgs'); 7309 7310 $.ajax({ 7311 type: 'post', 7312 url: houzez.Core.config.ajaxurl, 7313 dataType: 'json', 7314 data: $form.serialize(), 7315 beforeSend: function () { 7316 currnt 7317 .find('.houzez-loader-js') 7318 .addClass('loader-show'); 7319 }, 7320 complete: function () { 7321 currnt 7322 .find('.houzez-loader-js') 7323 .removeClass('loader-show'); 7324 }, 7325 success: function (response) { 7326 if (response.success) { 7327 membershipData(currnt); 7328 } else { 7329 $('html, body').animate( 7330 { 7331 scrollTop: $( 7332 '.frontend-submission-page' 7333 ).offset().top, 7334 }, 7335 'slow' 7336 ); 7337 houzez.Core.util.showError($messages, response.msg); 7338 } 7339 }, 7340 error: function (xhr, status, error) { 7341 var err = eval('(' + xhr.responseText + ')'); 7342 console.log(err.Message); 7343 houzez.Core.util.showError( 7344 $messages, 7345 err.Message || 'An error occurred' 7346 ); 7347 }, 7348 }); 7349 }; 7350 7351 // Handle membership payment submission 7352 $('#houzez_complete_membership').on('click', function (e) { 7353 e.preventDefault(); 7354 var currnt = $(this); 7355 if ( 7356 parseInt(houzez.Core.config.userID, 10) === 0 || 7357 houzez.Core.config.userID === undefined 7358 ) { 7359 registerUserWithMembership(currnt); 7360 return; 7361 } 7362 membershipData(currnt); 7363 }); 7364 7365 // Expose functions for external use 7366 return { 7367 stripePackagePayment: stripePackagePayment, 7368 paypalPackagePayment: paypalPackagePayment, 7369 recuringPaypalPackagePayment: recuringPaypalPackagePayment, 7370 directBankTransferPackage: directBankTransferPackage, 7371 freeMembershipPackage: freeMembershipPackage, 7372 membershipData: membershipData, 7373 registerUserWithMembership: registerUserWithMembership, 7374 }; 7375 }; 7376 7377 const init = function () { 7378 changeListingFeeForFeatured(); 7379 initPaymentEvents(); 7380 initPaymentOptions(); 7381 7382 // Initialize membership payment 7383 const membershipFunctions = initMembershipPayment(); 7384 7385 // Expose membership functions to the module 7386 Object.assign(this, membershipFunctions); 7387 }; 7388 7389 return { 7390 init: init, 7391 changeListingFeeForFeatured: changeListingFeeForFeatured, 7392 paypalPerListingPayment: paypalPerListingPayment,
7393 stripePerListingPayment: stripePerListingPayment, 7394 bankTransferPerListing: bankTransferPerListing, 7395 wooCommercePerListingPayment: wooCommercePerListingPayment, 7396 wooCommercePackagePayment: wooCommercePackagePayment, 7397 initPaymentOptions: initPaymentOptions, 7398 initMembershipPayment: initMembershipPayment, 7399 }; 7400 })(); 7401 7402 houzez.DirectMessage = (function () { 7403 /** 7404 * Initialize direct message functionality 7405 */ 7406 const initDirectMessage = function () { 7407 $('.msg-login-required').on('click', function () { 7408 $('.modal-toggle-1').addClass('active'); 7409 jQuery('.login-form-tab').addClass('active show'); 7410 }); 7411 7412 $('.houzez-send-message').on('click', function (e) { 7413 e.preventDefault(); 7414 7415 var $result; 7416 var $this = $(this); 7417 var $form = $this.parents('form'); 7418 var $form_wrap = $this.parents('.property-form-wrap'); 7419 $result = $form_wrap.find('.form_messages'); 7420 var $is_bottom = $('.is_bottom').val(); 7421 if ($is_bottom == 'bottom') { 7422 $result = $form.find('.form_messages'); 7423 } 7424 $result.empty(); 7425 7426 var property_id = $('input[name="listing_id"]').val(); 7427 var message = $form.find('.hz-form-message').val(); 7428 var security = $( 7429 'input[name="property_agent_contact_security"]' 7430 ).val(); 7431 7432 $.ajax({ 7433 url: houzez.Core.config.ajaxurl, 7434 data: { 7435 action: 'houzez_start_thread', 7436 property_id: property_id, 7437 message: message, 7438 start_thread_form_ajax: security, 7439 }, 7440 method: $form.attr('method'), 7441 dataType: 'JSON', 7442 7443 beforeSend: function () { 7444 $this.find('.houzez-loader-js').addClass('loader-show'); 7445 }, 7446 success: function (response) { 7447 if (response.success) { 7448 $form 7449 .find( 7450 'input[name="name"], input[name="mobile"], input[name="email"]' 7451 ) 7452 .val(''); 7453 $form.find('textarea').val(''); 7454 if ($is_bottom == 'bottom') { 7455 houzez.Core.util.showSuccess( 7456 $result, 7457 response.msg 7458 ); 7459 } else { 7460 $result 7461 .empty() 7462 .append( 7463 '<p class="success text-success"><i class="fa fa-check"></i> ' + 7464 response.msg + 7465 '</p>' 7466 ); 7467 } 7468 } else { 7469 if ($is_bottom == 'bottom') { 7470 houzez.Core.util.showError( 7471 $result, 7472 response.msg 7473 ); 7474 } else { 7475 $result 7476 .empty() 7477 .append( 7478 '<p class="error text-danger"><i class="fas fa-times"></i> ' + 7479 response.msg + 7480 '</p>' 7481 ); 7482 } 7483 } 7484 7485 $this 7486 .find('.houzez-loader-js') 7487 .removeClass('loader-show'); 7488 7489 // Reset captcha (supports both reCaptcha and Turnstile) 7490 houzez.Core.util.resetCaptcha($form); 7491 7492 if (houzez_vars.agent_redirection != '') { 7493 setTimeout(function () { 7494 window.location.replace( 7495 houzez_vars.agent_redirection 7496 ); 7497 }, 500); 7498 } 7499 }, 7500 error: function (xhr, status, error) { 7501 var err = eval('(' + xhr.responseText + ')'); 7502 console.log(err.Message); 7503 }, 7504 complete: function () { 7505 $this 7506 .find('.houzez-loader-js') 7507 .removeClass('loader-show'); 7508 }, 7509 }); 7510 }); 7511 }; 7512 7513 const init = function () { 7514 // Only initialize if we're on a property page and the message form exists 7515 if ( 7516 houzez.Core.config.is_singular_property && 7517 $('.houzez-send-message').length > 0 7518 ) { 7519 initDirectMessage(); 7520 } 7521 }; 7522 7523 return { 7524 init: init, 7525 initDirectMessage: initDirectMessage, 7526 }; 7527 })(); 7528 // For backward compatibility 7529 window.houzezGetCookie = houzez.Core.util.getCookie; 7530 window.houzezSetCookie = houzez.Core.util.setCookie; 7531 // window.houzezAddCommas = houzez.Core.util.addCommas; 7532 // window.houzezFormatNumber = houzez.Core.util.formatNumber; 7533 // window.houzezGetLoaderHtml = houzez.Core.util.getLoaderHtml; 7534 // window.houzezProcessingModal = houzez.Core.util.processingModal; 7535 // window.houzezProcessingModalClose = houzez.Core.util.processingModalClose; 7536 // window.houzezGetSuccessHtml = houzez.Core.util.getSuccessHtml; 7537 // window.houzezGetErrorHtml = houzez.Core.util.getErrorHtml; 7538 // window.houzezShowSuccess = houzez.Core.util.showSuccess; 7539 // window.houzezShowError = houzez.Core.util.showError; 7540 window.HouzezDebounce = houzez.Core.util.debounce; 7541 // window.parseBool = houzez.Core.util.parseBool; 7542 // window.number_format = houzez.Core.util.numberFormat; 7543 // window.thousandSeparator = houzez.Core.util.thousandSeparator; 7544 // window.currencyFormate = houzez.Core.util.currencyFormat; 7545 7546 // Login module backward compatibility 7547 window.houzez_process_login = houzez.Login.processLogin; 7548 window.houzez_process_register = houzez.Login.processRegister; 7549 window.houzez_process_reset = houzez.Login.processForgotPassword; 7550 window.houzez_social_login_panel = houzez.Login.socialLoginPanel; 7551 7552 // Favorites module backward compatibility 7553 window.houzez_init_add_favorite = houzez.Favorites.initAddFavorite; 7554 window.houzez_init_remove_favorite = houzez.Favorites.initRemoveFavorite; 7555 window.add_to_favorite = houzez.Favorites.add_to_favorite; 7556 window.remove_from_favorite = houzez.Favorites.remove_from_favorite; 7557 window.houzez_check_favourites = houzez.Favorites.check_favorites; 7558 7559 // Compare module backward compatibility 7560 window.add_to_compare = houzez.Compare.add_to_compare; 7561 window.remove_from_compare = houzez.Compare.remove_from_compare; 7562 window.compare_for_ajax = houzez.Compare.compare_for_ajax; 7563 window.compare_for_ajax_map = houzez.Compare.compare_for_ajax; 7564 7565 // Sliders module backward compatibility 7566 // window.propertyDetailGallery = houzez.Sliders.propertyDetailGallery; 7567 // window.propertyBannerSlider = houzez.Sliders.propertyBannerSlider; 7568 // window.lightboxSlider = houzez.Sliders.lightboxSlider; 7569 // window.variableWidthSlider = houzez.Sliders.variableWidthSlider; 7570 // window.testimonialsSliderV1 = houzez.Sliders.testimonialsSliderV1; 7571 // window.agentsCarousel = houzez.Sliders.agentsCarousel;
7572 // window.partnersCarousel = houzez.Sliders.partnersCarousel; 7573 7574 // Mortgage module backward compatibility 7575 window.calculateMonthlyPayment = houzez.Core.util.calculateMonthlyPayment; 7576 window.mortgage_calucaltion_section = houzez.Mortgage.mortgageCalculation; 7577 window.updateChart = houzez.Mortgage.updateChart; 7578 7579 // Property preview backward compatibility 7580 window.houzez_listing_lightbox = function () { 7581 houzez.PropertyPreview.init(); 7582 }; 7583 7584 // Add backward compatibility for grid gallery 7585 window.houzez_grid_image_gallery = function () { 7586 houzez.Sliders.gridImageGallery(); 7587 }; 7588 7589 window.houzez_grid_call_to_action = function () { 7590 houzez.Core.gridCallToAction(); 7591 }; 7592 7593 /** 7594 * Initialize all modules when DOM is ready 7595 */ 7596 $(document).ready(function () { 7597 // Initialize core first 7598 houzez.Core.init(); 7599 houzez.Logo.init(); 7600 houzez.ElementorMobileMenu.init(); 7601 houzez.MobileNav.init(); 7602 houzez.TopBannerFullScreen.init(); 7603 houzez.Sticky.init(); 7604 houzez.Search.init(); 7605 houzez.PropertyPreview.init(); 7606 houzez.LocationFilter.init(); 7607 7608 // Then initialize other modules 7609 houzez.Blog.init(); 7610 houzez.Login.init(); 7611 houzez.Favorites.init(); 7612 houzez.Compare.init(); 7613 //houzez.Properties.init(); 7614 houzez.Sliders.init(); 7615 houzez.Pagination.init(); 7616 houzez.Mortgage.init(); 7617 houzez.ContactForms.init(); 7618 houzez.PropertiesTabs.init(); 7619 houzez.SingleProperty.init(); 7620 houzez.RealtorStats.init(); 7621 houzez.CurrencySwitcher.init(); 7622 houzez.AreaSwitcher.init(); 7623 houzez.Payments.init(); 7624 houzez.DirectMessage.init(); 7625 7626 if ( 7627 $('.hz-facebook-login').length > 0 || 7628 $('.hz-google-login').length > 0 || 7629 $('.hz-fb-main-wrap').length > 0 7630 ) { 7631 houzez.SocialLogin.init(); 7632 } 7633 7634 // Initialize Reviews module only if the submit button exists 7635 if ($('#submit-review').length > 0) { 7636 houzez.Reviews.init(); 7637 } 7638 if ($('.houzez-parallax').length > 0) { 7639 houzez.Parallax.init(); 7640 } 7641 }); 7642 7643 // Make houzez object available globally 7644 window.houzez = houzez; 7645})(jQuery);
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.