PageSourceSearch

https://www.travelcounsellors.ie/scripts/optimization/helpers.js

js travelcounsellors.ie collected 2026-09-26 07:34:00 UTC 30,816 bytes, 879 lines download raw bytes

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.