1const baseUrl = window.location.origin; 2const locationURL = window.location.origin + "/Locations/search" 3 4 5 6function debounce(fn, delay) { 7 let timer; 8 9 return function (...args) { 10 const context = this; 11 12 clearTimeout(timer); 13 timer = setTimeout(() => { 14 fn.apply(context, args); 15 }, delay); 16 }; 17} 18 19async function generateRecaptchaToken() { 20 const recaptchKey = document.querySelector("script[data-recaptcha-site]").dataset.recaptchaSite 21 const reacptchaToken = await window.grecaptcha.execute(recaptchKey); 22 return reacptchaToken 23} 24 25function addFallBackImage({ 26 selector, 27 fallbackUrl, 28 shouldSetProperty = false, 29 propertyName = "--bg-image", 30}) { 31 const images = document.querySelectorAll(selector); 32 33 images.forEach((img) => { 34 if (!(img instanceof HTMLImageElement)) return; 35 36 const onError = () => { 37 img.removeEventListener("error", onError); 38 39 img.src = fallbackUrl; 40 41 const shouldSetPropertyOnParent = shouldSetProperty && img.parentElement 42 shouldSetPropertyOnParent && img.parentElement.style.setProperty(propertyName, `url("${fallbackUrl}")`); 43 }; 44 45 img.addEventListener("error", onError); 46 47 // handle images that already failed before listener was attached 48 if (img.complete && img.naturalWidth === 0) { 49 onError(); 50 } 51 }); 52} 53 54const viewMoreElements = ({ buttonSelector, elementsSelector, numberToShow = 3 }) => { 55 const viewMore = document.querySelector(buttonSelector) 56 if (!viewMore) return; 57 58 viewMore.addEventListener("click", () => { 59 const blocks = document.querySelectorAll(elementsSelector) 60 if (blocks.length === 0) return; 61 if (blocks.length <= numberToShow) { 62 viewMore.classList.add("d-none") 63 } 64 for (let i = 0; i < numberToShow && i < blocks.length; i++) { 65 blocks[i].classList.remove("d-none"); 66 } 67 }) 68} 69 70const searchLocations = async ({ value, countryCode }) => { 71 try { 72 const searchparams = new URLSearchParams() 73 searchparams.append("location", value) 74 searchparams.append("countryCode", countryCode) 75 const url = `${locationURL}` 76 const res = await fetch(`${url}?${searchparams.toString()}`, { 77 method: "GET", 78 }) 79 const locationsArray = await res.json() 80 return locationsArray.length ? locationsArray : [{}] 81 } catch (e) { 82 return [{}] 83 } 84} 85 86const handleLocationChange = ({ 87 countryCodeInputSelector = ".umbraco-forms-field.location input", 88 locationInputSelector = "input[name='countryCode']", 89 positionObject = {} 90}) => { 91 const countryCode = document.querySelector(countryCodeInputSelector)?.value || ''; 92 const locationInput = document.querySelector(locationInputSelector) 93 if (!locationInput) return 94 95 $(locationInputSelector).loccomplete({ 96 minLength: 2, 97 ...positionObject, 98 source: function (request, response) { 99 $.ajax({
100 url: locationURL, 101 dataType: "json", 102 data: { 103 location: request.term, 104 countryCode: countryCode, 105 }, 106 search: function (event, ui) { 107 $("button.btn.search:first").addClass("spinner"); 108 }, 109 response: function (event, ui) { 110 $("button.btn.search:first").removeClass("spinner"); 111 }, 112 success: function (data) { 113 response(data.length ? data : [{}]); 114 }, 115 error: function (data) { 116 $("button.btn.search:first").removeClass("spinner"); 117 $(".ui-autocomplete-title").html( 118 'No results for "' + $("input#search").val() 119 ); 120 }, 121 }); 122 }, 123 select: function (event, ui) { 124 if (ui.item.postCode && ui.item.postCode.trim() != "") { 125 $(locationInputSelector).val(ui.item.postCode); 126 } else { 127 $(locationInputSelector).val(ui.item.basicOverview); 128 } 129 $(locationInputSelector).loccomplete("close"); 130 document.querySelector(locationInputSelector).dispatchEvent( 131 new Event('change', { bubbles: true }) 132 ); 133 return false; 134 }, 135 }); 136} 137 138 139// dataArray shape: 140// [{ elementSelector: string, data: string, method: FillMethod, attribute?:string }] 141const FillMethod = Object.freeze({ 142 INNER_TEXT: "innerText", 143 INNER_HTML: "innerHTML", 144 VALUE: "value", 145 HREF: "href", 146 ATTRIBUTE: "attr" 147}); 148 149const openModalAndFillData = ({ 150 dataArray = [], 151 modalSelector = "" 152}) => { 153 if (!modalSelector) return; 154 155 const modal = $(modalSelector); 156 if (!modal.length) return; 157 158 dataArray.forEach(({ elementSelector, data, method, attribute }) => { 159 if (!elementSelector) return; 160 161 const element = modal.find(elementSelector); 162 if (!element.length) return; 163 164 if (!data) { 165 element.hide(); 166 return; 167 } 168 169 switch (method) { 170 case FillMethod.INNER_TEXT: 171 element.text(data); 172 break; 173 174 case FillMethod.INNER_HTML: 175 element.html(data); 176 break; 177 178 case FillMethod.VALUE: 179 element.val(data); 180 break; 181 182 case FillMethod.HREF: 183 element.attr('href', data); 184 break; 185 186 case FillMethod.ATTRIBUTE: 187 element.attr(attribute, data) 188 break; 189 190 default: 191 console.warn(`Unsupported method: ${method}`); 192 } 193 element.show(); 194 }); 195 196 modal.modal("show"); 197}; 198 199 200const countryCallingCodes = { 201 "US": "1", 202 "AE": "971", 203 "GB": "44", 204 "IE": "353", 205 "ZA": "27", 206 "BE": "32", 207 "NL": "31", 208} 209 210const formatPhoneNumber = ({ 211 phone = "", 212 culture = "" 213}) => { 214 if (!phone || !culture) return 215 const phoneWithNoSpaces = phone.replaceAll(" ", "") 216 if (phoneWithNoSpaces.startsWith("+")) return; 217 const country = culture.split("-")[1] 218 const countryCallingCode = countryCallingCodes?.[country] || ""; 219 if (phoneWithNoSpaces.startsWith(countryCallingCode)) return `+${phoneWithNoSpaces}` 220 221 return `+${countryCallingCode}${phoneWithNoSpaces}` 222} 223 224const openContactModal = (button) => { 225 const id = button.dataset.tcId; 226 const websiteUrl = button.dataset.websiteUrl 227 const phone = button.dataset.phone 228 const whatsAppLink = button.dataset.whatsapp 229 const culture = button.dataset.culture 230 const meetingLink = button.dataset.meetingLink 231 232 const formattedPhoneNumber = formatPhoneNumber({ 233 phone, 234 culture 235 }); 236 237 openModalAndFillData({ 238 modalSelector: "#contact-modal", 239 dataArray: [ 240 { elementSelector: "#contact-modal__travel-counsellor-id", data: id, method: FillMethod.VALUE }, 241 { elementSelector: "#contact-modal__website-url", data: websiteUrl, method: FillMethod.VALUE }, 242 { elementSelector: "#contact-modal__contact-details #whatsapp", data: whatsAppLink, method: FillMethod.HREF }, 243 { elementSelector: "#contact-modal__contact-details #meetingLink", data: meetingLink, method: FillMethod.HREF }, 244 { elementSelector: "#contact-modal__contact-details #phoneLink", data: phone.replaceAll(" ", ""), method: FillMethod.ATTRIBUTE, attribute: "data-phone" }, 245 ] 246 }) 247} 248 249 250const handleClickOnPhone = ({ 251 phoneContainer 252}) => { 253 if (!phoneContainer) return; 254 255 256 phoneContainer.addEventListener("click", (e) => { 257 e.preventDefault(); 258 259 const span = phoneContainer.querySelector("span") 260 const phoneText = phoneContainer.dataset.phone || span.innerText 261 262 const isMobile = /Android|iPhone|iPad|iPod|Windows Phone/i.test(navigator.userAgent); 263 264 if (isMobile) { 265 window.location.href = `tel:${phoneText}`; 266 return 267 } 268 269 270 navigator.clipboard.writeText(phoneText).then(() => { 271 span.innerText = phoneContainer.dataset.phone; 272 }); 273 }) 274} 275 276 277// CONFIG 278 279// { 280// formSelector, 281// hiddenFormFieldSelector, 282// hideClass, 283// formConfigDivSelector 284 285// currentStepInputSelector, 286// totalStepsSpanSelector, 287// currentStepSpanSelector, 288// progressTrackSelector, 289 290// normalBackButtonSelector, 291// showNormalBackButtonClass, 292 293// countryCodeInputSelector, 294// locationInputSelector, 295 296// locationSuggestionsInputSelector, 297// tagsContainerSelector, 298 299// checkboxContainerSelector, 300// radioButtonContainerSelector, 301 302// wrapperSelector, 303// triggerSelector, 304// menuSelector, 305// closeButtonSelector, 306// itemsSelector, 307// menuOpenClass, 308 309// selectedItemClass, 310// formBackButtonSelector, 311// previousClickedInputSelector, 312// submitButtonSelector, 313 314// Final step - loading screen and modal (for workflow submission) 315// loadingScreenSelector, 316// loadingScreenShowClass, 317// bannerModalSelector 318// } 319 320class UmbracoMultiStepForm { 321 constructor(config) { 322 this.config = config 323 this.formValues = []; 324 } 325 326 debouncedSearchLocations = debounce(async ({ 327 value, 328 countryCode,
329 locationInputElement, 330 tagsContainerSelector 331 }) => { 332 try { 333 const tagsContainer = document.querySelector(tagsContainerSelector); 334 if (!tagsContainer) return; 335 336 const results = await searchLocations({ value, countryCode }); 337 if (!results?.length) return; 338 339 tagsContainer.innerHTML = ""; 340 341 results.forEach(result => { 342 if (!Object.keys(result)?.length) return 343 344 const button = document.createElement("button"); 345 button.type = "button"; 346 button.classList.add("matcher-location-suggestion"); 347 348 let value = "" 349 350 if (result?.postCode?.trim()) { 351 value = result.postCode 352 } else if (result?.basicOverview) { 353 value = result.basicOverview 354 } 355 356 button.onclick = e => { 357 e.preventDefault(); 358 359 locationInputElement.value = value; 360 361 locationInputElement.dispatchEvent( 362 new Event("change", { bubbles: true }) 363 ); 364 365 document.querySelectorAll(".matcher-location-suggestion").forEach( 366 tag => tag.remove() 367 ) 368 369 }; 370 371 button.innerText = value ?? ""; 372 tagsContainer.appendChild(button); 373 }); 374 375 } catch (err) { 376 console.error(err); 377 } 378 }, 200); 379 380 handleClickOnCheckBoxAndRadio() { 381 const { checkboxContainerSelector, radioButtonContainerSelector } = this.config; 382 const matcherCheckBoxes = document.querySelectorAll(`${checkboxContainerSelector}, ${radioButtonContainerSelector}`) 383 if (matcherCheckBoxes.length == 0) return; 384 385 matcherCheckBoxes.forEach(container => { 386 container.addEventListener("click", (e) => { 387 if (["INPUT", "LABEL"].includes(e.target.tagName)) { 388 return 389 } 390 container.querySelector("label").click() 391 }) 392 }) 393 } 394 395 attachCustomSelectEvents = () => { 396 const { 397 wrapperSelector, 398 triggerSelector, 399 menuSelector, 400 closeButtonSelector, 401 itemsSelector, 402 menuOpenClass, 403 selectedItemClass 404 } = this.config 405 406 const wrapper = document.querySelector(wrapperSelector) 407 if (!wrapper) return; 408 409 410 const trigger = wrapper?.querySelector(triggerSelector); 411 const menu = wrapper?.querySelector(menuSelector); 412 const select = wrapper?.querySelector('select'); 413 414 const menuCloseButton = wrapper?.querySelector(closeButtonSelector) 415 const customSelectItems = menu?.querySelectorAll(itemsSelector) 416 417 menuCloseButton?.addEventListener("click", () => { 418 wrapper?.classList?.remove(menuOpenClass) 419 }) 420 421 // toggle menu open class 422 trigger?.addEventListener('click', () => { 423 wrapper?.classList?.toggle(menuOpenClass); 424 }); 425 426 customSelectItems?.forEach(item => { 427 item?.addEventListener('click', () => { 428 // remove select class from alll 429 customSelectItems.forEach(li => li.classList.remove(selectedItemClass)) 430 431 const value = item.getAttribute('data-value'); 432 const text = item.innerText; 433 item.classList.add(selectedItemClass) 434 435 // Update hidden select 436 select.value = value; 437 438 // Update trigger text 439 trigger.innerText = text; 440 441 wrapper.classList.remove(menuOpenClass) 442 443 // Trigger change event for Umbraco Forms validation 444 const event = new Event('change', { bubbles: true }); 445 select.dispatchEvent(event); 446 }); 447 }) 448 449 // close on click outside 450 document.addEventListener('click', e => { 451 if (!wrapper?.contains(e.target)) { 452 wrapper?.classList.remove(menuOpenClass) 453 } 454 }); 455 } 456 457 handleLocationWithSuggestionsChange() { 458 const { 459 locationSuggestionsInputSelector, 460 countryCodeInputSelector, 461 tagsContainerSelector 462 } = this.config 463 464 const countryCode = document.querySelector(countryCodeInputSelector).value; 465 const locationSuggestions = document.querySelector(locationSuggestionsInputSelector); 466 if (!locationSuggestions || !countryCode) return; 467 468 locationSuggestions.addEventListener("input", async (e) => { 469 const tagsContainer = document.querySelector(tagsContainerSelector); 470 const trimmed = (e?.target?.value ?? "").trim(); 471 if (!trimmed) { 472 if (tagsContainer) tagsContainer.innerHTML = ""; 473 return; 474 } 475 if (!e.isTrusted) return; 476 477 this.debouncedSearchLocations({ 478 value: e.target.value, 479 locationInputElement: locationSuggestions, 480 countryCode,
481 tagsContainerSelector 482 }); 483 }) 484 } 485 486 handleLocationChange() { 487 const { 488 countryCodeInputSelector, 489 locationInputSelector, 490 } = this.config 491 492 handleLocationChange({ 493 countryCodeInputSelector, 494 locationInputSelector, 495 }) 496 } 497 498 async handleGenerateRecaptcha() { 499 const { formSelector } = this.config 500 const recaptchaInput = document.querySelector(`${formSelector} input[name="g-recaptcha-response"]`) 501 if (!recaptchaInput) return false 502 const token = await generateRecaptchaToken(); 503 recaptchaInput.value = token 504 return true 505 } 506 507 initFormProgressbar() { 508 const { 509 currentStepInputSelector, 510 totalStepsSelector, 511 totalStepsSpanSelector, 512 currentStepSpanSelector, 513 progressTrackSelector 514 } = this.config 515 516 if ( 517 !currentStepInputSelector || 518 !totalStepsSelector || 519 !totalStepsSpanSelector || 520 !currentStepSpanSelector || 521 !progressTrackSelector 522 ) return 523 524 const currentStepIndex = document.querySelector(currentStepInputSelector).value 525 const totalSteps = document.querySelector(totalStepsSelector).dataset.pageCount 526 const totalNumberSpan = document.querySelector(totalStepsSpanSelector) 527 const currentStepSpan = document.querySelector(currentStepSpanSelector) 528 const track = document.querySelector(progressTrackSelector) 529 530 531 const currentStep = +currentStepIndex + 1 532 const currentWidth = (currentStep / totalSteps) * 100 533 534 currentStepSpan.innerHTML = currentStep 535 totalNumberSpan.innerHTML = totalSteps 536 track.style.setProperty("--progress-current-width", `${currentWidth}%`) 537 } 538 539 updateProgressbar() { 540 const { 541 currentStepInputSelector, 542 currentStepSpanSelector, 543 totalStepsSpanSelector, 544 progressTrackSelector 545 } = this.config 546 547 if (!currentStepInputSelector || !currentStepSpanSelector || !totalStepsSpanSelector || !progressTrackSelector) 548 return 549 550 const currentStepIndex = document.querySelector(currentStepInputSelector).value 551 const currentStepSpan = document.querySelector(currentStepSpanSelector) 552 const totalNumberSpanContent = document.querySelector(totalStepsSpanSelector).innerText 553 const track = document.querySelector(progressTrackSelector) 554 555 const currentStep = +currentStepIndex + 1 556 const currentWidth = (currentStep / +totalNumberSpanContent) * 100 557 558 559 currentStepSpan.innerHTML = currentStep 560 track.style.setProperty("--progress-current-width", `${currentWidth}%`) 561 } 562 563 showConditionalHiddenFormField() { 564 const { 565 formSelector, 566 hiddenFormFieldSelector, 567 hideClass 568 } = this.config; 569 570 const formElement = document.querySelector(formSelector) 571 const formsFields = formElement.querySelectorAll(hiddenFormFieldSelector); 572 const current = formElement.querySelector(hiddenFormFieldSelector) || formsFields[0]; 573 if (current) { 574 current.classList.remove(hideClass) 575 } 576 } 577 578 storeFormValues({ form }) { 579 if (!form) return []; 580 581 const formFields = form.querySelectorAll("input[data-alias], select[data-alias], input[id]"); 582 const formValuesMap = {}; // Map to store unique alias for aggregation 583 584 formFields.forEach(field => { 585 let alias = field.getAttribute("data-alias") || field.closest?.(".umbraco-forms-field")?.classList?.[1]; 586 if (!alias) return; 587 const id = field.id || null; 588 589 if (field.type === "checkbox") { 590 if (!field.checked) return; 591 592 // Initialize if not already 593 if (!formValuesMap[alias]) { 594 formValuesMap[alias] = { id, alias, value: [] }; 595 } 596 formValuesMap[alias].value.push(field.value); 597 } else if (field.type === "radio") { 598 if (!field.checked) return; 599 formValuesMap[alias] = { id, alias, value: field.value }; 600 } else { 601 formValuesMap[alias] = { id, alias, value: field.value }; 602 } 603 }); 604
605 Object.values(formValuesMap).forEach(item => { 606 if (Array.isArray(item.value)) { 607 item.value = item.value.join(", "); 608 } 609 }); 610 611 formValuesMap && Object.values(formValuesMap).forEach(newItem => { 612 const existingIndex = this.formValues.findIndex(item => item.alias === newItem.alias); 613 if (existingIndex > -1) { 614 // Replace existing 615 this.formValues[existingIndex] = newItem; 616 } else { 617 // Add new 618 this.formValues.push(newItem); 619 } 620 }); 621 622 return this.formValues; 623 } 624 625 626 getNewForm({ data }) { 627 const { formSelector } = this.config 628 const tempDiv = document.createElement("div"); 629 tempDiv.innerHTML = data; 630 const newForm = tempDiv.querySelector(formSelector); 631 if (!newForm) { 632 return { newForm: null, parentSibling: null } 633 } 634 const parentSibling = newForm.parentElement?.nextElementSibling || null; 635 return { newForm, parentSibling } 636 } 637 638 submitFormSuccess({ data }) { 639 const { formSelector, previousClickedInputSelector, normalBackButtonSelector, showNormalBackButtonClass } = this.config 640 641 const { newForm, parentSibling } = this.getNewForm({ data }) 642 if (!newForm) return 643 644 // this should stay here to get the old input value and new input values 645 const isLastStep = this.isLastStep({ newForm }) 646 647 const form = document.querySelector(formSelector); 648 if (!form) return; 649 650 651 form.innerHTML = newForm.innerHTML; 652 const oldSibling = form.parentElement.nextElementSibling 653 const newSibling = newForm.parentElement.nextElementSibling 654 655 if (oldSibling && newSibling) { 656 oldSibling.replaceWith(newSibling); 657 } 658 659 const previousClickedInput = document.querySelector(previousClickedInputSelector) 660 if (previousClickedInput) previousClickedInput.value = "" 661 662 if (normalBackButtonSelector) { 663 const backButton = document.querySelector(normalBackButtonSelector) 664 backButton && backButton.classList.remove(showNormalBackButtonClass) 665 } 666 667 this.reinit() 668 669 // Check if the final page should be auto-skipped (all fieldsets hidden due to conditions) 670 // Use setTimeout to allow umbracoforms.min.js to evaluate conditions first 671 const currentForm = document.querySelector(formSelector); 672 if (this.isOnLastPage({ form: currentForm })) { 673 setTimeout(() => { 674 const shouldSkip = this.shouldAutoSkipPage({ form: currentForm }); 675 if (shouldSkip) { 676 // Use requestSubmit() to trigger submit event (loading screen, modal hide). 677 // Native submit() bypasses the event and would not show loading. 678 const submitBtn = currentForm.querySelector('input[data-umb="submit-forms-form"]'); 679 if (typeof currentForm.requestSubmit === 'function') { 680 currentForm.requestSubmit(); 681 } else if (submitBtn) { 682 submitBtn.click(); 683 } else { 684 currentForm.submit(); 685 } 686 } 687 }, 100); 688 return; 689 } 690 } 691 692 isLastStep({ newForm }) { 693 const { currentStepInputSelector, previousClickedInputSelector, totalStepsSpanSelector } = this.config 694 const previousInput = document.querySelector(previousClickedInputSelector) 695 const currentStepInput = document.querySelector(currentStepInputSelector) 696 const totalStepsSpan = document.querySelector(totalStepsSpanSelector) 697 if (!previousInput || !currentStepInput || !totalStepsSpan) return false 698 699 const isPreviousClicked = previousInput.value === "true" 700 const currentStep = +currentStepInput.value 701 const totalNumberOfSteps = +totalStepsSpan.innerText 702 const newFormCurrentStepInput = newForm.querySelector(currentStepInputSelector) 703 const newFormCurrentStep = newFormCurrentStepInput ? +newFormCurrentStepInput.value : -1 704 const errorMessage = newForm.querySelector(".field-validation-error") 705 706 return ( 707 newFormCurrentStep === 0 && 708 !isPreviousClicked && 709 ( 710 totalNumberOfSteps === 1 || 711 (currentStep !== 0 && totalNumberOfSteps > 1) 712 )) && !errorMessage 713 } 714 715 isOnLastPage({ form }) { 716 if (!form) return false; 717 const submitButton = form.querySelector("input[data-umb='submit-forms-form']"); 718 return submitButton !== null; 719 } 720 721 shouldAutoSkipPage({ form }) { 722 const { hideClass } = this.config; 723 724 if (!form) return false; 725 726 // Check if ALL fieldsets on this page are hidden due to conditions 727 const allFieldsets = form.querySelectorAll(".umbraco-forms-fieldset"); 728 if (allFieldsets.length === 0) return false; 729 730 const visibleFieldsets = Array.from(allFieldsets).filter( 731 fs => !fs.classList.contains(hideClass) 732 ); 733 734 return visibleFieldsets.length === 0; 735 } 736 737 handleFormSubmission() { 738 const { formSelector, loadingScreenSelector, loadingScreenShowClass, bannerModalSelector } = this.config 739 740 const form = document.querySelector(formSelector); 741 if (!form) return; 742 743 form.addEventListener("submit", (e) => { 744 // On final step, show loading screen and allow normal form submission 745 // so that Umbraco Forms workflows can execute server-side 746 if (this.isOnLastPage({ form })) { 747 if (loadingScreenSelector && loadingScreenShowClass) { 748 const loadingScreen = document.querySelector(loadingScreenSelector); 749 if (loadingScreen) { 750 loadingScreen.classList.add(loadingScreenShowClass); 751 } 752 } 753 if (bannerModalSelector) { 754 $(bannerModalSelector).hide(); 755 } 756 // Allow normal form submission - don't prevent default 757 return; 758 } 759 760 // For intermediate steps, use AJAX submission 761 e.preventDefault(); 762 const formData = $(form).serializeArray(); 763 764 this.storeFormValues({ form }) 765 766 $.ajax({
767 url: $(form).attr("action"), 768 type: "POST", 769 data: $.param(formData), 770 success: (data) => this.submitFormSuccess({ data }), 771 error: () => { 772 const formEl = document.querySelector(formSelector); 773 const errorEl = formEl?.querySelector(".field-validation-error"); 774 if (!errorEl) { 775 const container = formEl?.closest(".umbraco-forms-form"); 776 if (container) { 777 const msg = document.createElement("div"); 778 msg.className = "field-validation-error"; 779 msg.setAttribute("role", "alert"); 780 msg.textContent = "Something went wrong. Please try again."; 781 container.insertBefore(msg, container.firstChild); 782 } 783 } 784 } 785 }) 786 }) 787 788 } 789 790 onClickBackMutliStepForm() { 791 const { 792 formBackButtonSelector, 793 previousClickedInputSelector, 794 submitButtonSelector 795 } = this.config 796 797 document.addEventListener("click", function (e) { 798 const isMatcherFormBackButton = [e.target.id, e.target.parentElement?.id].includes(formBackButtonSelector) 799 if (!isMatcherFormBackButton) return; 800 e.preventDefault(); 801 802 const previousInput = document.querySelector(previousClickedInputSelector); 803 const submitButton = document.querySelector(submitButtonSelector); 804 if (!previousInput || !submitButton) return; 805 806 previousInput.value = "true"; 807 submitButton.click(); 808 }); 809 } 810 811 handleShowSubmitButton() { 812 const { formSelector, wrapperSelector } = this.config 813 814 const inputs = document.querySelectorAll(`${formSelector} input[type='text']`); 815 const radioAndCheckBoxes = document.querySelectorAll(`${formSelector} input[type='checkbox'], ${formSelector} input[type='radio']`) 816 const selectElements = document.querySelectorAll(`${wrapperSelector} select`) 817 818 819 inputs.forEach(input => input.addEventListener("input", (e) => { 820 const isTextInputFilled = !!e?.target?.value?.trim()?.length 821 const submitButton = document.querySelector(`${formSelector} .submit-to-next-form-page-button`) 822 if (isTextInputFilled) { 823 input.nextElementSibling.classList.remove("d-none") 824 } else { 825 input.nextElementSibling.classList.add("d-none") 826 submitButton.classList.add("d-none") 827 } 828 })) 829 830 radioAndCheckBoxes.forEach(input => 831 input.addEventListener("change", (e) => { 832 const isInputChecked = Array.from(radioAndCheckBoxes).some(ele => ele.checked); 833 const submitButton = document.querySelector(`${formSelector} .submit-to-next-form-page-button`) 834 if (isInputChecked) { 835 submitButton.classList.remove("d-none") 836 } else { 837 submitButton.classList.add("d-none") 838 } 839 }) 840 ) 841 842 selectElements.forEach(select => 843 select.addEventListener("change", (e) => { 844 const submitButton = document.querySelector(`${formSelector} .submit-to-next-form-page-button`) 845 if (e.target.value) { 846 submitButton.classList.remove("d-none") 847 } else { 848 submitButton.classList.add("d-none") 849 } 850 }) 851 ) 852 853 } 854 855 init() { 856 this.handleClickOnCheckBoxAndRadio(); 857 this.handleLocationChange(); 858 this.handleLocationWithSuggestionsChange(); 859 this.attachCustomSelectEvents(); 860 this.initFormProgressbar(); 861 this.onClickBackMutliStepForm(); 862 this.handleGenerateRecaptcha(); 863 this.handleShowSubmitButton(); 864 865 this.handleFormSubmission(); 866 } 867 868 reinit() { 869 this.handleClickOnCheckBoxAndRadio(); 870 this.handleLocationChange(); 871 this.handleLocationWithSuggestionsChange(); 872 this.attachCustomSelectEvents(); 873 this.updateProgressbar(); 874 // Removed: showConditionalHiddenFormField() - let umbracoforms.min.js handle condition-based visibility 875 this.handleGenerateRecaptcha(); 876 this.handleShowSubmitButton(); 877 878 } 879}
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.