PageSourceSearch

https://bestforbritain.org/wp-content/plugins/chd-action-centre/assets/js/form-frontend.js?ver=1.0.231

js bestforbritain.org collected 2026-09-24 09:22:26 UTC 11,924 bytes, 301 lines download raw bytes

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.