PluginProbe
Contact Forms by Cimatti / trunk
Contact Forms by Cimatti vtrunk
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-fields.js

form-fields.js in Contact Forms by Cimatti trunk, at assets/js/admin/form-fields.js

654 lines 25.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 var accuaWidgets = {};
2 (function($) {
3
4 accuaWidgets = {
5
6 init : function() {
7 var rem, sidebars = $('div.widgets-sortables'), isRTL = !! ( 'undefined' != typeof isRtl && isRtl ),
8 margin = ( isRtl ? 'marginRight' : 'marginLeft' ), the_id;
9
10 //Evento per widget-action (aggiunto in modalità "live")
11 // Uses CSS class toggle instead of jQuery slideDown/slideUp
12 $(document).on('click', 'a.widget-action', function(){
13 var $action = $(this),
14 widget = $action.closest('div.widget'),
15 $inside = widget.find('.widget-inside'),
16 isOpen = widget.hasClass('open');
17
18 if ( !isOpen ) {
19 // Opening: add .open class, fix labels, update ARIA
20 accuaWidgets.fixLabels(widget);
21 widget.addClass('open');
22 // Remove any inline display style (from jQuery .hide() during drag)
23 $inside.css('display', '');
24 $action.attr('aria-expanded', 'true');
25 } else {
26 // Closing: remove .open class, update ARIA
27 widget.removeClass('open');
28 $action.attr('aria-expanded', 'false');
29 }
30 return false;
31 });
32
33 //Click sul bottone di salvataggio
34 // $('input.widget-control-save').live('click', function(){
35 $(document).on('click', 'input.widget-control-save', function(){
36 var $button = $(this);
37 var originalText = $button.val();
38
39 // Add saving state with "Saving..." text
40 $button.addClass('accua-saving-active').val(accua_forms_i18n.saving || 'Saving...');
41
42 accuaWidgets.save( $button.closest('div.widget'), 0, 1, 0, function(success) {
43 // Restore button text immediately
44 $button.val(originalText);
45
46 if (success) {
47 // Show success state
48 $button.removeClass('accua-saving-active').addClass('accua-saved-active');
49
50 // Remove success state after 1.5 seconds
51 setTimeout(function() {
52 $button.removeClass('accua-saved-active');
53 }, 1500);
54 } else {
55 // Show error state
56 $button.removeClass('accua-saving-active').addClass('accua-error');
57
58 // Remove error state after 2 seconds
59 setTimeout(function() {
60 $button.removeClass('accua-error');
61 }, 2000);
62 }
63 });
64 return false;
65 });
66
67 //Click sul bottone di rimozione
68 //$('a.widget-control-remove').live('click', function(){
69 $(document).on('click', 'a.widget-control-remove', function(){
70 accuaWidgets.save( $(this).closest('div.widget'), 1, 1, 0 );
71 return false;
72 });
73
74 //Click sul bottone di chiusura
75 //$('a.widget-control-close').live('click', function(){
76 $(document).on('click', 'a.widget-control-close', function(){
77 accuaWidgets.close( $(this).closest('div.widget') );
78 return false;
79 });
80
81 // Click "+" button to add field to form
82 $(document).on('click', '#widget-list a.widget-add-action', function(e){
83 e.preventDefault();
84 var widget = $(this).closest('div.widget');
85 accuaWidgets.addFieldToForm(widget);
86 });
87
88 // Refresh Preview link for Custom HTML fields
89 $(document).on('click', 'a.accua-refresh-preview', function(e){
90 e.preventDefault();
91 var widget = $(this).closest('div.widget');
92 accuaWidgets.save( widget, 0, 0, 0, function(success) {
93 if (success) {
94 accuaWidgets.refreshPreviewWithCurrentOrder();
95 }
96 });
97 });
98
99 // Auto-save + preview refresh when field options change
100 // Only for widgets inside the form area (not the available-widgets sidebar)
101 var previewDebounceTimers = {};
102
103 function saveAndRefreshPreview(widget) {
104 accuaWidgets.saveSilent(widget, function(success) {
105 if (success) {
106 accuaWidgets.refreshPreviewWithCurrentOrder();
107 }
108 });
109 }
110
111 function debouncedSaveAndRefresh(widget, delay) {
112 var widgetId = widget.attr('id');
113 if (previewDebounceTimers[widgetId]) {
114 clearTimeout(previewDebounceTimers[widgetId]);
115 }
116 previewDebounceTimers[widgetId] = setTimeout(function() {
117 delete previewDebounceTimers[widgetId];
118 saveAndRefreshPreview(widget);
119 }, delay);
120 }
121
122 // Immediate change: checkboxes, selects
123 $(document).on('change', 'div.widgets-sortables .widget-content select, div.widgets-sortables .widget-content input[type="checkbox"]', function() {
124 var widget = $(this).closest('div.widget');
125 saveAndRefreshPreview(widget);
126 });
127
128 // Debounced change: text inputs and textareas (800ms delay)
129 $(document).on('input', 'div.widgets-sortables .widget-content input[type="text"], div.widgets-sortables .widget-content textarea', function() {
130 var widget = $(this).closest('div.widget');
131 debouncedSaveAndRefresh(widget, 800);
132 // Live-update widget title bar for fieldset fields
133 if (widget.data('field-type') === 'fieldset-begin') {
134 accuaWidgets.appendTitle(widget[0]);
135 widget.nextAll('[data-field-type="fieldset-end"]').first().each(function() {
136 accuaWidgets.appendTitle(this);
137 });
138 }
139 });
140
141 //Impostazione titolo e apertura widget se c'è un errore
142 sidebars.children('.widget').each(function() {
143 accuaWidgets.appendTitle(this);
144 if ( $('p.widget-error', this).length )
145 $('a.widget-action', this).click();
146 });
147
148 // Show/hide static submit widget based on whether a custom submit is in the form
149 accuaWidgets.updateDefaultSubmitVisibility();
150 // Watch for fields added/removed from the sortable area
151 if (window.MutationObserver) {
152 sidebars.each(function() {
153 new MutationObserver(function() {
154 accuaWidgets.updateDefaultSubmitVisibility();
155 }).observe(this, { childList: true });
156 });
157 }
158
159 //Trascinabilità dei widget nella lista
160 $('#widget-list').children('.widget').draggable({
161 connectToSortable: 'div.widgets-sortables',
162 handle: '> .widget-top > .widget-title',
163 distance: 2,
164 helper: 'clone',
165 zIndex: 5,
166 containment: 'document',
167 start: function(e,ui) {
168 accuaWidgets.fixWebkit(1);
169 ui.helper.find('div.widget-description').hide();
170 the_id = this.id;
171 },
172 stop: function(e,ui) {
173 if ( rem )
174 $(rem).hide();
175 rem = '';
176 accuaWidgets.fixWebkit();
177 }
178 });
179
180 //Ordinabilità dei widget
181 sidebars.sortable({
182 placeholder: 'widget-placeholder',
183 items: '> .widget:not(.accua-static-widget)',
184 handle: '> .widget-top > .widget-title',
185 cursor: 'move',
186 distance: 2,
187 containment: 'document',
188 start: function(e,ui) {
189 accuaWidgets.fixWebkit(1);
190 ui.item.children('.widget-inside').hide();
191 ui.item.css({margin:'', 'width':''});
192 },
193 stop: function(e,ui) {
194 // Mark form as having unsaved changes
195 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
196
197 if ( ui.item.hasClass('ui-draggable') && ui.item.data('draggable') )
198 ui.item.draggable('destroy');
199
200 if ( ui.item.hasClass('deleting') ) {
201 accuaWidgets.save( ui.item, 1, 0, 1 ); // delete widget
202 ui.item.remove();
203 return;
204 }
205
206 var add = ui.item.find('input.add_new').val(),
207 n = ui.item.find('input.multi_number').val(),
208 id = the_id,
209 sb = $(this).attr('id');
210
211 ui.item.css({margin:'', 'width':''});
212 the_id = '';
213
214 accuaWidgets.fixWebkit();
215 if ( add ) {
216 if ( 'multi' == add ) {
217 ui.item.html( ui.item.html().replace(/<[^<>]+>/g, function(m){ return m.replace(/__i__|%i%/g, n); }) );
218 ui.item.attr( 'id', id.replace(/__i__|%i%/g, n) );
219 n++;
220 $('div#' + id).find('input.multi_number').val(n);
221 } else if ( 'single' == add ) {
222 ui.item.attr( 'id', 'new-' + id );
223 rem = 'div#' + id;
224 }
225 // Save the new widget to DB, but DON'T save order yet
226 accuaWidgets.save( ui.item, 0, 0, 0, function(success) {
227 if (success) {
228 // Refresh preview with current order
229 accuaWidgets.refreshPreviewWithCurrentOrder();
230 }
231 });
232 ui.item.find('input.add_new').val('');
233 ui.item.find('a.widget-action').click();
234 return;
235 }
236 // Ensure static widget always stays last
237 var $staticWidget = $(this).children('.accua-static-widget');
238 if ($staticWidget.length && $staticWidget.next().length) {
239 $(this).append($staticWidget);
240 }
241 // Refresh preview (without saving to database)
242 accuaWidgets.refreshPreviewWithCurrentOrder();
243 },
244 receive: function(e,ui) {
245 var t = $(this);
246 if ( !t.is(':visible') ) {
247 t.sortable('cancel');
248 }
249 }
250 }).sortable('option', 'connectWith', 'div.widgets-sortables').parent().filter('.closed').children('.widgets-sortables').sortable('disable');
251
252 //Droppabilità dei widget disponibili
253 $('#available-widgets').droppable({
254 tolerance: 'pointer',
255 accept: function(o){
256 return $(o).parent().attr('id') != 'widget-list';
257 },
258 drop: function(e,ui) {
259 // Mark form as having unsaved changes
260 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
261 ui.draggable.addClass('deleting');
262 },
263 over: function(e,ui) {
264 ui.draggable.addClass('deleting');
265 $('div.widget-placeholder').hide();
266 },
267 out: function(e,ui) {
268 ui.draggable.removeClass('deleting');
269 $('div.widget-placeholder').show();
270 }
271 });
272
273 // Wrapper divs are now in PHP to prevent layout shift on load
274 // var holders = $('#widgets-left .widget-holder, #widgets-right .widgets-sortables');
275 // holders.wrap('<div class="accua-form-widget-scroll-wrapper"></div>');
276 },
277
278 // Get current field order from sortable areas (without saving)
279 getCurrentOrder: function() {
280 var order = {};
281 $('div.widgets-sortables').each(function() {
282 var $sortable = $(this);
283 if ($sortable.sortable) {
284 order[$sortable.attr('id')] = $sortable.sortable('toArray').join(',');
285 }
286 });
287 return order;
288 },
289
290 // Refresh preview with current (unsaved) order via POST
291 refreshPreviewWithCurrentOrder: function() {
292 var self = this;
293 var $iframe = $('#accua_form_preview_area');
294 var $wrapper = $('#accua_form_preview_area_wrapper');
295
296 if (!$iframe.length || typeof accua_forms_nonces === 'undefined') {
297 return;
298 }
299
300 var formId = $('#accua_form_save_settings_id').val();
301 if (!formId) {
302 return;
303 }
304
305 // Show loading state
306 $wrapper.addClass('accua-form-preview-loading');
307
308 // Get current unsaved order
309 var orderData = self.getCurrentOrder();
310
311 // Create a hidden form to POST the order to a new iframe
312 // This allows preview to render with unsaved order
313 var previewUrl = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId + '&_wpnonce=' + accua_forms_nonces.preview_nonce;
314
315 // Add order as URL parameter (encoded JSON)
316 previewUrl += '&preview_order=' + encodeURIComponent(JSON.stringify(orderData));
317
318 $iframe[0].src = previewUrl;
319
320 // Remove loading state when iframe loads
321 $iframe.off('load.previewRefresh').on('load.previewRefresh', function() {
322 $wrapper.removeClass('accua-form-preview-loading');
323 });
324 },
325
326 // Refresh preview from database (after save)
327 refreshPreview : function() {
328 var $iframe = $('#accua_form_preview_area');
329 var $wrapper = $('#accua_form_preview_area_wrapper');
330 if ($iframe.length && typeof accua_forms_nonces !== 'undefined') {
331 var formId = $('#accua_form_save_settings_id').val();
332 if (formId) {
333 // Show loading state
334 $wrapper.addClass('accua-form-preview-loading');
335 // Refresh from database
336 $iframe[0].src = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId + '&_wpnonce=' + accua_forms_nonces.preview_nonce;
337 // Remove loading state when iframe loads
338 $iframe.off('load.previewRefresh').on('load.previewRefresh', function() {
339 $wrapper.removeClass('accua-form-preview-loading');
340 });
341 }
342 }
343 },
344
345 // Save field order to database via AJAX
346 saveOrder : function(sb, callback) {
347 var self = this;
348
349 // Collect field order from all sortable areas
350 var orderData = {
351 action: 'accua-form-fields-order',
352 _nonce_edit_form: $('#_nonce_edit_form').val(),
353 sidebars: self.getCurrentOrder()
354 };
355
356 // Save field order via AJAX
357 $.post(ajaxurl, orderData, function(response) {
358 if (typeof callback === 'function') {
359 callback(true);
360 }
361 }).fail(function() {
362 console.error('Failed to save field order');
363 if (typeof callback === 'function') {
364 callback(false);
365 }
366 });
367
368 self.resize();
369 },
370
371 //Salvataggio stato widget
372 save : function(widget, del, animate, order, callback) {
373 var sb = widget.closest('div.widgets-sortables').attr('id'), data = widget.find('form').serialize(), a;
374 widget = $(widget);
375
376 // Nothing to save for a widget that carries no field form: the static
377 // "Default Submit button" placeholder is one, and the global Save
378 // chain walks every widget in the drop zone. Its serialized data holds
379 // no form-id, so the handler could only reject the request - and did,
380 // logging a warning and three PHP 8 deprecations on every save. The
381 // reply was "-1", too short for the markup refresh below, so skipping
382 // the round trip leaves the outcome exactly as it was. Deletes still
383 // go through: their DOM cleanup lives in the response handler.
384 if (!del && !widget.find('form [name="form-id"]').length) {
385 if (callback) callback(true);
386 return;
387 }
388
389 a = {
390 action: 'accua-save-form-field',
391 _nonce_edit_form: $('#_nonce_edit_form').val(),
392 sidebar: sb
393 };
394
395 if ( del )
396 a['delete_widget'] = 1;
397
398 data += '&' + $.param(a);
399
400 $.post( ajaxurl, data, function(r){
401 var id;
402
403 if ( del ) {
404 if ( !$('input.widget_number', widget).val() ) {
405 id = $('input.widget-id', widget).val();
406 $('#available-widgets').find('input.widget-id').each(function(){
407 if ( $(this).val() == id )
408 $(this).closest('div.widget').show();
409 });
410 }
411
412 if ( animate ) {
413 order = 0;
414 widget.slideUp('fast', function(){
415 $(this).remove();
416 // Refresh preview
417 accuaWidgets.refreshPreviewWithCurrentOrder();
418 });
419 } else {
420 widget.remove();
421 accuaWidgets.resize();
422 }
423 if (callback) callback(true);
424 } else {
425 if ( r && r.length > 2 ) {
426 $('div.widget-content', widget).html(r);
427 accuaWidgets.appendTitle(widget);
428 // Also update the paired fieldset-end title if this is fieldset-begin
429 if (widget.data('field-type') === 'fieldset-begin') {
430 widget.nextAll('[data-field-type="fieldset-end"]').first().each(function() {
431 accuaWidgets.appendTitle(this);
432 });
433 }
434 accuaWidgets.fixLabels(widget);
435 }
436 if (callback) callback(true);
437 }
438 // Don't save order here - order is saved when user clicks global Save button
439 }).fail(function() {
440 if (callback) callback(false);
441 });
442 },
443
444 // Save field to draft without replacing widget content (for auto-preview)
445 saveSilent : function(widget, callback) {
446 // Same skip as save(): no field form, nothing the handler could store.
447 if (!$(widget).find('form [name="form-id"]').length) {
448 if (callback) callback(true);
449 return;
450 }
451 var sb = widget.closest('div.widgets-sortables').attr('id'),
452 data = widget.find('form').serialize(),
453 a = {
454 action: 'accua-save-form-field',
455 _nonce_edit_form: $('#_nonce_edit_form').val(),
456 sidebar: sb
457 };
458
459 data += '&' + $.param(a);
460
461 $.post(ajaxurl, data, function() {
462 if (callback) callback(true);
463 }).fail(function() {
464 if (callback) callback(false);
465 });
466 },
467
468 //Aggiunta titolo
469 appendTitle : function(widget) {
470 var $widget = $(widget);
471 var fieldType = $widget.data('field-type');
472 var title;
473
474 if (fieldType === 'fieldset-end') {
475 // Mirror the title from the nearest preceding fieldset-begin
476 var $begin = $widget.prevAll('[data-field-type="fieldset-begin"]').first();
477 if (!$begin.length) { return; }
478 title = $begin.find('input[id*="-label"]').val();
479 if (title) {
480 title = title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
481 $widget.children('.widget-top').children('.widget-title').children()
482 .children('.in-widget-title').html(': ' + title);
483 }
484 return;
485 }
486
487 if (fieldType === 'fieldset-begin') {
488 title = $('input[id*="-label"]', widget);
489 } else {
490 title = $('input[id*="-title"]', widget);
491 }
492 if ( title = title.val() ) {
493 title = title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
494 $widget.children('.widget-top').children('.widget-title').children()
495 .children('.in-widget-title').html(': ' + title);
496 }
497 },
498
499 // Show the static "Default Submit button" widget only when no custom submit field is in the form
500 updateDefaultSubmitVisibility: function() {
501 var hasSubmit = $('div.widgets-sortables .widget[data-field-type="submit"]').length > 0;
502 $('#accua-default-submit-widget').toggle(!hasSubmit);
503 },
504
505 resize : function() {
506 /*
507 $('div.widgets-sortables').not('#wp_inactive_widgets').each(function(){
508 var t = $(this);
509 var h = 50, H = t.children('.widget').length;
510 h = h + parseInt(H * 48, 10);
511 t.css( 'minHeight', h + 'px' );
512 });
513 */
514 },
515
516 fixWebkit : function(n) {
517 n = n ? 'none' : '';
518 $('body').css({
519 WebkitUserSelect: n,
520 KhtmlUserSelect: n
521 });
522 },
523
524 fixLabels : function(widget) {
525 widget.children('.widget-inside').find('label').each(function(){
526 var t = $(this);
527 var f = t.attr('for');
528 if ( f && f == $('input', this).attr('id') ) {
529 t.removeAttr('for');
530 }
531 });
532 },
533
534 //Chiusura widget
535 close : function(widget) {
536 // Use CSS transition instead of jQuery slideUp
537 widget.removeClass('open');
538 widget.find('a.widget-action').attr('aria-expanded', 'false');
539 },
540
541 /**
542 * Add a field from the available list to the end of the form drop zone.
543 */
544 addFieldToForm: function(widget) {
545 var id = widget.attr('id'),
546 add = widget.find('input.add_new').val(),
547 n = widget.find('input.multi_number').val(),
548 $sortable = $('div.widgets-sortables').first(),
549 clone;
550
551 if (add === 'multi') {
552 // Multi-use field: clone with new number
553 clone = widget.clone();
554 clone.html(clone.html().replace(/<[^<>]+>/g, function(m){ return m.replace(/__i__|%i%/g, n); }));
555 clone.attr('id', id.replace(/__i__|%i%/g, n));
556 n++;
557 widget.find('input.multi_number').val(n);
558 } else {
559 // Single-use field: move to form, hide original
560 clone = widget.clone();
561 clone.attr('id', 'new-' + id);
562 widget.hide();
563 widget.data('in-form-hidden', true);
564 }
565
566 // Remove the "+" button from the clone in the form area
567 clone.find('a.widget-add-action').remove();
568
569 // Append to sortable area, before the static submit widget if present
570 var $staticWidget = $sortable.children('.accua-static-widget');
571 if ($staticWidget.length) {
572 $staticWidget.before(clone);
573 } else {
574 $sortable.append(clone);
575 }
576
577 // Remove draggable class/data from the clone in the form
578 clone.removeClass('ui-draggable');
579 if (clone.data('draggable')) clone.draggable('destroy');
580
581 // Save the new widget
582 clone.find('input.add_new').val('');
583 accuaWidgets.save(clone, 0, 0, 0, function(success) {
584 if (success) {
585 accuaWidgets.refreshPreviewWithCurrentOrder();
586 }
587 });
588
589 // Open the widget settings
590 clone.find('a.widget-action').click();
591
592 // Scroll the drop zone column so the new widget is visible
593 var $dropCol = clone.closest('.widget-liquid-right');
594 if ($dropCol.length) {
595 $dropCol[0].scrollTop = $dropCol[0].scrollHeight;
596 }
597
598 // Mark form as dirty
599 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
600 },
601
602 /**
603 * Instant filter for available fields list.
604 * Matches if query appears as substring in the field name.
605 */
606 initFieldsFilter: function() {
607 var $filter = $('#accua-fields-filter');
608 if (!$filter.length) return;
609
610 var $widgets = $('#widget-list > .widget');
611
612 // Snapshot which widgets are already in the form (inline display:none from PHP)
613 $widgets.each(function() {
614 if (this.style.display === 'none') {
615 $(this).data('in-form-hidden', true);
616 }
617 });
618
619 function fuzzyMatch(text, query) {
620 return text.toLowerCase().indexOf(query.toLowerCase()) !== -1;
621 }
622
623 $filter.on('input', function() {
624 var query = $.trim(this.value);
625 if (!query) {
626 $widgets.each(function() {
627 $(this).removeClass('accua-filter-hidden');
628 if (!$(this).data('in-form-hidden')) {
629 this.style.removeProperty('display');
630 }
631 });
632 return;
633 }
634 $widgets.each(function() {
635 // Skip widgets already placed in the form
636 if ($(this).data('in-form-hidden')) return;
637
638 var name = $(this).find('.widget-title h4').text();
639 if (fuzzyMatch(name, query)) {
640 $(this).removeClass('accua-filter-hidden');
641 this.style.removeProperty('display');
642 } else {
643 $(this).addClass('accua-filter-hidden');
644 this.style.display = 'none';
645 }
646 });
647 });
648 }
649 };
650
651 $(document).ready(function($){ accuaWidgets.init(); accuaWidgets.initFieldsFilter(); });
652
653 })(jQuery);
654