PluginProbe
Contact Forms by Cimatti / 2.1.2
Contact Forms by Cimatti v2.1.2
2.3.6 2.3.5 2.3.0 2.2.32 2.2.4 2.2.0 2.1.2 2.1.1 trunk 1.0 1.1 1.2 1.2.1 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 All 62 releases
contact-forms / assets / js / admin / form-settings.js

form-settings.js in Contact Forms by Cimatti 2.1.2, at assets/js/admin/form-settings.js

898 lines 32.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Contact Forms - Form Settings Editor
3 *
4 * Handles real-time preview updates and form settings saving.
5 * Version: 2.0.0 (refactored for smoother UX)
6 */
7 jQuery(function($){
8 'use strict';
9
10 // ========================================================================
11 // Configuration
12 // ========================================================================
13 var DEBOUNCE_DELAY = 100; // ms delay for debounced updates
14 var PREVIEW_LOAD_DELAY = 300; // ms delay after iframe load before updating
15
16 // All style fields that support override checkbox + value
17 var ALL_STYLE_FIELDS = {
18 // Form container styles
19 form: [
20 'style_margin',
21 'style_border_color',
22 'style_border_width',
23 'style_border_radius',
24 'style_background_color',
25 'style_padding',
26 'style_color',
27 'style_font_size'
28 ],
29 // Field styles
30 field: [
31 'style_field_spacing',
32 'style_field_border_color',
33 'style_field_border_width',
34 'style_field_border_radius',
35 'style_field_background_color',
36 'style_field_padding',
37 'style_field_color'
38 ],
39 // Submit button styles
40 submit: [
41 'style_submit_border_color',
42 'style_submit_border_width',
43 'style_submit_border_radius',
44 'style_submit_background_color',
45 'style_submit_padding',
46 'style_submit_color',
47 'style_submit_font_size'
48 ]
49 };
50
51 // Color fields that use wpColorPicker
52 var COLOR_FIELDS = [
53 'style_border_color',
54 'style_background_color',
55 'style_color',
56 'style_field_border_color',
57 'style_field_background_color',
58 'style_field_color',
59 'style_submit_border_color',
60 'style_submit_background_color',
61 'style_submit_color'
62 ];
63
64 // ========================================================================
65 // State
66 // ========================================================================
67 var previewReady = false;
68 var $saveButton = $('.accua_form_save_settings_button');
69
70 // ========================================================================
71 // Utility Functions
72 // ========================================================================
73
74 /**
75 * Simple debounce function
76 */
77 function debounce(func, wait) {
78 var timeout;
79 return function() {
80 var context = this, args = arguments;
81 clearTimeout(timeout);
82 timeout = setTimeout(function() {
83 func.apply(context, args);
84 }, wait);
85 };
86 }
87
88 /**
89 * Get TinyMCE content or fallback to textarea value
90 */
91 function getTinyMCEContent(name) {
92 if ($('#' + name + ' .wp-editor-wrap').hasClass('tmce-active') && typeof tinyMCE !== 'undefined') {
93 var editor = tinyMCE.get(name + '_textarea');
94 return editor ? editor.getContent() : $('#' + name + '_textarea').val();
95 }
96 return $('#' + name + '_textarea').val();
97 }
98
99 /**
100 * Parse a numeric value and add px suffix if needed
101 */
102 function parseStyleValue(value) {
103 if (!value || value === '') return '';
104 value = String(value).trim();
105 // If it's a pure number, add px
106 if (/^-?\d+(\.\d+)?$/.test(value)) {
107 return value + 'px';
108 }
109 return value;
110 }
111
112 // ========================================================================
113 // Preview Document Access
114 // ========================================================================
115
116 /**
117 * Safely get the preview iframe document
118 */
119 function getPreviewDocument() {
120 var iframe = document.getElementById('accua_form_preview_area');
121 if (!iframe) return null;
122 try {
123 var doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
124 return doc && doc.body ? doc : null;
125 } catch (e) {
126 return null;
127 }
128 }
129
130 /**
131 * Get the form element from preview
132 */
133 function getPreviewForm() {
134 var doc = getPreviewDocument();
135 return doc ? doc.querySelector('form.accua-form') : null;
136 }
137
138 // ========================================================================
139 // Style Value Retrieval
140 // ========================================================================
141
142 /**
143 * Get style value if the override checkbox is checked
144 */
145 function getStyleValue(key) {
146 var $container = $('#accua_form_' + key);
147 var $checkbox = $container.find('.accua_form_check_override');
148
149 // Check if override is enabled
150 if (!$checkbox.length || !$checkbox.is(':checked')) {
151 return '';
152 }
153
154 // For color pickers, get value from the hidden input (wpColorPicker syncs to it)
155 var $valueInput = $container.find('.accua_form_value');
156 return $valueInput.val() || '';
157 }
158
159 // ========================================================================
160 // Layout Preview
161 // ========================================================================
162
163 /**
164 * Reload preview iframe with new layout parameter.
165 * Layout changes require HTML structure rebuild, not just CSS class toggling.
166 * Different layouts (inline, toplabel, sidebyside) generate different HTML.
167 */
168 function reloadPreviewWithLayout(layout) {
169 var formId = $('#accua_form_save_settings_id').val();
170 var $iframe = $('#accua_form_preview_area');
171 var $wrapper = $('#accua_form_preview_area_wrapper');
172
173 previewReady = false;
174
175 // Show loading state
176 $wrapper.addClass('accua-form-preview-loading');
177
178 // Build preview URL with layout override parameter
179 var previewUrl = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId +
180 '&_wpnonce=' + accua_forms_nonces.preview_nonce;
181
182 // Always pass layout parameter - use 'default' when empty to signal global default should be used
183 var layoutParam = (layout && layout !== '') ? layout : 'default';
184 previewUrl += '&preview_layout=' + encodeURIComponent(layoutParam);
185
186 $iframe[0].src = previewUrl;
187 }
188
189 /**
190 * Update preview layout class based on dropdown selection
191 * NOTE: This only toggles CSS classes - used for CSS-only style changes.
192 * For actual layout changes, use reloadPreviewWithLayout() instead.
193 */
194 function updatePreviewLayout() {
195 var form = getPreviewForm();
196 if (!form) return;
197
198 var layout = $('#accua_form_layout .accua_form_value').val() || '';
199
200 // Remove all layout classes
201 form.classList.remove(
202 'accua-form-view-standard',
203 'accua-form-view-sidebyside',
204 'accua-form-view-inlinelabel'
205 );
206
207 // Add appropriate class based on selection
208 switch (layout) {
209 case 'toplabel':
210 form.classList.add('accua-form-view-standard');
211 break;
212 case 'inlinelabel':
213 form.classList.add('accua-form-view-inlinelabel');
214 break;
215 case 'sidebyside':
216 form.classList.add('accua-form-view-sidebyside');
217 break;
218 default:
219 // Empty/default - use sidebyside as fallback for backwards compatibility
220 form.classList.add('accua-form-view-sidebyside');
221 break;
222 }
223 }
224
225 // ========================================================================
226 // Styles Preview
227 // ========================================================================
228
229 /**
230 * Build inline style string from style object
231 */
232 function buildStyleString(styles) {
233 var parts = [];
234 for (var prop in styles) {
235 if (styles.hasOwnProperty(prop) && styles[prop]) {
236 parts.push(prop + ':' + styles[prop]);
237 }
238 }
239 return parts.join(';');
240 }
241
242 /**
243 * Update all preview styles
244 */
245 function updatePreviewStyles() {
246 var form = getPreviewForm();
247 if (!form) return;
248
249 var doc = getPreviewDocument();
250 if (!doc) return;
251
252 // ---- Form Container Styles ----
253 var formStyles = {};
254
255 var margin = getStyleValue('style_margin');
256 if (margin) formStyles['margin'] = parseStyleValue(margin);
257
258 var borderColor = getStyleValue('style_border_color');
259 if (borderColor) formStyles['border-color'] = borderColor;
260
261 var borderWidth = getStyleValue('style_border_width');
262 if (borderWidth) {
263 formStyles['border-width'] = parseStyleValue(borderWidth);
264 formStyles['border-style'] = 'solid';
265 }
266
267 var borderRadius = getStyleValue('style_border_radius');
268 if (borderRadius) formStyles['border-radius'] = parseStyleValue(borderRadius);
269
270 var backgroundColor = getStyleValue('style_background_color');
271 if (backgroundColor) formStyles['background-color'] = backgroundColor;
272
273 var padding = getStyleValue('style_padding');
274 if (padding) formStyles['padding'] = parseStyleValue(padding);
275
276 var color = getStyleValue('style_color');
277 if (color) formStyles['color'] = color;
278
279 var fontSize = getStyleValue('style_font_size');
280 if (fontSize) formStyles['font-size'] = parseStyleValue(fontSize);
281
282 form.style.cssText = buildStyleString(formStyles);
283
284 // ---- Field Styles ----
285 var fieldStyles = {};
286
287 var fieldBorderColor = getStyleValue('style_field_border_color');
288 if (fieldBorderColor) fieldStyles['border-color'] = fieldBorderColor;
289
290 var fieldBorderWidth = getStyleValue('style_field_border_width');
291 if (fieldBorderWidth) {
292 fieldStyles['border-width'] = parseStyleValue(fieldBorderWidth);
293 fieldStyles['border-style'] = 'solid';
294 }
295
296 var fieldBorderRadius = getStyleValue('style_field_border_radius');
297 if (fieldBorderRadius) fieldStyles['border-radius'] = parseStyleValue(fieldBorderRadius);
298
299 var fieldBackgroundColor = getStyleValue('style_field_background_color');
300 if (fieldBackgroundColor) fieldStyles['background-color'] = fieldBackgroundColor;
301
302 var fieldPadding = getStyleValue('style_field_padding');
303 if (fieldPadding) fieldStyles['padding'] = parseStyleValue(fieldPadding);
304
305 var fieldColor = getStyleValue('style_field_color');
306 if (fieldColor) fieldStyles['color'] = fieldColor;
307
308 var fieldStyleString = buildStyleString(fieldStyles);
309 var fields = doc.querySelectorAll('.pfbc-textbox, .pfbc-textarea, .pfbc-select, input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select');
310 for (var i = 0; i < fields.length; i++) {
311 fields[i].style.cssText = fieldStyleString;
312 }
313
314 // Field spacing (margin-bottom on pfbc-element)
315 var fieldSpacing = getStyleValue('style_field_spacing');
316 if (fieldSpacing) {
317 var elements = doc.querySelectorAll('.pfbc-element');
318 var spacingValue = parseStyleValue(fieldSpacing);
319 for (var j = 0; j < elements.length; j++) {
320 elements[j].style.marginBottom = spacingValue;
321 }
322 }
323
324 // ---- Submit Button Styles ----
325 var submitStyles = {};
326
327 var submitBorderColor = getStyleValue('style_submit_border_color');
328 if (submitBorderColor) submitStyles['border-color'] = submitBorderColor;
329
330 var submitBorderWidth = getStyleValue('style_submit_border_width');
331 if (submitBorderWidth) {
332 submitStyles['border-width'] = parseStyleValue(submitBorderWidth);
333 submitStyles['border-style'] = 'solid';
334 }
335
336 var submitBorderRadius = getStyleValue('style_submit_border_radius');
337 if (submitBorderRadius) submitStyles['border-radius'] = parseStyleValue(submitBorderRadius);
338
339 var submitBackgroundColor = getStyleValue('style_submit_background_color');
340 if (submitBackgroundColor) submitStyles['background-color'] = submitBackgroundColor;
341
342 var submitPadding = getStyleValue('style_submit_padding');
343 if (submitPadding) submitStyles['padding'] = parseStyleValue(submitPadding);
344
345 var submitColor = getStyleValue('style_submit_color');
346 if (submitColor) submitStyles['color'] = submitColor;
347
348 var submitFontSize = getStyleValue('style_submit_font_size');
349 if (submitFontSize) submitStyles['font-size'] = parseStyleValue(submitFontSize);
350
351 var submitButton = doc.querySelector('.pfbc-buttons button, .pfbc-buttons input[type="submit"], button[type="submit"], input[type="submit"]');
352 if (submitButton) {
353 submitButton.style.cssText = buildStyleString(submitStyles);
354 }
355 }
356
357 // Create debounced version for smoother UX
358 var updatePreviewStylesDebounced = debounce(updatePreviewStyles, DEBOUNCE_DELAY);
359
360 // ========================================================================
361 // Combined Preview Update
362 // ========================================================================
363
364 /**
365 * Update preview styles after iframe load.
366 * Layout is NOT toggled here — the server-rendered iframe already has the
367 * correct layout class and HTML structure. Layout changes go through
368 * reloadPreviewWithLayout() which rebuilds the iframe entirely.
369 */
370 function updateFullPreview() {
371 if (!previewReady) return;
372 updatePreviewStyles();
373 }
374
375 // ========================================================================
376 // Color Picker Initialization
377 // ========================================================================
378
379 /**
380 * Close all open Iris color pickers
381 * WordPress Iris adds .wp-picker-active class to open pickers
382 */
383 function closeAllColorPickers() {
384 $('.accua-style-content .wp-picker-container').each(function() {
385 var $container = $(this);
386 var $holder = $container.find('.wp-picker-holder');
387 var $iris = $holder.find('.iris-picker');
388
389 // Hide the Iris picker
390 if ($iris.length) {
391 $iris.hide();
392 }
393
394 // Remove the active state
395 $container.removeClass('wp-picker-active');
396 });
397 }
398
399 /**
400 * Initialize a color picker with change handler
401 * Follows WordPress admin UI guidelines:
402 * - Only one color picker open at a time
403 * - Hex input always visible for keyboard accessibility
404 */
405 function initColorPicker(fieldKey) {
406 var $input = $('#accua_form_' + fieldKey + ' .accua_form_value');
407 if (!$input.length) return;
408
409 $input.wpColorPicker({
410 change: function(event, ui) {
411 // wpColorPicker change event - update preview
412 updatePreviewStylesDebounced();
413 },
414 clear: function() {
415 updatePreviewStylesDebounced();
416 }
417 });
418
419 // Get the container elements after wpColorPicker initialization
420 var $container = $input.closest('.wp-picker-container');
421 var $inputWrap = $container.find('.wp-picker-input-wrap');
422 var $hexInput = $container.find('.wp-color-picker');
423 var $colorButton = $container.find('.wp-color-result');
424 var $holder = $container.find('.wp-picker-holder');
425
426 // Always show the hex input field for accessibility (keyboard-accessible text input)
427 // WordPress hides it by default and only shows when picker is open
428 $inputWrap.css('display', '');
429 $hexInput.css('display', '').removeAttr('disabled');
430
431 // Close other pickers when this color button is clicked
432 $colorButton.on('click', function(e) {
433 // Check if this picker is about to open (will become active)
434 var wasActive = $container.hasClass('wp-picker-active');
435
436 if (!wasActive) {
437 // Close all other pickers before this one opens
438 $('.accua-style-content .wp-picker-container').not($container).each(function() {
439 var $other = $(this);
440 var $otherIris = $other.find('.iris-picker');
441 if ($otherIris.length) {
442 $otherIris.hide();
443 }
444 $other.removeClass('wp-picker-active');
445 });
446 }
447 });
448
449 // Also close other pickers when hex input is focused
450 $hexInput.on('focus', function() {
451 // Close all other pickers
452 $('.accua-style-content .wp-picker-container').not($container).each(function() {
453 var $other = $(this);
454 var $otherIris = $other.find('.iris-picker');
455 if ($otherIris.length) {
456 $otherIris.hide();
457 }
458 $other.removeClass('wp-picker-active');
459 });
460 });
461 }
462
463 // Initialize all color pickers
464 $.each(COLOR_FIELDS, function(i, key) {
465 initColorPicker(key);
466 });
467
468 // Close all color pickers when clicking outside
469 $(document).on('click', function(e) {
470 var $target = $(e.target);
471
472 // If click is not inside a color picker container, close all pickers
473 if (!$target.closest('.wp-picker-container').length) {
474 closeAllColorPickers();
475 }
476 });
477
478 // ========================================================================
479 // Event Listeners - Style Fields
480 // ========================================================================
481
482 /**
483 * Attach event listeners for a style field
484 */
485 function attachStyleFieldListeners(fieldKey, isColorField) {
486 var $container = $('#accua_form_' + fieldKey);
487 if (!$container.length) return;
488
489 // Checkbox toggle - always triggers preview update
490 $container.find('.accua_form_check_override').on('change', function() {
491 updatePreviewStylesDebounced();
492 });
493
494 // Value input - for non-color fields
495 if (!isColorField) {
496 $container.find('.accua_form_value').on('input change', function() {
497 updatePreviewStylesDebounced();
498 });
499 }
500 }
501
502 // Attach listeners to all form style fields
503 $.each(ALL_STYLE_FIELDS.form, function(i, key) {
504 attachStyleFieldListeners(key, COLOR_FIELDS.indexOf(key) !== -1);
505 });
506
507 // Attach listeners to all field style fields
508 $.each(ALL_STYLE_FIELDS.field, function(i, key) {
509 attachStyleFieldListeners(key, COLOR_FIELDS.indexOf(key) !== -1);
510 });
511
512 // Attach listeners to all submit style fields
513 $.each(ALL_STYLE_FIELDS.submit, function(i, key) {
514 attachStyleFieldListeners(key, COLOR_FIELDS.indexOf(key) !== -1);
515 });
516
517 // ========================================================================
518 // Event Listeners - Layout
519 // ========================================================================
520
521 $('#accua_form_layout .accua_form_value').on('change', function() {
522 // Reload preview with new layout - layout changes require HTML rebuild
523 // (different layouts generate different HTML structure, not just CSS)
524 reloadPreviewWithLayout($(this).val());
525 });
526
527 // ========================================================================
528 // Preview Iframe Load Handler
529 // ========================================================================
530
531 $('#accua_form_preview_area').on('load', function() {
532 previewReady = true;
533
534 // Remove loading state
535 $('#accua_form_preview_area_wrapper').removeClass('accua-form-preview-loading');
536
537 // Small delay to ensure iframe content is fully rendered
538 setTimeout(function() {
539 updateFullPreview();
540 }, PREVIEW_LOAD_DELAY);
541 });
542
543 // ========================================================================
544 // Save Handler
545 // ========================================================================
546
547 $('.accua_form_save_settings_button').on('click', function(e) {
548 e.preventDefault();
549
550 var $clickedButton = $(this);
551
552 // Store original button text and add saving state with "Saving..." text
553 var originalText = $clickedButton.val();
554 $saveButton.addClass('accua-saving').prop('disabled', true);
555 $clickedButton.val(accua_forms_i18n.saving || 'Saving...').addClass('accua-saving-active');
556
557 var formId = $('#accua_form_save_settings_id').val();
558
559 // Build data object
560 var data = {
561 'action': 'accua-save-form-settings',
562 'form-id': formId,
563 '_nonce_edit_form': $('#_nonce_edit_form').val(),
564 'title': $('#title').val(),
565 'use_ajax': $('#accua_form_use_ajax .accua_form_value').is(':checked') ? 1 : 0
566 };
567
568 // Layout
569 var layout = $('#accua_form_layout .accua_form_value').val();
570 if (layout === 'toplabel' || layout === 'sidebyside' || layout === 'inlinelabel') {
571 data.layout = layout;
572 } else {
573 data.layout = '';
574 }
575
576 // GADS conversion tracking
577 var gads = $('#gads_conversion_code_input').val();
578 if (gads && gads !== 'undefined' && gads !== 'null') {
579 data.gads_conversion_tracking_code = gads;
580 }
581
582 // Collect all override fields
583 var overrideFields = [
584 'success_message', 'error_message',
585 'emails_from_name', 'emails_from', 'admin_emails_to', 'emails_bcc',
586 'admin_emails_subject', 'admin_emails_message',
587 'confirmation_emails_subject', 'confirmation_emails_message'
588 ];
589
590 // Add all style fields
591 $.each(ALL_STYLE_FIELDS.form, function(i, key) { overrideFields.push(key); });
592 $.each(ALL_STYLE_FIELDS.field, function(i, key) { overrideFields.push(key); });
593 $.each(ALL_STYLE_FIELDS.submit, function(i, key) { overrideFields.push(key); });
594
595 $.each(overrideFields, function(i, key) {
596 var $checkbox = $('#accua_form_' + key + ' .accua_form_check_override:checked');
597 var checkValue = $checkbox.val();
598
599 if (checkValue !== undefined && checkValue != 0) {
600 if (checkValue == -1) {
601 // "No message" option
602 data[key] = '';
603 data[key + '_no_message'] = 1;
604 } else {
605 var $element = $('#accua_form_' + key + ' .accua_form_value');
606 if ($element.is('textarea')) {
607 data[key] = getTinyMCEContent('accua_form_' + key);
608 } else {
609 data[key] = $element.val();
610 }
611 }
612 }
613 });
614
615 // Trigger widget save buttons (saves individual field settings to draft)
616 if (typeof accuaWidgets !== 'undefined' && accuaWidgets) {
617 $('#widgets-right .button-primary.widget-control-save').click();
618 }
619
620 // Data Retention fields
621 data.submission_retention_override = $('#accua_form_retention_override').is(':checked') ? 1 : 0;
622 data.submission_retention_value = $('#submission_retention_value').val();
623 data.submission_retention_unit = $('#submission_retention_unit').val();
624 data.submission_retention_mode = $('input[name="submission_retention_mode"]:checked').val();
625
626 // Save field order to draft
627 if (typeof accuaWidgets !== 'undefined' && accuaWidgets.saveOrder) {
628 accuaWidgets.saveOrder();
629 }
630
631 // Step 1: Save settings to draft
632 $.ajax({
633 url: ajaxurl,
634 type: 'POST',
635 data: data,
636 success: function() {
637 // Step 2: Publish draft to live database
638 $.ajax({
639 url: ajaxurl,
640 type: 'POST',
641 data: {
642 'action': 'accua-publish-form-draft',
643 'form-id': formId,
644 '_nonce_edit_form': $('#_nonce_edit_form').val()
645 },
646 success: function() {
647 // Reload preview to show saved state (include current layout)
648 reloadPreviewWithLayout($('#accua_form_layout .accua_form_value').val());
649
650 // Update URL without reload
651 try {
652 if (history.pushState && window.location.search.indexOf('page=accua_forms_list') === -1) {
653 history.pushState('', document.title, 'admin.php?page=accua_forms_list&fid=' + formId);
654 window.onpopstate = function() { location.reload(); };
655 }
656 } catch (e) {}
657
658 // Restore button text immediately (was cleared to show spinner)
659 $clickedButton.val(originalText);
660
661 // Show success state on button briefly
662 $saveButton.removeClass('accua-saving').addClass('accua-saved').prop('disabled', false);
663 $clickedButton.removeClass('accua-saving-active').addClass('accua-saved-active');
664
665 // Remove success visual state after 1.5 seconds
666 setTimeout(function() {
667 $saveButton.removeClass('accua-saved');
668 $clickedButton.removeClass('accua-saved-active');
669 }, 1500);
670 },
671 error: function() {
672 // Show error state
673 $saveButton.removeClass('accua-saving').prop('disabled', false);
674 $clickedButton.removeClass('accua-saving-active').val(originalText).addClass('accua-error');
675 setTimeout(function() {
676 $clickedButton.removeClass('accua-error');
677 }, 3000);
678 }
679 });
680 },
681 error: function() {
682 // Show error state
683 $saveButton.removeClass('accua-saving').prop('disabled', false);
684 $clickedButton.removeClass('accua-saving-active').val(originalText).addClass('accua-error');
685 setTimeout(function() {
686 $clickedButton.removeClass('accua-error');
687 }, 3000);
688 }
689 });
690 });
691
692 // ========================================================================
693 // Accessibility: Clickable Labels for Checkboxes
694 // ========================================================================
695
696 /**
697 * Toggle visibility of input field based on checkbox state.
698 * Inputs are hidden when unchecked to save space.
699 */
700 function toggleOptionFieldVisibility($checkbox) {
701 var $container = $checkbox.closest('.accua-style-row, .label_container, .label_input, [id^="accua_form_"]');
702 var isChecked = $checkbox.is(':checked');
703
704 // Find the associated value input
705 var $valueInput = $container.find('.accua_form_value').first();
706 var $colorPicker = $container.find('.wp-picker-container');
707 var $helpText = $container.find('.accua-style-help');
708
709 if ($colorPicker.length) {
710 // For color pickers, toggle visibility
711 if (isChecked) {
712 $colorPicker.show();
713 $colorPicker.find('input').prop('disabled', false);
714 } else {
715 $colorPicker.hide();
716 $colorPicker.find('input').prop('disabled', true);
717 }
718 } else if ($valueInput.length) {
719 // For regular text inputs - hide/show and toggle disabled
720 if (isChecked) {
721 $valueInput.show().prop('disabled', false);
722 } else {
723 $valueInput.hide().prop('disabled', true);
724 }
725 }
726
727 // Toggle help text visibility
728 if ($helpText.length) {
729 if (isChecked) {
730 $helpText.show();
731 } else {
732 $helpText.hide();
733 }
734 }
735 }
736
737 /**
738 * Initialize option field visibility based on initial checkbox states.
739 */
740 function initOptionFieldVisibility() {
741 $('#accua_tab_customise input.accua_form_check_override[type="checkbox"]').each(function() {
742 toggleOptionFieldVisibility($(this));
743 });
744 }
745
746 /**
747 * Make checkbox labels clickable for better accessibility.
748 * Uses click handlers on strong/text elements to toggle associated checkboxes.
749 */
750 function initClickableLabels() {
751 // Process containers with checkboxes in the customise tab (new and legacy selectors)
752 // Exclude #accua_form_layout which contains the dropdown
753 $('#accua_tab_customise .accua-style-row, #accua_tab_customise .label_container, #accua_tab_customise .label_input').each(function() {
754 var $container = $(this);
755 var $checkbox = $container.find('input.accua_form_check_override[type="checkbox"]').first();
756
757 if (!$checkbox.length) return;
758
759 // Skip if already processed
760 if ($checkbox.data('label-initialized')) return;
761 $checkbox.data('label-initialized', true);
762
763 // Attach change handler for toggling option field visibility
764 $checkbox.on('change.optionVisibility', function() {
765 toggleOptionFieldVisibility($(this));
766 });
767
768 // Find the label text elements
769 // New design: .accua-style-label is in .accua-style-content sibling to .accua-style-toggle
770 // Legacy: strong element directly after checkbox
771 var $labelElements = $container.find('.accua-style-label');
772
773 if (!$labelElements.length) {
774 // Fallback to legacy strong element
775 $labelElements = $checkbox.nextAll('strong').first();
776 }
777
778 if ($labelElements.length) {
779 // Make the label element clickable
780 $labelElements.addClass('clickable-label').css('cursor', 'pointer');
781 $labelElements.on('click', function(e) {
782 e.preventDefault();
783 e.stopPropagation();
784 $checkbox.prop('checked', !$checkbox.prop('checked')).trigger('change');
785 });
786 }
787 });
788
789 // Handle #accua_form_use_ajax checkbox (special case - it's in a <p>)
790 var $ajaxCheckbox = $('#accua_form_use_ajax input[type="checkbox"]');
791 if ($ajaxCheckbox.length && !$ajaxCheckbox.data('label-initialized')) {
792 $ajaxCheckbox.data('label-initialized', true);
793
794 // Get the text after the checkbox and make it clickable
795 var $parent = $ajaxCheckbox.parent();
796 var textNode = $ajaxCheckbox[0].nextSibling;
797 if (textNode && textNode.nodeType === 3 && textNode.textContent.trim()) {
798 var text = textNode.textContent.trim();
799 var $span = $('<span class="clickable-label" style="cursor:pointer;">' + text + '</span>');
800 $(textNode).replaceWith($span);
801 $span.on('click', function(e) {
802 e.preventDefault();
803 e.stopPropagation();
804 $ajaxCheckbox.prop('checked', !$ajaxCheckbox.prop('checked')).trigger('change');
805 });
806 }
807 }
808 }
809
810 // Initialize clickable labels and option field visibility after a short delay to ensure DOM is ready
811 setTimeout(function() {
812 initClickableLabels();
813 initOptionFieldVisibility();
814 }, 100);
815
816 // Re-initialize after tab switch
817 $('#accua_tabs2').on('tabsactivate', function() {
818 setTimeout(function() {
819 initClickableLabels();
820 initOptionFieldVisibility();
821 }, 100);
822 });
823
824 // Make entire tab clickable (delegate click on li to trigger click on a)
825 $('#accua_tabs2').on('click', 'li:not(.ui-tabs-active)', function(e) {
826 // Only trigger if click was on li itself, not on the link
827 if (e.target.tagName !== 'A') {
828 $(this).find('a').trigger('click');
829 }
830 });
831
832 // ========================================================================
833 // Unsaved Changes Warning (WordPress standard beforeunload pattern)
834 // ========================================================================
835
836 var formDirty = false;
837
838 function markDirty() {
839 formDirty = true;
840 }
841
842 function markClean() {
843 formDirty = false;
844 }
845
846 // Expose globally so form-fields.js (and other scripts) can mark dirty
847 window.accuaFormsEditorDirty = {
848 mark: markDirty,
849 clean: markClean,
850 isDirty: function() { return formDirty; }
851 };
852
853 // Track changes on all form inputs within the edit page
854 $('#accua_forms_edit_page').on('input change', 'input, textarea, select', markDirty);
855
856 // Track color picker changes (wpColorPicker fires irischange on the document)
857 $(document).on('irischange', '#accua_forms_edit_page .wp-picker-container input', markDirty);
858
859 // Note: Field drag/drop/reorder is tracked via window.accuaFormsEditorDirty.mark()
860 // called directly from form-fields.js sortable stop and droppable drop handlers.
861
862 // TinyMCE editors: mark dirty on content change
863 function bindTinyMCEDirty(editor) {
864 editor.on('input change keyup', markDirty);
865 }
866 $(document).on('tinymce-editor-init', function(event, editor) {
867 bindTinyMCEDirty(editor);
868 });
869 // Also bind to any editors already initialized (race condition safety)
870 if (typeof tinyMCE !== 'undefined' && tinyMCE.editors) {
871 $.each(tinyMCE.editors, function(i, editor) {
872 if (editor) { bindTinyMCEDirty(editor); }
873 });
874 }
875
876 // Mark clean after successful save (publish draft)
877 $(document).ajaxComplete(function(event, xhr, settings) {
878 if (settings && settings.data && typeof settings.data === 'string' && settings.data.indexOf('action=accua-publish-form-draft') !== -1) {
879 if (xhr.status === 200) {
880 markClean();
881 }
882 }
883 });
884
885 // Allow intentional form submissions (e.g. delete form) without warning
886 $('#accua_forms_edit_page').on('submit', 'form', markClean);
887
888 // Browser beforeunload warning
889 $(window).on('beforeunload', function() {
890 if (formDirty) {
891 return (typeof accua_forms_i18n !== 'undefined' && accua_forms_i18n.unsaved_changes)
892 ? accua_forms_i18n.unsaved_changes
893 : true;
894 }
895 });
896
897 });
898