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

581 lines 22.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 });
133
134 //Impostazione titolo e apertura widget se c'è un errore
135 sidebars.children('.widget').each(function() {
136 accuaWidgets.appendTitle(this);
137 if ( $('p.widget-error', this).length )
138 $('a.widget-action', this).click();
139 });
140
141 //Trascinabilità dei widget nella lista
142 $('#widget-list').children('.widget').draggable({
143 connectToSortable: 'div.widgets-sortables',
144 handle: '> .widget-top > .widget-title',
145 distance: 2,
146 helper: 'clone',
147 zIndex: 5,
148 containment: 'document',
149 start: function(e,ui) {
150 accuaWidgets.fixWebkit(1);
151 ui.helper.find('div.widget-description').hide();
152 the_id = this.id;
153 },
154 stop: function(e,ui) {
155 if ( rem )
156 $(rem).hide();
157 rem = '';
158 accuaWidgets.fixWebkit();
159 }
160 });
161
162 //Ordinabilità dei widget
163 sidebars.sortable({
164 placeholder: 'widget-placeholder',
165 items: '> .widget',
166 handle: '> .widget-top > .widget-title',
167 cursor: 'move',
168 distance: 2,
169 containment: 'document',
170 start: function(e,ui) {
171 accuaWidgets.fixWebkit(1);
172 ui.item.children('.widget-inside').hide();
173 ui.item.css({margin:'', 'width':''});
174 },
175 stop: function(e,ui) {
176 // Mark form as having unsaved changes
177 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
178
179 if ( ui.item.hasClass('ui-draggable') && ui.item.data('draggable') )
180 ui.item.draggable('destroy');
181
182 if ( ui.item.hasClass('deleting') ) {
183 accuaWidgets.save( ui.item, 1, 0, 1 ); // delete widget
184 ui.item.remove();
185 return;
186 }
187
188 var add = ui.item.find('input.add_new').val(),
189 n = ui.item.find('input.multi_number').val(),
190 id = the_id,
191 sb = $(this).attr('id');
192
193 ui.item.css({margin:'', 'width':''});
194 the_id = '';
195
196 accuaWidgets.fixWebkit();
197 if ( add ) {
198 if ( 'multi' == add ) {
199 ui.item.html( ui.item.html().replace(/<[^<>]+>/g, function(m){ return m.replace(/__i__|%i%/g, n); }) );
200 ui.item.attr( 'id', id.replace(/__i__|%i%/g, n) );
201 n++;
202 $('div#' + id).find('input.multi_number').val(n);
203 } else if ( 'single' == add ) {
204 ui.item.attr( 'id', 'new-' + id );
205 rem = 'div#' + id;
206 }
207 // Save the new widget to DB, but DON'T save order yet
208 accuaWidgets.save( ui.item, 0, 0, 0, function(success) {
209 if (success) {
210 // Refresh preview with current order
211 accuaWidgets.refreshPreviewWithCurrentOrder();
212 }
213 });
214 ui.item.find('input.add_new').val('');
215 ui.item.find('a.widget-action').click();
216 return;
217 }
218 // Refresh preview (without saving to database)
219 accuaWidgets.refreshPreviewWithCurrentOrder();
220 },
221 receive: function(e,ui) {
222 var t = $(this);
223 if ( !t.is(':visible') ) {
224 t.sortable('cancel');
225 }
226 }
227 }).sortable('option', 'connectWith', 'div.widgets-sortables').parent().filter('.closed').children('.widgets-sortables').sortable('disable');
228
229 //Droppabilità dei widget disponibili
230 $('#available-widgets').droppable({
231 tolerance: 'pointer',
232 accept: function(o){
233 return $(o).parent().attr('id') != 'widget-list';
234 },
235 drop: function(e,ui) {
236 // Mark form as having unsaved changes
237 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
238 ui.draggable.addClass('deleting');
239 $('#removing-widget').hide().children('span').html('');
240 },
241 over: function(e,ui) {
242 ui.draggable.addClass('deleting');
243 $('div.widget-placeholder').hide();
244
245 if ( ui.draggable.hasClass('ui-sortable-helper') )
246 $('#removing-widget').show().children('span')
247 .html( ui.draggable.find('div.widget-title').children('h4').html() );
248 },
249 out: function(e,ui) {
250 ui.draggable.removeClass('deleting');
251 $('div.widget-placeholder').show();
252 $('#removing-widget').hide().children('span').html('');
253 }
254 });
255
256 // Wrapper divs are now in PHP to prevent layout shift on load
257 // var holders = $('#widgets-left .widget-holder, #widgets-right .widgets-sortables');
258 // holders.wrap('<div class="accua-form-widget-scroll-wrapper"></div>');
259 },
260
261 // Get current field order from sortable areas (without saving)
262 getCurrentOrder: function() {
263 var order = {};
264 $('div.widgets-sortables').each(function() {
265 var $sortable = $(this);
266 if ($sortable.sortable) {
267 order[$sortable.attr('id')] = $sortable.sortable('toArray').join(',');
268 }
269 });
270 return order;
271 },
272
273 // Refresh preview with current (unsaved) order via POST
274 refreshPreviewWithCurrentOrder: function() {
275 var self = this;
276 var $iframe = $('#accua_form_preview_area');
277 var $wrapper = $('#accua_form_preview_area_wrapper');
278
279 if (!$iframe.length || typeof accua_forms_nonces === 'undefined') {
280 return;
281 }
282
283 var formId = $('#accua_form_save_settings_id').val();
284 if (!formId) {
285 return;
286 }
287
288 // Show loading state
289 $wrapper.addClass('accua-form-preview-loading');
290
291 // Get current unsaved order
292 var orderData = self.getCurrentOrder();
293
294 // Create a hidden form to POST the order to a new iframe
295 // This allows preview to render with unsaved order
296 var previewUrl = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId + '&_wpnonce=' + accua_forms_nonces.preview_nonce;
297
298 // Add order as URL parameter (encoded JSON)
299 previewUrl += '&preview_order=' + encodeURIComponent(JSON.stringify(orderData));
300
301 $iframe[0].src = previewUrl;
302
303 // Remove loading state when iframe loads
304 $iframe.off('load.previewRefresh').on('load.previewRefresh', function() {
305 $wrapper.removeClass('accua-form-preview-loading');
306 });
307 },
308
309 // Refresh preview from database (after save)
310 refreshPreview : function() {
311 var $iframe = $('#accua_form_preview_area');
312 var $wrapper = $('#accua_form_preview_area_wrapper');
313 if ($iframe.length && typeof accua_forms_nonces !== 'undefined') {
314 var formId = $('#accua_form_save_settings_id').val();
315 if (formId) {
316 // Show loading state
317 $wrapper.addClass('accua-form-preview-loading');
318 // Refresh from database
319 $iframe[0].src = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId + '&_wpnonce=' + accua_forms_nonces.preview_nonce;
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 },
327
328 // Save field order to database via AJAX
329 saveOrder : function(sb, callback) {
330 var self = this;
331
332 // Collect field order from all sortable areas
333 var orderData = {
334 action: 'accua-form-fields-order',
335 _nonce_edit_form: $('#_nonce_edit_form').val(),
336 sidebars: self.getCurrentOrder()
337 };
338
339 // Save field order via AJAX
340 $.post(ajaxurl, orderData, function(response) {
341 if (typeof callback === 'function') {
342 callback(true);
343 }
344 }).fail(function() {
345 console.error('Failed to save field order');
346 if (typeof callback === 'function') {
347 callback(false);
348 }
349 });
350
351 self.resize();
352 },
353
354 //Salvataggio stato widget
355 save : function(widget, del, animate, order, callback) {
356 var sb = widget.closest('div.widgets-sortables').attr('id'), data = widget.find('form').serialize(), a;
357 widget = $(widget);
358
359 a = {
360 action: 'accua-save-form-field',
361 _nonce_edit_form: $('#_nonce_edit_form').val(),
362 sidebar: sb
363 };
364
365 if ( del )
366 a['delete_widget'] = 1;
367
368 data += '&' + $.param(a);
369
370 $.post( ajaxurl, data, function(r){
371 var id;
372
373 if ( del ) {
374 if ( !$('input.widget_number', widget).val() ) {
375 id = $('input.widget-id', widget).val();
376 $('#available-widgets').find('input.widget-id').each(function(){
377 if ( $(this).val() == id )
378 $(this).closest('div.widget').show();
379 });
380 }
381
382 if ( animate ) {
383 order = 0;
384 widget.slideUp('fast', function(){
385 $(this).remove();
386 // Refresh preview
387 accuaWidgets.refreshPreviewWithCurrentOrder();
388 });
389 } else {
390 widget.remove();
391 accuaWidgets.resize();
392 }
393 if (callback) callback(true);
394 } else {
395 if ( r && r.length > 2 ) {
396 $('div.widget-content', widget).html(r);
397 accuaWidgets.appendTitle(widget);
398 accuaWidgets.fixLabels(widget);
399 }
400 if (callback) callback(true);
401 }
402 // Don't save order here - order is saved when user clicks global Save button
403 }).fail(function() {
404 if (callback) callback(false);
405 });
406 },
407
408 // Save field to draft without replacing widget content (for auto-preview)
409 saveSilent : function(widget, callback) {
410 var sb = widget.closest('div.widgets-sortables').attr('id'),
411 data = widget.find('form').serialize(),
412 a = {
413 action: 'accua-save-form-field',
414 _nonce_edit_form: $('#_nonce_edit_form').val(),
415 sidebar: sb
416 };
417
418 data += '&' + $.param(a);
419
420 $.post(ajaxurl, data, function() {
421 if (callback) callback(true);
422 }).fail(function() {
423 if (callback) callback(false);
424 });
425 },
426
427 //Aggiunta titolo
428 appendTitle : function(widget) {
429 var title = $('input[id*="-title"]', widget);
430 if ( title = title.val() ) {
431 title = title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
432 $(widget).children('.widget-top').children('.widget-title').children()
433 .children('.in-widget-title').html(': ' + title);
434 }
435 },
436
437 resize : function() {
438 /*
439 $('div.widgets-sortables').not('#wp_inactive_widgets').each(function(){
440 var t = $(this);
441 var h = 50, H = t.children('.widget').length;
442 h = h + parseInt(H * 48, 10);
443 t.css( 'minHeight', h + 'px' );
444 });
445 */
446 },
447
448 fixWebkit : function(n) {
449 n = n ? 'none' : '';
450 $('body').css({
451 WebkitUserSelect: n,
452 KhtmlUserSelect: n
453 });
454 },
455
456 fixLabels : function(widget) {
457 widget.children('.widget-inside').find('label').each(function(){
458 var t = $(this);
459 var f = t.attr('for');
460 if ( f && f == $('input', this).attr('id') ) {
461 t.removeAttr('for');
462 }
463 });
464 },
465
466 //Chiusura widget
467 close : function(widget) {
468 // Use CSS transition instead of jQuery slideUp
469 widget.removeClass('open');
470 widget.find('a.widget-action').attr('aria-expanded', 'false');
471 },
472
473 /**
474 * Add a field from the available list to the end of the form drop zone.
475 */
476 addFieldToForm: function(widget) {
477 var id = widget.attr('id'),
478 add = widget.find('input.add_new').val(),
479 n = widget.find('input.multi_number').val(),
480 $sortable = $('div.widgets-sortables').first(),
481 clone;
482
483 if (add === 'multi') {
484 // Multi-use field: clone with new number
485 clone = widget.clone();
486 clone.html(clone.html().replace(/<[^<>]+>/g, function(m){ return m.replace(/__i__|%i%/g, n); }));
487 clone.attr('id', id.replace(/__i__|%i%/g, n));
488 n++;
489 widget.find('input.multi_number').val(n);
490 } else {
491 // Single-use field: move to form, hide original
492 clone = widget.clone();
493 clone.attr('id', 'new-' + id);
494 widget.hide();
495 widget.data('in-form-hidden', true);
496 }
497
498 // Remove the "+" button from the clone in the form area
499 clone.find('a.widget-add-action').remove();
500
501 // Append to sortable area
502 $sortable.append(clone);
503
504 // Remove draggable class/data from the clone in the form
505 clone.removeClass('ui-draggable');
506 if (clone.data('draggable')) clone.draggable('destroy');
507
508 // Save the new widget
509 clone.find('input.add_new').val('');
510 accuaWidgets.save(clone, 0, 0, 0, function(success) {
511 if (success) {
512 accuaWidgets.refreshPreviewWithCurrentOrder();
513 }
514 });
515
516 // Open the widget settings
517 clone.find('a.widget-action').click();
518
519 // Scroll the drop zone column so the new widget is visible
520 var $dropCol = clone.closest('.widget-liquid-right');
521 if ($dropCol.length) {
522 $dropCol[0].scrollTop = $dropCol[0].scrollHeight;
523 }
524
525 // Mark form as dirty
526 if (window.accuaFormsEditorDirty) { window.accuaFormsEditorDirty.mark(); }
527 },
528
529 /**
530 * Instant filter for available fields list.
531 * Matches if query appears as substring in the field name.
532 */
533 initFieldsFilter: function() {
534 var $filter = $('#accua-fields-filter');
535 if (!$filter.length) return;
536
537 var $widgets = $('#widget-list > .widget');
538
539 // Snapshot which widgets are already in the form (inline display:none from PHP)
540 $widgets.each(function() {
541 if (this.style.display === 'none') {
542 $(this).data('in-form-hidden', true);
543 }
544 });
545
546 function fuzzyMatch(text, query) {
547 return text.toLowerCase().indexOf(query.toLowerCase()) !== -1;
548 }
549
550 $filter.on('input', function() {
551 var query = $.trim(this.value);
552 if (!query) {
553 $widgets.each(function() {
554 $(this).removeClass('accua-filter-hidden');
555 if (!$(this).data('in-form-hidden')) {
556 this.style.removeProperty('display');
557 }
558 });
559 return;
560 }
561 $widgets.each(function() {
562 // Skip widgets already placed in the form
563 if ($(this).data('in-form-hidden')) return;
564
565 var name = $(this).find('.widget-title h4').text();
566 if (fuzzyMatch(name, query)) {
567 $(this).removeClass('accua-filter-hidden');
568 this.style.removeProperty('display');
569 } else {
570 $(this).addClass('accua-filter-hidden');
571 this.style.display = 'none';
572 }
573 });
574 });
575 }
576 };
577
578 $(document).ready(function($){ accuaWidgets.init(); accuaWidgets.initFieldsFilter(); });
579
580 })(jQuery);
581