PluginProbe
Contact Forms by Cimatti / 2.3.0
Contact Forms by Cimatti v2.3.0
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 2.3.0, at assets/js/admin/form-fields.js

636 lines 24.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 a = {
377 action: 'accua-save-form-field',
378 _nonce_edit_form: $('#_nonce_edit_form').val(),
379 sidebar: sb
380 };
381
382 if ( del )
383 a['delete_widget'] = 1;
384
385 data += '&' + $.param(a);
386
387 $.post( ajaxurl, data, function(r){
388 var id;
389
390 if ( del ) {
391 if ( !$('input.widget_number', widget).val() ) {
392 id = $('input.widget-id', widget).val();
393 $('#available-widgets').find('input.widget-id').each(function(){
394 if ( $(this).val() == id )
395 $(this).closest('div.widget').show();
396 });
397 }
398
399 if ( animate ) {
400 order = 0;
401 widget.slideUp('fast', function(){
402 $(this).remove();
403 // Refresh preview
404 accuaWidgets.refreshPreviewWithCurrentOrder();
405 });
406 } else {
407 widget.remove();
408 accuaWidgets.resize();
409 }
410 if (callback) callback(true);
411 } else {
412 if ( r && r.length > 2 ) {
413 $('div.widget-content', widget).html(r);
414 accuaWidgets.appendTitle(widget);
415 // Also update the paired fieldset-end title if this is fieldset-begin
416 if (widget.data('field-type') === 'fieldset-begin') {
417 widget.nextAll('[data-field-type="fieldset-end"]').first().each(function() {
418 accuaWidgets.appendTitle(this);
419 });
420 }
421 accuaWidgets.fixLabels(widget);
422 }
423 if (callback) callback(true);
424 }
425 // Don't save order here - order is saved when user clicks global Save button
426 }).fail(function() {
427 if (callback) callback(false);
428 });
429 },
430
431 // Save field to draft without replacing widget content (for auto-preview)
432 saveSilent : function(widget, callback) {
433 var sb = widget.closest('div.widgets-sortables').attr('id'),
434 data = widget.find('form').serialize(),
435 a = {
436 action: 'accua-save-form-field',
437 _nonce_edit_form: $('#_nonce_edit_form').val(),
438 sidebar: sb
439 };
440
441 data += '&' + $.param(a);
442
443 $.post(ajaxurl, data, function() {
444 if (callback) callback(true);
445 }).fail(function() {
446 if (callback) callback(false);
447 });
448 },
449
450 //Aggiunta titolo
451 appendTitle : function(widget) {
452 var $widget = $(widget);
453 var fieldType = $widget.data('field-type');
454 var title;
455
456 if (fieldType === 'fieldset-end') {
457 // Mirror the title from the nearest preceding fieldset-begin
458 var $begin = $widget.prevAll('[data-field-type="fieldset-begin"]').first();
459 if (!$begin.length) { return; }
460 title = $begin.find('input[id*="-label"]').val();
461 if (title) {
462 title = title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
463 $widget.children('.widget-top').children('.widget-title').children()
464 .children('.in-widget-title').html(': ' + title);
465 }
466 return;
467 }
468
469 if (fieldType === 'fieldset-begin') {
470 title = $('input[id*="-label"]', widget);
471 } else {
472 title = $('input[id*="-title"]', widget);
473 }
474 if ( title = title.val() ) {
475 title = title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
476 $widget.children('.widget-top').children('.widget-title').children()
477 .children('.in-widget-title').html(': ' + title);
478 }
479 },
480
481 // Show the static "Default Submit button" widget only when no custom submit field is in the form
482 updateDefaultSubmitVisibility: function() {
483 var hasSubmit = $('div.widgets-sortables .widget[data-field-type="submit"]').length > 0;
484 $('#accua-default-submit-widget').toggle(!hasSubmit);
485 },
486
487 resize : function() {
488 /*
489 $('div.widgets-sortables').not('#wp_inactive_widgets').each(function(){
490 var t = $(this);
491 var h = 50, H = t.children('.widget').length;
492 h = h + parseInt(H * 48, 10);
493 t.css( 'minHeight', h + 'px' );
494 });
495 */
496 },
497
498 fixWebkit : function(n) {
499 n = n ? 'none' : '';
500 $('body').css({
501 WebkitUserSelect: n,
502 KhtmlUserSelect: n
503 });
504 },
505
506 fixLabels : function(widget) {
507 widget.children('.widget-inside').find('label').each(function(){
508 var t = $(this);
509 var f = t.attr('for');
510 if ( f && f == $('input', this).attr('id') ) {
511 t.removeAttr('for');
512 }
513 });
514 },
515
516 //Chiusura widget
517 close : function(widget) {
518 // Use CSS transition instead of jQuery slideUp
519 widget.removeClass('open');
520 widget.find('a.widget-action').attr('aria-expanded', 'false');
521 },
522
523 /**
524 * Add a field from the available list to the end of the form drop zone.
525 */
526 addFieldToForm: function(widget) {
527 var id = widget.attr('id'),
528 add = widget.find('input.add_new').val(),
529 n = widget.find('input.multi_number').val(),
530 $sortable = $('div.widgets-sortables').first(),
531 clone;
532
533 if (add === 'multi') {
534 // Multi-use field: clone with new number
535 clone = widget.clone();
536 clone.html(clone.html().replace(/<[^<>]+>/g, function(m){ return m.replace(/__i__|%i%/g, n); }));
537 clone.attr('id', id.replace(/__i__|%i%/g, n));
538 n++;
539 widget.find('input.multi_number').val(n);
540 } else {
541 // Single-use field: move to form, hide original
542 clone = widget.clone();
543 clone.attr('id', 'new-' + id);
544 widget.hide();
545 widget.data('in-form-hidden', true);
546 }
547
548 // Remove the "+" button from the clone in the form area
549 clone.find('a.widget-add-action').remove();
550
551 // Append to sortable area, before the static submit widget if present
552 var $staticWidget = $sortable.children('.accua-static-widget');
553 if ($staticWidget.length) {
554 $staticWidget.before(clone);
555 } else {
556 $sortable.append(clone);
557 }
558
559 // Remove draggable class/data from the clone in the form
560 clone.removeClass('ui-draggable');
561 if (clone.data('draggable')) clone.draggable('destroy');
562
563 // Save the new widget
564 clone.find('input.add_new').val('');
565 accuaWidgets.save(clone, 0, 0, 0, function(success) {
566 if (success) {
567 accuaWidgets.refreshPreviewWithCurrentOrder();
568 }
569 });
570
571 // Open the widget settings
572 clone.find('a.widget-action').click();
573
574 // Scroll the drop zone column so the new widget is visible
575 var $dropCol = clone.closest('.widget-liquid-right');
576 if ($dropCol.length) {
577 $dropCol[0].scrollTop = $dropCol[0].scrollHeight;
578 }
579
580 // Mark form as dirty
581 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
582 },
583
584 /**
585 * Instant filter for available fields list.
586 * Matches if query appears as substring in the field name.
587 */
588 initFieldsFilter: function() {
589 var $filter = $('#accua-fields-filter');
590 if (!$filter.length) return;
591
592 var $widgets = $('#widget-list > .widget');
593
594 // Snapshot which widgets are already in the form (inline display:none from PHP)
595 $widgets.each(function() {
596 if (this.style.display === 'none') {
597 $(this).data('in-form-hidden', true);
598 }
599 });
600
601 function fuzzyMatch(text, query) {
602 return text.toLowerCase().indexOf(query.toLowerCase()) !== -1;
603 }
604
605 $filter.on('input', function() {
606 var query = $.trim(this.value);
607 if (!query) {
608 $widgets.each(function() {
609 $(this).removeClass('accua-filter-hidden');
610 if (!$(this).data('in-form-hidden')) {
611 this.style.removeProperty('display');
612 }
613 });
614 return;
615 }
616 $widgets.each(function() {
617 // Skip widgets already placed in the form
618 if ($(this).data('in-form-hidden')) return;
619
620 var name = $(this).find('.widget-title h4').text();
621 if (fuzzyMatch(name, query)) {
622 $(this).removeClass('accua-filter-hidden');
623 this.style.removeProperty('display');
624 } else {
625 $(this).addClass('accua-filter-hidden');
626 this.style.display = 'none';
627 }
628 });
629 });
630 }
631 };
632
633 $(document).ready(function($){ accuaWidgets.init(); accuaWidgets.initFieldsFilter(); });
634
635 })(jQuery);
636