1(function($) { 2 'use strict'; 3 4 if (typeof chdFormFrontend === 'undefined') return; 5 6 // ââ reCAPTCHA v3 lazy loading ââââââââââââââââââ 7 var recaptchaLoaded = false; 8 if (chdFormFrontend.recaptcha_site_key) { 9 $(document).one('focus', '.chd-form input, .chd-form select, .chd-form textarea', function() { 10 if (recaptchaLoaded) return; 11 recaptchaLoaded = true; 12 var script = document.createElement('script'); 13 script.src = 'https://www.google.com/recaptcha/api.js?render=' + chdFormFrontend.recaptcha_site_key; 14 document.head.appendChild(script); 15 }); 16 } 17 18 // ââ Conditional Logic Evaluation ââââââââââââââââââ 19 function evaluateConditionals($form) { 20 var conditionals = chdFormFrontend.conditionals || {}; 21 $.each(conditionals, function(fieldId, config) { 22 var action = config.action || 'show'; // 'show' or 'hide' 23 var logic = config.logic || 'all'; // 'all' or 'any' 24 var rules = config.rules || []; 25 if (!rules.length) return; 26 27 var results = []; 28 $.each(rules, function(i, rule) { 29 results.push(evaluateRule($form, rule)); 30 }); 31 32 var passed = logic === 'all' 33 ? results.every(function(r) { return r; }) 34 : results.some(function(r) { return r; }); 35 36 var shouldShow = (action === 'show') ? passed : !passed; 37 var $field = $form.find('.chd-form-field').filter(function() { 38 return $(this).find('[name="' + fieldId + '"], [name="' + fieldId + '_first"], [name="' + fieldId + '[]"]').length > 0; 39 }); 40 if ($field.length) { 41 $field.toggle(shouldShow); 42 // Disable hidden fields so they don't submit 43 $field.find('input, select, textarea').prop('disabled', !shouldShow); 44 } 45 }); 46 } 47 48 function evaluateRule($form, rule) { 49 var fieldId = rule.field_id; 50 var operator = rule.operator; 51 var expected = rule.value || ''; 52 var val = getFieldValue($form, fieldId); 53 54 switch (operator) { 55 case 'is': return val === expected; 56 case 'is_not': return val !== expected; 57 case 'contains': return val.indexOf(expected) !== -1; 58 case 'not_contains': return val.indexOf(expected) === -1; 59 case 'starts_with': return val.indexOf(expected) === 0; 60 case 'ends_with': return val.slice(-expected.length) === expected; 61 case 'is_empty': return val === ''; 62 case 'is_not_empty': return val !== ''; 63 case 'is_checked': return val === '1'; 64 case 'is_not_checked': return val !== '1'; 65 case 'greater_than': return parseFloat(val) > parseFloat(expected); 66 case 'less_than': return parseFloat(val) < parseFloat(expected); 67 default: return false; 68 } 69 } 70 71 function getFieldValue($form, fieldId) { 72 // Checkbox group (multiple values) 73 var $checkboxes = $form.find('input[name="' + fieldId + '[]"]:checked'); 74 if ($checkboxes.length) { 75 return $checkboxes.map(function() { return $(this).val(); }).get().join(','); 76 } 77 // Radio 78 var $radios = $form.find('input[name="' + fieldId + '"]:radio'); 79 if ($radios.length) { 80 var $checked = $radios.filter(':checked'); 81 return $checked.length ? $checked.val() : ''; 82 } 83 // Single checkbox / consent 84 var $cb = $form.find('input[name="' + fieldId + '"][type="checkbox"]'); 85 if ($cb.length) return $cb.is(':checked') ? '1' : ''; 86 // Select / text / etc 87 var $input = $form.find('[name="' + fieldId + '"]'); 88 return $input.val() || ''; 89 } 90 91 // Bind change events and run initial evaluation 92 $(document).on('change input', '.chd-form input, .chd-form select, .chd-form textarea', function() { 93 evaluateConditionals($(this).closest('.chd-form')); 94 }); 95 // Initial run on page load 96 $('.chd-form').each(function() { 97 evaluateConditionals($(this)); 98 }); 99 100 // ââ Validation helpers ââââââââââââââââââ 101 function addError($input, msg) { 102 $input.addClass('chd-error'); 103 var $field = $input.closest('.chd-form-field'); 104 if (!$field.find('.chd-field-error').length) { 105 $field.append('<div class="chd-field-error">' + msg + '</div>'); 106 } 107 } 108 109 function validateForm($form) {
110 var valid = true; 111 $form.find('.chd-field-error').remove(); 112 $form.find('.chd-error').removeClass('chd-error'); 113 114 // Only validate visible, enabled fields 115 $form.find('input:visible:not(:disabled), textarea:visible:not(:disabled), select:visible:not(:disabled)').each(function() { 116 var $input = $(this); 117 var val = ($input.val() || '').toString().trim(); 118 var type = $input.attr('type') || ''; 119 var tagName = this.tagName.toLowerCase(); 120 121 // Required check 122 if ($input.prop('required') && (!val || (Array.isArray($input.val()) && !$input.val().length))) { 123 valid = false; 124 addError($input, 'This field is required.'); 125 return; // skip further checks for this field 126 } 127 128 // Skip empty optional fields 129 if (!val) return; 130 131 // Email 132 if (type === 'email') { 133 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) { 134 valid = false; 135 addError($input, 'Please enter a valid email address.'); 136 return; 137 } 138 } 139 140 // URL 141 if (type === 'url') { 142 if (!/^https?:\/\/.+\..+/.test(val)) { 143 valid = false; 144 addError($input, 'Please enter a valid URL (e.g. https://example.com).'); 145 return; 146 } 147 } 148 149 // Phone (tel) 150 if (type === 'tel') { 151 if (!/^[+\d][\d\s().-]{6,}$/.test(val)) { 152 valid = false; 153 addError($input, 'Please enter a valid phone number.'); 154 return; 155 } 156 } 157 158 // Number 159 if (type === 'number') { 160 var num = parseFloat(val); 161 if (isNaN(num)) { 162 valid = false; 163 addError($input, 'Please enter a valid number.'); 164 return; 165 } 166 var min = $input.attr('min'); 167 var max = $input.attr('max'); 168 if (min !== undefined && min !== '' && num < parseFloat(min)) { 169 valid = false; 170 addError($input, 'Value must be at least ' + min + '.'); 171 return; 172 } 173 if (max !== undefined && max !== '' && num > parseFloat(max)) { 174 valid = false; 175 addError($input, 'Value must be no more than ' + max + '.'); 176 return; 177 } 178 } 179 180 // Date 181 if (type === 'date') { 182 if (isNaN(Date.parse(val))) { 183 valid = false; 184 addError($input, 'Please enter a valid date.'); 185 return; 186 } 187 } 188 189 // Minlength 190 var minLen = $input.attr('minlength'); 191 if (minLen && val.length < parseInt(minLen, 10)) { 192 valid = false; 193 addError($input, 'Must be at least ' + minLen + ' characters.'); 194 return; 195 } 196 197 // Maxlength (browser enforces this but check anyway) 198 var maxLen = $input.attr('maxlength'); 199 if (maxLen && val.length > parseInt(maxLen, 10)) { 200 valid = false; 201 addError($input, 'Must be no more than ' + maxLen + ' characters.'); 202 return; 203 } 204 205 // Pattern 206 var pattern = $input.attr('pattern'); 207 if (pattern) { 208 var re = new RegExp('^(?:' + pattern + ')$'); 209 if (!re.test(val)) { 210 valid = false; 211 addError($input, $input.attr('title') || 'Please match the required format.'); 212 return; 213 } 214 } 215 }); 216 217 return valid; 218 } 219 220 // Clear errors on input 221 $(document).on('input change', '.chd-form input, .chd-form select, .chd-form textarea', function() { 222 var $input = $(this); 223 if ($input.hasClass('chd-error')) { 224 $input.removeClass('chd-error'); 225 $input.closest('.chd-form-field').find('.chd-field-error').remove(); 226 } 227 }); 228 229 function submitFormAjax($form) { 230 if ($form.data('chd-submitting')) return; 231 $form.data('chd-submitting', true); 232 233 var $btn = $form.find('.chd-form-btn'); 234 var $msg = $form.find('.chd-form-message'); 235 236 $btn.prop('disabled', true).addClass('is-loading'); 237 $msg.hide().removeClass('chd-form-success chd-form-error'); 238 239 var formData = new FormData($form[0]); 240 241 $.ajax({ 242 url: chdFormFrontend.ajax_url, 243 type: 'POST', 244 data: formData, 245 processData: false, 246 contentType: false, 247 success: function(res) { 248 $form.data('chd-submitting', false); 249 $btn.prop('disabled', false).removeClass('is-loading'); 250 251 if (res.success) { 252 if (res.data.redirect) { 253 window.location.href = res.data.redirect; 254 return; 255 } 256 $form.find('.chd-form-fields, .chd-form-submit').hide(); 257 $msg.html(res.data.message).addClass('chd-form-success').show(); 258 } else { 259 $msg.html(res.data.message || 'An error occurred.').addClass('chd-form-error').show(); 260 } 261 }, 262 error: function() { 263 $form.data('chd-submitting', false); 264 $btn.prop('disabled', false).removeClass('is-loading'); 265 $msg.html('An error occurred. Please try again.').addClass('chd-form-error').show(); 266 } 267 }); 268 } 269 270 $(document).on('submit', '.chd-form', function(e) { 271 e.preventDefault(); 272 var $form = $(this); 273 274 if ($form.data('chd-submitting')) return; 275 if (!validateForm($form)) return; 276 277 // If reCAPTCHA is configured, verify before submitting 278 if (chdFormFrontend.recaptcha_site_key && typeof grecaptcha !== 'undefined') { 279 var $btn = $form.find('.chd-form-btn'); 280 var $msg = $form.find('.chd-form-message'); 281 $btn.prop('disabled', true); 282 283 grecaptcha.ready(function() { 284 grecaptcha.execute(chdFormFrontend.recaptcha_site_key, { action: 'form_submit' }).then(function(token) { 285 // Append token to form data 286 if (!$form.find('input[name="chd_recaptcha_token"]').length) { 287 $form.append('<input type="hidden" name="chd_recaptcha_token">'); 288 } 289 $form.find('input[name="chd_recaptcha_token"]').val(token); 290 submitFormAjax($form); 291 }).catch(function() { 292 // reCAPTCHA failed â submit anyway (fail open) 293 submitFormAjax($form); 294 }); 295 }); 296 } else { 297 submitFormAjax($form); 298 } 299 }); 300 301})(jQuery);
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.