PluginProbe
Contact Forms by Cimatti / 2.2.32
Contact Forms by Cimatti v2.2.32
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.2.32, at assets/js/admin/form-settings.js

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