1/* Static site forms: validate via worker, then CallTrk.captureForm (Elementor AJAX does not run). 2 Document-level listeners so Elementor popup clones still validate. */ 3(function () { 4 const VALIDATION_WORKER_URL = 5 window.FORM_VALIDATION_WORKER_URL || 6 'https://form-validation-worker.michael-215.workers.dev/'; 7 8 const NAME_RE = /^[A-Za-z\s'-]{2,30}$/; 9 const EMAIL_RE = /^\S+@\S+\.\S+$/; 10 const ZIP_RE = /^\d{5}(-\d{4})?$/; 11 12 function captureWithCallRail(form) { 13 form.dataset.fvPass = 'true'; 14 try { 15 if (typeof window.submitToCallRail === 'function') { 16 window.submitToCallRail(form); 17 return true; 18 } 19 if (window.CallTrk && typeof CallTrk.captureForm === 'function') { 20 CallTrk.captureForm(form); 21 return true; 22 } 23 form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); 24 return false; 25 } finally { 26 delete form.dataset.fvPass; 27 } 28 } 29 30 function fieldsOf(form) { 31 if (!form || !form.querySelector) return null; 32 const submitBtn = form.querySelector('button[type="submit"]'); 33 const firstName = form.querySelector('[name="form_fields[ct_fName]"]'); 34 const lastName = form.querySelector('[name="form_fields[ct_lName]"]'); 35 const phoneField = form.querySelector('[name="form_fields[ct_pNumber]"]'); 36 const emailField = form.querySelector('[name="form_fields[ct_email]"]'); 37 const zipField = form.querySelector('[name="form_fields[ct_zCode]"]'); 38 if (!submitBtn || !firstName || !lastName || !phoneField || !emailField || !zipField) { 39 return null; 40 } 41 return { submitBtn, firstName, lastName, phoneField, emailField, zipField }; 42 } 43 44 function prepareForm(form) { 45 const f = fieldsOf(form); 46 if (!f) return null; 47 form.setAttribute('novalidate', 'novalidate'); 48 f.phoneField.setAttribute('type', 'text'); 49 f.emailField.setAttribute('type', 'text'); 50 return f; 51 } 52 53 function removeElementorErrors(form) { 54 form 55 .querySelectorAll( 56 '.elementor-message-danger,' + 57 '.elementor-message,' + 58 '.elementor-error,' + 59 '.elementor-field-error' 60 ) 61 .forEach(function (el) { 62 el.remove(); 63 }); 64 } 65 66 async function validateAndSend(form, e) { 67 if (form.dataset.fvPass === 'true') return; 68 const f = prepareForm(form); 69 if (!f) return; 70 71 if (e) { 72 e.preventDefault(); 73 e.stopPropagation(); 74 e.stopImmediatePropagation(); 75 } 76 77 removeElementorErrors(form); 78 79 const fName = f.firstName.value.trim(); 80 const lName = f.lastName.value.trim(); 81 const phone = f.phoneField.value.trim(); 82 const email = f.emailField.value.trim(); 83 const zip = f.zipField.value.trim(); 84 const cleanPhone = phone.replace(/\D/g, ''); 85 86 if (!NAME_RE.test(fName)) { 87 alert('Enter a valid first name.'); 88 f.firstName.focus(); 89 return; 90 } 91 if (!NAME_RE.test(lName)) { 92 alert('Enter a valid last name.'); 93 f.lastName.focus(); 94 return; 95 } 96 if (cleanPhone.length !== 10) { 97 alert('Enter a valid 10-digit phone number.'); 98 f.phoneField.focus(); 99 return; 100 } 101 if (!EMAIL_RE.test(email)) { 102 alert('Enter a valid email address.'); 103 f.emailField.focus(); 104 return; 105 } 106 if (!ZIP_RE.test(zip)) { 107 alert('Enter a valid ZIP code.'); 108 f.zipField.focus(); 109 return; 110 } 111 112 f.submitBtn.disabled = true; 113 const originalText = f.submitBtn.textContent; 114 f.submitBtn.textContent = 'Submitting...'; 115 116 var data = null; 117 try { 118 const response = await fetch(VALIDATION_WORKER_URL, { 119 method: 'POST', 120 headers: { 'Content-Type': 'application/json' }, 121 body: JSON.stringify({ 122 firstName: fName, 123 lastName: lName, 124 phone: cleanPhone, 125 email: email, 126 zip: zip 127 }) 128 }); 129 if (response.status >= 500) { 130 throw new Error('validation worker returned ' + response.status); 131 } 132 data = await response.json(); 133 } catch (workerError) { 134 console.error('Worker unreachable, capturing lead without validation:', workerError); 135 removeElementorErrors(form); 136 captureWithCallRail(form);
137 alert('Thank you! Your request has been submitted successfully.'); 138 form.reset(); 139 f.submitBtn.disabled = false; 140 f.submitBtn.textContent = originalText; 141 return; 142 } 143 144 try { 145 if (!data || !data.success) { 146 alert((data && data.message) || 'Submission blocked. Please check your details.'); 147 f.submitBtn.disabled = false; 148 f.submitBtn.textContent = originalText; 149 return; 150 } 151 152 removeElementorErrors(form); 153 captureWithCallRail(form); 154 alert('Thank you! Your request has been submitted successfully.'); 155 form.reset(); 156 f.submitBtn.disabled = false; 157 f.submitBtn.textContent = originalText; 158 } catch (error) { 159 console.error('Worker validation failed:', error); 160 alert('Something went wrong. Please try again.'); 161 f.submitBtn.disabled = false; 162 f.submitBtn.textContent = originalText; 163 } 164 } 165 166 function boot() { 167 document.querySelectorAll('.elementor-form').forEach(prepareForm); 168 } 169 170 function start() { 171 if (!window.__ctFvDelegate) { 172 window.__ctFvDelegate = true; 173 document.addEventListener( 174 'click', 175 function (e) { 176 const btn = e.target.closest && e.target.closest('.elementor-form button[type="submit"]'); 177 if (!btn) return; 178 const form = btn.closest('.elementor-form'); 179 if (!fieldsOf(form)) return; 180 validateAndSend(form, e); 181 }, 182 true 183 ); 184 document.addEventListener( 185 'submit', 186 function (e) { 187 const form = e.target; 188 if (!form || !form.classList || !form.classList.contains('elementor-form')) return; 189 if (!fieldsOf(form)) return; 190 validateAndSend(form, e); 191 }, 192 true 193 ); 194 } 195 boot(); 196 setTimeout(boot, 500); 197 setTimeout(boot, 2000); 198 if (typeof MutationObserver !== 'undefined' && document.body) { 199 new MutationObserver(boot).observe(document.body, { childList: true, subtree: true }); 200 } 201 } 202 203 if (document.readyState === 'loading') { 204 document.addEventListener('DOMContentLoaded', start); 205 } else { 206 start(); 207 } 208})();
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.