1// Form Logic: Validation utilities 2// Dependencies: none (no direct load-time JS dependencies) 3// 4// WordPress enqueue dep: sw-form-logic-helpers (safeguard, ensures helpers.js 5// is present before this file runs, though validation.js does not call any 6// helpers function directly). 7// 8// Runtime globals â available by DOMContentLoaded when event handlers fire: 9// - isEmailValid declared in corp-form.js (load order: corp-form â this) 10// - checkSubmitButton declared in corp-form.js 11// Note: corp-form.js depends on this file (calls swEmailValidationInit), 12// so there is no circular WordPress enqueue dep â corp-form loads after this. 13// 14// Provides: 15// - checkBEValidationGET â GET request to the SolarAPI validation endpoint 16// - checkBEValidationPOST â POST request to the SolarAPI validation endpoint 17// - swEmailValidationInit â async email field validation on change 18// - swPhoneValidationInit â phone + phone-code field validation on blur/change 19 20async function checkBEValidationGET(validationEndpoint, queryParamsObject) { 21 const qs = new URLSearchParams(queryParamsObject).toString(); 22 const url = `/wp-json/solarapi/api/validation/${validationEndpoint}?${qs}`; 23 const res = await fetch(url); 24 return await res.json(); 25} 26 27async function checkBEValidationPOST(validationEndpoint, data) { 28 const url = `/wp-json/solarapi/api/validation/${validationEndpoint}`; 29 const res = await fetch(url, { 30 method: "POST", 31 headers: { "Content-Type": "application/json" }, 32 body: JSON.stringify(data) 33 }); 34 return await res.json(); 35} 36 37// Attaches async email validation to the #sw19_email field within the given form. 38// After each change event, runs parallel API checks and writes to the global 39// `isEmailValid` (declared in corp-form.js), then calls checkSubmitButton(). 40function swEmailValidationInit(form) { 41 const emailField = form.querySelector("#sw19_email"); 42 if (!emailField) return; 43 44 emailField.addEventListener("input", function () { 45 emailField.setCustomValidity(""); 46 }); 47 48 // If the field is pre-filled on page load (e.g. redirect), trigger immediately. 49 if (emailField.value.trim()) { 50 setTimeout(function () { 51 emailField.dispatchEvent(new Event("change")); 52 }, 0); 53 } 54 55 emailField.addEventListener("change", async function () { 56 const fieldParent = emailField.closest(".sw19s-corp-field"); 57 fieldParent.classList.remove("invalid"); 58 emailField.setCustomValidity(""); 59 emailField.checkValidity(); 60 61 const emailInUseNote = form.querySelector(".email-in-use-note"); 62 if (emailInUseNote) emailInUseNote.style.display = "none"; 63 64 if (!emailField.validity.valid) { 65 isEmailValid = false; 66 checkSubmitButton(); 67 return; 68 } 69 70 fieldParent.classList.add("loading"); 71 72 const emailVal = emailField.value.trim(); 73 const hasPingdomClass = Array.from(document.body.classList).some( 74 function (cls) { 75 return cls.includes("pingdom"); 76 } 77 ); 78 const hasPasswordField = !!form.querySelector('input[type="password"]'); 79 80 let isFreeEmailRes, isEmailValidRes, uniqueRes; 81 try { 82 [isFreeEmailRes, isEmailValidRes, uniqueRes] = await Promise.all([ 83 hasPingdomClass 84 ? Promise.resolve(false) 85 : checkBEValidationGET("free-email", { email: emailVal }), 86 checkBEValidationGET("email", { email: emailVal }), 87 hasPasswordField 88 ? checkBEValidationPOST("unique", { 89 ElementName: "email", 90 TYPE: 6, 91 Value: emailVal 92 }) 93 : Promise.resolve(null) 94 ]); 95 } catch (_) { 96 // Network or parse error â treat as valid so the user can proceed. 97 isEmailValid = true; 98 fieldParent.classList.remove("loading"); 99 checkSubmitButton(); 100 return; 101 } 102 103 fieldParent.classList.remove("loading"); 104 isEmailValid = isEmailValidRes?.IsValid; 105 106 if (isFreeEmailRes || !isEmailValid) { 107 emailField.setCustomValidity("Please enter a business email address."); 108 emailField.checkValidity(); 109 } else if (uniqueRes === false || uniqueRes?.IsValid === false) { 110 isEmailValid = false;
111 emailField.setCustomValidity("This email address is already in use."); 112 emailField.checkValidity(); 113 fieldParent.classList.add("invalid"); 114 if (emailInUseNote) emailInUseNote.style.display = ""; 115 } 116 117 checkSubmitButton(); 118 }); 119} 120 121// Attaches phone number + phone-code field cross-validation to the given form. 122function swPhoneValidationInit(form) { 123 const phoneCodeSelect = form.querySelector("#sw19_phone_code"); 124 const phoneInput = form.querySelector("#sw19_phone"); 125 if (!phoneCodeSelect || !phoneInput) return; 126 127 const phonePattern = /^[0-9\-\(\)\s+]{8,25}$/; 128 129 function validatePhoneFields() { 130 const codeVal = phoneCodeSelect.value.trim(); 131 const phoneVal = phoneInput.value.trim(); 132 phoneInput.setCustomValidity(""); 133 phoneCodeSelect.setCustomValidity(""); 134 135 if (!codeVal && !phoneVal) return; 136 137 if (phoneVal && !phonePattern.test(phoneVal)) { 138 phoneInput.setCustomValidity( 139 "Please enter a valid phone number (digits, spaces, dashes and parentheses only, 8\u201325 characters)." 140 ); 141 return; 142 } 143 if (phoneVal && !codeVal) { 144 phoneCodeSelect.setCustomValidity("Please select a country phone code."); 145 return; 146 } 147 if (codeVal && !phoneVal) { 148 phoneInput.setCustomValidity("Please enter a phone number."); 149 } 150 } 151 152 phoneInput.addEventListener("input", function () { 153 phoneInput.setCustomValidity(""); 154 phoneCodeSelect.setCustomValidity(""); 155 }); 156 157 phoneCodeSelect.addEventListener("change", function () { 158 phoneInput.setCustomValidity(""); 159 phoneCodeSelect.setCustomValidity(""); 160 validatePhoneFields(); 161 }); 162 163 phoneInput.addEventListener("blur", validatePhoneFields); 164 165 phoneInput.addEventListener("invalid", function () { 166 phoneInput.closest(".sw19s-corp-field")?.classList.add("invalid"); 167 }); 168 169 phoneCodeSelect.addEventListener("invalid", function () { 170 phoneCodeSelect.closest(".sw19s-corp-field")?.classList.add("invalid"); 171 }); 172}
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.