1// events.js - Site event handlers 2(function() { 3 window.dataLayer = window.dataLayer || []; 4 5 // SHA256 hash for privacy-safe email tracking (META, GA4, Google Ads) 6 function hashEmail(email) { 7 if (!email) return Promise.resolve(''); 8 var normalized = email.trim().toLowerCase(); 9 if (!normalized) return Promise.resolve(''); 10 if (window.crypto && window.crypto.subtle) { 11 var encoder = new TextEncoder(); 12 var data = encoder.encode(normalized); 13 return crypto.subtle.digest('SHA-256', data).then(function(buffer) { 14 return Array.from(new Uint8Array(buffer)).map(function(b) { 15 return ('0' + b.toString(16)).slice(-2); 16 }).join(''); 17 }); 18 } 19 return Promise.resolve(''); 20 } 21 22 // Push to dataLayer with SHA256 hashed email alongside plaintext 23 function pushWithHashedEmail(eventData) { 24 var email = eventData.email || ''; 25 delete eventData.email; 26 hashEmail(email).then(function(hashed) { 27 eventData.email_sha256 = hashed; 28 dataLayer.push(eventData); 29 }); 30 } 31 32 var searchStarted = false; 33 var formSubmitted = false; 34 var enquiryStarted = false; 35 36 // Chalet Search - capture Enter key before form submits 37 document.addEventListener('keydown', function(e) { 38 if (e.key === 'Enter' && e.target && e.target.id === 'search-input') { 39 e.preventDefault(); 40 e.stopPropagation(); 41 42 if (formSubmitted) return; 43 formSubmitted = true; 44 45 var searchQuery = e.target.value || ''; 46 var form = document.getElementById('header_search'); 47 48 dataLayer.push({ 49 'event': 'chalet_search', 50 'search_query': searchQuery, 51 'eventCallback': function() { 52 if (form) form.submit(); 53 }, 54 'eventTimeout': 500 55 }); 56 57 // Fallback if eventCallback doesn't fire 58 setTimeout(function() { 59 if (form) form.submit(); 60 }, 500); 61 } 62 }, true); 63 64 // Chalet Search Started - fires once when typing begins 65 document.addEventListener('DOMContentLoaded', function() { 66 var searchInput = document.getElementById('search-input'); 67 68 if (searchInput) { 69 searchInput.addEventListener('input', function() { 70 if (!searchStarted && this.value.length >= 2) { 71 searchStarted = true; 72 dataLayer.push({ 73 'event': 'chalet_search_started', 74 'search_query': this.value 75 }); 76 } 77 }); 78 } 79 }); 80 81 // Chalet Enquiry Tracking (chalet detail page) 82 document.addEventListener('DOMContentLoaded', function() { 83 var chaletKeyInfo = document.querySelector('.chalet_key_info'); 84 if (!chaletKeyInfo) return; 85 86 // Detect currency from price display on page 87 function detectCurrency() { 88 var priceRow = document.querySelector('.priceRange_row'); 89 if (priceRow) { 90 var text = priceRow.textContent || ''; 91 if (text.indexOf('EUR') !== -1 || text.indexOf('â¬') !== -1) return 'EUR'; 92 if (text.indexOf('GBP') !== -1 || text.indexOf('£') !== -1) return 'GBP'; 93 } 94 return 'CHF'; // Default 95 } 96 97 // Store values before form submission (form clears fields on submit) 98 var storedRentalValue = 0; 99 var storedChaletEmail = ''; 100 101 // Helper to get enquiry data 102 function getEnquiryData() { 103 var chaletId = document.getElementById('chaletID'); 104 var chaletName = document.getElementById('chaletName'); 105 var chaletDestination = document.getElementById('chaletDestination'); 106 var chaletCountry = document.getElementById('chaletCountry'); 107 var dateRange = document.getElementById('searchDateRange'); 108 var serviceLevel = document.getElementById('searchServiceLevel'); 109 var emailField = document.getElementById('field_tjr8l'); 110 var priceField = document.getElementById('field_ic0cf'); 111 var priceDisplay = document.getElementById('chaletPrice'); 112 113 // Get price value - try form field first, then parse from display 114 var rentalValue = 0; 115 116 // Try form field 117 if (priceField && priceField.value) { 118 var parsed = parseFloat(priceField.value.replace(/[^0-9]/g, '')); 119 if (!isNaN(parsed) && parsed > 0) { 120 rentalValue = parsed; 121 } 122 } 123 124 // Fallback: parse from price display element 125 if (rentalValue === 0 && priceDisplay) { 126 var priceText = priceDisplay.textContent || ''; 127 var match = priceText.match(/[\d.,]+/); 128 if (match) { 129 var parsed = parseFloat(match[0].replace(/[^0-9]/g, '')); 130 if (!isNaN(parsed) && parsed > 0) { 131 rentalValue = parsed; 132 } 133 } 134 } 135 136 // Use stored value if current is 0 137 if (rentalValue === 0 && storedRentalValue > 0) { 138 rentalValue = storedRentalValue; 139 } 140 141 // Store for later (before form clears) 142 if (rentalValue > 0) { 143 storedRentalValue = rentalValue; 144 } 145 146 // Store email before form clears 147 var emailVal = emailField ? emailField.value : ''; 148 if (emailVal) { 149 storedChaletEmail = emailVal; 150 } 151 152 var serviceLevelText = ''; 153 if (serviceLevel && serviceLevel.selectedIndex > 0) { 154 serviceLevelText = serviceLevel.options[serviceLevel.selectedIndex].text; 155 } 156 157 return { 158 'chalet_id': chaletId ? chaletId.value : '', 159 'chalet_name': chaletName ? chaletName.value : '', 160 'search_destination': chaletDestination ? chaletDestination.value : '', 161 'chalet_country': chaletCountry ? chaletCountry.value : '', 162 'search_daterange': dateRange ? dateRange.value : '', 163 'service_level': serviceLevelText, 164 'email': emailVal || storedChaletEmail || '', 165 'rental_value': rentalValue, 166 'currency': detectCurrency() 167 }; 168 } 169 170 // Chalet Inquiry Started - fires once when user interacts with form 171 function fireInquiryStarted() { 172 if (enquiryStarted) return; 173 enquiryStarted = true; 174 175 var data = getEnquiryData(); 176 dataLayer.push({ 177 'event': 'chalet_inquiry_started', 178 'chalet_id': data.chalet_id, 179 'chalet_name': data.chalet_name, 180 'search_destination': data.search_destination, 181 'chalet_country': data.chalet_country 182 }); 183 } 184 185 // Track interactions with datepicker and service level 186 var dateRangeInput = document.getElementById('searchDateRange'); 187 var serviceLevelSelect = document.getElementById('searchServiceLevel'); 188 var enquireBtn = document.getElementById('enqFormBtn'); 189 190 if (dateRangeInput) { 191 dateRangeInput.addEventListener('click', fireInquiryStarted); 192 dateRangeInput.addEventListener('focus', fireInquiryStarted); 193 } 194 if (serviceLevelSelect) { 195 serviceLevelSelect.addEventListener('click', fireInquiryStarted); 196 serviceLevelSelect.addEventListener('focus', fireInquiryStarted); 197 } 198 if (enquireBtn) { 199 enquireBtn.addEventListener('click', function() { 200 fireInquiryStarted(); 201 // Capture price when clicking Enquire (price is loaded at this point) 202 getEnquiryData(); 203 }); 204 } 205 206 // Capture email as user types (2-step form - field only visible in step 2, cleared on submit) 207 jQuery(document).on('input change', '#field_tjr8l', function() { 208 if (this.value) { 209 storedChaletEmail = this.value; 210 } 211 }); 212 213 // Chalet Inquiry Sent - fires on Formidable form submission 214 if (typeof jQuery !== 'undefined') { 215 jQuery(document).on('frmFormComplete', function(event, form, response) { 216 // Check if this is the chalet inquiry form (form ID 14) 217 var formId = jQuery(form).find('input[name="form_id"]').val(); 218 if (formId == '14') { 219 var data = getEnquiryData(); 220 var eventData = { 221 'event': 'chalet_inquiry_sent', 222 'chalet_id': data.chalet_id, 223 'chalet_name': data.chalet_name, 224 'search_destination': data.search_destination, 225 'chalet_country': data.chalet_country, 226 'search_daterange': data.search_daterange, 227 'service_level': data.service_level, 228 'email': data.email, 229 'rental_value': data.rental_value, 230 'currency': data.currency 231 }; 232 233 // Only add value if it's a valid number (for GA4 conversion tracking) 234 if (data.rental_value > 0) { 235 eventData.value = data.rental_value; 236 } 237 238 pushWithHashedEmail(eventData); 239 } 240 }); 241 } 242 });
243 244 // Wishlist Tracking (favorites page) 245 document.addEventListener('DOMContentLoaded', function() { 246 var favshareModal = document.getElementById('favshareModal'); 247 var favenquireModal = document.getElementById('favenquireModal'); 248 249 // Only run on favorites page 250 if (!favshareModal && !favenquireModal) return; 251 252 var wishlistInquiryStarted = false; 253 254 // Helper to get wishlist data from form fields 255 function getWishlistData() { 256 var chaletNamesField = document.getElementById('field_tmky42'); 257 var chaletIdsField = document.getElementById('field_outp2'); 258 259 return { 260 'chalet_names': chaletNamesField ? chaletNamesField.value : '', 261 'chalet_ids': chaletIdsField ? chaletIdsField.value : '' 262 }; 263 } 264 265 // Wishlist Shared - track share button clicks 266 if (typeof jQuery !== 'undefined') { 267 // Copy Link button 268 jQuery('#copyButton').on('click', function() { 269 dataLayer.push({ 270 'event': 'wishlist_shared', 271 'share_method': 'copy_link' 272 }); 273 }); 274 275 // WhatsApp link 276 jQuery('#favshareModal a[href*="wa.me"]').on('click', function() { 277 dataLayer.push({ 278 'event': 'wishlist_shared', 279 'share_method': 'whatsapp' 280 }); 281 }); 282 283 // Email - track when email is sent successfully 284 jQuery('#sendMessageBtn').on('click', function() { 285 dataLayer.push({ 286 'event': 'wishlist_shared', 287 'share_method': 'email' 288 }); 289 }); 290 } 291 292 // Wishlist Inquiry Started - track form interactions 293 function fireWishlistInquiryStarted() { 294 if (wishlistInquiryStarted) return; 295 wishlistInquiryStarted = true; 296 297 var data = getWishlistData(); 298 dataLayer.push({ 299 'event': 'wishlist_inquiry_started', 300 'chalet_names': data.chalet_names, 301 'chalet_ids': data.chalet_ids 302 }); 303 } 304 305 if (typeof jQuery !== 'undefined') { 306 // Track when enquire modal is opened 307 jQuery('#favenquireModal').on('shown.bs.modal', function() { 308 // Slight delay to ensure form fields are populated 309 setTimeout(function() { 310 // Track interaction with any form field 311 jQuery('#favenquireModal input, #favenquireModal select, #favenquireModal textarea').on('focus click', function() { 312 fireWishlistInquiryStarted(); 313 }); 314 }, 100); 315 }); 316
317 // Wishlist Inquiry Sent - fires on Formidable form 16 submission 318 jQuery(document).on('frmFormComplete', function(event, form, response) { 319 var formId = jQuery(form).find('input[name="form_id"]').val(); 320 if (formId == '16') { 321 var data = getWishlistData(); 322 dataLayer.push({ 323 'event': 'wishlist_inquiry_sent', 324 'chalet_names': data.chalet_names, 325 'chalet_ids': data.chalet_ids 326 }); 327 } 328 }); 329 } 330 }); 331 332 // Shared: Map budget range to midpoint value for GA4 conversion tracking 333 function getBudgetValue(budget) { 334 var map = { 335 'Less than 10000': 5000, 336 '10000-25000': 17500, 337 '25000-50000': 37500, 338 '50000-75000': 62500, 339 '75000-100K': 87500, 340 '100K-125K': 112500, 341 '125K-150K': 137500, 342 'More than 150K': 175000 343 }; 344 return map[budget] || 0; 345 } 346 347 // Booking Inquiry Form Tracking (form ID 21 - enquiryform2024) 348 document.addEventListener('DOMContentLoaded', function() { 349 var bookingForm = document.getElementById('form_enquiryform2024'); 350 if (!bookingForm) return; 351 352 var bookingInquiryStarted = false; 353 var storedBookingData = {}; 354 355 // Store form data before Formidable clears fields on submit 356 bookingForm.addEventListener('submit', function() { 357 storedBookingData = getBookingInquiryData(); 358 }); 359 360 // Helper to get booking inquiry form data 361 function getBookingInquiryData() { 362 var emailField = document.getElementById('field_pok6m3'); 363 var arrivalField = document.getElementById('field_jbjcw3'); 364 var departureField = document.getElementById('field_w4uql3'); 365 var budgetField = document.getElementById('field_v5fn02'); 366 367 // Combine dates into search_daterange format 368 var daterange = ''; 369 if (arrivalField && departureField && arrivalField.value && departureField.value) { 370 daterange = arrivalField.value + ' - ' + departureField.value; 371 } 372 373 var budgetText = budgetField ? budgetField.value : ''; 374 375 return { 376 'email': emailField ? emailField.value : '', 377 'search_daterange': daterange, 378 'inquiry_budget': budgetText, 379 'value': getBudgetValue(budgetText) 380 }; 381 } 382 383 // Booking Inquiry Started - fires once when user interacts with form 384 function fireBookingInquiryStarted() { 385 if (bookingInquiryStarted) return; 386 bookingInquiryStarted = true; 387 388 dataLayer.push({ 389 'event': 'booking_inquiry_started' 390 }); 391 } 392 393 // Track interactions with form fields 394 var formFields = bookingForm.querySelectorAll('input:not([type="hidden"]), select, textarea'); 395 formFields.forEach(function(field) { 396 field.addEventListener('focus', fireBookingInquiryStarted); 397 field.addEventListener('click', fireBookingInquiryStarted); 398 }); 399 400 // Booking Inquiry Sent - fires on Formidable form 21 submission 401 if (typeof jQuery !== 'undefined') { 402 jQuery(document).on('frmFormComplete', function(event, form, response) { 403 var formId = jQuery(form).find('input[name="form_id"]').val(); 404 if (formId == '21') { 405 var data = storedBookingData.email ? storedBookingData : getBookingInquiryData(); 406 var eventData = { 407 'event': 'booking_inquiry_sent', 408 'email': data.email, 409 'search_daterange': data.search_daterange, 410 'inquiry_budget': data.inquiry_budget 411 }; 412 413 if (data.value > 0) { 414 eventData.value = data.value; 415 eventData.currency = 'CHF'; 416 } 417 418 pushWithHashedEmail(eventData); 419 } 420 }); 421 } 422 });
423 424 // General Inquiry Form Tracking (form ID 18 - contactform2) 425 document.addEventListener('DOMContentLoaded', function() { 426 var generalForm = document.getElementById('form_contactform2'); 427 if (!generalForm) return; 428 429 var generalInquiryStarted = false; 430 var storedGeneralEmail = ''; 431 432 // Store email before Formidable clears fields on submit 433 generalForm.addEventListener('submit', function() { 434 var emailField = document.getElementById('field_zzk0i2'); 435 storedGeneralEmail = emailField ? emailField.value : ''; 436 }); 437 438 // General Inquiry Started - fires once when user interacts with form 439 function fireGeneralInquiryStarted() { 440 if (generalInquiryStarted) return; 441 generalInquiryStarted = true; 442 443 dataLayer.push({ 444 'event': 'general_inquiry_started' 445 }); 446 } 447 448 // Track interactions with form fields 449 var formFields = generalForm.querySelectorAll('input:not([type="hidden"]), select, textarea'); 450 formFields.forEach(function(field) { 451 field.addEventListener('focus', fireGeneralInquiryStarted); 452 field.addEventListener('click', fireGeneralInquiryStarted); 453 }); 454 455 // General Inquiry Sent - fires on Formidable form 18 submission 456 if (typeof jQuery !== 'undefined') { 457 jQuery(document).on('frmFormComplete', function(event, form, response) { 458 var formId = jQuery(form).find('input[name="form_id"]').val(); 459 if (formId == '18') { 460 pushWithHashedEmail({ 461 'event': 'general_inquiry_sent', 462 'email': storedGeneralEmail || '' 463 }); 464 } 465 }); 466 } 467 }); 468 469 // Callback Request Tracking (request a call back modal) 470 document.addEventListener('DOMContentLoaded', function() { 471 var checkAvailabilityBtn = document.getElementById('show_availability'); 472 if (!checkAvailabilityBtn) return; 473 474 // Callback Started - fires when user clicks "Check Availability" 475 checkAvailabilityBtn.addEventListener('click', function() { 476 dataLayer.push({ 477 'event': 'callback_started' 478 }); 479 }); 480 481 // Callback Sent - fires on Formidable form 13 submission 482 if (typeof jQuery !== 'undefined') { 483 jQuery(document).on('frmFormComplete', function(event, form, response) { 484 var formId = jQuery(form).find('input[name="form_id"]').val(); 485 if (formId == '13') { 486 var emailField = document.getElementById('field_sm0iv'); 487 pushWithHashedEmail({ 488 'event': 'callback_sent', 489 'email': emailField ? emailField.value : '' 490 }); 491 } 492 }); 493 } 494 }); 495 496 // French Inquiry Form Tracking (form ID 6) 497 document.addEventListener('DOMContentLoaded', function() { 498 var frenchContainer = document.getElementById('frm_form_6_container'); 499 if (!frenchContainer) return; 500 501 var frenchForm = frenchContainer.querySelector('form'); 502 if (!frenchForm) return; 503 504 var frenchInquiryStarted = false; 505 var storedFrenchData = {}; 506 507 // Store form data before Formidable clears fields on submit 508 frenchForm.addEventListener('submit', function() { 509 storedFrenchData = getFrenchInquiryData(); 510 }); 511 512 function getFrenchInquiryData() { 513 var emailField = document.getElementById('field_d9tly2'); 514 var budgetField = document.getElementById('field_1tr8d2'); 515 var budgetText = budgetField ? budgetField.value : ''; 516 517 return { 518 'email': emailField ? emailField.value : '', 519 'inquiry_budget': budgetText, 520 'value': getBudgetValue(budgetText) 521 }; 522 } 523 524 // French Inquiry Started - fires once when user interacts with form 525 function fireFrenchInquiryStarted() { 526 if (frenchInquiryStarted) return; 527 frenchInquiryStarted = true; 528 529 dataLayer.push({ 530 'event': 'french_inquiry_started' 531 }); 532 } 533 534 var formFields = frenchForm.querySelectorAll('input:not([type="hidden"]), select, textarea'); 535 formFields.forEach(function(field) { 536 field.addEventListener('focus', fireFrenchInquiryStarted); 537 field.addEventListener('click', fireFrenchInquiryStarted); 538 }); 539 540 // French Inquiry Sent - fires on Formidable form 6 submission 541 if (typeof jQuery !== 'undefined') { 542 jQuery(document).on('frmFormComplete', function(event, form, response) { 543 var formId = jQuery(form).find('input[name="form_id"]').val(); 544 if (formId == '6') { 545 var data = storedFrenchData.email ? storedFrenchData : getFrenchInquiryData(); 546 var eventData = { 547 'event': 'french_inquiry_sent', 548 'email': data.email, 549 'inquiry_budget': data.inquiry_budget 550 }; 551 552 if (data.value > 0) { 553 eventData.value = data.value; 554 eventData.currency = 'CHF'; 555 } 556 557 pushWithHashedEmail(eventData); 558 } 559 }); 560 } 561 });
562 563 // German Inquiry Form Tracking (form ID 4) 564 document.addEventListener('DOMContentLoaded', function() { 565 var germanContainer = document.getElementById('frm_form_4_container'); 566 if (!germanContainer) return; 567 568 var germanForm = germanContainer.querySelector('form'); 569 if (!germanForm) return; 570 571 var germanInquiryStarted = false; 572 var storedGermanData = {}; 573 574 // Store form data before Formidable clears fields on submit 575 germanForm.addEventListener('submit', function() { 576 storedGermanData = getGermanInquiryData(); 577 }); 578 579 function getGermanInquiryData() { 580 var emailField = document.getElementById('field_d9tly'); 581 var budgetField = document.getElementById('field_1tr8d'); 582 var budgetText = budgetField ? budgetField.value : ''; 583 584 return { 585 'email': emailField ? emailField.value : '', 586 'inquiry_budget': budgetText, 587 'value': getBudgetValue(budgetText) 588 }; 589 } 590 591 // German Inquiry Started - fires once when user interacts with form 592 function fireGermanInquiryStarted() { 593 if (germanInquiryStarted) return; 594 germanInquiryStarted = true; 595 596 dataLayer.push({ 597 'event': 'german_inquiry_started' 598 }); 599 } 600 601 var formFields = germanForm.querySelectorAll('input:not([type="hidden"]), select, textarea'); 602 formFields.forEach(function(field) { 603 field.addEventListener('focus', fireGermanInquiryStarted); 604 field.addEventListener('click', fireGermanInquiryStarted); 605 }); 606 607 // German Inquiry Sent - fires on Formidable form 4 submission 608 if (typeof jQuery !== 'undefined') { 609 jQuery(document).on('frmFormComplete', function(event, form, response) { 610 var formId = jQuery(form).find('input[name="form_id"]').val(); 611 if (formId == '4') { 612 var data = storedGermanData.email ? storedGermanData : getGermanInquiryData(); 613 var eventData = { 614 'event': 'german_inquiry_sent', 615 'email': data.email, 616 'inquiry_budget': data.inquiry_budget 617 }; 618 619 if (data.value > 0) { 620 eventData.value = data.value; 621 eventData.currency = 'CHF'; 622 } 623 624 pushWithHashedEmail(eventData); 625 } 626 }); 627 } 628 }); 629})();
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.