1jQuery(function ($) { 2 3 // -------------------------------------------------- 4 // Helpers 5 // -------------------------------------------------- 6 7 function normalize(str) { 8 return (str || '').toString().trim().toUpperCase().replace(/\s+/g, ' '); 9 } 10 11 function setAlert($box, type, html) { 12 var cls = 'mb-2 '; 13 14 switch (type) { 15 case 'validating': cls += 'alert alert-secondary'; break; 16 case 'suggestion': cls += ''; break; 17 case 'success': cls += 'alert alert-success'; break; 18 case 'error': cls += 'alert alert-danger'; break; 19 default: cls += 'alert alert-light border'; 20 } 21 22 $box 23 .removeClass() 24 .addClass(cls) 25 .html(html) 26 .stop(true, true) 27 .fadeIn(150); 28 } 29 30 // -------------------------------------------------- 31 // Initializer (runs once per form) 32 // -------------------------------------------------- 33 34 function initForm($form) { 35 36 if ($form.data('address-validation-ready')) { 37 return; 38 } 39 $form.data('address-validation-ready', true); 40 41 var $address1 = $form.find('[name="address1"]'); 42 var $address2 = $form.find('[name="address2"]'); 43 var $city = $form.find('[name="city"]'); 44 var $state = $form.find('[name="stateCode"]'); 45 var $zip = $form.find('[name="zipCode"]'); 46 47 if (! $address1.length || ! $city.length || ! $state.length || ! $zip.length) { 48 return; 49 } 50 51 var $inputs = $form.find( 52 '[name="address1"], ' + 53 '[name="address2"], ' + 54 '[name="city"], ' + 55 '[name="stateCode"], ' + 56 '[name="zipCode"]' 57 ); 58 59 var $suggestionBox = $form.find('.address-suggestion'); 60 if (! $suggestionBox.length) { 61 var $submit = $form.find(':submit').last(); 62 63 $suggestionBox = $('<div class="address-suggestion mb-2" style="display:none;"></div>'); 64 65 if ($submit.length) { 66 $suggestionBox.insertBefore($submit); 67 } else { 68 $suggestionBox.appendTo($form); 69 } 70 } 71 72 function findApplyButton() { 73 74 // 1. Prefer submit buttons inside the form 75 var $btn = $form.find(':submit').last(); 76 if ($btn.length) { 77 return $btn; 78 } 79 80 // 2. Fallback: external submit button using form attribute 81 var formId = $form.attr('id'); 82 if (! formId) { 83 return $(); 84 } 85 86 return $(':submit[form="' + formId + '"]').last(); 87 } 88 89 function setApplyEnabled(enabled) { 90 var $btn = findApplyButton(); 91 if (! $btn.length) { 92 return; 93 } 94 95 $btn 96 .prop('disabled', !enabled) 97 .toggleClass('disabled', !enabled); 98 } 99 100 function applyAddress(addr) { 101 if (! addr) { 102 return; 103 } 104 105 $address1.val(addr.address1); 106 $address2.val(addr.address2 || ''); 107 $city.val(addr.city); 108 $state.val(addr.state); 109 $zip.val(addr.zip); 110 } 111 112 function collect() { 113 return { 114 address1: $address1.val(), 115 address2: $address2.val(), 116 city: $city.val(), 117 state: $state.val(), 118 zip: $zip.val() 119 }; 120 } 121 122 var lastValidated = null; 123 var blurTimer = null; 124 var suppressValidation = false; 125 126 setApplyEnabled(false); 127 128 // -------------------------------------------------- 129 // Form submission loading state 130 // -------------------------------------------------- 131 132 $form.on('submit', function () { 133 134 var $btn = findApplyButton(); 135 if (! $btn.length || $btn.prop('disabled')) { 136 return false; 137 } 138 139 var originalHtml = $btn.html(); 140 141 $btn 142 .data('original-html', originalHtml) 143 .prop('disabled', true) 144 .addClass('disabled opacity-50') 145 .html( 146 '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>' + 147 'Saving...' 148 ); 149 }); 150 151 // -------------------------------------------------- 152 // Validation on blur 153 // -------------------------------------------------- 154
155 $inputs.on('blur', function () { 156 157 if (suppressValidation) { 158 return; 159 } 160 161 clearTimeout(blurTimer); 162 163 blurTimer = setTimeout(function () { 164 165 var data = collect(); 166 if (! data.address1 || ! data.city || ! data.state || ! data.zip) { 167 return; 168 } 169 170 var normalized = JSON.stringify({ 171 address1: normalize(data.address1), 172 address2: normalize(data.address2), 173 city: normalize(data.city), 174 state: normalize(data.state), 175 zip: normalize(data.zip) 176 }); 177 178 if (normalized === lastValidated) { 179 return; 180 } 181 lastValidated = normalized; 182 183 setApplyEnabled(false); 184 185 setAlert( 186 $suggestionBox, 187 'validating', 188 '<div class="d-flex align-items-center">' + 189 '<div class="spinner-border spinner-border-sm me-2"></div>' + 190 'Validating address...' + 191 '</div>' 192 ); 193 194 $.post('/wp-json/dpi/v1/validate-address', data) 195 .done(function (response) { 196 197 if (response && response.suggestion) { 198 var s = response.suggestion; 199 200 var differs = 201 normalize(s.address1) !== normalize(data.address1) || 202 normalize(s.address2) !== normalize(data.address2) || 203 normalize(s.city) !== normalize(data.city) || 204 normalize(s.state) !== normalize(data.state) || 205 normalize(s.zip) !== normalize(data.zip); 206 207 if (differs) { 208 209 var enteredHtml = 210 data.address1 + '<br>' + 211 (data.address2 ? data.address2 + '<br>' : '') + 212 data.city + ', ' + data.state + ' ' + data.zip; 213 214 var recommendedHtml = 215 s.address1 + '<br>' + 216 (s.address2 ? s.address2 + '<br>' : '') + 217 s.city + ', ' + s.state + ' ' + s.zip; 218 219 var html = 220 '<div class="fw-semibold w-100 mb-1 small">Confirm Address:</div>' + 221 222 '<div class="border border-2 rounded-3 p-2 mb-1">' + 223 '<label class="d-flex gap-2 align-items-start">' + 224 '<input type="radio" name="address_choice" value="entered">' + 225 '<div>' + 226 '<div class="fw-semibold fs-xs lh-sm">What you entered</div>' + 227 '<div class="small text-muted">' + enteredHtml + '</div>' + 228 '</div>' + 229 '</label>' + 230 '</div>' + 231 232 '<div class="border border-2 rounded-3 p-2">' + 233 '<label class="d-flex gap-2 align-items-start">' + 234 '<input type="radio" name="address_choice" value="recommended">' + 235 '<div>' + 236 '<div class="fw-semibold fs-xs lh-sm">Recommended</div>' + 237 '<div class="small text-muted">' + recommendedHtml + '</div>' + 238 '</div>' + 239 '</label>' + 240 '</div>'; 241 242 $suggestionBox 243 .data('entered', data) 244 .data('recommended', s); 245 246 setAlert($suggestionBox, 'suggestion', html); 247 setApplyEnabled(false); 248 return; 249 } 250 } 251 252 if (response && response.error) { 253 setAlert($suggestionBox, 'error', 'This address might be invalid or incomplete.'); 254 setApplyEnabled(false); 255 return; 256 } 257 258 setAlert($suggestionBox, 'success', 'Address validated successfully.'); 259 $suggestionBox.delay(2000).fadeOut(200); 260 setApplyEnabled(true); 261 }) 262 .fail(function () { 263 setAlert($suggestionBox, 'error', 'Unable to validate address at this time.'); 264 setApplyEnabled(false); 265 $suggestionBox.delay(2500).fadeOut(200); 266 }); 267 268 }, 300); 269 }); 270 271 // -------------------------------------------------- 272 // Radio selection = enable submit (no revalidation) 273 // -------------------------------------------------- 274 275 $form.on('change', 'input[name="address_choice"]', function () { 276 277 var choice = this.value; 278 var addr = $suggestionBox.data(choice); 279 280 if (choice === 'recommended') { 281 suppressValidation = true; 282 applyAddress(addr); 283 284 setTimeout(function () { 285 suppressValidation = false; 286 }, 0); 287 } 288 289 setApplyEnabled(true); 290 }); 291 } 292 293 // -------------------------------------------------- 294 // Lazy auto-init on first interaction 295 // -------------------------------------------------- 296 297 $(document).on(
298 'focus blur change', 299 'form.validate-address [name="address1"], ' + 300 'form.validate-address [name="address2"], ' + 301 'form.validate-address [name="city"], ' + 302 'form.validate-address [name="stateCode"], ' + 303 'form.validate-address [name="zipCode"]', 304 function () { 305 initForm($(this).closest('form.validate-address')); 306 } 307 ); 308 309});
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.