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-fields.js

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

642 lines 25.2 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 $('#removing-widget').hide().children('span').html('');
263 },
264 over: function(e,ui) {
265 ui.draggable.addClass('deleting');
266 $('div.widget-placeholder').hide();
267
268 if ( ui.draggable.hasClass('ui-sortable-helper') )
269 $('#removing-widget').show().children('span')
270 .html( ui.draggable.find('div.widget-title').children('h4').html() );
271 },
272 out: function(e,ui) {
273 ui.draggable.removeClass('deleting');
274 $('div.widget-placeholder').show();
275 $('#removing-widget').hide().children('span').html('');
276 }
277 });
278
279 // Wrapper divs are now in PHP to prevent layout shift on load
280 // var holders = $('#widgets-left .widget-holder, #widgets-right .widgets-sortables');
281 // holders.wrap('<div class="accua-form-widget-scroll-wrapper"></div>');
282 },
283
284 // Get current field order from sortable areas (without saving)
285 getCurrentOrder: function() {
286 var order = {};
287 $('div.widgets-sortables').each(function() {
288 var $sortable = $(this);
289 if ($sortable.sortable) {
290 order[$sortable.attr('id')] = $sortable.sortable('toArray').join(',');
291 }
292 });
293 return order;
294 },
295
296 // Refresh preview with current (unsaved) order via POST
297 refreshPreviewWithCurrentOrder: function() {
298 var self = this;
299 var $iframe = $('#accua_form_preview_area');
300 var $wrapper = $('#accua_form_preview_area_wrapper');
301
302 if (!$iframe.length || typeof accua_forms_nonces === 'undefined') {
303 return;
304 }
305
306 var formId = $('#accua_form_save_settings_id').val();
307 if (!formId) {
308 return;
309 }
310
311 // Show loading state
312 $wrapper.addClass('accua-form-preview-loading');
313
314 // Get current unsaved order
315 var orderData = self.getCurrentOrder();
316
317 // Create a hidden form to POST the order to a new iframe
318 // This allows preview to render with unsaved order
319 var previewUrl = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId + '&_wpnonce=' + accua_forms_nonces.preview_nonce;
320
321 // Add order as URL parameter (encoded JSON)
322 previewUrl += '&preview_order=' + encodeURIComponent(JSON.stringify(orderData));
323
324 $iframe[0].src = previewUrl;
325
326 // Remove loading state when iframe loads
327 $iframe.off('load.previewRefresh').on('load.previewRefresh', function() {
328 $wrapper.removeClass('accua-form-preview-loading');
329 });
330 },
331
332 // Refresh preview from database (after save)
333 refreshPreview : function() {
334 var $iframe = $('#accua_form_preview_area');
335 var $wrapper = $('#accua_form_preview_area_wrapper');
336 if ($iframe.length && typeof accua_forms_nonces !== 'undefined') {
337 var formId = $('#accua_form_save_settings_id').val();
338 if (formId) {
339 // Show loading state
340 $wrapper.addClass('accua-form-preview-loading');
341 // Refresh from database
342 $iframe[0].src = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId + '&_wpnonce=' + accua_forms_nonces.preview_nonce;
343 // Remove loading state when iframe loads
344 $iframe.off('load.previewRefresh').on('load.previewRefresh', function() {
345 $wrapper.removeClass('accua-form-preview-loading');
346 });
347 }
348 }
349 },
350
351 // Save field order to database via AJAX
352 saveOrder : function(sb, callback) {
353 var self = this;
354
355 // Collect field order from all sortable areas
356 var orderData = {
357 action: 'accua-form-fields-order',
358 _nonce_edit_form: $('#_nonce_edit_form').val(),
359 sidebars: self.getCurrentOrder()
360 };
361
362 // Save field order via AJAX
363 $.post(ajaxurl, orderData, function(response) {
364 if (typeof callback === 'function') {
365 callback(true);
366 }
367 }).fail(function() {
368 console.error('Failed to save field order');
369 if (typeof callback === 'function') {
370 callback(false);
371 }
372 });
373
374 self.resize();
375 },
376
377 //Salvataggio stato widget
378 save : function(widget, del, animate, order, callback) {
379 var sb = widget.closest('div.widgets-sortables').attr('id'), data = widget.find('form').serialize(), a;
380 widget = $(widget);
381
382 a = {
383 action: 'accua-save-form-field',
384 _nonce_edit_form: $('#_nonce_edit_form').val(),
385 sidebar: sb
386 };
387
388 if ( del )
389 a['delete_widget'] = 1;
390
391 data += '&' + $.param(a);
392
393 $.post( ajaxurl, data, function(r){
394 var id;
395
396 if ( del ) {
397 if ( !$('input.widget_number', widget).val() ) {
398 id = $('input.widget-id', widget).val();
399 $('#available-widgets').find('input.widget-id').each(function(){
400 if ( $(this).val() == id )
401 $(this).closest('div.widget').show();
402 });
403 }
404
405 if ( animate ) {
406 order = 0;
407 widget.slideUp('fast', function(){
408 $(this).remove();
409 // Refresh preview
410 accuaWidgets.refreshPreviewWithCurrentOrder();
411 });
412 } else {
413 widget.remove();
414 accuaWidgets.resize();
415 }
416 if (callback) callback(true);
417 } else {
418 if ( r && r.length > 2 ) {
419 $('div.widget-content', widget).html(r);
420 accuaWidgets.appendTitle(widget);
421 // Also update the paired fieldset-end title if this is fieldset-begin
422 if (widget.data('field-type') === 'fieldset-begin') {
423 widget.nextAll('[data-field-type="fieldset-end"]').first().each(function() {
424 accuaWidgets.appendTitle(this);
425 });
426 }
427 accuaWidgets.fixLabels(widget);
428 }
429 if (callback) callback(true);
430 }
431 // Don't save order here - order is saved when user clicks global Save button
432 }).fail(function() {
433 if (callback) callback(false);
434 });
435 },
436
437 // Save field to draft without replacing widget content (for auto-preview)
438 saveSilent : function(widget, callback) {
439 var sb = widget.closest('div.widgets-sortables').attr('id'),
440 data = widget.find('form').serialize(),
441 a = {
442 action: 'accua-save-form-field',
443 _nonce_edit_form: $('#_nonce_edit_form').val(),
444 sidebar: sb
445 };
446
447 data += '&' + $.param(a);
448
449 $.post(ajaxurl, data, function() {
450 if (callback) callback(true);
451 }).fail(function() {
452 if (callback) callback(false);
453 });
454 },
455
456 //Aggiunta titolo
457 appendTitle : function(widget) {
458 var $widget = $(widget);
459 var fieldType = $widget.data('field-type');
460 var title;
461
462 if (fieldType === 'fieldset-end') {
463 // Mirror the title from the nearest preceding fieldset-begin
464 var $begin = $widget.prevAll('[data-field-type="fieldset-begin"]').first();
465 if (!$begin.length) { return; }
466 title = $begin.find('input[id*="-label"]').val();
467 if (title) {
468 title = title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
469 $widget.children('.widget-top').children('.widget-title').children()
470 .children('.in-widget-title').html(': ' + title);
471 }
472 return;
473 }
474
475 if (fieldType === 'fieldset-begin') {
476 title = $('input[id*="-label"]', widget);
477 } else {
478 title = $('input[id*="-title"]', widget);
479 }
480 if ( title = title.val() ) {
481 title = title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
482 $widget.children('.widget-top').children('.widget-title').children()
483 .children('.in-widget-title').html(': ' + title);
484 }
485 },
486
487 // Show the static "Default Submit button" widget only when no custom submit field is in the form
488 updateDefaultSubmitVisibility: function() {
489 var hasSubmit = $('div.widgets-sortables .widget[data-field-type="submit"]').length > 0;
490 $('#accua-default-submit-widget').toggle(!hasSubmit);
491 },
492
493 resize : function() {
494 /*
495 $('div.widgets-sortables').not('#wp_inactive_widgets').each(function(){
496 var t = $(this);
497 var h = 50, H = t.children('.widget').length;
498 h = h + parseInt(H * 48, 10);
499 t.css( 'minHeight', h + 'px' );
500 });
501 */
502 },
503
504 fixWebkit : function(n) {
505 n = n ? 'none' : '';
506 $('body').css({
507 WebkitUserSelect: n,
508 KhtmlUserSelect: n
509 });
510 },
511
512 fixLabels : function(widget) {
513 widget.children('.widget-inside').find('label').each(function(){
514 var t = $(this);
515 var f = t.attr('for');
516 if ( f && f == $('input', this).attr('id') ) {
517 t.removeAttr('for');
518 }
519 });
520 },
521
522 //Chiusura widget
523 close : function(widget) {
524 // Use CSS transition instead of jQuery slideUp
525 widget.removeClass('open');
526 widget.find('a.widget-action').attr('aria-expanded', 'false');
527 },
528
529 /**
530 * Add a field from the available list to the end of the form drop zone.
531 */
532 addFieldToForm: function(widget) {
533 var id = widget.attr('id'),
534 add = widget.find('input.add_new').val(),
535 n = widget.find('input.multi_number').val(),
536 $sortable = $('div.widgets-sortables').first(),
537 clone;
538
539 if (add === 'multi') {
540 // Multi-use field: clone with new number
541 clone = widget.clone();
542 clone.html(clone.html().replace(/<[^<>]+>/g, function(m){ return m.replace(/__i__|%i%/g, n); }));
543 clone.attr('id', id.replace(/__i__|%i%/g, n));
544 n++;
545 widget.find('input.multi_number').val(n);
546 } else {
547 // Single-use field: move to form, hide original
548 clone = widget.clone();
549 clone.attr('id', 'new-' + id);
550 widget.hide();
551 widget.data('in-form-hidden', true);
552 }
553
554 // Remove the "+" button from the clone in the form area
555 clone.find('a.widget-add-action').remove();
556
557 // Append to sortable area, before the static submit widget if present
558 var $staticWidget = $sortable.children('.accua-static-widget');
559 if ($staticWidget.length) {
560 $staticWidget.before(clone);
561 } else {
562 $sortable.append(clone);
563 }
564
565 // Remove draggable class/data from the clone in the form
566 clone.removeClass('ui-draggable');
567 if (clone.data('draggable')) clone.draggable('destroy');
568
569 // Save the new widget
570 clone.find('input.add_new').val('');
571 accuaWidgets.save(clone, 0, 0, 0, function(success) {
572 if (success) {
573 accuaWidgets.refreshPreviewWithCurrentOrder();
574 }
575 });
576
577 // Open the widget settings
578 clone.find('a.widget-action').click();
579
580 // Scroll the drop zone column so the new widget is visible
581 var $dropCol = clone.closest('.widget-liquid-right');
582 if ($dropCol.length) {
583 $dropCol[0].scrollTop = $dropCol[0].scrollHeight;
584 }
585
586 // Mark form as dirty
587 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
588 },
589
590 /**
591 * Instant filter for available fields list.
592 * Matches if query appears as substring in the field name.
593 */
594 initFieldsFilter: function() {
595 var $filter = $('#accua-fields-filter');
596 if (!$filter.length) return;
597
598 var $widgets = $('#widget-list > .widget');
599
600 // Snapshot which widgets are already in the form (inline display:none from PHP)
601 $widgets.each(function() {
602 if (this.style.display === 'none') {
603 $(this).data('in-form-hidden', true);
604 }
605 });
606
607 function fuzzyMatch(text, query) {
608 return text.toLowerCase().indexOf(query.toLowerCase()) !== -1;
609 }
610
611 $filter.on('input', function() {
612 var query = $.trim(this.value);
613 if (!query) {
614 $widgets.each(function() {
615 $(this).removeClass('accua-filter-hidden');
616 if (!$(this).data('in-form-hidden')) {
617 this.style.removeProperty('display');
618 }
619 });
620 return;
621 }
622 $widgets.each(function() {
623 // Skip widgets already placed in the form
624 if ($(this).data('in-form-hidden')) return;
625
626 var name = $(this).find('.widget-title h4').text();
627 if (fuzzyMatch(name, query)) {
628 $(this).removeClass('accua-filter-hidden');
629 this.style.removeProperty('display');
630 } else {
631 $(this).addClass('accua-filter-hidden');
632 this.style.display = 'none';
633 }
634 });
635 });
636 }
637 };
638
639 $(document).ready(function($){ accuaWidgets.init(); accuaWidgets.initFieldsFilter(); });
640
641 })(jQuery);
642