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.