PageSourceSearch

https://www.valentineroof.com/Content/js/ContactScript-Standard.js?v=20260729

js valentineroof.com collected 2026-10-04 21:03:27 UTC 23,802 bytes, 587 lines download raw bytes

1(function (exports) {
2    const recaptchaField = 'g-recaptcha-response';
3    var formValidators = {}, formStarted = {}, _recaptchaKey = '';
4    let responseInProgress = false, estimatorInUse = false;
5
6    function getGoogleRecaptchaSiteKey() {
7        if (_recaptchaKey) { return _recaptchaKey; }
8        var script = document.getElementById('GoogleRecaptchaScript');
9        var key = '';
10        if (script && script.src) {
11            var params = new URL(script.src).searchParams;
12            key = params.has('render') ? params.get('render') : '';
13        }
14        _recaptchaKey = key;
15        return key;
16    }
17
18    exports.setupForm = function (form) {
19        if (formValidators.hasOwnProperty(form.id)) { return; }
20        form.action = form.getAttribute("data-action");
21        form.removeAttribute("data-action");
22
23        estimatorInUse = form.getAttribute("data-estimator") == "true";
24        if (estimatorInUse && typeof (EstimatorEntries) === "object" && typeof (EstimatorEntries.AddEstimatorToForm) === "function") {
25            EstimatorEntries.AddEstimatorToForm(form);
26        }
27
28        // enable form submit buttons
29        formElementToggleActive(form, true);
30
31        // setup additional analytics
32        formStarted[form.id] = false;
33
34        // setup validation
35        formValidators[form.id] = $(form).validate({
36            submitHandler: function (form, event) {
37                if (form.action && form.method.toLowerCase() === "post") {
38                    clearFormMessage(form);
39                    formElementToggleActive(form, false);
40                    $(form).find('button[type="submit"]').addClass('loading');
41                    ignoredFieldsToggleDisabled(form, true);
42                    var recaptchaSiteKey = getGoogleRecaptchaSiteKey();
43                    if (recaptchaSiteKey && typeof grecaptcha === 'object') {
44                        // action name validated server-side, do not change
45                        grecaptcha.execute(recaptchaSiteKey, { action: 'ContactFormSubmission' }).then(function (token) {
46                            submitForm(form, token);
47                        });
48                    } else {
49                        // unable to execute the recaptcha
50                        $(form).find('button[type="submit"]').removeClass('loading');
51                        showFormMessage(form, ".js-formMessage-failure");
52                    }
53                }
54            },
55            invalidHandler: function () {
56                logFormEvent(form, "form_validation_failure");
57            },
58            errorPlacement: function (error, element) {
59                error.insertAfter(element);
60            }
61        });
62
63       $(form).find(":input").on("blur", interactionListener).on('change', function () {
64            $(form).find('button[type="submit"]').toggleClass('form-valid', formValidators[form.id].checkForm());
65        });
66    }
67
68    function interactionListener() {
69        var form = $(this).parents('form')[0];
70        if (!formStarted[form.id]) {
71            formStarted[form.id] = true;
72            logFormEvent(form, "begin_form");
73            // remove listener since we only need it once.
74            $(form).find(":input").off("blur", interactionListener);
75        }
76    };
77
78    function submitForm(form, recaptchaToken) {
79        var formData = new FormData(form);
80        if (recaptchaToken) {
81            formData.append(recaptchaField, recaptchaToken);
82        }
83        $.ajax({
84            type: "POST",
85            url: form.action,
86            data: formData,
87            contentType: false,
88            processData: false,
89            dataType: "json"
90        }).then(function (data) {
91            formElementToggleActive(form, true);
92            ignoredFieldsToggleDisabled(form, false);
93            if (data.success) {
94                if (estimatorInUse && typeof (EstimatorEntries) === "object" && typeof (EstimatorEntries.ClearEstimatorEntries) === "function") {
95                    EstimatorEntries.ClearEstimatorEntries();
96                }
97                logFormEvent(form, "form_submitted");
98                if (typeof gtag === 'function') {
99                    googleAdsTriggers(form, data.appointment, data.metaPixelEventId, data.country);
100                } else {
101                    redirectOrShowResponse(form, data.appointment);
102                }
103            } else {
104                $(form).find('button[type="submit"]').removeClass('loading');
105                // validation failed
106                var errors = {};
107                var captchaFailed = false;
108                for (var key in data.errors) {
109                    var field = form.elements[key];
110                    var errorMessage;
111                    switch (data.errors[key]) {
112                        case "recaptcha-failure":
113                            errorMessage = "reCAPTCHA failed validation, please complete the reCAPTCHA again.";
114                            captchaFailed = true;
115                            break;
116                        case "file-extension-invalid":
117                            errorMessage = "Please enter a value with a valid extension.";
118                            break;
119                        case "file-size-invalid":
120                            var sizes = [0, 5];
121                            try {
122                                sizes = JSON.parse(field.getAttribute("data-rule-maxfilesize"));
123                            } catch (e) { }
124                            errorMessage = $.validator.format("Please select a file less than {1} megabytes.").apply(this, sizes);
125                            break;
126                        default:
127                            errorMessage = "This field is invalid";
128                            break;
129                    }
130                    if (key != recaptchaField) {
131                        errors[key] = errorMessage;
132                    }
133                }
134                formValidators[form.id].showErrors(errors);
135                if (captchaFailed) {
136                    showFormMessage(form, ".js-formMessage-failure");
137                }
138                logFormEvent(form, "form_validation_failure");
139            }
140        }, function () {
141            $(form).find('button[type="submit"]').removeClass('loading');
142            formElementToggleActive(form, true);
143            ignoredFieldsToggleDisabled(form, false);
144            showFormMessage(form, ".js-formMessage-failure");
145        });
146    }
147
148    function redirectOrShowResponse(form, appointment) {
149        var redirectLocation = form.hasAttribute("data-redirect") ? form.getAttribute("data-redirect") : "";
150        if (redirectLocation) {
151            if (appointment) {
152                const url = new URL(redirectLocation, window.location.origin);
153                const params = url.searchParams;
154                params.append('appointment', 'true');
155                redirectLocation = url.toString();
156            }
157            window.location.href = redirectLocation;
158        } else {
159            responseInProgress = false;
160            $(form).find('button[type="submit"]').removeClass('loading');
161            showFormMessage(form, ".js-formMessage-success");
162            form.reset();
163            formStarted[form.id] = false;
164            $(form).find(":input").on("blur", interactionListener);
165        }
166    }
167
168    function ignoredFieldsToggleDisabled(form, isDisabled) {
169        var fields = form.querySelectorAll('.ignore');
170        if (fields && fields.length > 0) {
171            for (var i = 0; i < fields.length; i++) {
172                fields[i].disabled = isDisabled;
173            }
174        }
175    }
176
177    function googleAdsTriggers(form, appointment, metaPixelEventId, country) {
178        var formType = $(form).find('[name="AdsFormType"]').val() ?? 'IHS_Customer_Service';
179        var email = $(form).find('[name="Email"]').val() ?? '';
180        var phone = '', rawPhone = $(form).find('[name="Phone"]').val();
181        if (rawPhone) {
182            phone = '+1' + rawPhone.replace(/\D/g, '');
183        }
184        var lastName = $(form).find('[name="Last Name"]').val() ?? '';
185        var firstName = $(form).find('[name="First Name"]').val() ?? '';
186        var street = $(form).find('[name="Street Address"]').val() ?? '';
187        var rawStreet2 = $(form).find('[name="Street Address 2"]').val();
188        if (street && rawStreet2) {
189            street = street + ' ' + rawStreet2;
190        } else if (rawStreet2) {
191            street = rawStreet2;
192        }
193        var city = $(form).find('[name="City"]').val() ?? '';
194        var state = $(form).find('[name="State"]').val() ?? '';
195        var zipCodeKey = $(form).find('[name="ZipCodeKey"]').val() ?? 'Zipcode';
196        var zipCode = $(form).find(`[name="${zipCodeKey}"]`).val() ?? '';
197
198        if (responseInProgress) { return; }
199        responseInProgress = true;
200        dataLayer.push({
201            'event': 'formSubmit',
202            'formType': formType,
203            'emailAddress': email,
204            'phoneNumber': phone,
205            'lastName': lastName,
206            'firstName': firstName,
207            'street': street,
208            'city': city,
209            'state': state,
210            'zipCode': zipCode,
211            'country': country,
212            'metaPixelEventId': metaPixelEventId,
213            'eventCallback': function () {
214                redirectOrShowResponse(form, appointment);
215            },
216            'eventTimeout': 1000
217        });
218        setTimeout(function () {
219            redirectOrShowResponse(form, appointment);
220        }, 1000);
221    }
222
223    function logFormEvent(form, eventAction, eventParams, isRecommendedEvent) {
224        if (typeof eventParams !== "object") {
225            eventParams = {};
226        }
227        if (!isRecommendedEvent) {
228            if (!eventParams.event_label) {
229                var name = form.elements["FormName"];
230                if (name) {
231                    eventParams.event_label = name.value;
232                }
233            }
234        }
235        if (typeof gtag === "function") {
236            gtag("event", eventAction, eventParams);
237        }
238    }
239
240    function showFormMessage(form, message) {
241        var eContainer = form.querySelector(".js-formMessages");
242        if (eContainer) {
243            var eMessage = eContainer.querySelector(message);
244            if (eMessage) {
245                eMessage.hidden = false;
246            }
247        }
248    }
249
250    function clearFormMessage(form) {
251        var eContainer = form.querySelector(".js-formMessages");
252        if (eContainer) {
253            var eMessages = eContainer.querySelectorAll(".js-formMessage");
254            if (eMessages) {
255                for (i = 0; i < eMessages.length; i++) {
256                    eMessages[i].hidden = true;
257                }
258            }
259        }
260    }
261
262    function formElementToggleActive(form, active) {
263        for (var i = 0; i < form.elements.length; i++) {
264            var element = form.elements[i];
265            if (element instanceof HTMLInputElement && element.type.toLowerCase() === "submit" || element instanceof HTMLButtonElement && element.type.toLowerCase() === "submit") {
266                element.disabled = !active;
267            } else if (element instanceof HTMLInputElement) {
268                element.readOnly = !active;
269            }
270        }
271
272        if ($('#datepicker').length > 0 && UtcFormat.HasDate()) {
273            UtcFormat.CheckRestrictedDates();
274        }
275    }
276})(this.ContactForms = {});
277
278function initForms() {
279    $(function () {
280        var forms = document.querySelectorAll('form.js-ContactForm');
281        for (var i = 0; i < forms.length; i++) {
282            ContactForms.setupForm(forms[i]);
283        }
284    });
285}
286function loadScript(x) {
287    var p = $.Deferred();
288    var el = document.createElement('script');
289    el.onload = p.resolve
290    el.onerror = p.reject
291    el.type = 'text/javascript';
292    el.async = 'async';
293    el.defer = 'defer';
294    for (key in x) {
295        el[key] = x[key];
296    }
297    document.body.appendChild(el);
298    return p.promise();
299}
300function loadStyle(src) {
301    var s = document.createElement('link');
302    s.rel = 'stylesheet';
303    s.href = src;
304    document.head.appendChild(s);
305}
306
307(function (exports) {
308    let blockedDates = [], blockedRanges = [], minDate = new Date(), minDateMoment = {}, hasDate = false;
309    const timeSlots = {
310        0: [],
311        1: [],
312        2: [],
313        3: [],
314        4: [],
315        5: [],
316        6: []
317    }, allSlots = [], skipSlots = [], minBusinessDays = 4;
318
319    const utcRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(\.?\d{0,3})[Z]{1}$/;
320    const dRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})/;
321    const noSecRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})/;
322    var isInit = false;
323
324    var formatDateObj = function (p) {
325        if (p == undefined) { return ''; }
326        return formatDate(p.getFullYear(), p.getMonth(), p.getDate());
327    }
328    var formatDateTimeObj = function (p) {
329        if (p == undefined) { return ''; }
330        return formatDateTime(p.getFullYear(), p.getMonth(), p.getDate(), p.getHours(), p.getMinutes(), p.getSeconds());
331    }
332    var formatDateTimeObjUtc = function (p) {
333        if (p == undefined) { return ''; }
334        return formatDateTime(p.getUTCFullYear(), p.getUTCMonth(), p.getUTCDate(), p.getUTCHours(), p.getUTCMinutes(), p.getUTCSeconds()) + 'Z';
335    }
336    var formatDateTime = function (y, mm, d, h, m, s) {
337        mm++;
338        if (mm < 10) { mm = '0' + mm; }
339        if (d < 10) { d = '0' + d; }
340        if (h < 10) { h = '0' + h; }
341        if (m < 10) { m = '0' + m; }
342        if (s < 10) { s = '0' + s; }
343        return y + '-' + mm + '-' + d + 'T' + h + ':' + m + ':' + s;
344    }
345
346    let formatDate = function (y, mm, d) {
347        mm++;
348        if (mm < 10) { mm = '0' + mm; }
349        if (d < 10) { d = '0' + d; }
350        return mm + '/' + d + '/' + y;
351    }
352
353    function UtcInput(container) {
354        var self = this;
355        self.con = container;
356        self.hidden = self.con.querySelector('input[type="hidden"]');
357        self.$hidden = $(self.hidden);
358        var $el = $(self.con).find('.js-Date');
359        var $timeSel = $('#AppointmentTime');
360        var $clearBtn = $(self.con).find('.js-ClearDate');
361        minDateMoment = moment(minDate);
362        const bail = 365;
363        let retries = 0, validDays = 0;
364        do {
365            let isValid = showDate(minDate)[0];
366            if (isValid && validDays > minBusinessDays) {
367                hasDate = true;
368            } else {
369                if (isValid && !skipSlots.includes(minDate.getDay())) {
370                    validDays++;
371                }
372                minDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate() + 1);
373                minDateMoment = moment(minDate);
374                retries++;
375            }
376        } while (!hasDate && retries < bail);
377
378        if (retries <= validDays) {
379            var presentDate = new Date();
380            minDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), presentDate.getHours(), presentDate.getMinutes(), presentDate.getSeconds());
381            minDateMoment = moment(minDate);
382        }
383
384        function showDate(date) {
385            if (date < new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate())) { return [false, '']; }
386            let timeSlot = timeSlots[date.getDay()];
387            if (timeSlot.length == 0) { return [false, '']; }
388            for (var i = 0; i < timeSlot.length; i++) {
389                let times = timeSlot[i].split(':');
390                let d = new Date(date.getFullYear(), date.getMonth(), date.getDate(), times[0], times[1], times[2]);
391                if (!isBlocked(d)) {
392                    return [true, ''];
393                }
394            }
395            return [false, ''];
396        }
397        function toggleTimes(dateText) {
398            $timeSel.val('');
399            if (typeof dateText === 'string') {
400                $('#PickedDate').val(dateText);
401            } else {
402                $('#PickedDate').val(formatDateObj(dateText));
403            }
404            let d = moment(dateText, "MM/DD/YYYY");
405            let ts = timeSlots[d.day()];
406            $('.js-Time').hide().prop('disabled', true).addClass('disabled');
407            $clearBtn.show();
408            let currTime = $timeSel[0].value;//val() does some weird data conversion on time spans
409            let hasCurrTime = false;
410            for (var i = 0; i < ts.length; i++) {
411                let t = ts[i].split(':');
412                d.set('hour', t[0]);
413                d.set('minute', t[1]);
414                d.set('second', t[2]);
415                let $timeInp = $('.js-Time[value="' + ts[i] + '"]');
416                if (!isBlocked(d)) {
417                    $timeInp.show().prop('disabled', false).removeClass('disabled');
418                    if (!hasCurrTime) {
419                        hasCurrTime = ts[i] == currTime;
420                    }
421                }
422            }
423            if (!hasCurrTime) {
424                $timeSel.find(':not(.disabled)').first().prop('selected', true);
425            }
426            exports.CheckRestrictedDates();
427        }
428
429        if (hasDate) {
430            $el.datepicker({
431                beforeShowDay: showDate,
432                defaultDate: null,
433                onSelect: toggleTimes
434            });
435
436            setTimeout(() => {
437                toggleTimes($el.datepicker('getDate'));
438            }, 100);
439        } else {
440            $(self.con).hide();
441            $('#datepicker-error').show();
442        }
443    }
444
445    function isBlocked(d) {
446        if (minDateMoment.isAfter(d)) { return true; }
447        for (var j = 0; j < blockedDates.length; j++) {
448            if (blockedDates[j].isSame(d)) {
449                return true;
450            }
451        }
452        let dMoment = moment(d);
453        for (var x = 0; x < blockedRanges.length; x++) {
454            let b = blockedRanges[x];
455            if (dMoment.isBetween(b.Start, b.End, undefined, '[]')) {
456                return true;
457            }
458        }
459        return false;
460    }
461
462    exports.HasDate = function () {
463        return hasDate;
464    }
465
466    exports.CheckRestrictedDates = function () {
467        if (skipSlots.includes($('.js-Date').datepicker('getDate').getDay())) {
468            $(".js-CallToSchedule").show();
469            $(".js-ClickToSchedule").hide();
470            $('#AppointmentTime').find('.js-Default').prop('selected', true);
471            $('button[type=submit]').prop('disabled', true);
472        } else {
473            $(".js-CallToSchedule").hide();
474            $(".js-ClickToSchedule").show();
475            if (typeof grecaptcha !== 'undefined') {
476                $('button[type=submit]').prop('disabled', false);
477            }
478        }
479    }
480
481    exports.Parse = function (str) {
482        if (!str) { return undefined; }
483        if (utcRegex.test(str)) {
484            var a = str.split(/[^0-9]/);
485            return new Date(Date.UTC(a[0], a[1] - 1, a[2], a[3], a[4], a[5]));
486        }
487        if (dRegex.test(str)) {
488            var a = str.split(/[^0-9]/);
489            return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
490        }
491        if (noSecRegex.test(str)) {
492            var a = str.split(/[^0-9]/);
493            return new Date(a[0], a[1] - 1, a[2], a[3], a[4], 0);
494        }
495        return undefined;
496    }
497
498    exports.Init = function () {
499        let promise = $.Deferred();
500        if (isInit) {
501            promise.resolve();
502            return promise;
503        }
504        isInit = true;
505        loadStyle('/shared_Gen/jQuery/jQuery-ui/v1.10.2/themes/base/minified/jquery.ui.core.min.css');
506        loadStyle('/shared_Gen/jQuery/jQuery-ui/v1.10.2/themes/base/minified/jquery.ui.datepicker.min.css');
507        loadStyle('/shared_Gen/jQuery/jQuery-ui/v1.10.2/themes/base/minified/jquery.ui.theme.min.css');
508        let promises = [loadScript({ src: '/Content/js/moment-2.17.1.min.js' }),
509        loadScript({ src: '/shared_Gen/jquery/jquery-ui/v1.10.2/ui/minified/jquery-ui.min.js' })
510        ];
511        $.when.apply(this, promises).then(() => {
512            let p1 = $.post('/find/Contact/AjaxGetTimeData').done(function (resp) {
513                for (var i = 0; i < resp.BlockedTimes.length; i++) {
514                    blockedDates.push(moment(resp.BlockedTimes[i]));
515                }
516                for (var i = 0; i < resp.BlockedRanges.length; i++) {
517                    blockedRanges.push({ Start: moment(resp.BlockedRanges[i].BlockStart), End: moment(resp.BlockedRanges[i].BlockEnd) });
518                }
519                for (var i = 0; i < resp.Timeslots.length; i++) {
520                    let ts = resp.Timeslots[i];
521                    if (allSlots.indexOf(ts.TimeStart) == -1) {
522                        allSlots.push(ts.TimeStart);
523                    }
524                    if (timeSlots[ts.DayOfWeek].indexOf(ts.TimeStart) == -1) {
525                        timeSlots[ts.DayOfWeek].push(ts.TimeStart);
526                    }
527                }
528                for (var i = 0; i < 7; i++) {
529                    if (timeSlots[i].length == 1 && timeSlots[i][0] == '23:00:00') {
530                        skipSlots.push(i);
531                    }
532                }
533            });
534            return $.when.apply(this, [p1]);
535        }).then(() => {
536            new UtcInput(document.getElementById('datepicker'));
537        });
538    }
539})(this.UtcFormat = {});
540
541(function (exports) {
542    const evs = 'blur keypress keydown change click', recapSel = 're
542captchaSrc';
543    let reCapTimeout, isInit = false, loadFired = false, $watchEls;
544
545    function loadReCaptcha() {
546        if (loadFired) { return true; }
547        loadFired = true;
548        $watchEls.off(evs, loadReCaptcha);
549        if (reCapTimeout) {
550            try { clearTimeout(reCapTimeout); reCapTimeout = null; } catch { }
551        }
552        let recapEl = document.getElementById(recapSel);
553        if (recapEl) {
554            $.when(loadScript({ src: '/shared_Gen/jQuery/Validate/1.16.0/jquery.validate.min.js' })).then((res) => {
555                var promises = [];
556                [{ src: '/shared_Gen/jQuery/Validate/1.16.0/additional-methods.min.js' },
557                { src: '/Content/js/jquery.validate.additional-methods.js?v=20260520' },
558                ].forEach(function (p) {
559                    promises.push(loadScript(p));
560                });
561                $.when.apply(promises).done(function () {
562                    loadScript({
563                        src: recapEl.value,
564                        id: 'GoogleRecaptchaScript'
565                    });
566                });
567                recapEl.parentNode.removeChild(recapEl);
568            }, (rej) => { });
569        }
570    }
571
572    exports.Init = () => {
573        if (isInit) { return; }
574        isInit = true;
575
576        if ($('#datepicker').length > 0) {
577            UtcFormat.Init();
578        }
579        $watchEls = $('form.js-ContactForm').find('input,select,textarea');
580        $watchEls.one(evs, loadReCaptcha);
581        $calculatorEls = $('div.js-calculator-maps-input').find('input,select,textarea');
582        $calculatorEls.one(evs, loadReCaptcha);
583        reCapTimeout = setTimeout(loadReCaptcha, 10000);
584    }
585})(this.recapLoader = {});
586
587$(document).ready(recapLoader.Init);

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.