PageSourceSearch

https://www.caymanairports.com/resources/pages/scripts/packages/ad…/new/formbuilder-front.js?v=1791100734

js caymanairports.com collected 2026-10-04 08:00:36 UTC 70,607 bytes, 1,685 lines download raw bytes

1import { badWordsList } from './badwordlist.js';
2
3const errorMessagesByType = {
4    checkbox: 'Field is required.',
5    date: 'Please select a date for {name}.',
6    email: 'Please enter a valid {name}.',
7    files: 'Please upload a file for {name}.',
8    matrix: 'Please complete the matrix for {name}.',
9    multi_select: 'Please select at least one option for {name}.',
10    'nf-code': 'Please enter the code for {name}.',
11    'nf-divider': '',
12    'nf-page-break': '',
13    'nf-text': 'Please enter a response for {name}.',
14    number: 'Please enter a number for {name}.',
15    phone_number: 'Please enter a phone number for {name}.',
16    rating: 'Please provide a rating for {name}.',
17    scale: 'Please select a scale value for {name}.',
18    select: 'Please select an option for {name}.',
19    signature: 'Please provide your signature for {name}.',
20    slider: 'Please adjust the slider for {name}.',
21    text: 'Please enter {name}.',
22    url: 'Please enter a valid URL for {name}.',
23    // ✅ New types based on logic (if used)
24    exists_in_submissions: 'The {name} already exists. Please provide a unique value.',
25    does_not_exist_in_submissions: 'The {name} does not exist in previous submissions.',
26};
27function getFormFields(formId) {
28    return window.formFieldsMap?.[formId] || [];
29}
30function buildFieldNameMap(formFields) {
31    const map = {};
32    formFields.forEach(f => {
33        map[f.id] = f.name || f.id;
34    });
35    return map;
36}
37window.formRuntime = window.formRuntime || {};
38
39
40(window.formIds || []).forEach(function (formId) {
41    const formFields = getFormFields(formId);
42    window.formRuntime[formId] = {
43        formFields,
44        fieldNameMap: buildFieldNameMap(formFields)
45    };
46    initFormBuilder(formId, formFields);
47});
48//alert(formId);
49//const formFields = getFormFields(formId);
50function initFormBuilder(formId, formFields) {
51    const rules = {};
52    const messages = {};
53    formFields.forEach(field => {
54        const fieldId = field.id;
55        const $form = $('#formbuildername_' + formId);
56        const nameAttr = $form.find(`[id="${fieldId}"]`).attr('name');
57        const fieldName = field.name || 'this field';
58        const placeholderName = field.placeholder || 'this field';
59        const inputType = field.type;
60        const multiLines = field?.multi_lines || false;
61        const actions = field?.logic?.actions || [];
62        const hasMakeItOptional = actions.includes('make-it-optional');
63        const hasMakeItRequied = actions.includes('require-answer');
64//            alert(hasMakeItRequied);
65
66        let hasRequired = false;
67        if (typeof field.required === 'undefined') {
68            hasRequired = false;
69        }
70        if ((field.required === '1' || field.required === 1 || field.required === true || field.required == true)) {
71            hasRequired = true;
72        }
73//            if ((field.required === '1' || field.required === 1 || field.required === true || hasMakeItRequied === true))
74//            {
75        if (inputType === 'select') {
76            const isWithoutDropdown = field?.without_dropdown;
77            if (isWithoutDropdown) {
78                if (hasRequired) {
79                    rules[`${fieldId}`] = {checkedRequired: `${fieldId}`};
80                    let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
81                    messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
82                    messages[`${fieldId}`] = {checkedRequired: messageTemplate};
83                }
84            } else {
85                rules[nameAttr] = {required: hasRequired};
86                let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
87                messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
88                messages[nameAttr] = {required: messageTemplate};
89            }
90        } else if (inputType === 'multi_select') {
91            const isWithoutDropdown = field?.without_dropdown;
92            if (isWithoutDropdown) {
93                if (hasRequired) {
94                    rules[`${fieldId}[]`] = {checkedRequired: `${fieldId}[]`};
95                    let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
96                    messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
97                    messages[`${fieldId}[]`] = {checkedRequired: messageTemplate};
98                }
99            } else {
100                rules[`${fieldId}[]`] = {required: hasRequired};
101                let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
102                messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
103                messages[`${fieldId}[]`] = {required: messageTemplate};
104            }
105        } else if (inputType === 'matrix') {
106            if (hasRequired) {
107                rules[fieldId] = {matrixRequired: fieldId};
108                let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
109                messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
110                messages[fieldId] = {matrixRequired: messageTemplate};
111            }
112        } else if (inputType === 'rating') {
113            if (hasRequired) {
114                rules[fieldId] = {ratingRequired: hasRequired};
115                let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
116                messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
117                messages[fieldId] = {ratingRequired: messageTemplate};
118            }
119        } else if (inputType === 'scale') {
120            if (hasRequired) {
121                rules[fieldId] = {scaleRequired: fieldId};
122                let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
123                messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
124                messages[fieldId] = {scaleRequired: messageTemplate};
125            }
126        } else if (inputType === 'slider') {
127            if (hasRequired) {
128                rules[fieldId] = {sliderRequired: hasRequired};
129                let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
130                messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
131                messages[fieldId] = {sliderRequired: messageTemplate};
132            }
133        } else if (inputType === 'files') {
134            rules[nameAttr] = {
135                required: hasRequired
136            };
137            let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
138            messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
139            messages[nameAttr] = {
140                required: messageTemplate
141            };
142            // ✅ Add file type rule if allowed_file_types is set
143            if (field.allowed_file_types && field.allowed_file_types.trim() !== '') {
144                rules[nameAttr].fileTypeAllowed = field.allowed_file_types;
145                messages[nameAttr].fileTypeAllowed = `Only files of type ${field.allowed_file_types} are allowed.`;
146            }
147            // ✅ Add file size rule if max_file_size is set
148            if (field.max_file_size && !isNaN(field.max_file_size)) {
149                rules[nameAttr].maxFileSize = field.max_file_size;
150                messages[nameAttr].maxFileSize = `Each file must be less than ${field.max_file_size} MB.`;
151            }
152        } else {
153            // Add custom validation by inputType
154            let customRule = {};
155            let customMsg = {};
156            if (inputType === 'phone' || fieldName.toLowerCase().includes('phone') || placeholderName.toLowerCase().includes('phone') || fieldName.toLowerCase().includes('fax') || placeholderName.toLowerCase().includes('fax')) {
157                customRule = { customPhoneFax: true };
158                // customMsg = { customPhoneFax: 'Only numbers, spaces, dash, plus, and parentheses allowed. Cannot be all zeros.' };
159            } else if (inputType === 'text') {
160                 if (multiLines === false) {
161                     customRule = { customName: true };
162                 } else {
163                    customRule = { customAddress: true };
164                 }
165                // customMsg = { customName: 'Only letters, spaces, dash, apostrophe, and underscore allowed.' };
166            } else if (inputType === 'email') {
167                customRule = { customEmail: true };
168                // customMsg = { customEmail: 'Only alphanumeric, @, ., _, dash, apostrophe allowed.' };
169            }
170            rules[nameAttr] = Object.assign({required: hasRequired}, {filterBadWords: true}, customRule);
171            let messageTemplate = errorMessagesByType[inputType] || 'Please complete {name}.';
172            messageTemplate = messageTemplate.replace('{name}', fieldName.toLowerCase());
173            messages[nameAttr] = Object.assign({required: messageTemplate}, customMsg);
174        }
175//            }
176        const hasLogicValidation = (
177                field?.validation?.error_conditions?.conditions?.children?.length > 0
178                );
179        if (hasLogicValidation) {
180            const logicFieldKey = field.id;
181            rules[logicFieldKey] = {
182                ...(rules[logicFieldKey] || {}),
183                logicValidation: {field}
184            };
185            // ✅ Leave out message so the callback above is used
186            messages[logicFieldKey] = {
187                ...(messages[logicFieldKey] || {})
188            };
189        }
190    }
191    );
192    // ✅ AFTER LOOP
193    rules['g-recaptcha-response'] = {
194        required: function () {
195            return grecaptcha.getResponse().length === 0;
196        }
197    };
198    messages['g-recaptcha-response'] = {
199        required: "Please verify that you're not a robot."
200    };
201    const $form = $('#formbuildername_' + formId);
202    $form.validate({
203        errorElement: 'span',
204        errorClass: 'help-block error',
205        ignore: [],
206        rules: rules,
207        messages: messages,
208        errorPlacement: function (error, element) {
209            const inputType = element.prop("type"); // <-- reliably gets input type 
210            var nameAttr = element.attr('name').replace(/\[\]$/, '');
211            if (
212                    element.attr('id')?.startsWith('g-recaptcha-response') ||
213                    element.attr("name") === "category"
214                    ) {
215                error.insertAfter(element.closest('.captcha_div'));
216            } else if (inputType == "checkbox") {
217                error.appendTo("#error_" + nameAttr);
218            } else if (inputType == "select-one") {
219                error.appendTo("#error_" + nameAttr);
220            } else if (inputType == "radio") {
221                error.appendTo("#error_" + nameAttr);
222            } else if (inputType == "select-multiple") {
223                error.appendTo("#error_" + nameAttr);
224            } else if (inputType == "hidden") {
225                error.appendTo("#error_" + nameAttr);
226            } else if (inputType == "range") {
227                error.appendTo("#error_" + nameAttr);
228            } else {
229                error.insertAfter(element);
230            }
231        },
232        invalidHandler: function () {
233            $('#formbuildername_' + formId)
234                    .find('.alert-danger')
235                    .show();
236        },
237        highlight: function (element) {
238            const $form = $(element).closest('form');
239            $form.find(`[name="${element.name}"]`)
240                    .closest('.form-group')
241                    .addClass('has-error');
242        },
243        unhighlight: function (element) {
244            const $form = $(element).closest('form');
245            $form.find(`[name="${element.name}"]`)
246                    .closest('.form-group')
247                    .removeClass('has-error');
248        },
249        submitHandler: function (form) {
250            const $form = $('#formbuildername_' + formId);
251            const $disabledFields = $form.find(':input:disabled');
252            $disabledFields.prop('disabled', false);
253            const isValid = $('#formbuildername_' + formId).valid();
254            $disabledFields.prop('disabled', true);
255            if (!isValid) {
256                $disabledFields.each(function () {
257                    $(this).valid();
258                });
259                return false;
260            }
261            checkLogic(formId);
262            form.submit();
263            return false; // prevent default for demo/testing
264        }
265    });
266}
267document.addEventListener('DOMContentLoaded', function () {
268    document.querySelectorAll('.char-count').forEach(function (counter) {
269        const targetId = counter.getAttribute('data-target');
270        const input = document.getElementById(targetId);
271        if (input) {
272            const updateCount = () => {
273                counter.textContent = input.value.length;
274            };
275            // Initial count
276            updateCount();
277            // Listen for changes
278            input.addEventListener('input', updateCount);
279        }
280    });
281});
282$(document).on('change', '.scale-radio', function () {
283    $(this).closest('.form-group').find('input.scale-radio').first().valid();
284});
285$(document).on('input change', '.form-range', function () {
286    $(this).valid();
287});
288$(document).ready(function () {
289        // --- Custom Validation Methods as per rules sheet ---
290        // 1. Full Name, Company Name, Business Name
291        $.validator.addMethod('customName', function (value, element, param) {
292            if (!value) return true;
293            let regex;
294            if (param === 'company' || param === 'business') {
295                // Allow: A-Z, a-z, 0-9, space, dash, apostrophe, underscore, ampersand, iOS special
296                regex = /^[A-Za-z0-9 _'\-&‘’\-–—]*$/;
297            } else {
298                // Full Name: A-Z, a-z, space, dash, apostrophe, underscore, iOS special
299                regex = /^[A-Za-z _'‘’\-–—,]*$/;
300            }
301            return regex.test(value);
302        }, 'Contains invalid characters.');
303
304        // 2. Phone/Fax Number
305        $.validator.addMethod('customPhoneFax', function (value, element) {
306            if (!value) return true;
307            // Allow: 0-9, space, dash, plus, parentheses, iOS special
308            const regex = /^[0-9 \-()+–—]*$/;
309            if (!regex.test(value)) return false;
310            // Disallow only zero digits
311            return !/^0+$/.test(value);
312        }, 'Please enter valid format.');
313
314        // 3. Email Address
315        $.validator.addMethod('customEmail', function (value, element) {
316            if (!value) return true;
317            // Allow: alphanumeric, @ . _ iOS special
318            const regex = /^[A-Za-z0-9@._'‘’\-–—]*$/;
319            return regex.test(value);
320        }, 'Invalid email address.');
321
322        // 4. Address
323        $.validator.addMethod('customAddress', function (value, element) {
324            if (!value) return true;
325            // Allow: alphanumeric, . , - # @ % & ( ) [ ] : space, iOS special
326            const regex = /^[A-Za-z0-9 .,#@%&()\[\]:_'‘’\-–—]*$/;
327            // Disallow HTML tags
328            if (!regex.test(value)) return false;fetch('./badwordlist.js')
329            return !/<[^>]*>/.test(value);
330        }, 'Contains invalid characters.');
331
332        $.validator.addMethod('filterBadWords', function (value, element, param) {
333            if (!value) return true;
334            var badWords = badWordsList;
335            var lowerValue = value.toLowerCase();
336
337            // Check if any bad word is present in the input
338            var hasBadWord = badWords.some(function(word) {
339                return lowerValue.includes(word);
340            });
341
342            return !hasBadWord; // valid only if no bad words found
343        }, 'Contains invalid characters.');
344        
345    $.validator.addMethod('maxFileSize', function (value, element, maxSizeMB) {
346        if (!value || !element.files || !element.files.length)
347            return true;
348        const maxSizeBytes = parseInt(maxSizeMB) * 1024 * 1024;
349        for (let i = 0; i < element.files.length; i++) {
350            if (element.files[i].size > maxSizeBytes) {
351                return false;
352            }
353        }
354        return true;
355    }, 'File size exceeds the maximum allowed.');
356    $.validator.addMethod('fileTypeAllowed', function (value, element, allowedTypesStr) {
357        if (!value)
358            return true; // Allow empty input if not required
359        const allowedTypes = allowedTypesStr.split(',').map(type => type.trim().toLowerCase());
360        const files = element.files;
361        for (let i = 0; i < files.length; i++) {
362            const ext = files[i].name.split('.').pop().toLowerCase();
363            if (!allowedTypes.includes(ext)) {
364                return false;
365            }
366        }
367        return true;
368    }, 'Invalid file type.');
369    $.validator.addMethod('checkedRequired', function (value, element, nameAttr) {
370        return $(`input[name="${nameAttr}"]:checked`).length > 0;
371    }, '');
372    $.validator.addMethod('matrixRequired', function (value, element, fieldId) {
373        let isValid = true;
374        // Check each row group like: a4afd09a...[Row 1]
375        $(`input[name^="${fieldId}["]`).each(function () {
376            const rowName = $(this).attr('name');
377            if ($(`input[name="${rowName}"]:checked`).length === 0) {
378                isValid = false;
379                return false; // break loop
380            }
381        });
382        return isValid;
383    }, '');
384    $.validator.addMethod("ratingRequired", function (value, element, param) {
385        // Consider value "0" or empty as unselected
386        return parseInt(value) > 0;
387    }, "");
388    $.validator.addMethod("scaleRequired", function (value, element, param) {
389        // Ensure one of the radios with this name is checked
390        return $(`input[name="${param}"]:checked`).length > 0;
391    }, "");
392    $.validator.addMethod("sliderRequired", function (value, element, param) {
393        // Slider should have a value greater than 0 to be considered valid
394        return parseInt(value) > 0;
395    }, "");
396    $.validator.addMethod('logicValidation', function (value, element, param) {
397        const field = param.field;
398        const $form = $(element).closest('form[data-form-id]');
399        if (!$form.length)
400            return true;
401        const formId = $form.data('form-id');
402        const {result} = buildMixedExpression(
403                field.validation.error_conditions.conditions,
404                field,
405                formId
406                );
407        return !result;
408    }, function (params, element) {
409        const field = params.field;
410        // ✅ Use static message if provided
411        if (field?.validation?.error_message?.trim()) {
412            return field.validation.error_message.trim();
413        }
414        const label = field.name || 'This field';
415        const conditions = field?.validation?.error_conditions?.conditions?.children || [];
416        if (!conditions.length)
417            return 'This field is invalid.';
418        const reverseOpMap = {
419            equals: 'must not be',
420            does_not_equal: 'must be',
421            contains: 'must not contain',
422            does_not_contain: 'must contain',
423            starts_with: 'must not start with',
424            ends_with: 'must not end with',
425            is_empty: 'must not be empty',
426            is_not_empty: 'must be empty',
427            content_length_equals: 'length must not equal',
428            content_length_does_not_equal: 'length must equal',
429            content_length_greater_than: 'length must not be greater than',
430            content_length_greater_than_or_equal_to: 'length must be less than',
431            content_length_less_than: 'length must not be less than',
432            content_length_less_than_or_equal_to: 'length must be greater than',
433            matches_regex: 'must not match',
434            does_not_match_regex: 'must match',
435            before: 'must not be before',
436            after: 'must not be after',
437            on_or_before: 'must be after',
438            on_or_after: 'must be before',
439            past_week: 'must not be within the past week',
440            past_month: 'must not be within the past month',
441            past_year: 'must not be within the past year',
442            next_week: 'must not be within the next week',
443            next_month: 'must not be within the next month',
444            next_year: 'must not be within the next year',
445            is_checked: 'must not be checked',
446            is_not_checked: 'must be checked',
447            exists_in_submissions: 'must not already exist in submissions',
448            does_not_exist_in_submissions: 'must already exist in submissions',
449        };
450        const parts = conditions.map(cond => {
451            const op = cond.value?.operator;
452            const expected = cond.value?.value;
453            const friendly = reverseOpMap[op] || `must not meet condition (${op})`;
454            if ([
455                'is_empty', 'is_not_empty',
456                'past_week', 'past_month', 'past_year',
457                'next_week', 'next_month', 'next_year',
458                'is_checked', 'is_not_checked'
459            ].includes(op)) {
460                return `${label} ${friendly}`;
461            }
462            return `${label} ${friendly} "${expected}"`;
463        }).filter(Boolean);
464        return `This field is invalid because: ${parts.join(' and ')}`;
465    });
466});
467window.logicDebounce = window.logicDebounce || {};
468//// Map field ID to label
469//window.formRuntime[formId] = {
470//    formFields,
471//    fieldNameMap: buildFieldNameMap(formFields)
472//};
473function evaluateCondition(operator, actual, expected, field) {
474    actual = actual || ''; // safeguard
475    const isDateComparison = field?.type === 'date';
476    if (isDateComparison) {
477        if (operator === 'is_empty') {
478            return actual === '' || actual === null || actual === undefined;
479        }
480        if (operator === 'is_not_empty') {
481            return actual !== '' && actual !== null && actual !== undefined;
482        }
483        if (actual === '' || expected === '') {
484            return false;
485        }
486        switch (operator) {
487            case 'equals':
488                return actual === expected;
489            case 'does_not_equal':
490                return actual !== expected;
491            case 'before':
492                return actual < expected;
493            case 'after':
494                return actual > expected;
495            case 'on_or_before':
496                return actual <= expected;
497            case 'on_or_after':
498                return actual >= expected;
499            case 'matches_regex':
500            try {
501                return new RegExp(expected).test(actual);
502            } catch (e) {
503                return false;
504            }
505            case 'does_not_match_regex':
506            try {
507                return !new RegExp(expected).test(actual);
508            } catch (e) {
509                return false;
510            }
511            case 'past_week':
512            {
513                const today = new Date();
514                const pastWeek = new Date();
515                pastWeek.setDate(pastWeek.getDate() - 7);
516                const todayStr = today.toISOString().split('T')[0];
517                const pastWeekStr = pastWeek.toISOString().split('T')[0];
518                return actual >= pastWeekStr && actual <= todayStr;
519            }
520            case 'past_month':
521            {
522                const today = new Date();
523                const pastMonth = new Date();
524                pastMonth.setMonth(pastMonth.getMonth() - 1);
525                const todayStr = today.toISOString().split('T')[0];
526                const pastMonthStr = pastMonth.toISOString().split('T')[0];
527                return actual >= pastMonthStr && actual <= todayStr;
528            }
529            case 'past_year':
530            {
531                const today = new Date();
532                const pastYear = new Date();
533                pastYear.setFullYear(pastYear.getFullYear() - 1);
534                const todayStr = today.toISOString().split('T')[0];
535                const pastYearStr = pastYear.toISOString().split('T')[0];
536                return actual >= pastYearStr && actual <= todayStr;
537            }
538            case 'next_week':
539            {
540                const today = new Date();
541                const nextWeek = new Date();
542                nextWeek.setDate(nextWeek.getDate() + 7);
543                const todayStr = today.toISOString().split('T')[0];
544                const nextWeekStr = nextWeek.toISOString().split('T')[0];
545                return actual >= todayStr && actual <= nextWeekStr;
546            }
547            case 'next_month':
548            {
549                const today = new Date();
550                const nextMonth = new Date();
551                nextMonth.setMonth(nextMonth.getMonth() + 1);
552                const todayStr = today.toISOString().split('T')[0];
553                const nextMonthStr = nextMonth.toISOString().split('T')[0];
554                return actual >= todayStr && actual <= nextMonthStr;
555            }
556            case 'next_year':
557            {
558                const today = new Date();
559                const nextYear = new Date();
560                nextYear.setFullYear(nextYear.getFullYear() + 1);
561                const todayStr = today.toISOString().split('T')[0];
562                const nextYearStr = nextYear.toISOString().split('T')[0];
563                return actual >= todayStr && actual <= nextYearStr;
564            }
565            default:
566                return false;
567        }
568    } else if (field?.type === 'phone_number') {
569        switch (operator) {
570            case 'equals':
571                return actual === expected;
572            case 'does_not_equal':
573                return actual !== expected;
574            case 'contains':
575                return actual.includes(expected);
576            case 'does_not_contain':
577                return !actual.includes(expected);
578            case 'starts_with':
579                return actual.startsWith(expected);
580            case 'ends_with':
581                return actual.endsWith(expected);
582            case 'is_empty':
583                return actual.trim() === '';
584            case 'is_not_empty':
585                return actual.trim() !== '';
586            case 'content_length_equals':
587                return actual.length === parseInt(expected);
588            case 'content_length_does_not_equal':
589                return actual.length !== parseInt(expected);
590            case 'content_length_greater_than':
591                return actual.length > parseInt(expected);
592            case 'content_length_greater_than_or_equal_to':
593                return actual.length >= parseInt(expected);
594            case 'content_length_less_than':
595                return actual.length < parseInt(expected);
596            case 'content_length_less_than_or_equal_to':
597                return actual.length <= parseInt(expected);
598            case 'matches_regex':
599            try {
600                return new RegExp(expected).test(actual);
601            } catch (e) {
602                return false;
603            }
604            case 'does_not_match_regex':
605            try {
606                return !new RegExp(expected).test(actual);
607            } catch (e) {
608                return false;
609            }
610            // ✅ New operators for phone_number
611            case 'exists_in_submissions':
612                return checkIfExistsInSubmissions(field.id, actual);
613            case 'does_not_exist_in_submissions':
614                return !checkIfExistsInSubmissions(field.id, actual);
615            default:
616                return false;
617        }
618    } else if (field?.type === 'email') {
619        switch (operator) {
620            case 'equals':
621                return actual === expected;
622            case 'does_not_equal':
623                return actual !== expected;
624            case 'contains':
625                return actual.includes(expected);
626            case 'does_not_contain':
627                return !actual.includes(expected);
628            case 'starts_with':
629                return actual.startsWith(expected);
630            case 'ends_with':
631                return actual.endsWith(expected);
632            case 'is_empty':
633                return actual === '';
634            case 'is_not_empty':
635                return actual !== '';
636            case 'content_length_equals':
637                return actual.length === parseInt(expected);
638            case 'content_length_does_not_equal':
639                return actual.length !== parseInt(expected);
640            case 'content_length_greater_than':
641                return actual.length > parseInt(expected);
642            case 'content_length_greater_than_or_equal_to':
643                return actual.length >= parseInt(expected);
644            case 'content_length_less_than':
645                return actual.length < parseInt(expected);
646            case 'content_length_less_than_or_equal_to':
647                return actual.length <= parseInt(expected);
648            case 'matches_regex':
649            try {
650                return new RegExp(expected).test(actual);
651            } catch (e) {
652                return false;
653            }
654            case 'does_not_match_regex':
655            try {
656                return !new RegExp(expected).test(actual);
657            } catch (e) {
658                return false;
659            }
660            // ✅ New operators for email
661            case 'exists_in_submissions':
662                return checkIfExistsInSubmissions(field.id, actual);
663            case 'does_not_exist_in_submissions':
664                return !checkIfExistsInSubmissions(field.id, actual);
665            default:
666                return false;
667        }
668    } else if (field?.type === 'checkbox') {
669        const isChecked = actual === '1' || actual === true || actual === 'true';
670        switch (operator) {
671            case 'is_checked':
672                return isChecked;
673            case 'is_not_checked':
674                return !isChecked;
675            default:
676                return false;
677        }
678    } else if (field?.type === 'multi_select') {
679        switch (operator) {
680            case 'contains':
681                return actual.includes(expected);
682            case 'does_not_contain':
683                return !actual.includes(expected);
684            case 'is_empty':
685                return actual.length === 0;
686            case 'is_not_empty':
687                return actual.length > 0;
688        }
689    } else if (field?.type === 'matrix') {
690        const normalizedActual = {};
691        const normalizedExpected = {};
692        for (const key in expected) {
693            normalizedActual[key] = String(actual[key] ?? '');
694            normalizedExpected[key] = String(expected[key]);
695        }
696        const actualStr = JSON.stringify(normalizedActual);
697        const expectedStr = JSON.stringify(normalizedExpected);
698        switch (operator) {
699            case 'equals':
700                return actualStr === expectedStr;
701            case 'does_not_equal':
702                return actualStr !== expectedStr;
703            case 'contains':
704                return Object.entries(expected).every(([row, val]) => actual[row] === val);
705            case 'does_not_contain':
706                return Object.entries(expected).every(([row, val]) => actual[row] !== val);
707        }
708    } else if (['number', 'rating', 'scale', 'slider'].includes(field?.type)) {
709        const actualNumber = parseFloat(actual);
710        const expectedNumber = parseFloat(expected);
711//        alert(actualNumber);
712//        alert(expectedNumber);
713        switch (operator) {
714            case 'equals':
715                return actualNumber === expectedNumber;
716            case 'does_not_equal':
717                return actualNumber !== expectedNumber;
718            case 'greater_than':
719                return actualNumber > expectedNumber;
720            case 'less_than':
721                return actualNumber < expectedNumber;
722            case 'greater_than_or_equal_to':
723                return actualNumber >= expectedNumber;
724            case 'less_than_or_equal_to':
725                return actualNumber <= expectedNumber;
726            case 'is_empty':
727                return actual === '' || isNaN(actualNumber);
728            case 'is_not_empty':
729                return actual !== '';
730            case 'content_length_equals':
731                return String(actual).length === Number(expected);
732            case 'content_length_does_not_equal':
733                return String(actual).length !== Number(expected);
734            case 'content_length_greater_than':
735                return String(actual).length > Number(expected);
736            case 'content_length_greater_than_or_equal_to':
737                return String(actual).length >= Number(expected);
738            case 'content_length_less_than':
739                return String(actual).length < Number(expected);
740            case 'content_length_less_than_or_equal_to':
741                return String(actual).length <= Number(expected);
742                // ✅ New operator cases
743            case 'exists_in_submissions':
744                return checkIfExistsInSubmissions(field.id, actual);
745            case 'does_not_exist_in_submissions':
746                return !checkIfExistsInSubmissions(field.id, actual);
747            default:
748                return false;
749        }
750    } else if (field?.type === 'files') {
751        switch (operator) {
752            case 'is_empty':
753                return actual === 0 || actual === '' || actual === null;
754            case 'is_not_empty':
755                return actual !== 0 && actual !== '' && actual !== null;
756            default:
757                return false;
758        }
759    } else {
760        switch (operator) {
761            case 'equals':
762                return actual === expected;
763            case 'does_not_equal':
764                return actual !== expected;
765            case 'contains':
766                return actual.toLowerCase().includes(expected.toLowerCase());
767            case 'does_not_contain':
768                return !actual.toLowerCase().includes(expected.toLowerCase());
769            case 'starts_with':
770                return actual.toLowerCase().startsWith(expected.toLowerCase());
771            case 'ends_with':
772                return actual.toLowerCase().endsWith(expected.toLowerCase());
773            case 'is_empty':
774                return actual.trim() === '';
775            case 'is_not_empty':
776                return actual.trim() !== '';
777            case 'content_length_equals':
778                return actual.length === parseInt(expected);
779            case 'content_length_does_not_equal':
780                return actual.length !== parseInt(expected);
781            case 'content_length_greater_than':
782                return actual.length > parseInt(expected);
783            case 'content_length_greater_than_or_equal_to':
784                return actual.length >= parseInt(expected);
785            case 'content_length_less_than':
786                return actual.length < parseInt(expected);
787            case 'content_length_less_than_or_equal_to':
788                return actual.length <= parseInt(expected);
789            case 'matches_regex':
790            try {
791                return new RegExp(expected).test(actual);
792            } catch (e) {
793                return false;
794            }
795            case 'does_not_match_regex':
796            try {
797                return !new RegExp(expected).test(actual);
798            } catch (e) {
799                return false;
800            }
801            // ✅ New cases
802            case 'exists_in_submissions':
803                // You should implement this function based on actual stored submission data
804                return checkIfExistsInSubmissions(field.id, actual); // returns true/false
805            case 'does_not_exist_in_submissions':
806                return !checkIfExistsInSubmissions(field.id, actual); // returns true/false
807            default:
808                return false;
809        }
810    }
811}
812function checkIfExistsInSubmissions(fieldId, value) {
813    const existingValues = window.submissionDatabase?.[fieldId] || [];
814    return existingValues.includes(value);
815}
816function getReadableCondition(label, operator, expected, actual, result, isDate = false) {
817    const opMap = {
818        equals: '==',
819        does_not_equal: '!=',
820        contains: 'contains',
821        does_not_contain: 'does not contain',
822        starts_with: 'starts with',
823        ends_with: 'ends with',
824        is_empty: 'is empty',
825        is_not_empty: 'is not empty',
826        content_length_equals: 'length ==',
827        content_length_does_not_equal: 'length !=',
828        content_length_greater_than: 'length >',
829        content_length_greater_than_or_equal_to: 'length >=',
830        content_length_less_than: 'length <',
831        content_length_less_than_or_equal_to: 'length <=',
832        matches_regex: 'matches',
833        does_not_match_regex: 'does not match',
834        before: '<',
835        after: '>',
836        on_or_before: '<=',
837        on_or_after: '>=',
838        past_week: 'within past week',
839        past_month: 'within past month',
840        past_year: 'within past year',
841        next_week: 'within next week',
842        next_month: 'within next month',
843        next_year: 'within next year',
844        is_checked: 'is checked',
845        is_not_checked: 'is not checked',
846        exists_in_submissions: 'exists in submissions',
847        does_not_exist_in_submissions: 'does not exist in submissions'
848    };
849    const symbol = opMap[operator] || operator;
850    let str;
851    if (
852            [
853                'is_empty',
854                'is_not_empty',
855                'is_checked',
856                'is_not_checked',
857                'exists_in_submissions',
858                'does_not_exist_in_submissions'
859            ].includes(operator)
860            ) {
861        str = `${label} ${symbol}`;
862    } else if (operator.includes('past_') || operator.includes('next_')) {
863        str = `${label} ${symbol} (actual: "${actual}")`;
864    } else {
865        str = `${label} ${symbol} "${expected}" (actual: "${actual}")`;
866    }
867    return {conditionStr: str, result};
868}
869
870function evalNode(node, field, formId) {
871    if (node.value) {
872        let expected = node.value.value || '';
873        const operator = node.value.operator || '';
874        const $form = $('#formbuildername_' + formId);
875        let actual = $form.find(`[name="${node.identifier}"]`).val() || '';
876        const {formFields} = window.formRuntime[formId];
877        const sourceField = formFields.find(f => f.id === node.identifier) || {};
878        if (sourceField.type === 'phone_number') {
879            const countryVal = $form.find(`[name="${node.identifier}_country"]`).val() || '';
880            actual = `${countryVal}${actual}`;
881        } else if (sourceField.type === 'date') {
882            const dateFormat = sourceField?.date_format || 'dd/MM/yyyy';
883            let fieldValue = $form.find(`[name="${node.identifier}"]`).val()
884                    || $(`#${node.identifier}`).val()
885                    || $(`[data-field-id="${node.identifier}"]`).val() || '';
886            let actualDateStr = '';
887            if (fieldValue.trim()) {
888                let actualDatePart = fieldValue.trim().split(' ')[0];
889                if (dateFormat === 'dd/MM/yyyy') {
890                    const [day, month, year] = actualDatePart.split('/');
891                    if (day && month && year) {
892                        actualDateStr = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
893                    }
894                } else if (dateFormat === 'MM-dd-yyyy') {
895                    const [month, day, year] = actualDatePart.split('-');
896                    if (month && day && year) {
897                        actualDateStr = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
898                    }
899                } else {
900                    actualDateStr = actualDatePart;
901                }
902                actual = actualDateStr;
903            }
904            if (expected != true) {
905                if (expected.includes('T')) {
906                    expected = expected.split('T')[0];
907                } else if (expected.includes(' ')) {
908                    expected = expected.split(' ')[0];
909                }
910            }
911        } else if (sourceField.type === 'checkbox') {
912            actual = $form.find(`[name="${node.identifier}"]`).is(':checked') ? '1' : '0';
913        } else if (sourceField.type === 'select') {
914            const isWithoutDropdown = sourceField?.without_dropdown;
915            if (isWithoutDropdown) {
916                actual = ($form.find(`[name="${node.identifier}"]:checked`).val() || '').trim();
917            } else {
918                actual = ($(`#${node.identifier}`).val() || '').trim();
919            }
920        } else if (sourceField.type === 'multi_select') {
921            const isWithoutDropdown = sourceField?.without_dropdown;
922            actual = isWithoutDropdown
923                    ? $(`input[name="${node.identifier}[]"]:checked`).map(function () {
924                return $(this).val().trim();
925            }).get()
926                    : ($(`#${node.identifier}`).val() || []).map(val => val.trim());
927        } else if (sourceField.type === 'matrix') {
928            actual = {};
929            $(`#div_${node.identifier} table tbody tr`).each(function () {
930                const rowLabel = $(this).find('th').text().trim();
931                const selectedVal = $(this).find('input[type="radio"]:checked').val() || '';
932                actual[rowLabel] = selectedVal;
933            });
934        } else if (sourceField.type === 'number' || sourceField.type === 'scale' || sourceField.type === 'slider') {
935            actual = $form.find(`[name="${node.identifier}"]:checked`).val() || '';
936        } else if (sourceField.type === 'files') {
937            const inputEl = document.getElementById(node.identifier);
938            actual = (inputEl?.files || []).length;
939        }
940        const result = evaluateCondition(operator, actual, expected, sourceField);
941        const {fieldNameMap} = window.formRuntime[formId];
942        const label = fieldNameMap[node.identifier];
943        const {conditionStr: expr} = getReadableCondition(label, operator, expected, actual, result);
944        return {result, expr: `(${expr})`};
945    }
946    if (node.children && Array.isArray(node.children)) {
947        const logicOp = (node.operatorIdentifier || 'and').toUpperCase();
948        const results = node.children.map(child => evalNode(child, field, formId));
949        const combinedResult = logicOp === 'AND'
950                ? results.every(r => r.result)
951                : results.some(r => r.result);
952        const exprs = results.map(r =>
952 r.expr).filter(Boolean);
953        if (logicOp === 'OR' && exprs.length === 1) {
954            return {result: combinedResult, expr: exprs[0]};
955        } else {
956            return {result: combinedResult, expr: exprs.join(` ${logicOp} `)};
957        }
958    }
959    return {result: false, expr: ''};
960}
961function buildMixedExpression(node, field, formId) {
962    if (!node.children)
963        return evalNode(node, field, formId);
964    const rootOperator = (node.operatorIdentifier || 'and').toUpperCase();
965    const childResults = node.children.map(child =>
966        child.children
967                ? buildMixedExpression(child, field, formId)
968                : evalNode(child, field, formId)
969    );
970    const combinedResult = rootOperator === 'OR'
971            ? childResults.some(r => r.result)
972            : childResults.every(r => r.result);
973    return {
974        result: combinedResult,
975        expr: childResults.map(r => r.expr).join(` ${rootOperator} `)
976    };
977}
978function checkLogic(formId) {
979    const {formFields, fieldNameMap} = window.formRuntime[formId];
980    formFields.forEach(field => {
981        if (!field.logic?.conditions?.children)
982            return;
983        const {result: finalResult, expr: fullExpression} = buildMixedExpression(field.logic.conditions, field, formId);
984        const $form = $('#formbuildername_' + formId);
985        const fieldWrapper = $form.find(`#div_${field.id}`)
986        const {$fieldInput, nameAttr, $label} = getFieldElements(field, formId);
987        field.logic.actions.forEach(action => {
988            if (action === 'hide-block') {
989                if (finalResult) {
990                    // ❌ HIDE block
991                    fieldWrapper.addClass('d-none');
992                    // Remove validation & errors when hidden
993                    const {$fieldInput} = getFieldElements(field, formId);
994                    if ($fieldInput?.length) {
995                        $fieldInput.rules('remove');
996                    }
997                    $form.find(`#error_${field.id}`).empty();
998                } else {
999                    // ✅ SHOW block
1000                    fieldWrapper.removeClass('d-none');
1001                }
1002            } else if (action === 'show-block') {
1003                if (finalResult) {
1004                    // ✅ SHOW block
1005                    fieldWrapper.removeClass('d-none').show();
1006                    // OPTIONAL: re-enable inputs if previously disabled
1007                    fieldWrapper.find('input, textarea, select, button').each(function () {
1008                        const $el = $(this);
1009                        if (this.tomselect) {
1010                            this.tomselect.enable();
1011                        } else {
1012                            $el.prop('disabled', false);
1013                        }
1014                    });
1015                } else {
1016                    // ❌ HIDE block
1017                    fieldWrapper.addClass('d-none').hide();
1018                    // OPTIONAL: clear validation errors when hidden
1019                    fieldWrapper.find('.error').remove();
1020                    // OPTIONAL: remove validation rules to avoid hidden-field validation
1021                    const {$fieldInput} = getFieldElements(field, formId);
1022                    if ($fieldInput?.length) {
1023                        $fieldInput.rules('remove');
1024                    }
1025                }
1026            } else if (action === 'disable-block') {
1027                fieldWrapper.find('input, textarea, select, button').each(function () {
1028                    const $el = $(this);
1029                    const shouldApply = finalResult;
1030                    if (this.tomselect) {
1031                        shouldApply ? this.tomselect.disable() : this.tomselect.enable();
1032                    } else if (field?.type === 'rating') {
1033                        const ratingId = $el.attr('id');
1034                        $(`#${ratingId}-rating .star`).css({
1035                            'pointer-events': shouldApply ? 'none' : '',
1036                            'opacity': shouldApply ? '0.4' : ''
1037                        });
1038                    } else if (field?.type === 'scale') {
1039                        $(`#scale-group-${field.id} label.scale-box`).css({
1040                            'pointer-events': shouldApply ? 'none' : '',
1041                            'opacity': shouldApply ? '0.4' : ''
1042                        });
1043                    } else {
1044                        $el.prop('disabled', shouldApply);
1045                    }
1046                });
1047            } else if (action === 'require-answer') {
1048                const $requiredMark = $label.find('span.text-danger');
1049                if (finalResult) {
1050                    if ($label.length && !$requiredMark.length) {
1051                        $label.append('<span class="text-danger">*</span>');
1052                    }
1053                    const rulesObj = {};
1054                    const messagesObj = {};
1055                    const messageTemplate = errorMessagesByType[field.type] || 'Please complete {name}.';
1056                    const messageText = messageTemplate.replace('{name}', (field.name || 'this field').toLowerCase());
1057                    if (['select', 'multi_select'].includes(field.type) && field.without_dropdown) {
1058                        rulesObj.checkedRequired = resolveValidationKey(field, formId);
1059                        messagesObj.checkedRequired = messageText;
1060                    } else if (field.type === 'matrix') {
1061                        rulesObj.matrixRequired = field.id;
1062                        messagesObj.matrixRequired = messageText;
1063                    } else if (field.type === 'rating') {
1064                        rulesObj.ratingRequired = true;
1065                        messagesObj.ratingRequired = messageText;
1066                    } else if (field.type === 'scale') {
1067                        rulesObj.scaleRequired = field.id;
1068                        messagesObj.scaleRequired = messageText;
1069                    } else if (field.type === 'slider') {
1070                        rulesObj.sliderRequired = true;
1071                        messagesObj.sliderRequired = messageText;
1072                    } else if (field.type === 'files') {
1073                        rulesObj.required = true;
1074                        messagesObj.required = messageText;
1075                    } else {
1076                        rulesObj.required = true;
1077                        messagesObj.required = messageText;
1078                    }
1079                    applyValidationRules(field, rulesObj, messagesObj, formId);
1080                } else {
1081                    $requiredMark.remove();
1082                    if ($fieldInput?.length) {
1083                        $fieldInput.rules('remove');
1084                    }
1085                    $form.find(`#error_${field.id}`).empty();
1086                }
1087            } else if (action === 'make-it-optional') {
1088                const $requiredMark = $label.find('span.text-danger');
1089                // 🚫 Do nothing if field is hidden
1090                if (fieldWrapper.hasClass('d-none')) {
1091                    return;
1092                }
1093                if (finalResult) {
1094                    // ✅ MAKE OPTIONAL
1095                    $requiredMark.remove();
1096                    if ($fieldInput?.length) {
1097                        $fieldInput.rules('remove');
1098                    }
1099                    // Clear existing error
1100                    $form.find(`#error_${field.id}`).empty();
1101                } else {
1102                    // ❗ MAKE REQUIRED
1103                    if ($label.length && !$requiredMark.length) {
1104                        $label.append('<span class="text-danger">*</span>');
1105                    }
1106                    if ($fieldInput?.length) {
1107                        const messageTemplate =
1108                                errorMessagesByType[field.type] || 'Please complete {name}.';
1109                        const messageText = messageTemplate.replace(
1110                                '{name}',
1111                                (field.name || 'this field').toLowerCase()
1112                                );
1113                        // Add rule silently (no immediate validation)
1114                        const validator = $form.data('validator');
1115                        const prevOnKeyUp = validator?.settings.onkeyup;
1116                        const prevOnBlur = validator?.settings.onblur;
1117                        if (validator) {
1118                            validator.settings.onkeyup = false;
1119                            validator.settings.onblur = false;
1120                        }
1121                        $fieldInput.rules('add', {
1122                            required: true,
1123                            messages: {
1124                                required: messageText
1125                            }
1126                        });
1127                        if (validator) {
1128                            validator.settings.onkeyup = prevOnKeyUp;
1129                            validator.settings.onblur = prevOnBlur;
1130                        }
1131                    }
1132                }
1133            }
1134        });
1135    });
1136}
1137function applyValidationRules(field, rulesObj, messagesObj, formId) {
1138    const validationKey = resolveValidationKey(field, formId);
1139    const $form = $('#formbuildername_' + formId);
1140    const $input = $form.find(`[name="${validationKey}"]`);
1141    if ($input.length) {
1142        $input.rules('add', {
1143            ...rulesObj,
1144            messages: messagesObj
1145        });
1146//        $input.valid();
1147    }
1148}
1149
1150function resolveValidationKey(field, formId) {
1151    const $form = $('#formbuildername_' + formId);
1152    const inputType = field.type;
1153    const fieldId = field.id;
1154    switch (inputType) {
1155        case 'select':
1156            return field.without_dropdown ? `${fieldId}` : $form.find(`[id="${fieldId}"]`).attr('name');
1157        case 'multi_select':
1158            return field.without_dropdown ? `${fieldId}[]` : `${fieldId}[]`;
1159        case 'matrix':
1160        case 'rating':
1161        case 'scale':
1162        case 'slider':
1163            return `${fieldId}`;
1164        case 'files':
1165            const name = $form.find(`[id="${fieldId}"]`).attr('name');
1166            return name || fieldId;
1167        default:
1168            const name1 = $form.find(`[id="${fieldId}"]`).attr('name');
1169            return name1 || fieldId;
1170    }
1171}
1172function getFieldElements(field, formId) {
1173    const $form = $('#formbuildername_' + formId);
1174    const fieldId = field.id;
1175    let $fieldInput = $form.find(`[id="${fieldId}"]`);
1176    if ($fieldInput.length === 0) {
1177        $fieldInput = $form.find(`[name="${fieldId}"]`).first();
1178        if ($fieldInput.length === 0) {
1179            $fieldInput = $form.find(`[name="${fieldId}[]"]`).first();
1180        }
1181    }
1182    const nameAttr = $fieldInput.attr('name');
1183    const $label = $form.find(`#div_${fieldId} .cm-label`);
1184    return {$fieldInput, nameAttr, $label};
1185}
1186
1187$(document).on('input change', 'input, textarea, select', function () {
1188    const $form = $(this).closest('form[data-form-id]');
1189    if (!$form.length)
1190        return;
1191    const formId = $form.data('form-id');
1192    clearTimeout(window.logicDebounce[formId]);
1193    window.logicDebounce[formId] = setTimeout(() => {
1194        checkLogic(formId);
1195    }, 500);
1196});
1197function KeycheckOnlyPhonenumber(e) {
1198    var t = 0;
1199    t = document.all ? 3 : document.getElementById ? 1 : document.layers ? 2 : 0;
1200    if (document.all)
1201        e = window.event;
1202    var n = "";
1203    var r = "";
1204    if (t == 2) {
1205        if (e.which > 0)
1206            n = "(" + String.fromCharCode(e.which) + ")";
1207        r = e.which
1208    } else {
1209        if (t == 3) {
1210            r = window.event ? event.keyCode : e.which
1211        } else {
1212            if (e.charCode > 0)
1213                n = "(" + String.fromCharCode(e.charCode) + ")";
1214            r = e.charCode
1215        }
1216    }
1217    if (r >= 65 && r <= 90 || r >= 97 && r <= 122 || r >= 33 && r <= 39 || r >= 42 && r <= 42 || r >= 44 && r <= 44 || r >= 46 && r <= 47 || r >= 58 && r <= 64 || r >= 91 && r <= 96 || r >= 123 && r <= 126) {
1218        return false
1219    }
1220    return true
1221}
1222$(document).on('click', 'button[type="submit"]', function (e) {
1223    const $form = $(this).closest('form[data-form-id]');
1224    if (!$form.length)
1225        return;
1226    const formId = $form.data('form-id');
1227    checkLogic(formId); // ✅ scoped to correct form
1228});
1229
1230//Break Page
1231//document.addEventListener("DOMContentLoaded", function () {
1232//    const $formRoot = $('#form-root');
1233//    if (!$formRoot.length)
1234//        return;
1235//    const $children = $formRoot.children().toArray();
1236//    const sections = [];
1237//    let $currentSection = $('<div class="nf-section row mx-auto"></div>');
1238//    let hasPageBreak = false;
1239//    // Build sections
1240//    $children.forEach(function (el) {
1241//        const $el = $(el);
1242//        if ($el.hasClass('nf-page-break')) {
1243//            hasPageBreak = true;
1244//            if ($currentSection.children().length) {
1245//                sections.push($currentSection);
1246//                $formRoot.append($currentSection);
1247//            }
1248//            $currentSection = $('<div class="nf-section row mx-auto"></div>');
1249//            $el.remove();
1250//        } else {
1251//            $currentSection.append($el);
1252//        }
1253//    });
1254//    if ($currentSection.children().length) {
1255//        sections.push($currentSection);
1256//        $formRoot.append($currentSection);
1257//    }
1258//    /**
1259//     * ------------------------------------------------
1260//     * CASE 2: NO PAGE BREAK → SIMPLE FORM
1261//     * ------------------------------------------------
1262//     */
1263//    if (!hasPageBreak) {
1264//        const $submitDiv = $('#submitDiv');
1265//        if ($submitDiv.length) {
1266//            const $submitBtn = $('<button type="submit" class="btn btn-primary default">Submit</button>');
1267//            $submitDiv.append($submitBtn);
1268//        }
1269//        return;
1270//    }
1271//    /**
1272//     * ------------------------------------------------
1273//     * CASE 1: PAGE BREAK EXISTS → MULTI STEP FORM
1274//     * ------------------------------------------------
1275//     */
1276//    // Add Prev / Next buttons to each section
1277//    sections.forEach(function ($section, index) {
1278//        const $nav = $('<div class="nf-nav-buttons d-flex justify-content-between gap-2 mt-4"></div>');
1279//        const $prevBtn = $('<button type="button" class="btn btn-tertiary nf-prev-btn">Previous</button>');
1280//        const $nextBtn = $('<button type="button" class="btn btn-primary nf-next-btn">Next</button>');
1281//        $prevBtn.on('click', function () {
1282//            showSection(index - 1);
1283//        });
1284//        $nextBtn.on('click', function () {
1285//            showSection(index + 1);
1286//        });
1287//        $nav.append($prevBtn).append($nextBtn);
1288//        const $captchaMarker = $section.find('.nf-captcha-marker');
1289//        if ($captchaMarker.length) {
1290//            $nav.insertBefore($captchaMarker);
1291//        } else {
1292//            $section.append($nav);
1293//        }
1294//    });
1295//    // ✅ Add Submit button INTO the same nav row on the last section
1296//    const $lastSection = sections[sections.length - 1];
1297//    const $lastNav = $lastSection.find('.nf-nav-buttons').first();
1298//    const $submitBtn = $('<button type="submit" class="btn btn-primary nf-submit-btn">Submit</button>');
1299//    $lastNav.append($submitBtn);
1300//    let currentIndex = 0;
1301//    function showSection(index) {
1302//        sections.forEach(function ($sec, i) {
1303//            $sec.toggleClass('d-none', i !== index);
1304//        });
1305//        const $nav = sections[index].find('.nf-nav-buttons');
1306//        const $prevBtn = $nav.find('.nf-prev-btn');
1307//        const $nextBtn = $nav.find('.nf-next-btn');
1308//        // Prev visibility
1309//        if (index === 0) {
1310//            $prevBtn.addClass('d-none');
1311//        } else {
1312//            $prevBtn.removeClass('d-none');
1313//        }
1314//        // Next visibility (kept visible as per requirement)
1315//        if (index === sections.length - 1) {
1316//            $nextBtn.addClass('d-none'); // remove this line if you want Next visible
1317//        } else {
1318//            $nextBtn.removeClass('d-none');
1319//        }
1320//        currentIndex = index;
1321//    }
1322//    // Init
1323//    sections.forEach($sec => $sec.addClass('d-none'));
1324//    showSection(0);
1325//});
1326
1327document.addEventListener("DOMContentLoaded", function () {
1328
1329    // 🔁 LOOP EACH FORM (THIS IS THE ONLY REAL CHANGE)
1330//    document.querySelectorAll('#form-root').forEach(form => {
1331//
1332//        const $formRoot = $(form);
1333//        if (!$formRoot.length)
1334//            return;
1335//
1336//        // 🔴 SAME AS OLD CODE
1337//        const $children = $formRoot.children().toArray();
1338    document.querySelectorAll('#form-root').forEach(form => {
1339
1340        const $formRoot = $(form);
1341        if (!$formRoot.length)
1342            return;
1343
1344        const $children = $formRoot.children().toArray(); // keep if flat
1345        const sections = [];
1346        let $currentSection = $('<div class="nf-section row mx-auto"></div>');
1347        let hasPageBreak = false;
1348
1349        // ----------------------------------
1350        // BUILD SECTIONS (UNCHANGED LOGIC)
1351        // ---------------------------------- 
1352        $children.forEach(function (el) {
1353            const $el = $(el);
1354
1355            if ($el.hasClass('nf-page-break')) {
1356                hasPageBreak = true;
1357
1358                if ($currentSection.children().length) {
1359                    sections.push($currentSection);
1360                    $formRoot.append($currentSection);
1361                }
1362
1363                $currentSection = $('<div class="nf-section row mx-auto"></div>');
1364                $el.remove();
1365            } else {
1366                $currentSection.append($el);
1367            }
1368        });
1369
1370        if ($currentSection.children().length) {
1371            sections.push($currentSection);
1372            $formRoot.append($currentSection);
1373        }
1374
1375        /**
1376         * ------------------------------------------------
1377         * CASE 2: NO PAGE BREAK → SIMPLE FORM
1378         * ------------------------------------------------
1379         */
1380        if (!hasPageBreak) {
1381
1382            // Prevent duplicate submit buttons
1383            if (!$formRoot.find('button[type="submit"]').length) {
1384                const $submitDiv = $formRoot.find('#submitDiv');
1385
1386                if ($submitDiv.length) {
1387                    $submitDiv.append(
1388                            $('<button type="submit" class="btn btn-primary default">Submit</button>')
1389                            );
1390                } else {
1391                    $formRoot.append(
1392                            $('<button type="submit" class="btn btn-primary default mt-3">Submit</button>')
1393                            );
1394                }
1395            }
1396
1397            return; // ⛔ stop ONLY this form
1398        }
1399
1400        /**
1401         * ------------------------------------------------
1402         * CASE 1: PAGE BREAK EXISTS → MULTI STEP FORM
1403         * ------------------------------------------------
1404         */
1405        sections.forEach(function ($section, index) {
1406
1407            const $nav = $('<div class="nf-nav-buttons d-flex justify-content-between gap-2 mt-4"></div>');
1408            const $prevBtn = $('<button type="button" class="btn btn-tertiary nf-prev-btn">Previous</button>');
1409            const $nextBtn = $('<button type="button" class="btn btn-primary nf-next-btn">Next</button>');
1410
1411            $prevBtn.on('click', function () {
1412                showSection(index - 1);
1413            });
1414
1415            $nextBtn.on('click', function () {
1416                showSection(index + 1);
1417            });
1418
1419            $nav.append($prevBtn).append($nextBtn);
1420
1421            const $captchaMarker = $section.find('.nf-captcha-marker');
1422            if ($captchaMarker.length) {
1423                $nav.insertBefore($captchaMarker);
1424            } else {
1425                $section.append($nav);
1426            }
1427        });
1428
1429        // ✅ SUBMIT BUTTON ONLY ON LAST SECTION (PER FORM)
1430        const $lastSection = sections[sections.length - 1];
1431        const $lastNav = $lastSection.find('.nf-nav-buttons').first();
1432
1433        if (!$lastNav.find('.nf-submit-btn').length) {
1434            $lastNav.append(
1435                    $('<button type="submit" class="btn btn-primary nf-submit-btn">Submit</button>')
1436                    );
1437        }
1438
1439        let currentIndex = 0;
1440
1441        function showSection(index) {
1442            sections.forEach(function ($sec, i) {
1443                $sec.toggleClass('d-none', i !== index);
1444            });
1445
1446            const $activeSection = sections[index];
1447
1448            const $nav = $activeSection.find('.nf-nav-buttons');
1449            const $prevBtn = $nav.find('.nf-prev-btn');
1450            const $nextBtn = $nav.find('.nf-next-btn');
1451
1452            // Button visibility
1453            $prevBtn.toggleClass('d-none', index === 0);
1454            $nextBtn.toggleClass('d-none', index === sections.length - 1);
1455
1456            // 🌿 SMOOTH SCROLL FIRST
1457            $('html, body').stop().animate(
1458                    {
1459                        scrollTop: $activeSection.offset().top - 40
1460                    },
1461                    400, // 👈 scroll speed (increase = slower)
1462                    'swing',
1463                    function () {
1464                        // 🧠 FOCUS AFTER SCROLL FINISHES
1465                        const $firstInput = $activeSection
1466                                .find('input, select, textarea')
1467                                .filter(':visible')
1468                                .first();
1469
1470                        if ($firstInput.length) {
1471                            $firstInput.trigger('focus');
1472                        } else {
1473                            $activeSection.attr('tabindex', '-1').trigger('focus');
1474                        }
1475                    }
1476            );
1477
1478            currentIndex = index;
1479        }
1480
1481
1482
1483        // Init
1484        sections.forEach($sec => $sec.addClass('d-none'));
1485        showSection(0);
1486
1487    });
1488
1489});
1490
1491document.addEventListener('DOMContentLoaded', function () {
1492    // Find selects with either class
1493    const selects = document.querySelectorAll(
1494            '.js-tom-multiselect, .js-tom-select'
1495            );
1496    // ✅ If neither class exists, do nothing
1497    if (!selects.length) {
1498        return;
1499    }
1500    selects.forEach(function (el) {
1501        // ✅ Prevent duplicate initialization
1502        if (el.tomselect !== undefined) {
1503            return;
1504        }
1505        const isMulti = el.classList.contains('js-tom-multiselect');
1506        new TomSelect(el, {
1507            create: el.dataset.allowCreate === 'true',
1508            persist: false,
1509            maxOptions: isMulti ? 100 : 50,
1510            plugins: isMulti
1511                    ? ['remove_button', 'dropdown_input']
1512                    : ['dropdown_input'],
1513            render: isMulti ? {
1514                option_create: function (data, escape) {
1515                    return (
1516                            '<div class="create">Create <strong>' +
1517                            escape(data.input) +
1518                            '</strong>&hellip;</div>'
1519                            );
1520                }
1521            } : {}
1522        });
1523    });
1524});
1525(function () {
1526    window.onRecaptchaApiLoaded = function () {
1527        window.renderRecaptchas();
1528    };
1529    window.renderRecaptchas = function () {
1530        if (!window.grecaptcha || typeof grecaptcha.render !== 'function') {
1531            return;
1532        }
1533        if (!window.recaptchaForms) {
1534            return;
1535        }
1536        Object.keys(window.recaptchaForms).forEach(function (formId) {
1537            const rec = window.recaptchaForms[formId];
1538            if (rec.rendered)
1539                return;
1540            const el = document.getElementById(rec.elementId);
1541            if (!el)
1542                return;
1543            grecaptcha.render(el, {
1544                sitekey: window.GOOGLE_RECAPTCHA_SITE_KEY
1545            });
1546            rec.rendered = true;
1547        });
1548    };
1549})();
1550
1551
1552//FOR Copy Pase Div
1553//'<a href="javascript:;" class="js-copy-section">Add Director</a>'
1554//'<div data-repeat-start></div>'
1555//'<div data-repeat-end=""></div>'
1556(function () {
1557
1558    let cloneIndex = 1;
1559    const MAX_CLONES = 5;
1560
1561    document.addEventListener('click', function (e) {
1562        const trigger = e.target.closest('.js-copy-section');
1563        if (!trigger)
1564            return;
1565
1566        e.preventDefault();
1567        // 🚫 Stop if limit reached
1568        if (cloneIndex >= MAX_CLONES) {
1569            alert('You can add a maximum of ' + MAX_CLONES + '.');
1570            return;
1571        }
1572
1573        // 1️⃣ Find START marker
1574        const startBlock = trigger.closest('.nf-code-block');
1575        const startMarker = startBlock.querySelector('[data-repeat-start]');
1576
1577        if (!startMarker) {
1578            console.warn('Repeat start marker missing');
1579            return;
1580        }
1581
1582        // 2️⃣ Find END BLOCK (next nf-code-block that contains data-repeat-end)
1583        let endBlock = startBlock.nextElementSibling;
1584        let endMarker = null;
1585
1586        while (endBlock) {
1587            if (
1588                    endBlock.classList?.contains('nf-code-block') &&
1589                    endBlock.querySelector('[data-repeat-end]')
1590                    ) {
1591                endMarker = endBlock.querySelector('[data-repeat-end]');
1592                break;
1593            }
1594            endBlock = endBlock.nextElementSibling;
1595        }
1596
1597        if (!endMarker) {
1598            console.warn('Repeat end marker missing');
1599            return;
1600        }
1601
1602        // 3️⃣ Collect elements BETWEEN start & end blocks
1603        const elementsToClone = [];
1604        let node = startBlock.nextElementSibling;
1605
1606        while (node && node !== endBlock) {
1607            elementsToClone.push(node);
1608            node = node.nextElementSibling;
1609        }
1610
1611        if (!elementsToClone.length)
1612            return;
1613
1614        const fragment = document.createDocumentFragment();
1615
1616        elementsToClone.forEach(el => {
1617            const clone = el.cloneNode(true);
1618
1619            // 🔁 Fix IDs
1620            clone.querySelectorAll('input, textarea, select').forEach(input => {
1621
1622                if (!input.id)
1623                    return;
1624
1625                const oldId = input.id;
1626                const newId = oldId + '_clone_' + cloneIndex;
1627
1628                // Update input ID
1629                input.id = newId;
1630
1631                // Update matching label[for]
1632                const label = clone.querySelector(`label[for="${oldId}"]`);
1633                if (label) {
1634                    label.setAttribute('for', newId);
1635                }
1636            });
1637
1638            // 🔁 Fix names (smart duplication)
1639            clone.querySelectorAll('[name]').forEach(input => {
1640
1641                const name = input.getAttribute('name');
1642
1643                // Case 1️⃣ name="field"
1644                if (!name.includes('[')) {
1645                    input.setAttribute('name', name + '_' + cloneIndex);
1646                    return;
1647                }
1648
1649                // Case 2️⃣ name="field[]"
1650                if (name.endsWith('[]')) {
1651                    input.setAttribute(
1652                            'name',
1653                            name.replace(/\[\]$/, '[' + cloneIndex + ']')
1654                            );
1655                    return;
1656                }
1657
1658                // Case 3️⃣ name="field[0]" OR name="field[key]"
1659                input.setAttribute(
1660                        'name',
1661                        name.replace(/\[(.*?)\]/, '[' + cloneIndex + ']')
1662                        );
1663            });
1664
1665
1666            // ♻ Reset values
1667            clone.querySelectorAll('input, textarea, select').forEach(i => {
1668                if (['checkbox', 'radio'].includes(i.type)) {
1669                    i.checked = false;
1670                } else if (i.type !== 'hidden') {
1671                    i.value = '';
1672                }
1673            });
1674
1675            fragment.appendChild(clone);
1676        });
1677
1678        // 4️⃣ Insert AFTER end block
1679        endBlock.after(fragment);
1680
1681        cloneIndex++;
1682    });
1683
1684})();
1685

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.