PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.7.2
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.7.2
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / js / formidable_admin.js

formidable_admin.js in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.7.2, at js/formidable_admin.js

10,482 lines 314.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* exported frm_add_logic_row, frm_remove_tag, frm_show_div, frmCheckAll, frmCheckAllLevel */
2
3 var frmAdminBuild;
4
5 var FrmFormsConnect = window.FrmFormsConnect || ( function( document, window, $ ) {
6
7 /*global jQuery:false, frm_admin_js, frmGlobal, ajaxurl */
8
9 var el = {
10 licenseBox: document.getElementById( 'frm_license_top' ),
11 messageBox: document.getElementsByClassName( 'frm_pro_license_msg' )[0],
12 btn: document.getElementById( 'frm-settings-connect-btn' ),
13 reset: document.getElementById( 'frm_reconnect_link' )
14 };
15
16 /**
17 * Public functions and properties.
18 *
19 * @since 4.03
20 *
21 * @type {Object}
22 */
23 var app = {
24
25 /**
26 * Register connect button event.
27 *
28 * @since 4.03
29 */
30 init: function() {
31 $( document.getElementById( 'frm_deauthorize_link' ) ).on( 'click', app.deauthorize );
32 $( '.frm_authorize_link' ).on( 'click', app.authorize );
33 if ( el.reset !== null ) {
34 $( el.reset ).on( 'click', app.reauthorize );
35 }
36
37 $( el.btn ).on( 'click', function( e ) {
38 e.preventDefault();
39 app.gotoUpgradeUrl();
40 });
41
42 window.addEventListener( 'message', function( msg ) {
43 if ( msg.origin.replace( /\/$/, '' ) !== frmGlobal.app_url.replace( /\/$/, '' ) ) {
44 return;
45 }
46
47 if ( ! msg.data || 'object' !== typeof msg.data ) {
48 console.error( 'Messages from "' + frmGlobal.app_url + '" must contain an api key string.' );
49 return;
50 }
51
52 app.updateForm( msg.data );
53 });
54 },
55
56 /**
57 * Go to upgrade url.
58 *
59 * @since 4.03
60 */
61 gotoUpgradeUrl: function() {
62 var w = window.open( frmGlobal.app_url + '/api-connect/', '_blank', 'location=no,width=500,height=730,scrollbars=0' );
63 w.focus();
64 },
65
66 updateForm: function( response ) {
67
68 // Start spinner.
69 var btn = el.btn;
70 btn.classList.add( 'frm_loading_button' );
71
72 if ( response.url !== '' ) {
73 app.showProgress({
74 success: true,
75 message: 'Installing...'
76 });
77 var fallback = setTimeout( function() {
78 app.showProgress({
79 success: true,
80 message: 'Installing is taking longer than expected. <a class="frm-install-addon button button-primary frm-button-primary" rel="' + response.url + '" aria-label="Install">Install Now</a>'
81 });
82 }, 10000 );
83 $.ajax({
84 type: 'POST',
85 url: ajaxurl,
86 dataType: 'json',
87 data: {
88 action: 'frm_connect',
89 plugin: response.url,
90 nonce: frmGlobal.nonce
91 },
92 success: function() {
93 clearTimeout( fallback );
94 app.activateKey( response );
95 },
96 error: function( xhr, textStatus, e ) {
97 clearTimeout( fallback );
98 btn.classList.remove( 'frm_loading_button' );
99 app.showMessage({
100 success: false,
101 message: e
102 });
103 }
104 });
105 } else if ( response.key !== '' ) {
106 app.activateKey( response );
107 }
108 },
109
110 activateKey: function( response ) {
111 var btn = el.btn;
112 if ( response.key === '' ) {
113 btn.classList.remove( 'frm_loading_button' );
114 } else {
115 app.showProgress({
116 success: true,
117 message: 'Activating...'
118 });
119 $.ajax({
120 type: 'POST',
121 url: ajaxurl,
122 dataType: 'json',
123 data: {
124 action: 'frm_addon_activate',
125 license: response.key,
126 plugin: 'formidable_pro',
127 wpmu: 0,
128 nonce: frmGlobal.nonce
129 },
130 success: function( msg ) {
131 btn.classList.remove( 'frm_loading_button' );
132
133 if ( msg.success === true ) {
134 el.licenseBox.classList.replace( 'frm_unauthorized_box', 'frm_authorized_box' );
135 }
136
137 app.showMessage( msg );
138 },
139 error: function( xhr, textStatus, e ) {
140 btn.classList.remove( 'frm_loading_button' );
141 app.showMessage({
142 success: false,
143 message: e
144 });
145 }
146 });
147 }
148 },
149
150 /* Manual license authorization */
151 authorize: function() {
152 /*jshint validthis:true */
153 var button = this;
154 var pluginSlug = this.getAttribute( 'data-plugin' );
155 var input = document.getElementById( 'edd_' + pluginSlug + '_license_key' );
156 var license = input.value;
157 var wpmu = document.getElementById( 'proplug-wpmu' );
158 this.classList.add( 'frm_loading_button' );
159 if ( wpmu === null ) {
160 wpmu = 0;
161 } else if ( wpmu.checked ) {
162 wpmu = 1;
163 } else {
164 wpmu = 0;
165 }
166
167 $.ajax({
168 type: 'POST', url: ajaxurl, dataType: 'json',
169 data: {
170 action: 'frm_addon_activate',
171 license: license,
172 plugin: pluginSlug,
173 wpmu: wpmu,
174 nonce: frmGlobal.nonce
175 },
176 success: function( msg ) {
177 app.afterAuthorize( msg, input );
178 button.classList.remove( 'frm_loading_button' );
179 }
180 });
181 },
182
183 afterAuthorize: function( msg, input ) {
184 if ( msg.success === true ) {
185 input.value = '•••••••••••••••••••';
186 }
187
188 app.showMessage( msg );
189 },
190
191 showProgress: function( msg ) {
192 var messageBox = el.messageBox;
193 if ( msg.success === true ) {
194 messageBox.classList.remove( 'frm_error_style' );
195 messageBox.classList.add( 'frm_message', 'frm_updated_message' );
196 } else {
197 messageBox.classList.add( 'frm_error_style' );
198 messageBox.classList.remove( 'frm_message', 'frm_updated_message' );
199 }
200 messageBox.classList.remove( 'frm_hidden' );
201 messageBox.innerHTML = msg.message;
202 },
203
204 showMessage: function( msg ) {
205 var messageBox = el.messageBox;
206
207 if ( msg.success === true ) {
208 var d = el.licenseBox;
209 d.className = d.className.replace( 'frm_unauthorized_box', 'frm_authorized_box' );
210 messageBox.classList.remove( 'frm_error_style' );
211 messageBox.classList.add( 'frm_message', 'frm_updated_message' );
212 } else {
213 messageBox.classList.add( 'frm_error_style' );
214 messageBox.classList.remove( 'frm_message', 'frm_updated_message' );
215 }
216
217 messageBox.classList.remove( 'frm_hidden' );
218 messageBox.innerHTML = msg.message;
219 if ( msg.message !== '' ) {
220 setTimeout( function() {
221 messageBox.innerHTML = '';
222 messageBox.classList.add( 'frm_hidden' );
223 messageBox.classList.remove( 'frm_error_style', 'frm_message', 'frm_updated_message' );
224 }, 10000 );
225 var refreshPage = document.querySelectorAll( '#frm-welcome' );
226 if ( refreshPage.length > 0 ) {
227 window.location.reload();
228 }
229 }
230 },
231
232 /* Clear the site license cache */
233 reauthorize: function() {
234 /*jshint validthis:true */
235 this.innerHTML = '<span class="frm-wait frm_spinner" style="visibility:visible;float:none"></span>';
236
237 $.ajax({
238 type: 'POST',
239 url: ajaxurl,
240 dataType: 'json',
241 data: {
242 action: 'frm_reset_cache',
243 plugin: 'formidable_pro',
244 nonce: frmGlobal.nonce
245 },
246 success: function( msg ) {
247 el.reset.textContent = msg.message;
248 if ( el.reset.getAttribute( 'data-refresh' ) === '1' ) {
249 window.location.reload();
250 }
251 }
252 });
253 return false;
254 },
255
256 deauthorize: function() {
257 /*jshint validthis:true */
258 if ( ! confirm( frmGlobal.deauthorize ) ) {
259 return false;
260 }
261 var pluginSlug = this.getAttribute( 'data-plugin' ),
262 input = document.getElementById( 'edd_' + pluginSlug + '_license_key' ),
263 license = input.value,
264 link = this;
265
266 this.innerHTML = '<span class="frm-wait frm_spinner" style="visibility:visible;"></span>';
267
268 $.ajax({
269 type: 'POST',
270 url: ajaxurl,
271 data: {
272 action: 'frm_addon_deactivate',
273 license: license,
274 plugin: pluginSlug,
275 nonce: frmGlobal.nonce
276 },
277 success: function() {
278 el.licenseBox.className = el.licenseBox.className.replace( 'frm_authorized_box', 'frm_unauthorized_box' );
279 input.value = '';
280 link.innerHTML = '';
281 }
282 });
283 return false;
284 }
285 };
286
287 // Provide access to public functions/properties.
288 return app;
289
290 }( document, window, jQuery ) );
291
292 function frmAdminBuildJS() {
293 //'use strict';
294
295 /*global jQuery:false, frm_admin_js, frmGlobal, ajaxurl, fromDom */
296
297 const { tag, div, span, a, svg, img } = frmDom;
298 const { onClickPreventDefault } = frmDom.util;
299 const { doJsonFetch, doJsonPost } = frmDom.ajax;
300 const icons = {
301 save: svg({ href: '#frm_save_icon' }),
302 drag: svg({ href: '#frm_drag_icon', classList: [ 'frm_drag_icon', 'frm-drag' ] })
303 };
304
305 let $newFields = jQuery( document.getElementById( 'frm-show-fields' ) ),
306 builderForm = document.getElementById( 'new_fields' ),
307 thisForm = document.getElementById( 'form_id' ),
308 copyHelper = false,
309 fieldsUpdated = 0,
310 thisFormId = 0,
311 autoId = 0,
312 optionMap = {},
313 lastNewActionIdReturned = 0;
314
315 const { __, sprintf } = wp.i18n;
316 let debouncedSyncAfterDragAndDrop, postBodyContent, $postBodyContent;
317
318 if ( thisForm !== null ) {
319 thisFormId = thisForm.value;
320 }
321
322 const currentURL = new URL( window.location.href );
323 const urlParams = currentURL.searchParams;
324
325 // Global settings
326 var s;
327
328 function showElement( element ) {
329 element[0].style.display = '';
330 }
331
332 function hideElement( element ) {
333 element[0].style.display = 'none';
334 }
335
336 function empty( $obj ) {
337 if ( $obj !== null ) {
338 while ( $obj.firstChild ) {
339 $obj.removeChild( $obj.firstChild );
340 }
341 }
342 }
343
344 function addClass( $obj, className ) {
345 if ( $obj.classList ) {
346 $obj.classList.add( className );
347 } else {
348 $obj.className += ' ' + className;
349 }
350 }
351
352 function confirmClick( e ) {
353 /*jshint validthis:true */
354 e.stopPropagation();
355 e.preventDefault();
356 confirmLinkClick( this );
357 }
358
359 function confirmLinkClick( link ) {
360 var message = link.getAttribute( 'data-frmverify' ),
361 loadedFrom = link.getAttribute( 'data-loaded-from' ) ;
362
363 if ( message === null || link.id === 'frm-confirmed-click' ) {
364 return true;
365 }
366
367 if ( 'entries-list' === loadedFrom ) {
368 return wp.hooks.applyFilters( 'frm_on_multiple_entries_delete', { link, initModal });
369 }
370
371 return confirmModal( link );
372 }
373
374 function confirmModal( link ) {
375 var verify, $confirmMessage, i, dataAtts, btnClass,
376 $info = initModal( '#frm_confirm_modal', '400px' ),
377 continueButton = document.getElementById( 'frm-confirmed-click' );
378
379 if ( $info === false ) {
380 return false;
381 }
382
383 verify = link.getAttribute( 'data-frmverify' );
384 btnClass = verify ? link.getAttribute( 'data-frmverify-btn' ) : '';
385 $confirmMessage = jQuery( '.frm-confirm-msg' );
386 $confirmMessage.empty();
387
388 if ( verify ) {
389 $confirmMessage.append( document.createTextNode( verify ) );
390 if ( btnClass ) {
391 continueButton.classList.add( btnClass );
392 }
393 }
394
395 removeAtts = continueButton.dataset;
396 for ( i in dataAtts ) {
397 continueButton.removeAttribute( 'data-' + i );
398 }
399
400 dataAtts = link.dataset;
401 for ( i in dataAtts ) {
402 if ( i !== 'frmverify' ) {
403 continueButton.setAttribute( 'data-' + i, dataAtts[i]);
404 }
405 }
406
407 /**
408 * Triggers the pre-open action for a confirmation modal. This action passes
409 * relevant modal information and associated link to any listening hooks.
410 *
411 * @param {Object} options An object containing modal elements and data.
412 * @param {HTMLElement} options.$info The HTML element containing modal information.
413 * @param {string} options.link The link associated with the modal action.
414 */
415 wp.hooks.doAction( 'frmAdmin.beforeOpenConfirmModal', { $info, link });
416
417 $info.dialog( 'open' );
418 continueButton.setAttribute( 'href', link.getAttribute( 'href' ) );
419 return false;
420 }
421
422 function infoModal( msg ) {
423 var $info = initModal( '#frm_info_modal', '400px' );
424
425 if ( $info === false ) {
426 return false;
427 }
428
429 jQuery( '.frm-info-msg' ).html( msg );
430
431 $info.dialog( 'open' );
432 return false;
433 }
434
435 function toggleItem( e ) {
436 /*jshint validthis:true */
437 var toggle = this.getAttribute( 'data-frmtoggle' ),
438 text = this.getAttribute( 'data-toggletext' ),
439 items = jQuery( toggle );
440
441 e.preventDefault();
442
443 if ( items.is( ':visible' ) ) {
444 items.show();
445 } else {
446 items.hide();
447 }
448
449 if ( text !== null && text !== '' ) {
450 this.setAttribute( 'data-toggletext', this.innerHTML );
451 this.textContent = text;
452 }
453
454 return false;
455 }
456
457 /**
458 * Toggle a class on target elements when an anchor is clicked, or when a radio or checkbox has been selected.
459 *
460 * @param {Event} e Event with either the change or click type.
461 * @returns {false}
462 */
463 function hideShowItem( e ) {
464 /*jshint validthis:true */
465 let hide = this.getAttribute( 'data-frmhide' );
466 let show = this.getAttribute( 'data-frmshow' );
467
468 // Flip unchecked checkboxes so an off value undoes the on value.
469 if ( isUncheckedCheckbox( this ) ) {
470 if ( hide !== null ) {
471 show = hide;
472 hide = null;
473 } else if ( show !== null ) {
474 hide = show;
475 show = null;
476 }
477 }
478
479 e.preventDefault();
480
481 const toggleClass = this.getAttribute( 'data-toggleclass' ) || 'frm_hidden';
482
483 if ( hide !== null ) {
484 jQuery( hide ).addClass( toggleClass );
485 }
486
487 if ( show !== null ) {
488 jQuery( show ).removeClass( toggleClass );
489 }
490
491 var current = this.parentNode.querySelectorAll( 'a.current' );
492 if ( current !== null ) {
493 for ( var i = 0; i < current.length; i++ ) {
494 current[ i ].classList.remove( 'current' );
495 }
496 this.classList.add( 'current' );
497 }
498
499 return false;
500 }
501
502 function isUncheckedCheckbox( element ) {
503 return 'INPUT' === element.nodeName && 'checkbox' === element.type && ! element.checked;
504 }
505
506 function setupMenuOffset() {
507 window.onscroll = document.documentElement.onscroll = setMenuOffset;
508 setMenuOffset();
509 }
510
511 function setMenuOffset() {
512 var fields = document.getElementById( 'frm_adv_info' );
513 if ( fields === null ) {
514 return;
515 }
516
517 var currentOffset = document.documentElement.scrollTop || document.body.scrollTop; // body for Safari
518 if ( currentOffset === 0 ) {
519 fields.classList.remove( 'frm_fixed' );
520 return;
521 }
522
523 var posEle = document.getElementById( 'frm_position_ele' );
524 if ( posEle === null ) {
525 return;
526 }
527
528 var eleOffset = jQuery( posEle ).offset();
529 var offset = eleOffset.top;
530 var desiredOffset = offset - currentOffset;
531 var menuHeight = 0;
532
533 var menu = document.getElementById( 'wpadminbar' );
534 if ( menu !== null ) {
535 menuHeight = menu.offsetHeight;
536 }
537
538 if ( desiredOffset < menuHeight ) {
539 desiredOffset = menuHeight;
540 }
541
542 if ( desiredOffset > menuHeight ) {
543 fields.classList.remove( 'frm_fixed' );
544 } else {
545 fields.classList.add( 'frm_fixed' );
546 if ( desiredOffset !== 32 ) {
547 fields.style.top = desiredOffset + 'px';
548 }
549 }
550 }
551
552 function loadTooltips() {
553 var wrapClass = jQuery( '.wrap, .frm_wrap' ),
554 confirmModal = document.getElementById( 'frm_confirm_modal' ),
555 doAction = false,
556 confirmedBulkDelete = false;
557
558 jQuery( confirmModal ).on( 'click', '[data-deletefield]', deleteFieldConfirmed );
559 jQuery( confirmModal ).on( 'click', '[data-removeid]', removeThisTag );
560 jQuery( confirmModal ).on( 'click', '[data-trashtemplate]', trashTemplate );
561
562 wrapClass.on( 'click', '.frm_remove_tag, .frm_remove_form_action', removeThisTag );
563 wrapClass.on( 'click', 'a[data-frmverify]', confirmClick );
564 wrapClass.on( 'click', 'a[data-frmtoggle]', toggleItem );
565 wrapClass.on( 'click', 'a[data-frmhide], a[data-frmshow]', hideShowItem );
566 wrapClass.on( 'change', 'input[data-frmhide], input[data-frmshow]', hideShowItem );
567 wrapClass.on( 'click', '.widget-top,a.widget-action', clickWidget );
568
569 wrapClass.on( 'mouseenter.frm', '.frm_bstooltip, .frm_help', function() {
570 jQuery( this ).off( 'mouseenter.frm' );
571
572 jQuery( '.frm_bstooltip, .frm_help' ).tooltip( );
573 jQuery( this ).tooltip( 'show' );
574 });
575
576 jQuery( '.frm_bstooltip, .frm_help' ).tooltip( );
577
578 jQuery( document ).on( 'click', '#doaction, #doaction2', function( event ) {
579 var isTop = this.id === 'doaction',
580 suffix = isTop ? 'top' : 'bottom',
581 bulkActionSelector = document.getElementById( 'bulk-action-selector-' + suffix ),
582 confirmBulkDelete = document.getElementById( 'confirm-bulk-delete-' + suffix );
583
584 if ( bulkActionSelector !== null && confirmBulkDelete !== null ) {
585 doAction = this;
586
587 if ( ! confirmedBulkDelete && bulkActionSelector.value === 'bulk_delete' ) {
588 event.preventDefault();
589 confirmLinkClick( confirmBulkDelete );
590 return false;
591 }
592 } else {
593 doAction = false;
594 }
595 });
596
597 jQuery( document ).on( 'click', '#frm-confirmed-click', function( event ) {
598 if ( doAction === false || event.target.classList.contains( 'frm-btn-inactive' ) ) {
599 return;
600 }
601
602 if ( this.getAttribute( 'href' ) === 'confirm-bulk-delete' ) {
603 event.preventDefault();
604 confirmedBulkDelete = true;
605 doAction.click();
606 return false;
607 }
608 });
609 }
610
611 function deleteTooltips() {
612 document.querySelectorAll( '.tooltip' ).forEach(
613 function( tooltip ) {
614 tooltip.remove();
615 }
616 );
617 }
618
619 function removeThisTag() {
620 /*jshint validthis:true */
621 let show, hide, removeMore;
622
623 if ( parseInt( this.getAttribute( 'data-skip-frm-js' ) ) || confirmLinkClick( this ) === false ) {
624 return;
625 }
626
627 const deleteButton = jQuery( this );
628 const id = deleteButton.attr( 'data-removeid' );
629
630 show = deleteButton.attr( 'data-showlast' );
631 if ( typeof show === 'undefined' ) {
632 show = '';
633 }
634
635 hide = deleteButton.attr( 'data-hidelast' );
636 if ( typeof hide === 'undefined' ) {
637 hide = '';
638 }
639
640 removeMore = deleteButton.attr( 'data-removemore' );
641
642 if ( show !== '' ) {
643 if ( deleteButton.closest( '.frm_add_remove' ).find( '.frm_remove_tag:visible' ).length > 1 ) {
644 show = '';
645 hide = '';
646 }
647 } else if ( id.indexOf( 'frm_postmeta_' ) === 0 ) {
648 if ( jQuery( '#frm_postmeta_rows .frm_postmeta_row' ).length < 2 ) {
649 show = '.frm_add_postmeta_row.button';
650 }
651 if ( jQuery( '.frm_toggle_cf_opts' ).length && jQuery( '#frm_postmeta_rows .frm_postmeta_row:not(#' + id + ')' ).last().length ) {
652 if ( show !== '' ) {
653 show += ',';
654 }
655 show += '#' + jQuery( '#frm_postmeta_rows .frm_postmeta_row:not(#' + id + ')' ).last().attr( 'id' ) + ' .frm_toggle_cf_opts';
656 }
657 }
658
659 const $fadeEle = jQuery( document.getElementById( id ) );
660 $fadeEle.fadeOut( 400, function() {
661 $fadeEle.remove();
662 fieldUpdated();
663
664 if ( hide !== '' ) {
665 jQuery( hide ).hide();
666 }
667
668 if ( show !== '' ) {
669 jQuery( show + ' a,' + show ).removeClass( 'frm_hidden' ).fadeIn( 'slow' );
670 }
671
672 if ( this.closest( '.frm_form_action_settings' ) ) {
673 const type = this.closest( '.frm_form_action_settings' ).querySelector( '.frm_action_name' ).value;
674 afterActionRemoved( type );
675 }
676 });
677
678 if ( typeof removeMore !== 'undefined' ) {
679 removeMore = jQuery( removeMore );
680 removeMore.fadeOut( 400, function() {
681 removeMore.remove();
682 });
683 }
684
685 if ( show !== '' ) {
686 jQuery( this ).closest( '.frm_logic_rows' ).fadeOut( 'slow' );
687 }
688
689 return false;
690 }
691
692 function afterActionRemoved( type ) {
693 checkActiveAction( type );
694
695 const hookName = 'frm_after_action_removed';
696 const hookArgs = { type };
697 wp.hooks.doAction( hookName, hookArgs );
698 }
699
700 function clickWidget( event, b ) {
701 /*jshint validthis:true */
702 if ( typeof b === 'undefined' ) {
703 b = this;
704 }
705
706 popCalcFields( b, false );
707
708 const cont = jQuery( b ).closest( '.frm_form_action_settings' );
709 const target = event.target;
710
711 if ( cont.length && typeof target !== 'undefined' ) {
712 const className = target.parentElement.className;
713 if ( 'string' === typeof className ) {
714 if ( className.indexOf( 'frm_email_icons' ) > -1 || className.indexOf( 'frm_toggle' ) > -1 ) {
715 // clicking on delete icon shouldn't open it
716 event.stopPropagation();
717 return;
718 }
719 }
720 }
721
722 let inside = cont.children( '.widget-inside' );
723
724 if ( cont.length && inside.find( 'p, div, table' ).length < 1 ) {
725 const actionId = cont.find( 'input[name$="[ID]"]' ).val();
726 const actionType = cont.find( 'input[name$="[post_excerpt]"]' ).val();
727 if ( actionType ) {
728 inside.html( '<span class="frm-wait frm_spinner"></span>' );
729 cont.find( '.spinner' ).fadeIn( 'slow' );
730 jQuery.ajax({
731 type: 'POST',
732 url: ajaxurl,
733 data: {
734 action: 'frm_form_action_fill',
735 action_id: actionId,
736 action_type: actionType,
737 nonce: frmGlobal.nonce
738 },
739 success: function( html ) {
740 inside.html( html );
741 initiateMultiselect();
742 showInputIcon( '#' + cont.attr( 'id' ) );
743 frmDom.autocomplete.initAutocomplete( 'page', inside );
744 jQuery( b ).trigger( 'frm-action-loaded' );
745
746 /**
747 * Fires after filling form action content when opening.
748 *
749 * @since 5.5.4
750 *
751 * @param {Object} insideElement JQuery object of form action inside element.
752 */
753 wp.hooks.doAction( 'frm_filled_form_action', inside );
754 }
755 });
756 }
757 }
758
759 jQuery( b ).closest( '.frm_field_box' ).siblings().find( '.widget-inside' ).slideUp( 'fast' );
760 if ( ( typeof b.className !== 'undefined' && b.className.indexOf( 'widget-action' ) !== -1 ) || jQuery( b ).closest( '.start_divider' ).length < 1 ) {
761 return;
762 }
763
764 inside = jQuery( b ).closest( 'div.widget' ).children( '.widget-inside' );
765 if ( inside.is( ':hidden' ) ) {
766 inside.slideDown( 'fast' );
767 } else {
768 inside.slideUp( 'fast' );
769 }
770 }
771
772 function clickNewTab() {
773 /*jshint validthis:true */
774 var t = this.getAttribute( 'href' ),
775 c = t.replace( '#', '.' ),
776 $link = jQuery( this );
777
778 if ( typeof t === 'undefined' ) {
779 return false;
780 }
781
782 $link.closest( 'li' ).addClass( 'frm-tabs active' ).siblings( 'li' ).removeClass( 'frm-tabs active starttab' );
783 $link.closest( 'div' ).children( '.tabs-panel' ).not( t ).not( c ).hide();
784 document.getElementById( t.replace( '#', '' ) ).style.display = 'block';
785
786 if ( this.id === 'frm_insert_fields_tab' ) {
787 clearSettingsBox();
788 }
789 return false;
790 }
791
792 function clickTab( link, auto ) {
793 link = jQuery( link );
794 var t = link.attr( 'href' );
795 if ( typeof t === 'undefined' ) {
796 return;
797 }
798
799 var c = t.replace( '#', '.' );
800
801 link.closest( 'li' ).addClass( 'frm-tabs active' ).siblings( 'li' ).removeClass( 'frm-tabs active starttab' );
802 if ( link.closest( 'div' ).find( '.tabs-panel' ).length ) {
803 link.closest( 'div' ).children( '.tabs-panel' ).not( t ).not( c ).hide();
804 } else {
805 if ( document.getElementById( 'form_global_settings' ) !== null ) {
806 /* global settings */
807 var ajax = link.data( 'frmajax' );
808 link.closest( '.frm_wrap' ).find( '.tabs-panel, .hide_with_tabs' ).hide();
809 if ( typeof ajax !== 'undefined' && ajax == '1' ) {
810 loadSettingsTab( t );
811 }
812 } else {
813 /* form settings page */
814 jQuery( '#frm-categorydiv .tabs-panel, .hide_with_tabs' ).hide();
815 }
816 }
817 jQuery( t ).show();
818 jQuery( c ).show();
819
820 hideShortcodes();
821
822 if ( auto !== 'auto' ) {
823 // Hide success message on tab change.
824 jQuery( '.frm_updated_message' ).hide();
825 jQuery( '.frm_warning_style' ).hide();
826 }
827
828 if ( jQuery( link ).closest( '#frm_adv_info' ).length ) {
829 return;
830 }
831
832 if ( jQuery( '.frm_form_settings' ).length ) {
833 jQuery( '.frm_form_settings' ).attr( 'action', '?page=formidable&frm_action=settings&id=' + jQuery( '.frm_form_settings input[name="id"]' ).val() + '&t=' + t.replace( '#', '' ) );
834 } else {
835 jQuery( '.frm_settings_form' ).attr( 'action', '?page=formidable-settings&t=' + t.replace( '#', '' ) );
836 }
837 }
838
839 function setupSortable( sortableSelector ) {
840 document.querySelectorAll( sortableSelector ).forEach(
841 list => {
842 makeDroppable( list );
843 Array.from( list.children ).forEach( child => makeDraggable( child, '.frm-move' ) );
844
845 const $sectionTitle = jQuery( list ).children( '[data-type="divider"]' ).children( '.divider_section_only' );
846 if ( $sectionTitle.length ) {
847 makeDroppable( $sectionTitle );
848 }
849 }
850 );
851 setupFieldOptionSorting( jQuery( '#frm_builder_page' ) );
852 }
853
854 function makeDroppable( list ) {
855 jQuery( list ).droppable({
856 accept: '.frmbutton, li.frm_field_box',
857 deactivate: handleFieldDrop,
858 over: onDragOverDroppable,
859 out: onDraggableLeavesDroppable,
860 tolerance: 'pointer'
861 });
862 }
863
864 function onDragOverDroppable( event, ui ) {
865 const droppable = getDroppableForOnDragOver( event.target );
866 const draggable = ui.draggable[0];
867
868 if ( ! allowDrop( draggable, droppable ) ) {
869 droppable.classList.remove( 'frm-over-droppable' );
870 jQuery( droppable ).parents( 'ul.frm_sorting' ).addClass( 'frm-over-droppable' );
871 return;
872 }
873
874 document.querySelectorAll( '.frm-over-droppable' ).forEach( droppable => droppable.classList.remove( 'frm-over-droppable' ) );
875 droppable.classList.add( 'frm-over-droppable' );
876 jQuery( droppable ).parents( 'ul.frm_sorting' ).addClass( 'frm-over-droppable' );
877 }
878
879 /**
880 * Maybe change the droppable.
881 * Section titles are made droppable, but are not a list, so we need to change the droppable to the section's list instead.
882 *
883 * @param {Element} droppable
884 * @returns {Element}
885 */
886 function getDroppableForOnDragOver( droppable ) {
887 if ( droppable.classList.contains( 'divider_section_only' ) ) {
888 droppable = jQuery( droppable ).nextAll( '.start_divider.frm_sorting' ).get( 0 );
889 }
890 return droppable;
891 }
892
893 function onDraggableLeavesDroppable( event ) {
894 const droppable = event.target;
895 droppable.classList.remove( 'frm-over-droppable' );
896 }
897
898 function makeDraggable( draggable, handle ) {
899 const settings = {
900 helper: getDraggableHelper,
901 revert: 'invalid',
902 delay: 10,
903 start: handleDragStart,
904 stop: handleDragStop,
905 drag: handleDrag,
906 cursor: 'grabbing',
907 refreshPositions: true,
908 cursorAt: {
909 top: 0,
910 left: 90 // The width of draggable button is 180. 90 should center the draggable on the cursor.
911 }
912 };
913 if ( 'string' === typeof handle ) {
914 settings.handle = handle;
915 }
916 jQuery( draggable ).draggable( settings );
917 }
918
919 function getDraggableHelper( event ) {
920 const draggable = event.delegateTarget;
921
922 if ( isFieldGroup( draggable ) ) {
923 const newTextFieldClone = document.getElementById( 'frm-insert-fields' ).querySelector( '.frm_ttext' ).cloneNode( true );
924 newTextFieldClone.querySelector( 'use' ).setAttributeNS( 'http://www.w3.org/1999/xlink', 'href', '#frm_field_group_layout_icon' );
925 newTextFieldClone.querySelector( 'span' ).textContent = __( 'Field Group' );
926 newTextFieldClone.classList.add( 'frm_field_box' );
927 newTextFieldClone.classList.add( 'ui-sortable-helper' );
928 return newTextFieldClone;
929 }
930
931 let copyTarget;
932 const isNewField = draggable.classList.contains( 'frmbutton' );
933 if ( isNewField ) {
934 copyTarget = draggable.cloneNode( true );
935 copyTarget.classList.add( 'ui-sortable-helper' );
936 draggable.classList.add( 'frm-new-field' );
937 return copyTarget;
938 }
939
940 if ( draggable.hasAttribute( 'data-ftype' ) ) {
941 const fieldType = draggable.getAttribute( 'data-ftype' );
942 copyTarget = document.getElementById( 'frm-insert-fields' ).querySelector( '.frm_t' + fieldType );
943 copyTarget = copyTarget.cloneNode( true );
944 copyTarget.classList.add( 'form-field' );
945
946 copyTarget.classList.add( 'ui-sortable-helper' );
947
948 if ( copyTarget ) {
949 return copyTarget.cloneNode( true );
950 }
951 }
952
953 return div({ className: 'frmbutton' });
954 }
955
956 function handleDragStart( event, ui ) {
957 const container = postBodyContent;
958 container.classList.add( 'frm-dragging-field' );
959
960 document.body.classList.add( 'frm-dragging' );
961 ui.helper.addClass( 'frm-sortable-helper' );
962 ui.helper.initialOffset = container.scrollTop;
963
964 event.target.classList.add( 'frm-drag-fade' );
965
966 unselectFieldGroups();
967 deleteEmptyDividerWrappers();
968 maybeRemoveGroupHoverTarget();
969 closeOpenFieldDropdowns();
970 deleteTooltips();
971 }
972
973 function handleDragStop() {
974 const container = postBodyContent;
975 container.classList.remove( 'frm-dragging-field' );
976 document.body.classList.remove( 'frm-dragging' );
977
978 const fade = document.querySelector( '.frm-drag-fade' );
979 if ( fade ) {
980 fade.classList.remove( 'frm-drag-fade' );
981 }
982 }
983
984 function handleDrag( event, ui ) {
985 maybeScrollBuilder( event );
986 const draggable = event.target;
987 const droppable = getDroppableTarget();
988
989 let placeholder = document.getElementById( 'frm_drag_placeholder' );
990
991 if ( ! allowDrop( draggable, droppable ) ) {
992 if ( placeholder ) {
993 placeholder.remove();
994 }
995 return;
996 }
997
998 if ( ! placeholder ) {
999 placeholder = tag( 'li', {
1000 id: 'frm_drag_placeholder',
1001 className: 'sortable-placeholder'
1002 });
1003 }
1004 const frmSortableHelper = ui.helper.get( 0 );
1005 if ( frmSortableHelper.classList.contains( 'form-field' ) || frmSortableHelper.classList.contains( 'frm_field_box' ) ) {
1006 // Sync the y position of the draggable so it still follows the cursor after scrolling up and down the field list.
1007 frmSortableHelper.style.transform = 'translateY(' + getDragOffset( ui.helper ) + 'px)';
1008 }
1009
1010 if ( 'frm-show-fields' === droppable.id || droppable.classList.contains( 'start_divider' ) ) {
1011 placeholder.style.left = 0;
1012 handleDragOverYAxis({ droppable, y: event.clientY, placeholder });
1013 return;
1014 }
1015
1016 placeholder.style.top = '';
1017 handleDragOverFieldGroup({ droppable, x: event.clientX, placeholder });
1018 }
1019
1020 function maybeScrollBuilder( event ) {
1021 $postBodyContent.scrollTop(
1022 ( _, v ) => {
1023 const moved = event.clientY;
1024 const h = postBodyContent.offsetHeight;
1025 const relativePos = event.clientY - postBodyContent.offsetTop;
1026 const y = relativePos - h / 2;
1027
1028 if ( relativePos > ( h - 50 ) && moved > 5 ) {
1029 // Scrolling down.
1030 return v + y * 0.1;
1031 }
1032
1033 if ( relativePos < 70 && moved < 130 ) {
1034 // Scrolling up.
1035 return v - Math.abs( y * 0.1 );
1036 }
1037
1038 return v;
1039 }
1040 );
1041 }
1042
1043 function getDragOffset( $helper ) {
1044 return postBodyContent.scrollTop - $helper.initialOffset;
1045 }
1046
1047 function getDroppableTarget() {
1048 let droppable = document.getElementById( 'frm-show-fields' );
1049 while ( droppable.querySelector( '.frm-over-droppable' ) ) {
1050 droppable = droppable.querySelector( '.frm-over-droppable' );
1051 }
1052 if ( 'frm-show-fields' === droppable.id && ! droppable.classList.contains( 'frm-over-droppable' ) ) {
1053 droppable = false;
1054 }
1055 return droppable;
1056 }
1057
1058 function handleFieldDrop( _, ui ) {
1059 const draggable = ui.draggable[0];
1060 const placeholder = document.getElementById( 'frm_drag_placeholder' );
1061
1062 if ( ! placeholder ) {
1063 ui.helper.remove();
1064 debouncedSyncAfterDragAndDrop();
1065 return;
1066 }
1067
1068 maybeOpenCollapsedPage( placeholder );
1069
1070 const $previousFieldContainer = ui.helper.parent();
1071 const previousSection = ui.helper.get( 0 ).closest( 'ul.start_divider' );
1072 const newSection = placeholder.closest( 'ul.frm_sorting' );
1073
1074 if ( draggable.classList.contains( 'frm-new-field' ) ) {
1075 insertNewFieldByDragging( draggable.id );
1076 } else {
1077 moveFieldThatAlreadyExists( draggable, placeholder );
1078 }
1079
1080 const previousSectionId = previousSection ? parseInt( previousSection.closest( '.edit_field_type_divider' ).getAttribute( 'data-fid' ) ) : 0;
1081 const newSectionId = newSection.classList.contains( 'start_divider' ) ? parseInt( newSection.closest( '.edit_field_type_divider' ).getAttribute( 'data-fid' ) ) : 0;
1082
1083 placeholder.remove();
1084 ui.helper.remove();
1085
1086 const $previousContainerFields = $previousFieldContainer.length ? getFieldsInRow( $previousFieldContainer ) : [];
1087 maybeUpdatePreviousFieldContainerAfterDrop( $previousFieldContainer, $previousContainerFields );
1088 maybeUpdateDraggableClassAfterDrop( draggable, $previousContainerFields );
1089
1090 if ( previousSectionId !== newSectionId ) {
1091 updateFieldAfterMovingBetweenSections( jQuery( draggable ), previousSection );
1092 }
1093
1094 debouncedSyncAfterDragAndDrop();
1095 }
1096
1097 /**
1098 * If a page if collapsed, expand it before dragging since only the page break will move.
1099 *
1100 * @param {Element} placeholder
1101 * @returns {void}
1102 */
1103 function maybeOpenCollapsedPage( placeholder ) {
1104 if ( ! placeholder.previousElementSibling || ! placeholder.previousElementSibling.classList.contains( 'frm-is-collapsed' ) ) {
1105 return;
1106 }
1107
1108 const $pageBreakField = jQuery( placeholder ).prevUntil( '[data-type="break"]' );
1109 if ( ! $pageBreakField.length ) {
1110 return;
1111 }
1112
1113 const collapseButton = $pageBreakField.find( '.frm-collapse-page' ).get( 0 );
1114 if ( collapseButton ) {
1115 collapseButton.click();
1116 }
1117 }
1118
1119 function maybeUpdatePreviousFieldContainerAfterDrop( $previousFieldContainer, $previousContainerFields ) {
1120 if ( ! $previousFieldContainer.length ) {
1121 return;
1122 }
1123
1124 if ( $previousContainerFields.length ) {
1125 syncLayoutClasses( $previousContainerFields.first() );
1126 } else {
1127 maybeDeleteAnEmptyFieldGroup( $previousFieldContainer.get( 0 ) );
1128 }
1129 }
1130
1131 function maybeUpdateDraggableClassAfterDrop( draggable, $previousContainerFields ) {
1132 if ( 0 !== $previousContainerFields.length || 1 !== getFieldsInRow( jQuery( draggable.parentNode ) ).length ) {
1133 syncLayoutClasses( jQuery( draggable ) );
1134 }
1135 }
1136
1137 /**
1138 * Remove an empty field group, but don't remove an empty section.
1139 *
1140 * @param {Element} previousFieldContainer
1141 * @returns {void}
1142 */
1143 function maybeDeleteAnEmptyFieldGroup( previousFieldContainer ) {
1144 const closestFieldBox = previousFieldContainer.closest( 'li.frm_field_box' );
1145 if ( closestFieldBox && ! closestFieldBox.classList.contains( 'edit_field_type_divider' ) ) {
1146 closestFieldBox.remove();
1147 }
1148 }
1149
1150 function handleDragOverYAxis({ droppable, y, placeholder }) {
1151 const $list = jQuery( droppable );
1152
1153 let top;
1154
1155 $children = $list.children().not( '.edit_field_type_end_divider' );
1156 if ( 0 === $children.length ) {
1157 $list.prepend( placeholder );
1158 top = 0;
1159 } else {
1160 const insertAtIndex = determineIndexBasedOffOfMousePositionInList( $list, y );
1161
1162 if ( insertAtIndex === $children.length ) {
1163 const $lastChild = jQuery( $children.get( insertAtIndex - 1 ) );
1164 top = $lastChild.offset().top + $lastChild.outerHeight();
1165 $list.append( placeholder );
1166
1167 // Make sure nothing gets inserted after the end divider.
1168 const $endDivider = $list.children( '.edit_field_type_end_divider' );
1169 if ( $endDivider.length ) {
1170 $list.append( $endDivider );
1171 }
1172 } else {
1173 top = jQuery( $children.get( insertAtIndex ) ).offset().top;
1174 jQuery( $children.get( insertAtIndex ) ).before( placeholder );
1175 }
1176 }
1177
1178 top -= $list.offset().top;
1179 placeholder.style.top = top + 'px';
1180 }
1181
1182 function determineIndexBasedOffOfMousePositionInList( $list, y ) {
1183 const $items = $list.children().not( '.edit_field_type_end_divider' );
1184 const length = $items.length;
1185
1186 let index, item, itemTop, returnIndex;
1187
1188 returnIndex = 0;
1189 for ( index = length - 1; index >= 0; --index ) {
1190 item = $items.get( index );
1191 itemTop = jQuery( item ).offset().top;
1192 if ( y > itemTop ) {
1193 returnIndex = index;
1194 if ( y > itemTop + ( jQuery( item ).outerHeight() / 2 ) ) {
1195 returnIndex = index + 1;
1196 }
1197 break;
1198 }
1199 }
1200
1201 return returnIndex;
1202 }
1203
1204 function handleDragOverFieldGroup({ droppable, x, placeholder }) {
1205 const $row = jQuery( droppable );
1206 const $children = getFieldsInRow( $row );
1207
1208 if ( ! $children.length ) {
1209 return;
1210 }
1211
1212 let left;
1213 const insertAtIndex = determineIndexBasedOffOfMousePositionInRow( $row, x );
1214
1215 if ( insertAtIndex === $children.length ) {
1216 const $lastChild = jQuery( $children.get( insertAtIndex - 1 ) );
1217 left = $lastChild.offset().left + $lastChild.outerWidth();
1218 $row.append( placeholder );
1219 } else {
1220 left = jQuery( $children.get( insertAtIndex ) ).offset().left;
1221 jQuery( $children.get( insertAtIndex ) ).before( placeholder );
1222
1223 const amountToOffsetLeftBy = 0 === insertAtIndex ? 4 : 8; // Offset by 8 in between rows, but only 4 for the first item in a group.
1224 left -= amountToOffsetLeftBy; // Offset the placeholder slightly so it appears between two fields.
1225 }
1226
1227 left -= $row.offset().left;
1228
1229 placeholder.style.left = left + 'px';
1230 }
1231
1232 function syncAfterDragAndDrop() {
1233 fixUnwrappedListItems();
1234 toggleSectionHolder();
1235 maybeFixEndDividers();
1236 maybeDeleteEmptyFieldGroups();
1237 updateFieldOrder();
1238
1239 const event = new Event( 'frm_sync_after_drag_and_drop', { bubbles: false });
1240 document.dispatchEvent( event );
1241 }
1242
1243 function maybeFixEndDividers() {
1244 document.querySelectorAll( '.edit_field_type_end_divider' ).forEach(
1245 endDivider => endDivider.parentNode.appendChild( endDivider )
1246 );
1247 }
1248
1249 function maybeDeleteEmptyFieldGroups() {
1250 document.querySelectorAll( 'li.form_field_box:not(.form-field)' ).forEach(
1251 fieldGroup => ! fieldGroup.children.length && fieldGroup.remove()
1252 );
1253 }
1254
1255 function fixUnwrappedListItems() {
1256 const lists = document.querySelectorAll( 'ul#frm-show-fields, ul.start_divider' );
1257 lists.forEach(
1258 list => {
1259 list.childNodes.forEach(
1260 child => {
1261 if ( 'undefined' === typeof child.classList ) {
1262 return;
1263 }
1264
1265 if ( child.classList.contains( 'edit_field_type_end_divider' ) ) {
1266 // Never wrap end divider in place.
1267 return;
1268 }
1269
1270 if ( 'undefined' !== typeof child.classList && child.classList.contains( 'form-field' ) ) {
1271 wrapFieldLiInPlace( child );
1272 }
1273 }
1274 );
1275 }
1276 );
1277 }
1278
1279 function deleteEmptyDividerWrappers() {
1280 const dividers = document.querySelectorAll( 'ul.start_divider' );
1281 if ( ! dividers.length ) {
1282 return;
1283 }
1284 dividers.forEach(
1285 function( divider ) {
1286 const children = [].slice.call( divider.children );
1287 children.forEach(
1288 function( child ) {
1289 if ( 0 === child.children.length ) {
1290 child.remove();
1291 } else if ( 1 === child.children.length && 'ul' === child.firstElementChild.nodeName.toLowerCase() && 0 === child.firstElementChild.children.length ) {
1292 child.remove();
1293 }
1294 }
1295 );
1296 }
1297 );
1298 }
1299
1300 function getFieldsInRow( $row ) {
1301 let $fields = jQuery();
1302
1303 const row = $row.get( 0 );
1304 if ( ! row.children ) {
1305 return $fields;
1306 }
1307
1308 Array.from( row.children ).forEach(
1309 child => {
1310 if ( 'none' === child.style.display ) {
1311 return;
1312 }
1313
1314 const classes = child.classList;
1315 if ( ! classes.contains( 'form-field' ) || classes.contains( 'edit_field_type_end_divider' ) || classes.contains( 'frm-sortable-helper' ) ) {
1316 return;
1317 }
1318
1319 $fields = $fields.add( child );
1320 }
1321 );
1322 return $fields;
1323 }
1324
1325 function determineIndexBasedOffOfMousePositionInRow( $row, x ) {
1326 var $inputs = getFieldsInRow( $row ),
1327 length = $inputs.length,
1328 index, input, inputLeft, returnIndex;
1329
1330 returnIndex = 0;
1331 for ( index = length - 1; index >= 0; --index ) {
1332 input = $inputs.get( index );
1333 inputLeft = jQuery( input ).offset().left;
1334 if ( x > inputLeft ) {
1335 returnIndex = index;
1336 if ( x > inputLeft + ( jQuery( input ).outerWidth() / 2 ) ) {
1337 returnIndex = index + 1;
1338 }
1339 break;
1340 }
1341 }
1342
1343 return returnIndex;
1344 }
1345
1346 function syncLayoutClasses( $item, type ) {
1347 var $fields, size, layoutClasses, classToAddFunction;
1348
1349 if ( 'undefined' === typeof type ) {
1350 type = 'even';
1351 }
1352
1353 $fields = $item.parent().children( 'li.form-field, li.frmbutton_loadingnow' ).not( '.edit_field_type_end_divider' );
1354 size = $fields.length;
1355 layoutClasses = getLayoutClasses();
1356
1357 if ( 'even' === type && 5 !== size ) {
1358 $fields.each( getSyncLayoutClass( layoutClasses, getEvenClassForSize( size ) ) );
1359 } else if ( 'clear' === type ) {
1360 $fields.each( getSyncLayoutClass( layoutClasses, '' ) );
1361 } else {
1362 if ( -1 !== [ 'left', 'right', 'middle', 'even' ].indexOf( type ) ) {
1363 classToAddFunction = function( index ) {
1364 return getClassForBlock( size, type, index );
1365 };
1366 } else {
1367 classToAddFunction = function( index ) {
1368 var size = type[ index ];
1369 return getLayoutClassForSize( size );
1370 };
1371 }
1372
1373 $fields.each( getSyncLayoutClass( layoutClasses, classToAddFunction ) );
1374 }
1375
1376 updateFieldGroupControls( $item.parent(), $fields.length );
1377 }
1378
1379 function updateFieldGroupControls( $row, count ) {
1380 var rowOffset, shouldShowControls, controls;
1381
1382 rowOffset = $row.offset();
1383
1384 if ( 'undefined' === typeof rowOffset ) {
1385 return;
1386 }
1387
1388 shouldShowControls = count >= 2;
1389
1390 controls = document.getElementById( 'frm_field_group_controls' );
1391 if ( null === controls ) {
1392 if ( ! shouldShowControls ) {
1393 // exit early. if we do not need controls and they do not exist, do nothing.
1394 return;
1395 }
1396
1397 controls = div();
1398 controls.id = 'frm_field_group_controls';
1399 controls.setAttribute( 'role', 'group' );
1400 controls.setAttribute( 'tabindex', 0 );
1401 setFieldControlsHtml( controls );
1402 document.getElementById( 'frm_builder_page' ).appendChild( controls );
1403 }
1404
1405 $row.append( controls );
1406 controls.style.display = shouldShowControls ? 'block' : 'none';
1407 }
1408
1409 function setFieldControlsHtml( controls ) {
1410 var layoutOption, moveOption;
1411
1412 layoutOption = document.createElement( 'span' );
1413 layoutOption.innerHTML = '<svg class="frmsvg"><use xlink:href="#frm_field_group_layout_icon"></use></svg>';
1414 const layoutOptionLabel = __( 'Set Row Layout', 'formidable' );
1415 addTooltip( layoutOption, layoutOptionLabel );
1416 makeTabbable( layoutOption, layoutOptionLabel );
1417
1418 moveOption = document.createElement( 'span' );
1419 moveOption.innerHTML = '<svg class="frmsvg"><use xlink:href="#frm_thick_move_icon"></use></svg>';
1420 moveOption.classList.add( 'frm-move' );
1421 const moveOptionLabel = __( 'Move Field Group', 'formidable' );
1422 addTooltip( moveOption, moveOptionLabel );
1423 makeTabbable( moveOption, moveOptionLabel );
1424
1425 controls.innerHTML = '';
1426 controls.appendChild( layoutOption );
1427 controls.appendChild( moveOption );
1428 controls.appendChild( getFieldControlsDropdown() );
1429 }
1430
1431 function addTooltip( element, title ) {
1432 element.setAttribute( 'data-toggle', 'tooltip' );
1433 element.setAttribute( 'data-container', 'body' );
1434 element.setAttribute( 'title', title );
1435 element.addEventListener(
1436 'mouseover',
1437 function() {
1438 if ( null === element.getAttribute( 'data-original-title' ) ) {
1439 jQuery( element ).tooltip();
1440 }
1441 }
1442 );
1443 }
1444
1445 function getFieldControlsDropdown() {
1446 const dropdown = span({ className: 'dropdown' });
1447 const trigger = a({
1448 className: 'frm_bstooltip frm-hover-icon frm-dropdown-toggle dropdown-toggle',
1449 children: [
1450 span({
1451 child: svg({ href: '#frm_thick_more_vert_icon' })
1452 }),
1453 span({
1454 className: 'screen-reader-text',
1455 text: __( 'Toggle More Options Dropdown', 'formidable' )
1456 })
1457 ]
1458 });
1459
1460 frmDom.setAttributes(
1461 trigger,
1462 {
1463 'title': __( 'More Options', 'formidable' ),
1464 'data-toggle': 'dropdown',
1465 'data-container': 'body'
1466 }
1467 );
1468 makeTabbable( trigger, __( 'More Options', 'formidable' ) );
1469 dropdown.appendChild( trigger );
1470
1471 const ul = div({
1472 className: 'frm-dropdown-menu dropdown-menu dropdown-menu-right'
1473 });
1474 ul.setAttribute( 'role', 'menu' );
1475 dropdown.appendChild( ul );
1476
1477 return dropdown;
1478 }
1479
1480 function getSyncLayoutClass( layoutClasses, classToAdd ) {
1481 return function( itemIndex ) {
1482 var currentClassToAdd, length, layoutClassIndex, currentClass, activeLayoutClass, fieldId, layoutClassesInput;
1483
1484 currentClassToAdd = 'function' === typeof classToAdd ? classToAdd( itemIndex ) : classToAdd;
1485 length = layoutClasses.length;
1486 activeLayoutClass = false;
1487 for ( layoutClassIndex = 0; layoutClassIndex < length; ++layoutClassIndex ) {
1488 currentClass = layoutClasses[ layoutClassIndex ];
1489 if ( this.classList.contains( currentClass ) ) {
1490 activeLayoutClass = currentClass;
1491 break;
1492 }
1493 }
1494
1495 fieldId = this.dataset.fid;
1496
1497 if ( 'undefined' === typeof fieldId ) {
1498 // we are syncing the drag/drop placeholder before the actual field has loaded.
1499 // this will get called again afterward and the input will exist then.
1500 this.classList.add( currentClassToAdd );
1501 return;
1502 }
1503
1504 moveFieldSettings( document.getElementById( 'frm-single-settings-' + fieldId ) );
1505 layoutClassesInput = document.getElementById( 'frm_classes_' + fieldId );
1506
1507 if ( null === layoutClassesInput ) {
1508 // not every field type has a layout class input.
1509 return;
1510 }
1511
1512 if ( false === activeLayoutClass ) {
1513 if ( '' !== currentClassToAdd ) {
1514 layoutClassesInput.value = layoutClassesInput.value.concat( ' ' + currentClassToAdd );
1515 }
1516 } else {
1517 this.classList.remove( activeLayoutClass );
1518 layoutClassesInput.value = layoutClassesInput.value.replace( activeLayoutClass, currentClassToAdd );
1519 }
1520
1521 if ( this.classList.contains( 'frm_first' ) ) {
1522 this.classList.remove( 'frm_first' );
1523 layoutClassesInput.value = layoutClassesInput.value.replace( 'frm_first', '' ).trim();
1524 }
1525
1526 if ( 0 === itemIndex ) {
1527 this.classList.add( 'frm_first' );
1528 layoutClassesInput.value = layoutClassesInput.value.concat( ' frm_first' );
1529 }
1530
1531 jQuery( layoutClassesInput ).trigger( 'change' );
1532 };
1533 }
1534
1535 function getLayoutClasses() {
1536 return [ 'frm_full', 'frm_half', 'frm_third', 'frm_fourth', 'frm_sixth', 'frm_two_thirds', 'frm_three_fourths', 'frm1', 'frm2', 'frm3', 'frm4', 'frm5', 'frm6', 'frm7', 'frm8', 'frm9', 'frm10', 'frm11', 'frm12' ];
1537 }
1538
1539 function setupFieldOptionSorting( sort ) {
1540 var opts = {
1541 items: '.frm_sortable_field_opts li',
1542 axis: 'y',
1543 opacity: 0.65,
1544 forcePlaceholderSize: false,
1545 handle: '.frm-drag',
1546 helper: function( e, li ) {
1547 copyHelper = li.clone().insertAfter( li );
1548 return li.clone();
1549 },
1550 stop: function( e, ui ) {
1551 copyHelper && copyHelper.remove();
1552 var fieldId = ui.item.attr( 'id' ).replace( 'frm_delete_field_', '' ).replace( '-' + ui.item.data( 'optkey' ) + '_container', '' );
1553 resetDisplayedOpts( fieldId );
1554 fieldUpdated();
1555 }
1556 };
1557 jQuery( sort ).sortable( opts );
1558 }
1559
1560 // Get the section where a field is dropped
1561 function getSectionForFieldPlacement( currentItem ) {
1562 var section = '';
1563 if ( typeof currentItem !== 'undefined' && ! currentItem.hasClass( 'edit_field_type_divider' ) ) {
1564 section = currentItem.closest( '.edit_field_type_divider' );
1565 }
1566 return section;
1567 }
1568
1569 // Get the form ID where a field is dropped
1570 function getFormIdForFieldPlacement( section ) {
1571 var formId = '';
1572
1573 if ( typeof section[0] !== 'undefined' ) {
1574 var sDivide = section.children( '.start_divider' );
1575 sDivide.children( '.edit_field_type_end_divider' ).appendTo( sDivide );
1576 if ( typeof section.attr( 'data-formid' ) !== 'undefined' ) {
1577 var fieldId = section.attr( 'data-fid' );
1578 formId = jQuery( 'input[name="field_options[form_select_' + fieldId + ']"]' ).val();
1579 }
1580 }
1581
1582 if ( typeof formId === 'undefined' || formId === '' ) {
1583 formId = thisFormId;
1584 }
1585
1586 return formId;
1587 }
1588
1589 // Get the section ID where a field is dropped
1590 function getSectionIdForFieldPlacement( section ) {
1591 var sectionId = 0;
1592 if ( typeof section[0] !== 'undefined' ) {
1593 sectionId = section.attr( 'id' ).replace( 'frm_field_id_', '' );
1594 }
1595
1596 return sectionId;
1597 }
1598
1599 /**
1600 * Update a field after it is dragged and dropped into, out of, or between sections
1601 *
1602 * @param {Object} currentItem
1603 * @param {Object} previousSection
1604 * @returns {void}
1605 */
1606 function updateFieldAfterMovingBetweenSections( currentItem, previousSection ) {
1607 if ( ! currentItem.hasClass( 'form-field' ) ) {
1608 // currentItem is a field group. Call for children recursively.
1609 getFieldsInRow( jQuery( currentItem.get( 0 ).firstChild ) ).each(
1610 function() {
1611 updateFieldAfterMovingBetweenSections( jQuery( this ), previousSection );
1612 }
1613 );
1614 return;
1615 }
1616
1617 const fieldId = currentItem.attr( 'id' ).replace( 'frm_field_id_', '' );
1618 const section = getSectionForFieldPlacement( currentItem );
1619 const formId = getFormIdForFieldPlacement( section );
1620 const sectionId = getSectionIdForFieldPlacement( section );
1621 const previousFormId = previousSection ? getFormIdForFieldPlacement( jQuery( previousSection.parentNode ) ) : 0;
1622
1623 jQuery.ajax({
1624 type: 'POST',
1625 url: ajaxurl,
1626 data: {
1627 action: 'frm_update_field_after_move',
1628 form_id: formId,
1629 field: fieldId,
1630 section_id: sectionId,
1631 previous_form_id: previousFormId,
1632 nonce: frmGlobal.nonce
1633 },
1634 success: function() {
1635 toggleSectionHolder();
1636 updateInSectionValue( fieldId, sectionId );
1637 }
1638 });
1639 }
1640
1641 // Update the in_section field value
1642 function updateInSectionValue( fieldId, sectionId ) {
1643 document.getElementById( 'frm_in_section_' + fieldId ).value = sectionId;
1644 }
1645
1646 /**
1647 * Add a new field by dragging and dropping it from the Fields sidebar
1648 *
1649 * @param {string} fieldType
1650 */
1651 function insertNewFieldByDragging( fieldType ) {
1652 const placeholder = document.getElementById( 'frm_drag_placeholder' );
1653 const loadingID = fieldType.replace( '|', '-' ) + '_' + getAutoId();
1654 const loading = tag(
1655 'li',
1656 {
1657 id: loadingID,
1658 className: 'frm-wait frmbutton_loadingnow'
1659 }
1660 );
1661 const $placeholder = jQuery( loading );
1662 const currentItem = jQuery( placeholder );
1663 const section = getSectionForFieldPlacement( currentItem );
1664 const formId = getFormIdForFieldPlacement( section );
1665 const sectionId = getSectionIdForFieldPlacement( section );
1666
1667 placeholder.parentNode.insertBefore( loading, placeholder );
1668 placeholder.remove();
1669 syncLayoutClasses( $placeholder );
1670
1671 let hasBreak = 0;
1672 if ( 'summary' === fieldType ) {
1673 // see if we need to insert a page break before this newly-added summary field. Check for at least 1 page break
1674 hasBreak = jQuery( '.frmbutton_loadingnow#' + loadingID ).prevAll( 'li[data-type="break"]' ).length ? 1 : 0;
1675 }
1676
1677 jQuery.ajax({
1678 type: 'POST', url: ajaxurl,
1679 data: {
1680 action: 'frm_insert_field',
1681 form_id: formId,
1682 field_type: fieldType,
1683 section_id: sectionId,
1684 nonce: frmGlobal.nonce,
1685 has_break: hasBreak
1686 },
1687 success: function( msg ) {
1688 let replaceWith;
1689 document.getElementById( 'frm_form_editor_container' ).classList.add( 'frm-has-fields' );
1690 const $siblings = $placeholder.siblings( 'li.form-field' ).not( '.edit_field_type_end_divider' );
1691 if ( ! $siblings.length ) {
1692 // if dragging into a new row, we need to wrap the li first.
1693 replaceWith = wrapFieldLi( msg );
1694 } else {
1695 replaceWith = msgAsjQueryObject( msg );
1696 if ( ! $placeholder.get( 0 ).parentNode.parentNode.classList.contains( 'ui-draggable' ) ) {
1697 // If a field group wasn't draggable because it only had a single field, make it draggable.
1698 makeDraggable( $placeholder.get( 0 ).parentNode.parentNode, '.frm-move' );
1699 }
1700 }
1701 $placeholder.replaceWith( replaceWith );
1702 updateFieldOrder();
1703 afterAddField( msg, false );
1704 if ( $siblings.length ) {
1705 syncLayoutClasses( $siblings.first() );
1706 }
1707 toggleSectionHolder();
1708
1709 if ( ! $siblings.length ) {
1710 makeDroppable( replaceWith.get( 0 ).querySelector( 'ul.frm_sorting' ) );
1711 makeDraggable( replaceWith.get( 0 ).querySelector( 'li.form-field' ), '.frm-move' );
1712 } else {
1713 makeDraggable( replaceWith.get( 0 ), '.frm-move' );
1714 }
1715
1716 },
1717 error: handleInsertFieldError
1718 });
1719 }
1720
1721 function moveFieldThatAlreadyExists( draggable, placeholder ) {
1722 placeholder.parentNode.insertBefore( draggable, placeholder );
1723 }
1724
1725 function msgAsjQueryObject( msg ) {
1726 const element = div();
1727 element.innerHTML = msg;
1728 return jQuery( element.firstChild );
1729 }
1730
1731 function handleInsertFieldError( jqXHR, _, errorThrown ) {
1732 maybeShowInsertFieldError( errorThrown, jqXHR );
1733 }
1734
1735 function maybeShowInsertFieldError( errorThrown, jqXHR ) {
1736 if ( ! jqXHRAborted( jqXHR ) ) {
1737 infoModal( errorThrown + '. Please try again.' );
1738 }
1739 }
1740
1741 function jqXHRAborted( jqXHR ) {
1742 return jqXHR.status === 0 || jqXHR.readyState === 0;
1743 }
1744
1745 /**
1746 * Get a unique id that automatically increments with every function call.
1747 * Can be used for any UI that requires a unique id.
1748 * Not to be used in data.
1749 *
1750 * @returns {integer}
1751 */
1752 function getAutoId() {
1753 return ++autoId;
1754 }
1755
1756 // Don't allow page break, embed form, or section inside section field
1757 // Don't allow page breaks inside of field groups.
1758 // Don't allow field groups with sections inside of sections.
1759 // Don't allow field groups in field groups.
1760 // Don't allow hidden fields inside of field groups but allow them in sections.
1761 function allowDrop( draggable, droppable ) {
1762 if ( false === droppable ) {
1763 // Don't show drop placeholder if dragging somewhere off of the droppable area.
1764 return false;
1765 }
1766
1767 if ( droppable.closest( '.frm-sortable-helper' ) ) {
1768 // Do not allow drop into draggable.
1769 return false;
1770 }
1771
1772 if ( 'frm-show-fields' === droppable.id ) {
1773 // Everything can be dropped into the main list of fields.
1774 return true;
1775 }
1776
1777 if ( ! droppable.classList.contains( 'start_divider' ) ) {
1778 const $fieldsInRow = getFieldsInRow( jQuery( droppable ) );
1779 if ( ! groupCanFitAnotherField( $fieldsInRow, jQuery( draggable ) ) ) {
1780 // Field group is full and cannot accept another field.
1781 return false;
1782 }
1783 }
1784
1785 const isNewField = draggable.classList.contains( 'frm-new-field' );
1786 if ( isNewField ) {
1787 return allowNewFieldDrop( draggable, droppable );
1788 }
1789
1790 return allowMoveField( draggable, droppable );
1791 }
1792
1793 // Don't allow a new page break or hidden field in a field group.
1794 // Don't allow a new field into a field group that includes a page break or hidden field.
1795 // Don't allow a new section inside of a section.
1796 // Don't allow an embedded form in a section.
1797 function allowNewFieldDrop( draggable, droppable ) {
1798 const classes = draggable.classList;
1799 const newPageBreakField = classes.contains( 'frm_tbreak' );
1800 const newHiddenField = classes.contains( 'frm_thidden' );
1801 const newSectionField = classes.contains( 'frm_tdivider' );
1802 const newEmbedField = classes.contains( 'frm_tform' );
1803
1804 const newFieldWillBeAddedToAGroup = ! ( 'frm-show-fields' === droppable.id || droppable.classList.contains( 'start_divider' ) );
1805 if ( newFieldWillBeAddedToAGroup ) {
1806 if ( groupIncludesBreakOrHidden( droppable ) ) {
1807 // Never allow any field beside a page break or a hidden field.
1808 return false;
1809 }
1810
1811 return ! newHiddenField && ! newPageBreakField;
1812 }
1813
1814 const fieldTypeIsAlwaysAllowed = ! newPageBreakField && ! newHiddenField && ! newSectionField && ! newEmbedField;
1815 if ( fieldTypeIsAlwaysAllowed ) {
1816 return true;
1817 }
1818
1819 const newFieldWillBeAddedToASection = droppable.classList.contains( 'start_divider' ) || null !== droppable.closest( '.start_divider' );
1820 if ( newFieldWillBeAddedToASection ) {
1821 // Don't allow a section or an embedded form in a section.
1822 return ! newEmbedField && ! newSectionField;
1823 }
1824
1825 return true;
1826 }
1827
1828 function allowMoveField( draggable, droppable ) {
1829 if ( isFieldGroup( draggable ) ) {
1830 return allowMoveFieldGroup( draggable, droppable );
1831 }
1832
1833 const isPageBreak = draggable.classList.contains( 'edit_field_type_break' );
1834 if ( isPageBreak ) {
1835 // Page breaks are only allowed in the main list of fields, not in sections or in field groups.
1836 return false;
1837 }
1838
1839 if ( droppable.classList.contains( 'start_divider' ) ) {
1840 return allowMoveFieldToSection( draggable );
1841 }
1842
1843 const isHiddenField = draggable.classList.contains( 'edit_field_type_hidden' );
1844 if ( isHiddenField ) {
1845 // Hidden fields should not be added to field groups since they're not shown and don't make sense with the grid distribution.
1846 return false;
1847 }
1848
1849 return allowMoveFieldToGroup( draggable, droppable );
1850 }
1851
1852 function isFieldGroup( draggable ) {
1853 return draggable.classList.contains( 'frm_field_box' ) && ! draggable.classList.contains( 'form-field' );
1854 }
1855
1856 function allowMoveFieldGroup( fieldGroup, droppable ) {
1857 if ( droppable.classList.contains( 'start_divider' ) && null === fieldGroup.querySelector( '.start_divider' ) ) {
1858 // Allow a field group with no section inside of a section.
1859 return true;
1860 }
1861 return false;
1862 }
1863
1864 function allowMoveFieldToSection( draggable ) {
1865 const draggableIncludeEmbedForm = draggable.classList.contains( 'edit_field_type_form' ) || draggable.querySelector( '.edit_field_type_form' );
1866 if ( draggableIncludeEmbedForm ) {
1867 // Do not allow an embedded form inside of a section.
1868 return false;
1869 }
1870
1871 const draggableIncludesSection = draggable.classList.contains( 'edit_field_type_divider' ) || draggable.querySelector( '.edit_field_type_divider' );
1872 if ( draggableIncludesSection ) {
1873 // Do not allow a section inside of a section.
1874 return false;
1875 }
1876
1877 return true;
1878 }
1879
1880 function allowMoveFieldToGroup( draggable, group ) {
1881 if ( groupIncludesBreakOrHidden( group ) ) {
1882 // Never allow any field beside a page break or a hidden field.
1883 return false;
1884 }
1885
1886 const isFieldGroup = jQuery( draggable ).children( 'ul.frm_sorting' ).not( '.start_divider' ).length > 0;
1887 if ( isFieldGroup ) {
1888 // Do not allow a field group directly inside of a field group unless it's in a section.
1889 return false;
1890 }
1891
1892 const draggableIncludesASection = draggable.classList.contains( 'edit_field_type_divider' ) || draggable.querySelector( '.edit_field_type_divider' );
1893 const draggableIsEmbedField = draggable.classList.contains( 'edit_field_type_form' );
1894 const groupIsInASection = null !== group.closest( '.start_divider' );
1895 if ( groupIsInASection && ( draggableIncludesASection || draggableIsEmbedField ) ) {
1896 // Do not allow a section or an embed field inside of a section.
1897 return false;
1898 }
1899
1900 return true;
1901 }
1902
1903 function groupIncludesBreakOrHidden( group ) {
1904 return null !== group.querySelector( '.edit_field_type_break, .edit_field_type_hidden' );
1905 }
1906
1907 function groupCanFitAnotherField( fieldsInRow, $field ) {
1908 var fieldId;
1909 if ( fieldsInRow.length < 6 ) {
1910 return true;
1911 }
1912 if ( fieldsInRow.length > 6 ) {
1913 return false;
1914 }
1915 fieldId = $field.attr( 'data-fid' );
1916 // allow 6 if we're not changing field groups.
1917 return 1 === jQuery( fieldsInRow ).filter( '[data-fid="' + fieldId + '"]' ).length;
1918 }
1919
1920 function loadFields( fieldId ) {
1921 const thisField = document.getElementById( fieldId );
1922 const $thisField = jQuery( thisField );
1923 const field = [];
1924 const addHtmlToField = element => {
1925 const frmHiddenFdata = element.querySelector( '.frm_hidden_fdata' );
1926 element.classList.add( 'frm_load_now' );
1927 if ( frmHiddenFdata !== null ) {
1928 field.push( frmHiddenFdata.innerHTML );
1929 }
1930 };
1931
1932 let nextElement = thisField;
1933 addHtmlToField( nextElement );
1934 while ( nextElement.nextElementSibling && field.length < 15 ) {
1935 addHtmlToField( nextElement.nextElementSibling );
1936 nextElement = nextElement.nextElementSibling;
1937 }
1938
1939 jQuery.ajax({
1940 type: 'POST',
1941 url: ajaxurl,
1942 data: {
1943 action: 'frm_load_field',
1944 field: field,
1945 form_id: thisFormId,
1946 nonce: frmGlobal.nonce
1947 },
1948 success: html => handleAjaxLoadFieldSuccess( html, $thisField, field )
1949 });
1950 }
1951
1952 function handleAjaxLoadFieldSuccess( html, $thisField, field ) {
1953 let key, $nextSet;
1954
1955 html = html.replace( /^\s+|\s+$/g, '' );
1956 if ( html.indexOf( '{' ) !== 0 ) {
1957 jQuery( '.frm_load_now' ).removeClass( '.frm_load_now' ).html( 'Error' );
1958 return;
1959 }
1960
1961 html = JSON.parse( html );
1962 for ( key in html ) {
1963 jQuery( '#frm_field_id_' + key ).replaceWith( html[key]);
1964 setupSortable( '#frm_field_id_' + key + '.edit_field_type_divider ul.frm_sorting' );
1965 makeDraggable( document.getElementById( 'frm_field_id_' + key ) );
1966 }
1967
1968 $nextSet = $thisField.nextAll( '.frm_field_loading:not(.frm_load_now)' );
1969 if ( $nextSet.length ) {
1970 loadFields( $nextSet.attr( 'id' ) );
1971 } else {
1972 // go up a level
1973 $nextSet = jQuery( document.getElementById( 'frm-show-fields' ) ).find( '.frm_field_loading:not(.frm_load_now)' );
1974 if ( $nextSet.length ) {
1975 loadFields( $nextSet.attr( 'id' ) );
1976 }
1977 }
1978
1979 initiateMultiselect();
1980 renumberPageBreaks();
1981 maybeHideQuantityProductFieldOption();
1982
1983 const loadedEvent = new Event( 'frm_ajax_loaded_field', { bubbles: false });
1984 loadedEvent.frmFields = field.map( f => JSON.parse( f ) );
1985 document.dispatchEvent( loadedEvent );
1986 }
1987
1988 function addFieldClick() {
1989 /*jshint validthis:true */
1990 const $thisObj = jQuery( this );
1991 // there is no real way to disable a <a> (with a valid href attribute) in HTML - https://css-tricks.com/how-to-disable-links/
1992 if ( $thisObj.hasClass( 'disabled' ) ) {
1993 return false;
1994 }
1995
1996 const $button = $thisObj.closest( '.frmbutton' );
1997 const fieldType = $button.attr( 'id' );
1998
1999 let hasBreak = 0;
2000 if ( 'summary' === fieldType ) {
2001 hasBreak = $newFields.children( 'li[data-type="break"]' ).length > 0 ? 1 : 0;
2002 }
2003
2004 const formId = thisFormId;
2005 jQuery.ajax({
2006 type: 'POST',
2007 url: ajaxurl,
2008 data: {
2009 action: 'frm_insert_field',
2010 form_id: formId,
2011 field_type: fieldType,
2012 section_id: 0,
2013 nonce: frmGlobal.nonce,
2014 has_break: hasBreak
2015 },
2016 success: function( msg ) {
2017 document.getElementById( 'frm_form_editor_container' ).classList.add( 'frm-has-fields' );
2018 const replaceWith = wrapFieldLi( msg );
2019 $newFields.append( replaceWith );
2020 afterAddField( msg, true );
2021
2022 replaceWith.each(
2023 function() {
2024 makeDroppable( this.querySelector( 'ul.frm_sorting' ) );
2025 makeDraggable( this.querySelector( '.form-field' ), '.frm-move' );
2026 }
2027 );
2028 },
2029 error: handleInsertFieldError
2030 });
2031 return false;
2032 }
2033
2034 function maybeHideQuantityProductFieldOption() {
2035 var hide = true,
2036 opts = document.querySelectorAll( '.frmjs_prod_field_opt_cont' );
2037
2038 if ( $newFields.find( 'li.edit_field_type_product' ).length > 1 ) {
2039 hide = false;
2040 }
2041
2042 for ( var i = 0; i < opts.length; i++ ) {
2043 if ( hide ) {
2044 opts[ i ].classList.add( 'frm_hidden' );
2045 } else {
2046 opts[ i ].classList.remove( 'frm_hidden' );
2047 }
2048 }
2049 }
2050
2051 function duplicateField() {
2052 var $field, fieldId, children, newRowId, fieldOrder;
2053
2054 $field = jQuery( this ).closest( 'li.form-field' );
2055
2056 if ( $field.hasClass( 'frm-page-collapsed' ) ) {
2057 return false;
2058 }
2059
2060 closeOpenFieldDropdowns();
2061 fieldId = $field.data( 'fid' );
2062 children = fieldsInSection( fieldId );
2063 newRowId = this.getAttribute( 'frm-target-row-id' );
2064
2065 if ( null !== newRowId ) {
2066 fieldOrder = this.getAttribute( 'frm-field-order' );
2067 }
2068
2069 jQuery.ajax({
2070 type: 'POST',
2071 url: ajaxurl,
2072 data: {
2073 action: 'frm_duplicate_field',
2074 field_id: fieldId,
2075 form_id: thisFormId,
2076 children: children,
2077 nonce: frmGlobal.nonce
2078 },
2079 success: function( msg ) {
2080 var newRow;
2081
2082 let replaceWith;
2083
2084 if ( null !== newRowId ) {
2085 newRow = document.getElementById( newRowId );
2086 if ( null !== newRow ) {
2087 replaceWith = msgAsjQueryObject( msg );
2088 jQuery( newRow ).append( replaceWith );
2089 makeDraggable( replaceWith.get( 0 ), '.frm-move' );
2090 if ( null !== fieldOrder ) {
2091 newRow.lastElementChild.setAttribute( 'frm-field-order', fieldOrder );
2092 }
2093 jQuery( newRow ).trigger(
2094 'frm_added_duplicated_field_to_row',
2095 {
2096 duplicatedFieldHtml: msg,
2097 originalFieldId: fieldId
2098 }
2099 );
2100 afterAddField( msg, false );
2101 return;
2102 }
2103 }
2104
2105 if ( $field.siblings( 'li.form-field' ).length ) {
2106 replaceWith = msgAsjQueryObject( msg );
2107 $field.after( replaceWith );
2108 syncLayoutClasses( $field );
2109 makeDraggable( replaceWith.get( 0 ), '.frm-move' );
2110 } else {
2111 replaceWith = wrapFieldLi( msg );
2112 $field.parent().parent().after( replaceWith );
2113 makeDroppable( replaceWith.get( 0 ).querySelector( 'ul.frm_sorting' ) );
2114 makeDraggable( replaceWith.get( 0 ).querySelector( 'li.form-field' ), '.frm-move' );
2115 }
2116
2117 updateFieldOrder();
2118 afterAddField( msg, false );
2119 maybeDuplicateUnsavedSettings( fieldId, msg );
2120 toggleOneSectionHolder( replaceWith.find( '.start_divider' ) );
2121 $field[0].querySelector( '.frm-dropdown-menu.dropdown-menu-right' )?.classList.remove( 'show' );
2122 }
2123 });
2124 return false;
2125 }
2126
2127 function maybeDuplicateUnsavedSettings( originalFieldId, newFieldHtml ) {
2128 var originalSettings, newFieldId, copySettings, fieldOptionKeys, originalDefault, copyDefault;
2129
2130 originalSettings = document.getElementById( 'frm-single-settings-' + originalFieldId );
2131 if ( null === originalSettings ) {
2132 return;
2133 }
2134
2135 newFieldId = jQuery( newFieldHtml ).attr( 'data-fid' );
2136 if ( 'undefined' === typeof newFieldId ) {
2137 return;
2138 }
2139
2140 copySettings = document.getElementById( 'frm-single-settings-' + newFieldId );
2141 if ( null === copySettings ) {
2142 return;
2143 }
2144
2145 fieldOptionKeys = [
2146 'name', 'required', 'unique', 'read_only', 'placeholder', 'description', 'size', 'max', 'format', 'prepend', 'append', 'separate_value'
2147 ];
2148
2149 originalSettings.querySelectorAll( 'input[name^="field_options["], textarea[name^="field_options["]' ).forEach(
2150 function( originalSetting ) {
2151 var key, tagType, copySetting;
2152
2153 key = getKeyFromSettingInput( originalSetting );
2154
2155 if ( 'options' === key ) {
2156 copyOption( originalSetting, copySettings, originalFieldId, newFieldId );
2157 return;
2158 }
2159
2160 if ( -1 === fieldOptionKeys.indexOf( key ) ) {
2161 return;
2162 }
2163
2164 tagType = originalSetting.matches( 'input' ) ? 'input' : 'textarea';
2165 copySetting = copySettings.querySelector( tagType + '[name="field_options[' + key + '_' + newFieldId + ']"]' );
2166 if ( null === copySetting ) {
2167 return;
2168 }
2169
2170 if ( 'checkbox' === originalSetting.type ) {
2171 if ( originalSetting.checked !== copySetting.checked ) {
2172 jQuery( copySetting ).trigger( 'click' );
2173 }
2174 } else if ( 'text' === originalSetting.type || 'textarea' === tagType ) {
2175 if ( originalSetting.value !== copySetting.value ) {
2176 copySetting.value = originalSetting.value;
2177 jQuery( copySetting ).trigger( 'change' );
2178 }
2179 }
2180 }
2181 );
2182
2183 originalDefault = originalSettings.querySelector( 'input[name="default_value_' + originalFieldId + '"]' );
2184 if ( null !== originalDefault ) {
2185 copyDefault = copySettings.querySelector( 'input[name="default_value_' + newFieldId + '"]' );
2186 if ( null !== copyDefault && originalDefault.value !== copyDefault.value ) {
2187 copyDefault.value = originalDefault.value;
2188 jQuery( copyDefault ).trigger( 'change' );
2189 }
2190 }
2191 }
2192
2193 function copyOption( originalSetting, copySettings, originalFieldId, newFieldId ) {
2194 var remainingKeyDetails, copyKey, copySetting;
2195 remainingKeyDetails = originalSetting.name.substr( 23 + ( '' + originalFieldId ).length );
2196 copyKey = 'field_options[options_' + newFieldId + ']' + remainingKeyDetails;
2197 copySetting = copySettings.querySelector( 'input[name="' + copyKey + '"]' );
2198 if ( null !== copySetting && copySetting.value !== originalSetting.value ) {
2199 copySetting.value = originalSetting.value;
2200 jQuery( copySetting ).trigger( 'change' );
2201 }
2202 }
2203
2204 function getKeyFromSettingInput( input ) {
2205 var nameWithoutPrefix, nameSplit;
2206 nameWithoutPrefix = input.name.substr( 14 );
2207 nameSplit = nameWithoutPrefix.split( '_' );
2208 nameSplit.pop();
2209 return nameSplit.join( '_' );
2210 }
2211
2212 function closeOpenFieldDropdowns() {
2213 var openSettings = document.querySelector( '.frm-field-settings-open' );
2214 if ( null !== openSettings ) {
2215 openSettings.classList.remove( 'frm-field-settings-open' );
2216 jQuery( document ).off( 'click', '#frm_builder_page', handleClickOutsideOfFieldSettings );
2217 jQuery( '.frm-field-action-icons .dropdown.open' ).removeClass( 'open' );
2218 }
2219 }
2220
2221 function handleClickOutsideOfFieldSettings( event ) {
2222 if ( ! jQuery( event.originalEvent.target ).closest( '.frm-field-action-icons' ).length ) {
2223 closeOpenFieldDropdowns();
2224 }
2225 }
2226
2227 function checkForMultiselectKeysOnMouseMove( event ) {
2228 var keyIsDown = ! ! ( event.ctrlKey || event.metaKey || event.shiftKey );
2229 jQuery( document.getElementById( 'frm_builder_page' ) ).toggleClass( 'frm-multiselect-key-is-down', keyIsDown );
2230 checkForActiveHoverTarget( event );
2231 }
2232
2233 function checkForActiveHoverTarget( event ) {
2234 var container, elementFromPoint, list, previousHoverTarget;
2235
2236 container = postBodyContent;
2237 if ( container.classList.contains( 'frm-dragging-field' ) ) {
2238 return;
2239 }
2240
2241 if ( null !== document.querySelector( '.frm-field-group-hover-target .frm-field-settings-open' ) ) {
2242 // do not set a hover target if a dropdown is open for the current hover target.
2243 return;
2244 }
2245
2246 elementFromPoint = document.elementFromPoint( event.clientX, event.clientY );
2247 if ( null !== elementFromPoint && ! elementFromPoint.classList.contains( 'edit_field_type_divider' ) ) {
2248
2249 list = elementFromPoint.closest( 'ul.frm_sorting' );
2250
2251 if ( null !== list && ! list.classList.contains( 'start_divider' ) && 'frm-show-fields' !== list.id ) {
2252 previousHoverTarget = maybeRemoveGroupHoverTarget();
2253 if ( false !== previousHoverTarget && ! jQuery( previousHoverTarget ).is( list ) ) {
2254 destroyFieldGroupPopup();
2255 }
2256 updateFieldGroupControls( jQuery( list ), getFieldsInRow( jQuery( list ) ).length );
2257 list.classList.add( 'frm-field-group-hover-target' );
2258 jQuery( '#wpbody-content' ).on( 'mousemove', maybeRemoveHoverTargetOnMouseMove );
2259 }
2260 }
2261 }
2262
2263 function maybeRemoveGroupHoverTarget() {
2264 var controls, previousHoverTarget;
2265
2266 controls = document.getElementById( 'frm_field_group_controls' );
2267 if ( null !== controls ) {
2268 controls.style.display = 'none';
2269 }
2270
2271 previousHoverTarget = document.querySelector( '.frm-field-group-hover-target' );
2272 if ( null === previousHoverTarget ) {
2273 return false;
2274 }
2275
2276 jQuery( '#wpbody-content' ).off( 'mousemove', maybeRemoveHoverTargetOnMouseMove );
2277 previousHoverTarget.classList.remove( 'frm-field-group-hover-target' );
2278 return previousHoverTarget;
2279 }
2280
2281 function maybeRemoveHoverTargetOnMouseMove( event ) {
2282 var elementFromPoint = document.elementFromPoint( event.clientX, event.clientY );
2283 if ( null !== elementFromPoint && null !== elementFromPoint.closest( '#frm-show-fields' ) ) {
2284 return;
2285 }
2286 maybeRemoveGroupHoverTarget();
2287 }
2288
2289 function onFieldActionDropdownShow( isFieldGroup ) {
2290 unselectFieldGroups();
2291 // maybe offset the dropdown if it goes off of the right of the screen.
2292 setTimeout(
2293 function() {
2294 var ul, $ul;
2295 ul = document.querySelector( '.dropdown.show .frm-dropdown-menu' );
2296 if ( null === ul ) {
2297 return;
2298 }
2299 if ( null === ul.getAttribute( 'aria-label' ) ) {
2300 ul.setAttribute( 'aria-label', __( 'More Options', 'formidable' ) );
2301 }
2302 if ( 0 === ul.children.length ) {
2303 fillFieldActionDropdown( ul, true === isFieldGroup );
2304 }
2305 $ul = jQuery( ul );
2306 if ( $ul.offset().left > jQuery( window ).width() - $ul.outerWidth() ) {
2307 ul.style.left = ( -$ul.outerWidth() ) + 'px';
2308 }
2309 const firstAnchor = ul.firstElementChild.querySelector( 'a' );
2310 if ( firstAnchor ) {
2311 firstAnchor.focus();
2312 }
2313 },
2314 0
2315 );
2316 }
2317
2318 function onFieldGroupActionDropdownShow() {
2319 onFieldActionDropdownShow( true );
2320 }
2321
2322 function changeSectionStyle( e ) {
2323 const collapsedSection = e.target.closest( '.frm-section-collapsed' );
2324 if ( ! collapsedSection ) {
2325 return;
2326 }
2327
2328 if ( e.type === 'show' ) {
2329 collapsedSection.style.zIndex = 3;
2330 } else {
2331 collapsedSection.style.zIndex = 1;
2332 }
2333 }
2334
2335 function fillFieldActionDropdown( ul, isFieldGroup ) {
2336 var classSuffix, options;
2337 classSuffix = isFieldGroup ? '_field_group' : '_field';
2338 options = [ getDeleteActionOption( isFieldGroup ), getDuplicateActionOption( isFieldGroup ) ];
2339 if ( ! isFieldGroup ) {
2340 options.push(
2341 { class: 'frm_select', icon: 'frm_settings_icon', label: __( 'Field Settings', 'formidable' ) }
2342 );
2343 }
2344 options.forEach(
2345 function( option ) {
2346 var li, anchor, span;
2347 li = document.createElement( 'div' );
2348 li.classList.add( 'frm_more_options_li', 'dropdown-item' );
2349
2350 anchor = document.createElement( 'a' );
2351 anchor.classList.add( option.class + classSuffix );
2352 anchor.setAttribute( 'href', '#' );
2353 makeTabbable( anchor );
2354
2355 span = document.createElement( 'span' );
2356 span.textContent = option.label;
2357 anchor.innerHTML = '<svg class="frmsvg"><use xlink:href="#' + option.icon + '"></use></svg>';
2358 anchor.appendChild( document.createTextNode( ' ' ) );
2359 anchor.appendChild( span );
2360
2361 li.appendChild( anchor );
2362 ul.appendChild( li );
2363 }
2364 );
2365 }
2366
2367 function getDeleteActionOption( isFieldGroup ) {
2368 var option = { class: 'frm_delete', icon: 'frm_delete_icon' };
2369 option.label = isFieldGroup ? __( 'Delete Group', 'formidable' ) : __( 'Delete', 'formidable' );
2370 return option;
2371 }
2372
2373 function getDuplicateActionOption( isFieldGroup ) {
2374 var option = { class: 'frm_clone', icon: 'frm_clone_icon' };
2375 option.label = isFieldGroup ? __( 'Duplicate Group', 'formidable' ) : __( 'Duplicate', 'formidable' );
2376 return option;
2377 }
2378
2379 function wrapFieldLi( field ) {
2380 const wrapper = div();
2381
2382 if ( 'string' === typeof field ) {
2383 wrapper.innerHTML = field;
2384 } else {
2385 wrapper.appendChild( field );
2386 }
2387
2388 let result = jQuery();
2389 Array.from( wrapper.children ).forEach(
2390 li => {
2391 result = result.add(
2392 jQuery( '<li>' )
2393 .addClass( 'frm_field_box' )
2394 .html(
2395 jQuery( '<ul>' ).addClass( 'frm_grid_container frm_sorting' ).append( li )
2396 )
2397 );
2398 }
2399 );
2400
2401 return result;
2402 }
2403
2404 function wrapFieldLiInPlace( li ) {
2405 const ul = tag(
2406 'ul',
2407 {
2408 className: 'frm_grid_container frm_sorting'
2409 }
2410 );
2411 const wrapper = tag(
2412 'li',
2413 {
2414 className: 'frm_field_box',
2415 child: ul
2416 }
2417 );
2418
2419 li.replaceWith( wrapper );
2420 ul.appendChild( li );
2421
2422 makeDroppable( ul );
2423 makeDraggable( wrapper, '.frm-move' );
2424 }
2425
2426 function afterAddField( msg, addFocus ) {
2427 var regex = /id="(\S+)"/,
2428 match = regex.exec( msg ),
2429 field = document.getElementById( match[1]),
2430 section = '#' + match[1] + '.edit_field_type_divider ul.frm_sorting.start_divider',
2431 $thisSection = jQuery( section ),
2432 type = field.getAttribute( 'data-type' ),
2433 toggled = false,
2434 $parentSection;
2435
2436 setupSortable( section );
2437
2438 if ( 'quantity' === type ) {
2439 // try to automatically attach a product field
2440 maybeSetProductField( field );
2441 }
2442
2443 if ( 'product' === type || 'quantity' === type ) {
2444 // quantity too needs to be a part of the if stmt especially cos of the very
2445 // 1st quantity field (or even if it's just one quantity field in the form).
2446 maybeHideQuantityProductFieldOption();
2447 }
2448
2449 if ( $thisSection.length ) {
2450 $thisSection.parent( '.frm_field_box' ).children( '.frm_no_section_fields' ).addClass( 'frm_block' );
2451 } else {
2452 $parentSection = jQuery( field ).closest( 'ul.frm_sorting.start_divider' );
2453 if ( $parentSection.length ) {
2454 toggleOneSectionHolder( $parentSection );
2455 toggled = true;
2456 }
2457 }
2458
2459 if ( msg.indexOf( 'frm-collapse-page' ) !== -1 ) {
2460 renumberPageBreaks();
2461 }
2462
2463 addClass( field, 'frm-newly-added' );
2464 setTimeout( function() {
2465 field.classList.remove( 'frm-newly-added' );
2466 }, 1000 );
2467
2468 if ( addFocus ) {
2469 var bounding = field.getBoundingClientRect(),
2470 container = document.getElementById( 'post-body-content' ),
2471 inView = ( bounding.top >= 0 &&
2472 bounding.left >= 0 &&
2473 bounding.right <= ( window.innerWidth || document.documentElement.clientWidth ) &&
2474 bounding.bottom <= ( window.innerHeight || document.documentElement.clientHeight )
2475 );
2476
2477 if ( ! inView ) {
2478 container.scroll({
2479 top: container.scrollHeight,
2480 left: 0,
2481 behavior: 'smooth'
2482 });
2483 }
2484
2485 if ( toggled === false ) {
2486 toggleOneSectionHolder( $thisSection );
2487 }
2488 }
2489
2490 deselectFields();
2491 initiateMultiselect();
2492
2493 const addedEvent = new Event( 'frm_added_field', { bubbles: false });
2494 addedEvent.frmField = field;
2495 addedEvent.frmSection = section;
2496 addedEvent.frmType = type;
2497 addedEvent.frmToggles = toggled;
2498 document.dispatchEvent( addedEvent );
2499 }
2500
2501 function clearSettingsBox( preventFieldGroups ) {
2502 jQuery( '#new_fields .frm-single-settings' ).addClass( 'frm_hidden' );
2503 jQuery( '#frm-options-panel > .frm-single-settings' ).removeClass( 'frm_hidden' );
2504 deselectFields( preventFieldGroups );
2505 }
2506
2507 function deselectFields( preventFieldGroups ) {
2508 jQuery( 'li.ui-state-default.selected' ).removeClass( 'selected' );
2509 if ( ! preventFieldGroups ) {
2510 unselectFieldGroups();
2511 }
2512 }
2513
2514 function scrollToField( field ) {
2515 var newPos = field.getBoundingClientRect().top,
2516 container = document.getElementById( 'post-body-content' );
2517
2518 if ( typeof animate === 'undefined' ) {
2519 jQuery( container ).scrollTop( newPos );
2520 } else {
2521 // TODO: smooth scroll
2522 jQuery( container ).animate({ scrollTop: newPos }, 500 );
2523 }
2524 }
2525
2526 function checkCalculationCreatedByUser() {
2527 var calculation = this.value;
2528 var warningMessage = checkMatchingParens( calculation );
2529 warningMessage += checkShortcodes( calculation, this );
2530
2531 if ( warningMessage !== '' ) {
2532 infoModal( calculation + '\n\n' + warningMessage );
2533 }
2534 }
2535
2536 /**
2537 * Checks the Detail Page slug to see if it's a reserved word and displays a message if it is.
2538 */
2539 function checkDetailPageSlug() {
2540 var slug = jQuery( '#param' ).val(),
2541 msg;
2542 slug = slug.trim().toLowerCase();
2543 if ( Array.isArray( frm_admin_js.unsafe_params ) && frm_admin_js.unsafe_params.includes( slug ) ) { // eslint-disable-line camelcase
2544 msg = frm_admin_js.slug_is_reserved; // eslint-disable-line camelcase
2545 msg = msg.replace( '****', addHtmlTags( slug, 'strong' ) );
2546 msg += '<br /><br />';
2547 msg += addHtmlTags( '<a href="https://codex.wordpress.org/WordPress_Query_Vars" target="_blank" class="frm-standard-link">' + frm_admin_js.reserved_words + '</a>', 'div' ); // eslint-disable-line camelcase
2548 infoModal( msg );
2549 }
2550 }
2551
2552 /**
2553 * Checks View filter value for params named with reserved words and displays a message if any are found.
2554 */
2555 function checkFilterParamNames() {
2556 var regEx = /\[\s*get\s*param\s*=\s*['"]?([a-zA-Z-_]+)['"]?/ig,
2557 filterValue = jQuery( this ).val(),
2558 match = regEx.exec( filterValue ),
2559 unsafeParams = '';
2560
2561 while ( match !== null ) {
2562 if ( Array.isArray( frm_admin_js.unsafe_params ) && frm_admin_js.unsafe_params.includes( match[1]) ) { // eslint-disable-line camelcase
2563 if ( unsafeParams !== '' ) {
2564 unsafeParams += '", "' + match[ 1 ];
2565 } else {
2566 unsafeParams = match[ 1 ];
2567 }
2568 }
2569 match = regEx.exec( filterValue );
2570 }
2571
2572 if ( unsafeParams !== '' ) {
2573 let msg = frm_admin_js.param_is_reserved; // eslint-disable-line camelcase
2574 msg = msg.replace( '****', addHtmlTags( unsafeParams, 'strong' ) );
2575 msg += '<br /><br />';
2576 msg += ' <a href="https://codex.wordpress.org/WordPress_Query_Vars" target="_blank" class="frm-standard-link">' + frm_admin_js.reserved_words + '</a>'; // eslint-disable-line camelcase
2577
2578 infoModal( msg );
2579 }
2580 }
2581
2582 function addHtmlTags( text, tag ) {
2583 tag = tag ? tag : 'p';
2584 return '<' + tag + '>' + text + '</' + tag + '>';
2585 }
2586
2587 /**
2588 * Checks a string for parens, brackets, and curly braces and returns a message if any unmatched are found.
2589 * @param formula
2590 * @returns {string}
2591 */
2592 function checkMatchingParens( formula ) {
2593
2594 var stack = [],
2595 formulaArray = formula.split( '' ),
2596 length = formulaArray.length,
2597 opening = [ '{', '[', '(' ],
2598 closing = {
2599 '}': '{',
2600 ')': '(',
2601 ']': '['
2602 },
2603 unmatchedClosing = [],
2604 msg = '',
2605 i, top;
2606
2607 for ( i = 0; i < length; i++ ) {
2608 if ( opening.includes( formulaArray[i]) ) {
2609 stack.push( formulaArray[i]);
2610 continue;
2611 }
2612 if ( closing.hasOwnProperty( formulaArray[i]) ) {
2613 top = stack.pop();
2614 if ( top !== closing[formulaArray[i]]) {
2615 unmatchedClosing.push( formulaArray[i]);
2616 }
2617 }
2618 }
2619
2620 if ( stack.length > 0 || unmatchedClosing.length > 0 ) {
2621 msg = frm_admin_js.unmatched_parens + '\n\n'; // eslint-disable-line camelcase
2622 return msg;
2623 }
2624
2625 return '';
2626 }
2627
2628 /**
2629 * Checks a calculation for shortcodes that shouldn't be in it and returns a message if found.
2630 * @param calculation
2631 * @param inputElement
2632 * @returns {string}
2633 */
2634 function checkShortcodes( calculation, inputElement ) {
2635 var msg = checkNonNumericShortcodes( calculation, inputElement );
2636 msg += checkNonFormShortcodes( calculation );
2637
2638 return msg;
2639 }
2640
2641 /**
2642 * Checks if a numeric calculation has shortcodes that output non-numeric strings and returns a message if found.
2643 * @param calculation
2644 *
2645 * @param inputElement
2646 * @returns {string}
2647 */
2648 function checkNonNumericShortcodes( calculation, inputElement ) {
2649
2650 var msg = '';
2651
2652 if ( isTextCalculation( inputElement ) ) {
2653 return msg;
2654 }
2655
2656 var nonNumericShortcodes = getNonNumericShortcodes();
2657
2658 if ( nonNumericShortcodes.test( calculation ) ) {
2659 msg = frm_admin_js.text_shortcodes + '\n\n'; // eslint-disable-line camelcase
2660 }
2661
2662 return msg;
2663 }
2664
2665 /**
2666 * Determines if the calculation input is from a text calculation.
2667 *
2668 * @param inputElement
2669 */
2670 function isTextCalculation( inputElement ) {
2671 return jQuery( inputElement ).siblings( 'label[for^="calc_type"]' ).children( 'input' ).prop( 'checked' );
2672 }
2673
2674 /**
2675 * Returns a regular expression of shortcodes that can't be used in numeric calculations.
2676 * @returns {RegExp}
2677 */
2678 function getNonNumericShortcodes() {
2679 return /\[(date|time|email|ip)\]/;
2680 }
2681
2682 /**
2683 * Checks if a string has any shortcodes that do not belong in forms and returns a message if any are found.
2684 * @param formula
2685 * @returns {string}
2686 */
2687 function checkNonFormShortcodes( formula ) {
2688 var nonFormShortcodes = getNonFormShortcodes(),
2689 msg = '';
2690
2691 if ( nonFormShortcodes.test( formula ) ) {
2692 msg += frm_admin_js.view_shortcodes + '\n\n'; // eslint-disable-line camelcase
2693 }
2694
2695 return msg;
2696 }
2697
2698 /**
2699 * Returns a regular expression of shortcodes that can't be used in forms but can be used in Views, Email
2700 * Notifications, and other Formidable areas.
2701 *
2702 * @returns {RegExp}
2703 */
2704 function getNonFormShortcodes() {
2705 return /\[id\]|\[key\]|\[if\s\w+\]|\[foreach\s\w+\]|\[created-at(\s*)?/g;
2706 }
2707
2708 function isCalcBoxType( box, listClass ) {
2709 var list = jQuery( box ).find( '.frm_code_list' );
2710 return 1 === list.length && list.hasClass( listClass );
2711 }
2712
2713 function extractExcludedOptions( exclude ) {
2714 var opts = [];
2715 if ( ! Array.isArray( exclude ) ) {
2716 return opts;
2717 }
2718
2719 for ( var i = 0; i < exclude.length; i++ ) {
2720 if ( exclude[ i ].startsWith( '[' ) ) {
2721 opts.push( exclude[ i ]);
2722 // remove it
2723 exclude.splice( i, 1 );
2724 // https://love2dev.com/blog/javascript-remove-from-array/#remove-from-array-splice-value
2725 i--;
2726 }
2727 }
2728
2729 return opts;
2730 }
2731
2732 function hasExcludedOption( field, excludedOpts ) {
2733 var hasOption = false;
2734 for ( var i = 0; i < excludedOpts.length; i++ ) {
2735 var inputs = document.getElementsByName( getFieldOptionInputName( excludedOpts[ i ], field.fieldId ) );
2736 // 2nd condition checks that there's at least one non-empty value
2737 if ( inputs.length && jQuery( inputs[0]).val() ) {
2738 hasOption = true;
2739 break;
2740 }
2741 }
2742 return hasOption;
2743 }
2744
2745 function getFieldOptionInputName( opt, fieldId ) {
2746 var at = opt.indexOf( ']' );
2747 return 'field_options' + opt.substring( 0, at ) + '_' + fieldId + opt.substring( at );
2748 }
2749
2750 function popCalcFields( v, force ) {
2751 var box, exclude, fields, i, list,
2752 p = jQuery( v ).closest( '.frm-single-settings' ),
2753 calc = p.find( '.frm-calc-field' );
2754
2755 if ( ! force && ( ! calc.length || calc.val() === '' || calc.is( ':hidden' ) ) ) {
2756 return;
2757 }
2758
2759 var isSummary = isCalcBoxType( v, 'frm_js_summary_list' );
2760
2761 var fieldId = p.find( 'input[name="frm_fields_submitted[]"]' ).val();
2762
2763 if ( force ) {
2764 box = v;
2765 } else {
2766 box = document.getElementById( 'frm-calc-box-' + fieldId );
2767 }
2768
2769 exclude = getExcludeArray( box, isSummary );
2770 var excludedOpts = extractExcludedOptions( exclude );
2771
2772 fields = getFieldList();
2773 list = document.getElementById( 'frm-calc-list-' + fieldId );
2774 list.innerHTML = '';
2775
2776 for ( i = 0; i < fields.length; i++ ) {
2777 if ( ( exclude && exclude.includes( fields[ i ].fieldType ) ) ||
2778 ( excludedOpts.length && hasExcludedOption( fields[ i ], excludedOpts ) ) ) {
2779 continue;
2780 }
2781
2782 var span = document.createElement( 'span' );
2783 span.appendChild( document.createTextNode( '[' + fields[i].fieldId + ']' ) );
2784
2785 var a = document.createElement( 'a' );
2786 a.setAttribute( 'href', '#' );
2787 a.setAttribute( 'data-code', fields[i].fieldId );
2788 a.classList.add( 'frm_insert_code' );
2789 a.appendChild( span );
2790 a.appendChild( document.createTextNode( fields[i].fieldName ) );
2791
2792 var li = document.createElement( 'li' );
2793 li.classList.add( 'frm-field-list-' + fieldId );
2794 li.classList.add( 'frm-field-list-' + fields[i].fieldType );
2795 li.appendChild( a );
2796 list.appendChild( li );
2797 }
2798 }
2799
2800 function getExcludeArray( calcBox, isSummary ) {
2801 var exclude = JSON.parse( calcBox.getElementsByClassName( 'frm_code_list' )[0].getAttribute( 'data-exclude' ) );
2802
2803 if ( isSummary ) {
2804 // includedExtras are those that are normally excluded from the summary but the form owner can choose to include,
2805 // when they have been chosen to be included, then they can now be manually excluded in the calc box.
2806 var includedExtras = getIncludedExtras();
2807 if ( includedExtras.length ) {
2808 for ( var i = 0; i < exclude.length; i++ ) {
2809 if ( includedExtras.includes( exclude[ i ]) ) {
2810 // remove it
2811 exclude.splice( i, 1 );
2812 // https://love2dev.com/blog/javascript-remove-from-array/#remove-from-array-splice-value
2813 i--;
2814 }
2815 }
2816 }
2817 }
2818
2819 return exclude;
2820 }
2821
2822 function getIncludedExtras() {
2823 var checked = [];
2824 var checkboxes = document.getElementsByClassName( 'frm_include_extras_field' );
2825
2826 for ( var i = 0; i < checkboxes.length; i++ ) {
2827 if ( checkboxes[i].checked ) {
2828 checked.push( checkboxes[i].value );
2829 }
2830 }
2831
2832 return checked;
2833 }
2834
2835 function rePopCalcFieldsForSummary() {
2836 popCalcFields( jQuery( '.frm-inline-modal.postbox:has(.frm_js_summary_list)' )[0], true );
2837 }
2838
2839 function getFieldList( fieldType ) {
2840 var i,
2841 fields = [],
2842 allFields = document.querySelectorAll( 'li.frm_field_box' ),
2843 checkType = 'undefined' !== typeof fieldType;
2844
2845 for ( i = 0; i < allFields.length; i++ ) {
2846 // data-ftype is better (than data-type) cos of fields loaded by AJAX - which might not be ready yet
2847 if ( checkType && allFields[ i ].getAttribute( 'data-ftype' ) !== fieldType ) {
2848 continue;
2849 }
2850
2851 var fieldId = allFields[ i ].getAttribute( 'data-fid' );
2852 if ( typeof fieldId !== 'undefined' && fieldId ) {
2853 fields.push({
2854 'fieldId': fieldId,
2855 'fieldName': getPossibleValue( 'frm_name_' + fieldId ),
2856 'fieldType': getPossibleValue( 'field_options_type_' + fieldId ),
2857 'fieldKey': getPossibleValue( 'field_options_field_key_' + fieldId )
2858 });
2859 }
2860 }
2861
2862 return wp.hooks.applyFilters( 'frm_admin_get_field_list', fields, fieldType, allFields );
2863 }
2864
2865 function popProductFields( field ) {
2866 var i, checked, id,
2867 options = [],
2868 current = getCurrentProductFields( field ),
2869 fName = field.getAttribute( 'data-frmfname' ),
2870 products = getFieldList( 'product' ),
2871 quantities = getFieldList( 'quantity' ),
2872 isSelect = field.tagName === 'SELECT', // for reverse compatibility.
2873 // whether we have just 1 product and 1 quantity field & should therefore attach the latter to the former
2874 auto = 1 === quantities.length && 1 === products.length;
2875
2876 if ( isSelect ) {
2877 // This fallback can be removed after 4.05.
2878 current = field.getAttribute( 'data-frmcurrent' );
2879 }
2880
2881 for ( i = 0 ; i < products.length ; i++ ) {
2882 // let's be double sure it's string, else indexOf will fail
2883 id = products[ i ].fieldId.toString();
2884 checked = auto || -1 !== current.indexOf( id );
2885 if ( isSelect ) {
2886 // This fallback can be removed after 4.05.
2887 checked = checked ? ' selected' : '';
2888 options.push( '<option value="' + id + '"' + checked + '>' + products[ i ].fieldName + '</option>' );
2889 } else {
2890 checked = checked ? ' checked' : '';
2891 options.push( '<label class="frm6">' );
2892 options.push( '<input type="checkbox" name="' + fName + '" value="' + id + '"' + checked + '> ' + products[ i ].fieldName );
2893 options.push( '</label>' );
2894 }
2895 }
2896
2897 field.innerHTML = options.join( '' );
2898 }
2899
2900 function getCurrentProductFields( prodFieldOpt ) {
2901 var products = prodFieldOpt.querySelectorAll( '[type="checkbox"]:checked' ),
2902 idsArray = [];
2903
2904 for ( var i = 0; i < products.length; i++ ) {
2905 idsArray.push( products[ i ].value );
2906 }
2907
2908 return idsArray;
2909 }
2910
2911 function popAllProductFields() {
2912 var opts = document.querySelectorAll( '.frmjs_prod_field_opt' );
2913 for ( var i = 0; i < opts.length; i++ ) {
2914 popProductFields( opts[ i ]);
2915 }
2916 }
2917
2918 function maybeSetProductField( field ) {
2919 var fieldId = field.getAttribute( 'data-fid' ),
2920 productFieldOpt = document.getElementById( 'field_options[product_field_' + fieldId + ']' );
2921
2922 if ( null === productFieldOpt ) {
2923 return;
2924 }
2925
2926 popProductFields( productFieldOpt );
2927 // in order to move its settings to that LHS panel where
2928 // the update form resides, else it'll lose this setting
2929 moveFieldSettings( document.getElementById( 'frm-single-settings-' + fieldId ) );
2930 }
2931
2932 /**
2933 * If the element doesn't exist, use a blank value.
2934 */
2935 function getPossibleValue( id ) {
2936 const field = document.getElementById( id );
2937 if ( field !== null ) {
2938 return field.value;
2939 } else {
2940 return '';
2941 }
2942 }
2943
2944 function liveChanges() {
2945 /*jshint validthis:true */
2946 var option,
2947 newValue = this.value,
2948 changes = document.getElementById( this.getAttribute( 'data-changeme' ) ),
2949 att = this.getAttribute( 'data-changeatt' );
2950
2951 if ( changes === null ) {
2952 return;
2953 }
2954
2955 if ( att !== null ) {
2956 if ( changes.tagName === 'SELECT' && att === 'placeholder' ) {
2957 option = changes.options[0];
2958 if ( option.value === '' ) {
2959 option.innerHTML = newValue;
2960 } else {
2961 // Create a placeholder option if there are no blank values.
2962 addBlankSelectOption( changes, newValue );
2963 }
2964 } else if ( att === 'class' ) {
2965 changeFieldClass( changes, this );
2966 } else if ( isSliderField( changes ) ) {
2967 updateSliderFieldPreview( changes, att, newValue );
2968 } else {
2969 changes.setAttribute( att, newValue );
2970 }
2971 } else if ( changes.id.indexOf( 'setup-message' ) === 0 ) {
2972 if ( newValue !== '' ) {
2973 changes.innerHTML = '<input type="text" value="" disabled />';
2974 }
2975 } else {
2976 changes.innerHTML = purifyHtml( newValue );
2977
2978 if ( 'TEXTAREA' === changes.nodeName && changes.classList.contains( 'wp-editor-area' ) ) {
2979 // Trigger change events on wysiwyg textareas so we can also sync default values in the visual tab.
2980 jQuery( changes ).trigger( 'change' );
2981 }
2982
2983 if ( changes.classList.contains( 'frm_primary_label' ) && 'break' === changes.nextElementSibling.getAttribute( 'data-ftype' ) ) {
2984 changes.nextElementSibling.querySelector( '.frm_button_submit' ).textContent = newValue;
2985 }
2986 }
2987 }
2988
2989 function updateSliderFieldPreview( field, att, newValue ) {
2990 if ( frmGlobal.proIncludesSliderJs ) {
2991 const hookName = 'frm_update_slider_field_preview';
2992 const hookArgs = { field, att, newValue };
2993 wp.hooks.doAction( hookName, hookArgs );
2994 return;
2995 }
2996
2997 // This functionality has been moved to pro since v5.4.3. This code should be removed eventually.
2998 if ( 'value' === att ) {
2999 if ( '' === newValue ) {
3000 newValue = getSliderMidpoint( field );
3001 }
3002 field.value = newValue;
3003 } else {
3004 field.setAttribute( att, newValue );
3005 }
3006
3007 if ( -1 === [ 'value', 'min', 'max' ].indexOf( att ) ) {
3008 return;
3009 }
3010
3011 if ( ( 'max' === att || 'min' === att ) && '' === getSliderDefaultValueInput( field.id ) ) {
3012 field.value = getSliderMidpoint( field );
3013 }
3014
3015 field.parentNode.querySelector( '.frm_range_value' ).textContent = field.value;
3016 }
3017
3018 function getSliderDefaultValueInput( previewInputId ) {
3019 return document.querySelector( 'input[data-changeme="' + previewInputId + '"][data-changeatt="value"]' ).value;
3020 }
3021
3022 function getSliderMidpoint( sliderInput ) {
3023 const max = parseFloat( sliderInput.getAttribute( 'max' ) );
3024 const min = parseFloat( sliderInput.getAttribute( 'min' ) );
3025 return ( max - min ) / 2 + min;
3026 }
3027
3028 function isSliderField( previewInput ) {
3029 return 'range' === previewInput.type && previewInput.parentNode.classList.contains( 'frm_range_container' );
3030 }
3031
3032 function toggleInvalidMsg() {
3033 /*jshint validthis:true */
3034 var typeDropdown, fieldType,
3035 fieldId = this.getAttribute( 'data-fid' ),
3036 value = '';
3037
3038 [ 'field_options_max_', 'frm_format_' ].forEach( function( id ) {
3039 var input = document.getElementById( id + fieldId );
3040 if ( ! input ) {
3041 return;
3042 }
3043
3044 value += input.value;
3045 });
3046
3047 typeDropdown = document.getElementsByName( 'field_options[type_' + fieldId + ']' )[0];
3048 fieldType = typeDropdown.options[typeDropdown.selectedIndex].value;
3049
3050 if ( fieldType === 'text' ) {
3051 toggleValidationBox( '' !== value, '.frm_invalid_msg' + fieldId );
3052 }
3053 }
3054
3055 function markRequired() {
3056 /*jshint validthis:true */
3057 var thisid = this.id.replace( 'frm_', '' ),
3058 fieldId = thisid.replace( 'req_field_', '' ),
3059 checked = this.checked,
3060 label = jQuery( '#field_label_' + fieldId + ' .frm_required' );
3061
3062 toggleValidationBox( checked, '.frm_required_details' + fieldId );
3063
3064 if ( checked ) {
3065 var $reqBox = jQuery( 'input[name="field_options[required_indicator_' + fieldId + ']"]' );
3066 if ( $reqBox.val() === '' ) {
3067 $reqBox.val( '*' );
3068 }
3069 label.removeClass( 'frm_hidden' );
3070 } else {
3071 label.addClass( 'frm_hidden' );
3072 }
3073 }
3074
3075 function toggleValidationBox( hasValue, messageClass ) {
3076 $msg = jQuery( messageClass );
3077 if ( hasValue ) {
3078 $msg.fadeIn( 'fast' ).closest( '.frm_validation_msg' ).fadeIn( 'fast' );
3079 } else {
3080 //Fade out validation options
3081 var v = $msg.fadeOut( 'fast' ).closest( '.frm_validation_box' ).children( ':not(' + messageClass + '):visible' ).length;
3082 if ( v === 0 ) {
3083 $msg.closest( '.frm_validation_msg' ).fadeOut( 'fast' );
3084 }
3085 }
3086 }
3087
3088 function markUnique() {
3089 /*jshint validthis:true */
3090 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
3091 var $thisField = jQuery( '.frm_unique_details' + fieldId );
3092 if ( this.checked ) {
3093 $thisField.fadeIn( 'fast' ).closest( '.frm_validation_msg' ).fadeIn( 'fast' );
3094 $unqDetail = jQuery( '.frm_unique_details' + fieldId + ' input' );
3095 if ( $unqDetail.val() === '' ) {
3096 $unqDetail.val( frm_admin_js.default_unique ); // eslint-disable-line camelcase
3097 }
3098 } else {
3099 var v = $thisField.fadeOut( 'fast' ).closest( '.frm_validation_box' ).children( ':not(.frm_unique_details' + fieldId + '):visible' ).length;
3100 if ( v === 0 ) {
3101 $thisField.closest( '.frm_validation_msg' ).fadeOut( 'fast' );
3102 }
3103 }
3104 }
3105
3106 //Fade confirmation field and validation option in or out
3107 function addConf() {
3108 /*jshint validthis:true */
3109 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
3110 var val = jQuery( this ).val();
3111 var $thisField = jQuery( document.getElementById( 'frm_field_id_' + fieldId ) );
3112
3113 toggleValidationBox( val !== '', '.frm_conf_details' + fieldId );
3114
3115 if ( val !== '' ) {
3116 //Add default validation message if empty
3117 var valMsg = jQuery( '.frm_validation_box .frm_conf_details' + fieldId + ' input' );
3118 if ( valMsg.val() === '' ) {
3119 valMsg.val( frm_admin_js.default_conf ); // eslint-disable-line camelcase
3120 }
3121
3122 setConfirmationFieldDescriptions( fieldId );
3123
3124 //Add or remove class for confirmation field styling
3125 if ( val === 'inline' ) {
3126 $thisField.removeClass( 'frm_conf_below' ).addClass( 'frm_conf_inline' );
3127 } else if ( val === 'below' ) {
3128 $thisField.removeClass( 'frm_conf_inline' ).addClass( 'frm_conf_below' );
3129 }
3130 jQuery( '.frm-conf-box-' + fieldId ).removeClass( 'frm_hidden' );
3131 } else {
3132 jQuery( '.frm-conf-box-' + fieldId ).addClass( 'frm_hidden' );
3133 setTimeout( function() {
3134 $thisField.removeClass( 'frm_conf_inline frm_conf_below' );
3135 }, 200 );
3136 }
3137 }
3138
3139 function setConfirmationFieldDescriptions( fieldId ) {
3140 var fieldType = document.getElementsByName( 'field_options[type_' + fieldId + ']' )[0].value;
3141
3142 var fieldDescription = document.getElementById( 'field_description_' + fieldId );
3143 var hiddenDescName = 'field_options[description_' + fieldId + ']';
3144 var newValue = frm_admin_js['enter_' + fieldType]; // eslint-disable-line camelcase
3145 maybeSetNewDescription( fieldDescription, hiddenDescName, newValue );
3146
3147 var confFieldDescription = document.getElementById( 'conf_field_description_' + fieldId );
3148 var hiddenConfName = 'field_options[conf_desc_' + fieldId + ']';
3149 var newConfValue = frm_admin_js['confirm_' + fieldType]; // eslint-disable-line camelcase
3150 maybeSetNewDescription( confFieldDescription, hiddenConfName, newConfValue );
3151 }
3152
3153 function maybeSetNewDescription( descriptionDiv, hiddenName, newValue ) {
3154 if ( descriptionDiv.innerHTML === frm_admin_js.desc ) { // eslint-disable-line camelcase
3155
3156 // Set the visible description value and the hidden description value
3157 descriptionDiv.innerHTML = newValue;
3158 document.getElementsByName( hiddenName )[0].value = newValue;
3159 }
3160 }
3161
3162 function initBulkOptionsOverlay() {
3163 /*jshint validthis:true */
3164 var $info = initModal( '#frm-bulk-modal', '700px' );
3165 if ( $info === false ) {
3166 return;
3167 }
3168
3169 jQuery( '.frm-insert-preset' ).on( 'click', insertBulkPreset );
3170
3171 jQuery( builderForm ).on( 'click', 'a.frm-bulk-edit-link', function( event ) {
3172 event.preventDefault();
3173 var i, key, label,
3174 content = '',
3175 optList,
3176 opts,
3177 fieldId = jQuery( this ).closest( '[data-fid]' ).data( 'fid' ),
3178 separate = usingSeparateValues( fieldId ),
3179 product = isProductField( fieldId );
3180
3181 optList = document.getElementById( 'frm_field_' + fieldId + '_opts' );
3182 if ( ! optList ) {
3183 return;
3184 }
3185
3186 opts = optList.getElementsByTagName( 'li' );
3187
3188 document.getElementById( 'bulk-field-id' ).value = fieldId;
3189
3190 for ( i = 0; i < opts.length; i++ ) {
3191 key = opts[i].getAttribute( 'data-optkey' );
3192 if ( key !== '000' ) {
3193 label = document.getElementsByName( 'field_options[options_' + fieldId + '][' + key + '][label]' )[0];
3194 if ( typeof label !== 'undefined' ) {
3195 content += label.value;
3196 if ( separate ) {
3197 content += '|' + document.getElementsByName( 'field_options[options_' + fieldId + '][' + key + '][value]' )[0].value;
3198 }
3199 if ( product ) {
3200 content += '|' + document.getElementsByName( 'field_options[options_' + fieldId + '][' + key + '][price]' )[0].value;
3201 }
3202 content += '\r\n';
3203 }
3204 }
3205
3206 if ( i >= opts.length - 1 ) {
3207 document.getElementById( 'frm_bulk_options' ).value = content;
3208 }
3209 }
3210
3211 $info.dialog( 'open' );
3212
3213 return false;
3214 });
3215
3216 jQuery( '#frm-update-bulk-opts' ).on( 'click', function() {
3217 var fieldId = document.getElementById( 'bulk-field-id' ).value;
3218 var optionType = document.getElementById( 'bulk-option-type' ).value;
3219
3220 if ( optionType ) {
3221 // Use custom handler for custom option type.
3222 return;
3223 }
3224
3225 this.classList.add( 'frm_loading_button' );
3226 frmAdminBuild.updateOpts( fieldId, document.getElementById( 'frm_bulk_options' ).value, $info );
3227 fieldUpdated();
3228 });
3229 }
3230
3231 function insertBulkPreset( event ) {
3232 /*jshint validthis:true */
3233 var opts = JSON.parse( this.getAttribute( 'data-opts' ) );
3234 event.preventDefault();
3235 document.getElementById( 'frm_bulk_options' ).value = opts.join( '\n' );
3236 return false;
3237 }
3238
3239 //Add new option or "Other" option to radio/checkbox/dropdown
3240 function addFieldOption() {
3241 /*jshint validthis:true */
3242 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' ),
3243 newOption = jQuery( '#frm_field_' + fieldId + '_opts .frm_option_template' ).prop( 'outerHTML' ),
3244 optType = jQuery( this ).data( 'opttype' ),
3245 optKey = 0,
3246 oldKey = '000',
3247 lastKey = getHighestOptKey( fieldId );
3248
3249 if ( lastKey !== oldKey ) {
3250 optKey = lastKey + 1;
3251 }
3252
3253 //Update hidden field
3254 if ( optType === 'other' ) {
3255 document.getElementById( 'other_input_' + fieldId ).value = 1;
3256
3257 //Hide "Add Other" option now if this is radio field
3258 var ftype = jQuery( this ).data( 'ftype' );
3259 if ( ftype === 'radio' || ftype === 'select' ) {
3260 jQuery( this ).fadeOut( 'slow' );
3261 }
3262
3263 var data = {
3264 action: 'frm_add_field_option',
3265 field_id: fieldId,
3266 opt_key: optKey,
3267 opt_type: optType,
3268 nonce: frmGlobal.nonce
3269 };
3270 jQuery.post( ajaxurl, data, function( msg ) {
3271 jQuery( document.getElementById( 'frm_field_' + fieldId + '_opts' ) ).append( msg );
3272 resetDisplayedOpts( fieldId );
3273 });
3274 } else {
3275 newOption = newOption.replace( new RegExp( 'optkey="' + oldKey + '"', 'g' ), 'optkey="' + optKey + '"' );
3276 newOption = newOption.replace( new RegExp( '-' + oldKey + '_', 'g' ), '-' + optKey + '_' );
3277 newOption = newOption.replace( new RegExp( '-' + oldKey + '"', 'g' ), '-' + optKey + '"' );
3278 newOption = newOption.replace( new RegExp( '\\[' + oldKey + '\\]', 'g' ), '[' + optKey + ']' );
3279 newOption = newOption.replace( 'frm_hidden frm_option_template', '' );
3280 newOption = { newOption };
3281 addSaveAndDragIconsToOption( fieldId, newOption );
3282 jQuery( document.getElementById( 'frm_field_' + fieldId + '_opts' ) ).append( newOption.newOption );
3283 resetDisplayedOpts( fieldId );
3284 }
3285 fieldUpdated();
3286 }
3287
3288 function getHighestOptKey( fieldId ) {
3289 var i = 0,
3290 optKey = 0,
3291 opts = jQuery( '#frm_field_' + fieldId + '_opts li' ),
3292 lastKey = 0;
3293
3294 for ( i; i < opts.length; i++ ) {
3295 optKey = opts[i].getAttribute( 'data-optkey' );
3296 if ( opts.length === 1 ) {
3297 return optKey;
3298 }
3299 if ( optKey !== '000' ) {
3300 optKey = optKey.replace( 'other_', '' );
3301 optKey = parseInt( optKey, 10 );
3302 }
3303
3304 if ( ! isNaN( lastKey ) && ( optKey > lastKey || lastKey === '000' ) ) {
3305 lastKey = optKey;
3306 }
3307 }
3308
3309 return lastKey;
3310 }
3311
3312 function toggleMultSel() {
3313 /*jshint validthis:true */
3314 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
3315 toggleMultiSelect( fieldId, this.value );
3316 }
3317
3318 function toggleMultiSelect( fieldId, value ) {
3319 var setting = jQuery( '.frm_multiple_cont_' + fieldId );
3320 if ( value === 'select' ) {
3321 setting.fadeIn( 'fast' );
3322 } else {
3323 setting.fadeOut( 'fast' );
3324 }
3325 }
3326
3327 function toggleSepValues() {
3328 /*jshint validthis:true */
3329 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
3330 toggle( jQuery( '.field_' + fieldId + '_option_key' ) );
3331 jQuery( '.field_' + fieldId + '_option' ).toggleClass( 'frm_with_key' );
3332 }
3333
3334 function toggleImageOptions() {
3335 /*jshint validthis:true */
3336 var hasImageOptions, imageSize,
3337 $field = jQuery( this ).closest( '.frm-single-settings' ),
3338 fieldId = $field.data( 'fid' ),
3339 displayField = document.getElementById( 'frm_field_id_' + fieldId );
3340
3341 refreshOptionDisplayNow( jQuery( this ) );
3342
3343 toggle( jQuery( '.field_' + fieldId + '_image_id' ) );
3344 toggle( jQuery( '.frm_toggle_image_options_' + fieldId ) );
3345 toggle( jQuery( '.frm_image_size_' + fieldId ) );
3346 toggle( jQuery( '.frm_alignment_' + fieldId ) );
3347 toggle( jQuery( '.frm-add-other#frm_add_field_' + fieldId ) );
3348
3349 hasImageOptions = imagesAsOptions( fieldId );
3350
3351 if ( hasImageOptions ) {
3352 setAlignment( fieldId, 'inline' );
3353 removeImageSizeClasses( displayField );
3354 imageSize = getImageOptionSize( fieldId );
3355 displayField.classList.add( 'frm_image_options' );
3356 displayField.classList.add( 'frm_image_size_' + imageSize );
3357 $field.find( '.frm-bulk-edit-link' ).hide();
3358 } else {
3359 displayField.classList.remove( 'frm_image_options' );
3360 removeImageSizeClasses( displayField );
3361 setAlignment( fieldId, 'block' );
3362 $field.find( '.frm-bulk-edit-link' ).show();
3363 }
3364 }
3365
3366 function removeImageSizeClasses( field ) {
3367 field.classList.remove( 'frm_image_size_', 'frm_image_size_small', 'frm_image_size_medium', 'frm_image_size_large', 'frm_image_size_xlarge' );
3368 }
3369
3370 function setAlignment( fieldId, alignment ) {
3371 jQuery( '#field_options_align_' + fieldId ).val( alignment ).trigger( 'change' );
3372 }
3373
3374 function setImageSize() {
3375 var $field = jQuery( this ).closest( '.frm-single-settings' ),
3376 fieldId = $field.data( 'fid' ),
3377 displayField = document.getElementById( 'frm_field_id_' + fieldId );
3378
3379 refreshOptionDisplay();
3380
3381 if ( imagesAsOptions( fieldId ) ) {
3382 removeImageSizeClasses( displayField );
3383 displayField.classList.add( 'frm_image_options' );
3384 displayField.classList.add( 'frm_image_size_' + getImageOptionSize( fieldId ) );
3385 }
3386 }
3387
3388 function refreshOptionDisplayNow( object ) {
3389 var $field = object.closest( '.frm-single-settings' ),
3390 fieldID = $field.data( 'fid' );
3391 jQuery( '.field_' + fieldID + '_option' ).trigger( 'change' );
3392 }
3393
3394 function refreshOptionDisplay() {
3395 /*jshint validthis:true */
3396 refreshOptionDisplayNow( jQuery( this ) );
3397 }
3398
3399 function addImageToOption( event ) {
3400 const imagePreview = event.target.closest( '.frm_image_preview_wrapper' );
3401
3402 event.preventDefault();
3403
3404 wp.media.model.settings.post.id = 0;
3405
3406 const fileFrame = wp.media.frames.file_frame = wp.media({
3407 multiple: false,
3408 library: {
3409 type: [ 'image' ]
3410 }
3411 });
3412
3413 fileFrame.on( 'select', function() {
3414 const attachment = fileFrame.state().get( 'selection' ).first().toJSON();
3415 const img = imagePreview.querySelector( 'img' );
3416
3417 img.setAttribute( 'src', attachment.url );
3418 img.classList.remove( 'frm_hidden' );
3419 img.removeAttribute( 'srcset' ); // Prevent the old image from sticking around.
3420
3421 imagePreview.querySelector( '.frm_image_preview_frame' ).style.display = 'block';
3422 imagePreview.querySelector( '.frm_image_preview_title' ).textContent = attachment.filename;
3423 imagePreview.querySelector( '.frm_choose_image_box' ).style.display = 'none';
3424
3425 const $imagePreview = jQuery( imagePreview );
3426 $imagePreview.siblings( 'input[name*="[label]"]' ).data( 'frmimgurl', attachment.url );
3427 $imagePreview.find( 'input.frm_image_id' ).val( attachment.id ).trigger( 'change' );
3428 wp.media.model.settings.post.id = 0;
3429 });
3430
3431 fileFrame.open();
3432 }
3433
3434 function removeImageFromOption( event ) {
3435 var $this = jQuery( this ),
3436 previewWrapper = $this.closest( '.frm_image_preview_wrapper' );
3437
3438 event.preventDefault();
3439 event.stopPropagation();
3440
3441 previewWrapper.find( 'img' ).attr( 'src', '' );
3442 previewWrapper.find( '.frm_image_preview_frame' ).hide();
3443 previewWrapper.find( '.frm_choose_image_box' ).show();
3444 previewWrapper.find( 'input.frm_image_id' ).val( 0 ).trigger( 'change' );
3445 }
3446
3447 function toggleMultiselect() {
3448 /*jshint validthis:true */
3449 var dropdown = jQuery( this ).closest( 'li' ).find( '.frm_form_fields select' );
3450 if ( this.checked ) {
3451 dropdown.attr( 'multiple', 'multiple' );
3452 } else {
3453 dropdown.removeAttr( 'multiple' );
3454 }
3455 }
3456
3457 /**
3458 * Allow typing on form switcher click without an extra click to search.
3459 */
3460 function focusSearchBox() {
3461 var searchBox = document.getElementById( 'dropform-search-input' );
3462 if ( searchBox !== null ) {
3463 setTimeout( function() {
3464 searchBox.focus();
3465 }, 100 );
3466 }
3467 }
3468
3469 /**
3470 * Dismiss a warning message and send an AJAX request to update the dismissal state.
3471 *
3472 * @since 6.3
3473 *
3474 * @param {Event} event The event object associated with the click on the dismiss icon.
3475 */
3476 function dismissWarningMessage( event ) {
3477 const target = event.target;
3478
3479 const warningEl = target.closest( '.frm_warning_style' );
3480 jQuery( warningEl ).fadeOut( 400, () => warningEl.remove() );
3481
3482 const action = target.dataset.action;
3483 const formData = new FormData();
3484 doJsonPost( action, formData );
3485 }
3486
3487 /**
3488 * If a field is clicked in the builder, prevent inputs from changing.
3489 */
3490 function stopFieldFocus( e ) {
3491 e.preventDefault();
3492 }
3493
3494 function deleteFieldOption() {
3495 /*jshint validthis:true */
3496 var otherInput,
3497 parentLi = this.parentNode,
3498 parentUl = parentLi.parentNode,
3499 fieldId = this.getAttribute( 'data-fid' );
3500
3501 jQuery( parentLi ).fadeOut( 'slow', function() {
3502 jQuery( parentLi ).remove();
3503
3504 var hasOther = jQuery( parentUl ).find( '.frm_other_option' );
3505 if ( hasOther.length < 1 ) {
3506 otherInput = document.getElementById( 'other_input_' + fieldId );
3507 if ( otherInput !== null ) {
3508 otherInput.value = 0;
3509 }
3510 jQuery( '#other_button_' + fieldId ).fadeIn( 'slow' );
3511 }
3512 });
3513 fieldUpdated();
3514 }
3515
3516 /**
3517 * If a radio button is set as default, allow a click to
3518 * deselect it.
3519 */
3520 function maybeUncheckRadio() {
3521 var $self, uncheck, unbind, up;
3522
3523 /*jshint validthis:true */
3524 $self = jQuery( this );
3525 if ( $self.is( ':checked' ) ) {
3526 uncheck = function() {
3527 setTimeout( function() {
3528 $self.prop( 'checked', false );
3529 }, 0 );
3530 };
3531 unbind = function() {
3532 $self.off( 'mouseup', up );
3533 };
3534 up = function() {
3535 uncheck();
3536 unbind();
3537 };
3538 $self.on( 'mouseup', up );
3539 $self.one( 'mouseout', unbind );
3540 }
3541 }
3542
3543 /**
3544 * If the field option has the default text, clear it out on click.
3545 */
3546 function maybeClearOptText() {
3547 /*jshint validthis:true */
3548 if ( this.value === frm_admin_js.new_option ) { // eslint-disable-line camelcase
3549 this.setAttribute( 'data-value-on-focus', this.value );
3550 this.value = '';
3551 }
3552 }
3553
3554 function confirmFieldsDeleteMessage( numberOfFields ) {
3555 /* translators: %1$s: Number of fields that are selected to be deleted. */
3556 return __( 'Are you sure you want to delete these %1$s selected field(s)?', 'formidable' ).replace( '%1$s', numberOfFields );
3557 }
3558
3559 function clickDeleteField() {
3560 /*jshint validthis:true */
3561 var confirmMsg = frm_admin_js.conf_delete, // eslint-disable-line camelcase
3562 maybeDivider = this.parentNode.parentNode.parentNode.parentNode.parentNode,
3563 li = maybeDivider.parentNode,
3564 field = jQuery( this ).closest( 'li.form-field' ),
3565 fieldId = field.data( 'fid' );
3566
3567 if ( field.data( 'ftype' ) === 'divider' ) {
3568 const fieldBoxes = document.querySelectorAll( '.frm-field-group-hover-target .start_divider .frm_field_box' );
3569 let fieldIdsToDelete = 0;
3570 fieldBoxes.forEach( fieldBox => {
3571 const fieldsInsideFieldBox = fieldBox.querySelectorAll( 'li.form-field' );
3572 if ( fieldsInsideFieldBox ) {
3573 fieldIdsToDelete += fieldsInsideFieldBox.length;
3574 }
3575 });
3576 if ( fieldIdsToDelete ) {
3577 confirmMsg = confirmFieldsDeleteMessage( ++fieldIdsToDelete );
3578 }
3579 }
3580
3581 if ( li.classList.contains( 'frm-section-collapsed' ) || li.classList.contains( 'frm-page-collapsed' ) ) {
3582 return false;
3583 }
3584
3585 // If deleting a section, use a special message.
3586 if ( maybeDivider.className === 'divider_section_only' ) {
3587 confirmMsg = frm_admin_js.conf_delete_sec; // eslint-disable-line camelcase
3588 }
3589
3590 this.setAttribute( 'data-frmverify', confirmMsg );
3591 this.setAttribute( 'data-frmverify-btn', 'frm-button-red' );
3592 this.setAttribute( 'data-deletefield', fieldId );
3593
3594 closeOpenFieldDropdowns();
3595
3596 confirmLinkClick( this );
3597 return false;
3598 }
3599
3600 function clickSelectField() {
3601 this.closest( 'li.form-field' ).click();
3602 }
3603
3604 function clickDeleteFieldGroup() {
3605 var hoverTarget, decoy;
3606
3607 hoverTarget = document.querySelector( '.frm-field-group-hover-target' );
3608 if ( null === hoverTarget ) {
3609 return;
3610 }
3611
3612 hoverTarget.classList.add( 'frm-selected-field-group' );
3613
3614 decoy = document.createElement( 'div' );
3615 decoy.classList.add( 'frm-delete-field-groups', 'frm_hidden' );
3616 document.body.appendChild( decoy );
3617 decoy.click();
3618 }
3619
3620 function duplicateFieldGroup() {
3621 const hoverTarget = document.querySelector( '.frm-field-group-hover-target' );
3622 if ( null === hoverTarget ) {
3623 return;
3624 }
3625
3626 const newRowId = 'frm_field_group_' + getAutoId();
3627 const placeholderUlChild = document.createTextNode( '' );
3628 wrapFieldLiInPlace( placeholderUlChild );
3629
3630 const newRow = jQuery( placeholderUlChild ).closest( 'li' ).get( 0 );
3631 newRow.classList.add( 'frm_hidden' );
3632
3633 const newRowUl = newRow.querySelector( 'ul' );
3634 newRowUl.id = newRowId;
3635
3636 jQuery( hoverTarget.closest( 'li.frm_field_box' ) ).after( newRow );
3637
3638 const $fields = getFieldsInRow( jQuery( hoverTarget ) );
3639 const syncDetails = [];
3640 const injectedCloneOptions = [];
3641
3642 const expectedLength = $fields.length;
3643 const originalFieldIdByDuplicatedFieldId = {};
3644
3645 let duplicatedCount = 0;
3646
3647 jQuery( newRow ).on(
3648 'frm_added_duplicated_field_to_row',
3649 function( _, args ) {
3650 originalFieldIdByDuplicatedFieldId[ jQuery( args.duplicatedFieldHtml ).attr( 'data-fid' ) ] = args.originalFieldId;
3651
3652 if ( expectedLength > ++duplicatedCount ) {
3653 return;
3654 }
3655
3656 const $newRowUl = jQuery( newRowUl );
3657 const $duplicatedFields = getFieldsInRow( $newRowUl );
3658
3659 injectedCloneOptions.forEach(
3660 function( cloneOption ) {
3661 cloneOption.remove();
3662 }
3663 );
3664
3665 for ( let index = 0; index < expectedLength; ++index ) {
3666 $newRowUl.append( $newRowUl.children( 'li.form-field[frm-field-order="' + index + '"]' ) );
3667 }
3668
3669 syncLayoutClasses( $duplicatedFields.first(), syncDetails );
3670 newRow.classList.remove( 'frm_hidden' );
3671 updateFieldOrder();
3672
3673 getFieldsInRow( $newRowUl ).each(
3674 function() {
3675 maybeDuplicateUnsavedSettings( originalFieldIdByDuplicatedFieldId[ this.getAttribute( 'data-fid' ) ], jQuery( this ).prop( 'outerHTML' ) );
3676 }
3677 );
3678 }
3679 );
3680
3681 $fields.each(
3682 function( index ) {
3683 var cloneOption;
3684 cloneOption = document.createElement( 'li' );
3685 cloneOption.classList.add( 'frm_clone_field' );
3686 cloneOption.setAttribute( 'frm-target-row-id', newRowId );
3687 cloneOption.setAttribute( 'frm-field-order', index );
3688 this.appendChild( cloneOption );
3689 cloneOption.click();
3690 injectedCloneOptions.push( cloneOption );
3691 syncDetails.push( getSizeOfLayoutClass( getLayoutClassName( this.classList ) ) );
3692 }
3693 );
3694 }
3695
3696 function clickFieldGroupLayout() {
3697 var hoverTarget, sizeOfFieldGroup, popupWrapper;
3698
3699 hoverTarget = document.querySelector( '.frm-field-group-hover-target' );
3700
3701 if ( null === hoverTarget ) {
3702 return;
3703 }
3704
3705 deselectFields();
3706
3707 sizeOfFieldGroup = getSizeOfFieldGroupFromChildElement( hoverTarget.querySelector( 'li.form-field' ) );
3708
3709 hoverTarget.classList.add( 'frm-has-open-field-group-popup' );
3710 jQuery( document ).on( 'click', '#frm_builder_page', destroyFieldGroupPopupOnOutsideClick );
3711
3712 popupWrapper = div();
3713 popupWrapper.style.position = 'relative';
3714 popupWrapper.appendChild( getFieldGroupPopup( sizeOfFieldGroup, this ) );
3715 this.parentNode.appendChild( popupWrapper );
3716
3717 const firstLayoutOption = popupWrapper.querySelector( '.frm-row-layout-option' );
3718 if ( firstLayoutOption ) {
3719 firstLayoutOption.focus();
3720 }
3721 }
3722
3723 function destroyFieldGroupPopupOnOutsideClick( event ) {
3724 if ( event.target.classList.contains( 'frm-custom-field-group-layout' ) || event.target.classList.contains( 'frm-cancel-custom-field-group-layout' ) ) {
3725 return;
3726 }
3727 if ( ! jQuery( event.target ).closest( '#frm_field_group_controls' ).length && ! jQuery( event.target ).closest( '#frm_field_group_popup' ).length ) {
3728 destroyFieldGroupPopup();
3729 }
3730 }
3731
3732 function getSizeOfFieldGroupFromChildElement( element ) {
3733 var $ul = jQuery( element ).closest( 'ul' );
3734 if ( $ul.length ) {
3735 return getFieldsInRow( $ul ).length;
3736 }
3737 return getSelectedFieldCount();
3738 }
3739
3740 function getFieldGroupPopup( sizeOfFieldGroup, childElement ) {
3741 var popup, wrapper, rowLayoutOptions, ul;
3742
3743 popup = document.getElementById( 'frm_field_group_popup' );
3744 if ( null === popup ) {
3745 popup = div();
3746 } else {
3747 popup.innerHTML = '';
3748 }
3749
3750 popup.id = 'frm_field_group_popup';
3751
3752 wrapper = div();
3753 wrapper.style.padding = '0 24px 12px';
3754 wrapper.appendChild( getRowLayoutTitle() );
3755
3756 rowLayoutOptions = getRowLayoutOptions( sizeOfFieldGroup );
3757
3758 ul = childElement.closest( 'ul.frm_sorting' );
3759 if ( null !== ul ) {
3760 maybeMarkRowLayoutAsActive( ul, rowLayoutOptions );
3761 }
3762
3763 wrapper.appendChild( rowLayoutOptions );
3764
3765 popup.appendChild( wrapper );
3766 popup.appendChild( separator() );
3767
3768 popup.appendChild( getCustomLayoutOption() );
3769 popup.appendChild( getBreakIntoDifferentRowsOption() );
3770
3771 return popup;
3772 }
3773
3774 function maybeMarkRowLayoutAsActive( activeRow, options ) {
3775 var length, index, currentRow;
3776
3777 length = options.children.length;
3778 for ( index = 0; index < length; ++index ) {
3779 currentRow = options.children[ index ];
3780 if ( rowLayoutsMatch( currentRow, activeRow ) ) {
3781 currentRow.classList.add( 'frm-active-row-layout' );
3782 return;
3783 }
3784 }
3785 }
3786
3787 function separator() {
3788 var hr = document.createElement( 'hr' );
3789 return hr;
3790 }
3791
3792 function getCustomLayoutOption() {
3793 var option = div();
3794 option.textContent = __( 'Custom layout', 'formidable' );
3795 jQuery( option ).prepend( getIconClone( 'frm_gear_svg' ) );
3796 option.classList.add( 'frm-custom-field-group-layout' );
3797 makeTabbable( option );
3798 return option;
3799 }
3800
3801 function makeTabbable( element, ariaLabel ) {
3802 element.setAttribute( 'tabindex', 0 );
3803 element.setAttribute( 'role', 'button' );
3804 if ( 'undefined' !== typeof ariaLabel ) {
3805 element.setAttribute( 'aria-label', ariaLabel );
3806 }
3807 }
3808
3809 function getIconClone( iconId ) {
3810 var clone = document.getElementById( iconId ).cloneNode( true );
3811 clone.id = '';
3812 return clone;
3813 }
3814
3815 function getBreakIntoDifferentRowsOption() {
3816 var option = div();
3817 option.textContent = __( 'Break into rows', 'formidable' );
3818 jQuery( option ).prepend( getIconClone( 'frm_break_field_group_svg' ) );
3819 option.classList.add( 'frm-break-field-group' );
3820 makeTabbable( option );
3821 return option;
3822 }
3823
3824 function getRowLayoutTitle() {
3825 var rowLayoutTitle = div();
3826 rowLayoutTitle.classList.add( 'frm-row-layout-title' );
3827 rowLayoutTitle.textContent = __( 'Row Layout', 'formidable' );
3828 return rowLayoutTitle;
3829 }
3830
3831 function getRowLayoutOptions( size ) {
3832 var wrapper, padding;
3833
3834 wrapper = getEmptyGridContainer();
3835 if ( 5 !== size ) {
3836 wrapper.appendChild( getRowLayoutOption( size, 'even' ) );
3837 }
3838 if ( size % 2 === 1 ) {
3839 // only include the middle option for odd numbers because even doesn't make a lot of sense.
3840 wrapper.appendChild( getRowLayoutOption( size, 'middle' ) );
3841 }
3842 if ( size < 6 ) {
3843 wrapper.appendChild( getRowLayoutOption( size, 'left' ) );
3844 wrapper.appendChild( getRowLayoutOption( size, 'right' ) );
3845 } else {
3846 padding = div();
3847 padding.classList.add( 'frm_fourth' );
3848 wrapper.prepend( padding );
3849 }
3850
3851 return wrapper;
3852 }
3853
3854 function getRowLayoutOption( size, type ) {
3855 var option, useClass;
3856
3857 option = div();
3858 option.classList.add( 'frm-row-layout-option' );
3859 makeTabbable( option, type );
3860
3861 switch ( size ) {
3862 case 6:
3863 useClass = 'frm_half';
3864 break;
3865 case 5:
3866 useClass = 'frm_third';
3867 break;
3868 default:
3869 useClass = size % 2 === 1 ? 'frm_fourth' : 'frm_third';
3870 break;
3871 }
3872
3873 option.classList.add( useClass );
3874 option.setAttribute( 'layout-type', type );
3875
3876 option.appendChild( getRowForSizeAndType( size, type ) );
3877 return option;
3878 }
3879
3880 function rowLayoutsMatch( row1, row2 ) {
3881 return getRowLayoutAsKey( row1 ) === getRowLayoutAsKey( row2 );
3882 }
3883
3884 function getRowLayoutAsKey( row ) {
3885 var $fields, sizes;
3886 if ( row.classList.contains( 'frm-row-layout-option' ) ) {
3887 $fields = jQuery( row ).find( '.frm_grid_container' ).children();
3888 } else {
3889 $fields = getFieldsInRow( jQuery( row ) );
3890 }
3891 sizes = [];
3892 $fields.each(
3893 function() {
3894 sizes.push( getSizeOfLayoutClass( getLayoutClassName( this.classList ) ) );
3895 }
3896 );
3897 return sizes.join( '-' );
3898 }
3899
3900 function getRowForSizeAndType( size, type ) {
3901 var row, index, block;
3902
3903 row = getEmptyGridContainer();
3904 for ( index = 0; index < size; ++index ) {
3905 block = div();
3906 block.classList.add( getClassForBlock( size, type, index ) );
3907 block.style.height = '16px';
3908 block.style.background = '#9EA9B8';
3909 block.style.borderRadius = '1px';
3910 row.appendChild( block );
3911 }
3912
3913 return row;
3914 }
3915
3916 /**
3917 * @param {int} size 2-6.
3918 * @param {string} type even, middle, left, or right.
3919 * @param {int} index 0-5.
3920 * @returns string
3921 */
3922 function getClassForBlock( size, type, index ) {
3923 if ( 'even' === type ) {
3924 return getEvenClassForSize( size, index );
3925 } else if ( 'middle' === type ) {
3926 if ( 3 === size ) {
3927 return 1 === index ? 'frm6' : 'frm3';
3928 }
3929 if ( 5 === size ) {
3930 return 2 === index ? 'frm4' : 'frm2';
3931 }
3932 } else if ( 'left' === type ) {
3933 return 0 === index ? getLargeClassForSize( size ) : getSmallClassForSize( size );
3934 } else if ( 'right' === type ) {
3935 return index === size - 1 ? getLargeClassForSize( size ) : getSmallClassForSize( size );
3936 }
3937 return 'frm12';
3938 }
3939
3940 function getEvenClassForSize( size, index ) {
3941 if ( -1 !== [ 2, 3, 4, 6 ].indexOf( size ) ) {
3942 return getLayoutClassForSize( 12 / size );
3943 }
3944 if ( 5 === size && 'undefined' !== typeof index ) {
3945 return 0 === index ? 'frm4' : 'frm2';
3946 }
3947 return 'frm12';
3948 }
3949
3950 function getSmallClassForSize( size ) {
3951 switch ( size ) {
3952 case 2: case 3:
3953 return 'frm3';
3954 case 4:
3955 return 'frm2';
3956 case 5:
3957 return 'frm2';
3958 case 6:
3959 return 'frm1';
3960 }
3961 return 'frm12';
3962 }
3963
3964 function getLargeClassForSize( size ) {
3965 switch ( size ) {
3966 case 2:
3967 return 'frm9';
3968 case 3: case 4:
3969 return 'frm6';
3970 case 5:
3971 return 'frm4';
3972 case 6:
3973 return 'frm7';
3974 }
3975 return 'frm12';
3976 }
3977
3978 function getEmptyGridContainer() {
3979 var wrapper = div();
3980 wrapper.classList.add( 'frm_grid_container' );
3981 return wrapper;
3982 }
3983
3984 /**
3985 * Handle when a field group layout option (that sets grid classes/column sizing) is selected in the "Row Layout" popup.
3986 *
3987 * @returns {void}
3988 */
3989 function handleFieldGroupLayoutOptionClick() {
3990 const row = document.querySelector( '.frm-field-group-hover-target' );
3991 if ( ! row ) {
3992 // The field group layout options also get clicked when merging multiple rows.
3993 // The following code isn't required for multiple rows though so just exit early.
3994 return;
3995 }
3996
3997 const type = this.getAttribute( 'layout-type' );
3998 syncLayoutClasses( getFieldsInRow( jQuery( row ) ).first(), type );
3999 destroyFieldGroupPopup();
4000 }
4001
4002 function handleFieldGroupLayoutOptionInsideMergeClick() {
4003 var $ul, type;
4004 $ul = mergeSelectedFieldGroups();
4005 type = this.getAttribute( 'layout-type' );
4006 syncLayoutClasses( getFieldsInRow( $ul ).first(), type );
4007 unselectFieldGroups();
4008 }
4009
4010 function mergeSelectedFieldGroups() {
4011 var $selectedFieldGroups = jQuery( '.frm-selected-field-group' ),
4012 $firstGroupUl = $selectedFieldGroups.first();
4013 $selectedFieldGroups.not( $firstGroupUl ).each(
4014 function() {
4015 getFieldsInRow( jQuery( this ) ).each(
4016 function() {
4017 var previousParent = this.parentNode;
4018 getFieldsInRow( $firstGroupUl ).last().after( this );
4019 if ( ! jQuery( previousParent ).children( 'li.form-field' ).length ) {
4020 // clean up the previous field group if we've removed all of its fields.
4021 previousParent.closest( 'li.frm_field_box' ).remove();
4022 }
4023 }
4024 );
4025 }
4026 );
4027 updateFieldOrder();
4028 syncLayoutClasses( getFieldsInRow( $firstGroupUl ).first() );
4029 return $firstGroupUl;
4030 }
4031
4032 function customFieldGroupLayoutClick() {
4033 var $fields;
4034 if ( null !== this.closest( '.frm-merge-fields-into-row' ) ) {
4035 return;
4036 }
4037 $fields = getFieldsInRow( jQuery( '.frm-field-group-hover-target' ) );
4038 setupCustomLayoutOptions( $fields );
4039 }
4040
4041 function setupCustomLayoutOptions( $fields ) {
4042 var size, popup, wrapper, layoutClass, inputRow, paddingElement, inputValueOverride, index, inputField, heading, label, buttonsWrapper, cancelButton, saveButton;
4043
4044 size = $fields.length;
4045
4046 popup = document.getElementById( 'frm_field_group_popup' );
4047 popup.innerHTML = '';
4048
4049 wrapper = div();
4050 wrapper.style.padding = '0 24px';
4051
4052 layoutClass = getEvenClassForSize( 5 === size ? 6 : size );
4053
4054 inputRow = div();
4055 inputRow.style.padding = '20px 0';
4056 inputRow.classList.add( 'frm_grid_container' );
4057
4058 if ( 5 === size ) {
4059 // add a span to pad the inputs by 1 column, to account for the missing 2 columns.
4060 paddingElement = document.createElement( 'span' );
4061 paddingElement.classList.add( 'frm1' );
4062 inputRow.appendChild( paddingElement );
4063 }
4064
4065 inputValueOverride = getSelectedFieldCount() > 0 ? getSizeOfLayoutClass( getEvenClassForSize( size ) ) : false;
4066 if ( false !== inputValueOverride && inputValueOverride >= 12 ) {
4067 inputValueOverride = Math.floor( 12 / size );
4068 }
4069
4070 for ( index = 0; index < size; ++index ) {
4071 inputField = document.createElement( 'input' );
4072 inputField.type = 'text';
4073 inputField.classList.add( layoutClass );
4074 inputField.classList.add( 'frm-custom-grid-size-input' );
4075 inputField.value = false !== inputValueOverride ? inputValueOverride : getSizeOfLayoutClass( getLayoutClassName( $fields.get( index ).classList ) );
4076 inputRow.appendChild( inputField );
4077 }
4078
4079 heading = div();
4080 heading.classList.add( 'frm-builder-popup-heading' );
4081 heading.textContent = __( 'Enter number of columns for each field', 'formidable' );
4082
4083 label = div();
4084 label.classList.add( 'frm-builder-popup-subheading' );
4085 label.textContent = __( 'Layouts are based on a 12-column grid system', 'formidable' );
4086
4087 wrapper.appendChild( heading );
4088 wrapper.appendChild( label );
4089
4090 wrapper.appendChild( inputRow );
4091
4092 buttonsWrapper = div();
4093 buttonsWrapper.style.textAlign = 'right';
4094
4095 cancelButton = getSecondaryButton();
4096 cancelButton.textContent = __( 'Cancel', 'formidable' );
4097 cancelButton.classList.add( 'frm-cancel-custom-field-group-layout' );
4098 cancelButton.style.marginRight = '10px';
4099
4100 saveButton = getPrimaryButton();
4101 saveButton.textContent = __( 'Save', 'formidable' );
4102 saveButton.classList.add( 'frm-save-custom-field-group-layout' );
4103
4104 buttonsWrapper.appendChild( cancelButton );
4105 buttonsWrapper.appendChild( saveButton );
4106
4107 wrapper.appendChild( buttonsWrapper );
4108
4109 popup.appendChild( wrapper );
4110
4111 setTimeout(
4112 function() {
4113 const firstInput = popup.querySelector( 'input.frm-custom-grid-size-input' ).focus();
4114 if ( firstInput ) {
4115 firstInput.focus();
4116 }
4117 },
4118 0
4119 );
4120 }
4121
4122 function customFieldGroupLayoutInsideMergeClick() {
4123 $fields = jQuery( '.frm-selected-field-group li.form-field' );
4124 setupCustomLayoutOptions( $fields );
4125 }
4126
4127 function getPrimaryButton() {
4128 var button = getButton();
4129 button.classList.add( 'button-primary', 'frm-button-primary' );
4130 return button;
4131 }
4132
4133 function getSecondaryButton() {
4134 var button = getButton();
4135 button.classList.add( 'button-secondary', 'frm-button-secondary' );
4136 return button;
4137 }
4138
4139 function getButton() {
4140 var button = document.createElement( 'a' );
4141 button.setAttribute( 'href', '#' );
4142 button.classList.add( 'button' );
4143 button.style.textDecoration = 'none';
4144 return button;
4145 }
4146
4147 function getSizeOfLayoutClass( className ) {
4148 switch ( className ) {
4149 case 'frm_half':
4150 return 6;
4151 case 'frm_third':
4152 return 4;
4153 case 'frm_two_thirds':
4154 return 8;
4155 case 'frm_fourth':
4156 return 3;
4157 case 'frm_three_fourths':
4158 return 9;
4159 case 'frm_sixth':
4160 return 2;
4161 }
4162
4163 if ( 0 === className.indexOf( 'frm' ) ) {
4164 return parseInt( className.substr( 3 ) );
4165 }
4166
4167 // Anything missing a layout class should be a full width row.
4168 return 12;
4169 }
4170
4171 function getLayoutClassName( classList ) {
4172 var classes, index, currentClass;
4173 classes = getLayoutClasses();
4174 for ( index = 0; index < classes.length; ++index ) {
4175 currentClass = classes[ index ];
4176 if ( classList.contains( currentClass ) ) {
4177 return currentClass;
4178 }
4179 }
4180 return '';
4181 }
4182
4183 function getLayoutClassForSize( size ) {
4184 return 'frm' + size;
4185 }
4186
4187 function breakFieldGroupClick() {
4188 var row = document.querySelector( '.frm-field-group-hover-target' );
4189 breakRow( row );
4190 destroyFieldGroupPopup();
4191 }
4192
4193 function breakRow( row ) {
4194 const $row = jQuery( row );
4195 getFieldsInRow( $row ).each(
4196 function( index ) {
4197 const field = this;
4198 if ( 0 !== index ) {
4199 $row.parent().after( wrapFieldLi( field ) );
4200 }
4201 stripLayoutFromFields( jQuery( field ) );
4202 }
4203 );
4204 }
4205
4206 function stripLayoutFromFields( field ) {
4207 syncLayoutClasses( field, 'clear' );
4208 }
4209
4210 function focusFieldGroupInputOnClick() {
4211 this.select();
4212 }
4213
4214 function cancelCustomFieldGroupClick() {
4215 revertToFieldGroupPopupFirstPage( this );
4216 }
4217
4218 function revertToFieldGroupPopupFirstPage( triggerElement ) {
4219 jQuery( document.getElementById( 'frm_field_group_popup' ) ).replaceWith(
4220 getFieldGroupPopup( getSizeOfFieldGroupFromChildElement( triggerElement ), triggerElement )
4221 );
4222 }
4223
4224 function destroyFieldGroupPopup() {
4225 var popup, wrapper;
4226 popup = document.getElementById( 'frm_field_group_popup' );
4227 if ( popup === null ) {
4228 return;
4229 }
4230 wrapper = document.querySelector( '.frm-has-open-field-group-popup' );
4231 if ( null !== wrapper ) {
4232 wrapper.classList.remove( 'frm-has-open-field-group-popup' );
4233 popup.parentNode.remove();
4234 }
4235 jQuery( document ).off( 'click', '#frm_builder_page', destroyFieldGroupPopupOnOutsideClick );
4236 }
4237
4238 function saveCustomFieldGroupClick() {
4239 var syncDetails, $controls, $ul;
4240
4241 syncDetails = [];
4242
4243 jQuery( document.getElementById( 'frm_field_group_popup' ).querySelectorAll( '.frm_grid_container input' ) )
4244 .each(
4245 function() {
4246 syncDetails.push( parseInt( this.value ) );
4247 }
4248 );
4249
4250 $controls = jQuery( document.getElementById( 'frm_field_group_controls' ) );
4251
4252 if ( $controls.length && 'none' !== $controls.get( 0 ).style.display ) {
4253 syncLayoutClasses( getFieldsInRow( jQuery( document.querySelector( '.frm-field-group-hover-target' ) ) ).first(), syncDetails );
4254 } else {
4255 $ul = mergeSelectedFieldGroups();
4256 syncLayoutClasses( getFieldsInRow( $ul ).first(), syncDetails );
4257 unselectFieldGroups();
4258 }
4259
4260 destroyFieldGroupPopup();
4261 }
4262
4263 function fieldGroupClick( e ) {
4264 maybeShowFieldGroupMessage();
4265
4266 if ( 'ul' !== e.originalEvent.target.nodeName.toLowerCase() ) {
4267 // only continue if the group itself was clicked / ignore when a field is clicked.
4268 return;
4269 }
4270
4271 const hoverTarget = document.querySelector( '.frm-field-group-hover-target' );
4272 if ( ! hoverTarget ) {
4273 return;
4274 }
4275
4276 const ctrlOrCmdKeyIsDown = e.ctrlKey || e.metaKey;
4277 const shiftKeyIsDown = e.shiftKey;
4278 const groupIsActive = hoverTarget.classList.contains( 'frm-selected-field-group' );
4279 const $selectedFieldGroups = getSelectedFieldGroups();
4280
4281 let numberOfSelectedGroups = $selectedFieldGroups.length;
4282
4283 if ( ctrlOrCmdKeyIsDown || shiftKeyIsDown ) {
4284 // multi-selecting
4285
4286 const selectedField = getSelectedField();
4287 if ( null !== selectedField && ! jQuery( selectedField ).siblings( 'li.form-field' ).length ) {
4288 // count a selected field on its own as a selected field group when multiselecting.
4289 selectedField.parentNode.classList.add( 'frm-selected-field-group' );
4290 ++numberOfSelectedGroups;
4291 }
4292
4293 if ( ctrlOrCmdKeyIsDown ) {
4294 if ( groupIsActive ) {
4295 // unselect if holding ctrl or cmd and the group was already active.
4296 --numberOfSelectedGroups;
4297 hoverTarget.classList.remove( 'frm-selected-field-group' );
4298 syncAfterMultiSelect( numberOfSelectedGroups );
4299 return; // exit early to avoid adding back frm-selected-field-group
4300 } else {
4301 ++numberOfSelectedGroups;
4302 }
4303 } else if ( shiftKeyIsDown && ! groupIsActive ) {
4304 ++numberOfSelectedGroups; // include the one we're selecting right now.
4305 const $firstGroup = $selectedFieldGroups.first();
4306
4307 let $range;
4308 if ( $firstGroup.parent().index() < jQuery( hoverTarget.parentNode ).index() ) {
4309 $range = $firstGroup.parent().nextUntil( hoverTarget.parentNode );
4310 } else {
4311 $range = $firstGroup.parent().prevUntil( hoverTarget.parentNode );
4312 }
4313
4314 $range.each(
4315 function() {
4316 var $fieldGroup = jQuery( this ).closest( 'li' ).find( 'ul.frm_sorting' );
4317 if ( ! $fieldGroup.hasClass( 'frm-selected-field-group' ) ) {
4318 ++numberOfSelectedGroups;
4319 $fieldGroup.addClass( 'frm-selected-field-group' );
4320 }
4321 }
4322 );
4323
4324 // when holding shift and clicking, text gets selected. unselect it.
4325 document.getSelection().removeAllRanges();
4326 }
4327 } else {
4328 // not multi-selecting
4329 unselectFieldGroups();
4330 numberOfSelectedGroups = 1;
4331 }
4332
4333 hoverTarget.classList.add( 'frm-selected-field-group' );
4334 syncAfterMultiSelect( numberOfSelectedGroups );
4335
4336 maybeHideFieldGroupMessage();
4337
4338 jQuery( document ).off( 'click', unselectFieldGroups );
4339 jQuery( document ).on( 'click', unselectFieldGroups );
4340 }
4341
4342 /**
4343 * Hide the field group message by manipulating classes.
4344 *
4345 * @param {Element} fieldGroupMessage The field group message element.
4346 * @return {void}
4347 */
4348 function hideFieldGroupMessage( fieldGroupMessage ) {
4349 if ( ! fieldGroupMessage ) {
4350 return;
4351 }
4352
4353 fieldGroupMessage.classList.add( 'frm_hidden' );
4354 fieldGroupMessage.classList.remove( 'frm-fadein-up-back' );
4355 }
4356
4357 /**
4358 * Show the field group message by manipulating classes.
4359 *
4360 * @param {Element} fieldGroupMessage The field group message element.
4361 * @return {void}
4362 */
4363 function showFieldGroupMessage( fieldGroupMessage ) {
4364 if ( ! fieldGroupMessage ) {
4365 return;
4366 }
4367
4368 fieldGroupMessage.classList.remove( 'frm_hidden' );
4369 fieldGroupMessage.classList.add( 'frm-fadein-up-back' );
4370 }
4371
4372 /**
4373 * Maybe show a message if there are at least two rows.
4374 *
4375 * @return {void}
4376 */
4377 function maybeShowFieldGroupMessage() {
4378 let fieldGroupMessage = document.getElementById( 'frm-field-group-message' );
4379 const rows = document.querySelectorAll( '.edit_form_item:not(.edit_field_type_end_divider)' );
4380
4381 if ( rows.length < 2 ) {
4382 hideFieldGroupMessage( fieldGroupMessage );
4383 return;
4384 }
4385
4386 if ( fieldGroupMessage ) {
4387 showFieldGroupMessage( fieldGroupMessage );
4388 return;
4389 }
4390
4391 fieldGroupMessage = div({
4392 id: 'frm-field-group-message',
4393 className: 'frm-flex-center frm-fadein-up-back',
4394 children: [
4395 span({
4396 id: 'frm-field-group-message-dismiss',
4397 className: 'frm-flex-center',
4398 child: svg({ href: '#frm_close_icon' })
4399 })
4400 ]
4401 });
4402
4403 // Insert the field group into the DOM
4404 document.getElementById( 'post-body-content' ).appendChild( fieldGroupMessage );
4405
4406 // Get and add the field group message text
4407 const messageText = getFieldGroupMessageText();
4408 fieldGroupMessage.prepend( messageText );
4409
4410 // Set up a click event listener
4411 document.getElementById( 'frm-field-group-message-dismiss' ).addEventListener( 'click', () => {
4412 hideFieldGroupMessage( document.getElementById( 'frm-field-group-message' ) );
4413 });
4414 }
4415
4416 /**
4417 * Get a span element with text about selecting multiple fields.
4418 *
4419 * @return {HTMLElement} A span element with the message and style classes.
4420 */
4421 function getFieldGroupMessageText() {
4422 const text = document.createElement( 'span' );
4423 text.classList.add( 'frm-field-group-message-text', 'frm-flex-center' );
4424 text.innerHTML = sprintf(
4425 /* translators: %1$s: Start span HTML, %2$s: end span HTML */
4426 frm_admin_js.holdShiftMsg, // eslint-disable-line camelcase
4427 '<span class="frm-shift-key frm-flex-center"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shift" viewBox="0 0 16 16"><path d="M7.3 2a1 1 0 0 1 1.4 0l6.4 6.8a1 1 0 0 1-.8 1.7h-2.8v3a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-3H1.7a1 1 0 0 1-.8-1.7L7.3 2zm7 7.5L8 2.7 1.7 9.5h2.8a1 1 0 0 1 1 1v3h5v-3a1 1 0 0 1 1-1h2.8z"/></svg>',
4428 '</span>'
4429 );
4430
4431 return text;
4432 }
4433
4434 /**
4435 * Maybe hide the field group message based on the number of selected rows.
4436 *
4437 * @return {void}
4438 */
4439 function maybeHideFieldGroupMessage() {
4440 const selectedRowCount = document.querySelectorAll( '.frm-selected-field-group' ).length;
4441 if ( selectedRowCount < 2 ) {
4442 return;
4443 }
4444
4445 const fieldGroupMessage = document.getElementById( 'frm-field-group-message' );
4446 hideFieldGroupMessage( fieldGroupMessage );
4447 }
4448
4449 function getSelectedField() {
4450 return document.getElementById( 'frm-show-fields' ).querySelector( 'li.form-field.selected' );
4451 }
4452
4453 function getSelectedFieldGroups() {
4454 const $fieldGroups = jQuery( '.frm-selected-field-group' );
4455 if ( $fieldGroups.length ) {
4456 return $fieldGroups;
4457 }
4458
4459 const selectedField = getSelectedField();
4460 if ( selectedField ) {
4461 // If there is only one field in a group and the field is selected, consider the field's group as selected for multi-select.
4462 const selectedFieldGroup = selectedField.closest( 'ul' );
4463 if ( selectedFieldGroup && 1 === getFieldsInRow( jQuery( selectedFieldGroup ) ).length ) {
4464 selectedFieldGroup.classList.add( 'frm-selected-field-group' );
4465 return jQuery( selectedFieldGroup );
4466 }
4467 }
4468
4469 return jQuery();
4470 }
4471
4472 function syncAfterMultiSelect( numberOfSelectedGroups ) {
4473 clearSettingsBox( true ); // unselect any fields if one is selected.
4474 if ( numberOfSelectedGroups >= 2 || ( 1 === numberOfSelectedGroups && selectedGroupHasMultipleFields() ) ) {
4475 addFieldMultiselectPopup();
4476 } else {
4477 maybeRemoveMultiselectPopup();
4478 }
4479 maybeRemoveGroupHoverTarget();
4480 }
4481
4482 function selectedGroupHasMultipleFields() {
4483 return getFieldsInRow( jQuery( document.querySelector( '.frm-selected-field-group' ) ) ).length > 1;
4484 }
4485
4486 function unselectFieldGroups( event ) {
4487 if ( 'undefined' !== typeof event ) {
4488 if ( null !== event.originalEvent.target.closest( '#frm-show-fields' ) ) {
4489 return;
4490 }
4491 if ( event.originalEvent.target.classList.contains( 'frm-merge-fields-into-row' ) ) {
4492 return;
4493 }
4494 if ( null !== event.originalEvent.target.closest( '.frm-merge-fields-into-row' ) ) {
4495 return;
4496 }
4497 if ( event.originalEvent.target.classList.contains( 'frm-custom-field-group-layout' ) ) {
4498 return;
4499 }
4500 if ( event.originalEvent.target.classList.contains( 'frm-cancel-custom-field-group-layout' ) ) {
4501 return;
4502 }
4503 }
4504 jQuery( '.frm-selected-field-group' ).removeClass( 'frm-selected-field-group' );
4505 jQuery( document ).off( 'click', unselectFieldGroups );
4506 maybeRemoveMultiselectPopup();
4507 }
4508
4509 function maybeRemoveMultiselectPopup() {
4510 var popup = document.getElementById( 'frm_field_multiselect_popup' );
4511 if ( null !== popup ) {
4512 popup.remove();
4513 }
4514 }
4515
4516 function addFieldMultiselectPopup() {
4517 getFieldMultiselectPopup();
4518 }
4519
4520 function getFieldMultiselectPopup() {
4521 var popup, mergeOption, caret, verticalSeparator, deleteOption;
4522
4523 popup = document.getElementById( 'frm_field_multiselect_popup' );
4524
4525 if ( null !== popup ) {
4526 popup.classList.toggle( 'frm-unmergable', ! selectedFieldsAreMergable() );
4527 return popup;
4528 }
4529
4530 popup = div();
4531 popup.id = 'frm_field_multiselect_popup';
4532 if ( ! selectedFieldsAreMergable() ) {
4533 popup.classList.add( 'frm-unmergable' );
4534 }
4535
4536 mergeOption = div();
4537 mergeOption.classList.add( 'frm-merge-fields-into-row' );
4538 mergeOption.textContent = __( 'Merge into row', 'formidable' );
4539
4540 caret = document.createElement( 'a' );
4541 caret.style.marginLeft = '5px';
4542 caret.classList.add( 'frm_icon_font', 'frm_arrowdown6_icon' );
4543 caret.setAttribute( 'href', '#' );
4544 mergeOption.appendChild( caret );
4545
4546 popup.appendChild( mergeOption );
4547
4548 verticalSeparator = div();
4549 verticalSeparator.classList.add( 'frm-multiselect-popup-separator' );
4550 popup.appendChild( verticalSeparator );
4551
4552 deleteOption = div();
4553 deleteOption.classList.add( 'frm-delete-field-groups' );
4554 deleteOption.appendChild( getIconClone( 'frm_trash_svg' ) );
4555 popup.appendChild( deleteOption );
4556
4557 document.getElementById( 'post-body-content' ).appendChild( popup );
4558
4559 jQuery( popup ).hide().fadeIn();
4560
4561 return popup;
4562 }
4563
4564 function selectedFieldsAreMergable() {
4565 var selectedFieldGroups, totalFieldCount, length, index, fieldGroup;
4566 selectedFieldGroups = document.querySelectorAll( '.frm-selected-field-group' );
4567 length = selectedFieldGroups.length;
4568 if ( 1 === length ) {
4569 return false;
4570 }
4571 totalFieldCount = 0;
4572 for ( index = 0; index < length; ++index ) {
4573 fieldGroup = selectedFieldGroups[ index ];
4574 if ( null !== fieldGroup.querySelector( '.edit_field_type_break, .edit_field_type_hidden' ) ) {
4575 return false;
4576 }
4577 totalFieldCount += getFieldsInRow( jQuery( fieldGroup ) ).length;
4578 if ( totalFieldCount > 6 ) {
4579 return false;
4580 }
4581 }
4582 return true;
4583 }
4584
4585 function mergeFieldsIntoRowClick( event ) {
4586 var size, popup;
4587
4588 if ( null !== event.originalEvent.target.closest( '#frm_field_group_popup' ) ) {
4589 // prevent clicks within the popup from triggering the button again.
4590 return;
4591 }
4592
4593 if ( event.originalEvent.target.classList.contains( 'frm-custom-field-group-layout' ) ) {
4594 // avoid switching back to the first page when clicking the custom option nested inside of the merge option.
4595 return;
4596 }
4597
4598 size = getSelectedFieldCount();
4599 popup = getFieldGroupPopup( size, document.querySelector( '.frm-selected-field-group' ).firstChild );
4600 this.appendChild( popup );
4601 }
4602
4603 function getSelectedFieldCount() {
4604 var count = 0;
4605 jQuery( document.querySelectorAll( '.frm-selected-field-group' ) ).each(
4606 function() {
4607 count += getFieldsInRow( jQuery( this ) ).length;
4608 }
4609 );
4610 return count;
4611 }
4612
4613 function deleteFieldGroupsClick() {
4614 var fieldIdsToDelete, deleteOnConfirm, multiselectPopup;
4615
4616 fieldIdsToDelete = getSelectedFieldIds();
4617 deleteOnConfirm = getDeleteSelectedFieldGroupsOnConfirmFunction( fieldIdsToDelete );
4618
4619 multiselectPopup = document.getElementById( 'frm_field_multiselect_popup' );
4620 if ( null !== multiselectPopup ) {
4621 multiselectPopup.remove();
4622 }
4623
4624 this.setAttribute( 'data-frmverify', confirmFieldsDeleteMessage( fieldIdsToDelete.length ) );
4625 confirmLinkClick( this );
4626
4627 jQuery( '#frm-confirmed-click' ).on( 'click', deleteOnConfirm );
4628 jQuery( '#frm_confirm_modal' ).one( 'dialogclose', function() {
4629 jQuery( '#frm-confirmed-click' ).off( 'click', deleteOnConfirm );
4630 });
4631 }
4632
4633 function getSelectedFieldIds() {
4634 var deleteFieldIds = [];
4635 jQuery( '.frm-selected-field-group > li.form-field' )
4636 .each(
4637 function() {
4638 deleteFieldIds.push( this.dataset.fid );
4639 }
4640 );
4641 return deleteFieldIds;
4642 }
4643
4644 function getDeleteSelectedFieldGroupsOnConfirmFunction( deleteFieldIds ) {
4645 return function( event ) {
4646 event.preventDefault();
4647 deleteAllSelectedFieldGroups( deleteFieldIds );
4648 };
4649 }
4650
4651 function deleteAllSelectedFieldGroups( deleteFieldIds ) {
4652 deleteFieldIds.forEach(
4653 function( fieldId ) {
4654 deleteFields( fieldId );
4655 }
4656 );
4657 }
4658
4659 function deleteFieldConfirmed() {
4660 /*jshint validthis:true */
4661 deleteFields( this.getAttribute( 'data-deletefield' ) );
4662 }
4663
4664 function deleteFields( fieldId ) {
4665 var field = jQuery( '#frm_field_id_' + fieldId );
4666
4667 deleteField( fieldId );
4668
4669 if ( field.hasClass( 'edit_field_type_divider' ) ) {
4670 field.find( 'li.frm_field_box' ).each( function() {
4671 //TODO: maybe delete only end section
4672 //if(n.hasClass('edit_field_type_end_divider')){
4673 deleteField( this.getAttribute( 'data-fid' ) );
4674 //}
4675 });
4676 }
4677 toggleSectionHolder();
4678 }
4679
4680 function deleteField( fieldId ) {
4681 jQuery.ajax({
4682 type: 'POST',
4683 url: ajaxurl,
4684 data: {
4685 action: 'frm_delete_field',
4686 field_id: fieldId,
4687 nonce: frmGlobal.nonce
4688 },
4689 success: function() {
4690 var $thisField = jQuery( document.getElementById( 'frm_field_id_' + fieldId ) ),
4691 settings = jQuery( '#frm-single-settings-' + fieldId );
4692
4693 // Remove settings from sidebar.
4694 if ( settings.is( ':visible' ) ) {
4695 document.getElementById( 'frm_insert_fields_tab' ).click();
4696 }
4697 settings.remove();
4698
4699 $thisField.fadeOut( 'slow', function() {
4700 var $section = $thisField.closest( '.start_divider' ),
4701 type = $thisField.data( 'type' ),
4702 $adjacentFields = $thisField.siblings( 'li.form-field' ),
4703 $liWrapper;
4704
4705 if ( ! $adjacentFields.length ) {
4706 if ( $thisField.is( '.edit_field_type_end_divider' ) ) {
4707 $adjacentFields.length = $thisField.closest( 'li.form-field' ).siblings();
4708 } else {
4709 $liWrapper = $thisField.closest( 'ul.frm_sorting' ).parent();
4710 }
4711 }
4712
4713 $thisField.remove();
4714 if ( type === 'break' ) {
4715 renumberPageBreaks();
4716 }
4717 if ( type === 'product' ) {
4718 maybeHideQuantityProductFieldOption();
4719 // a product field attached to a quantity field earlier might be the one deleted, so re-populate
4720 popAllProductFields();
4721 }
4722 if ( $adjacentFields.length ) {
4723 syncLayoutClasses( $adjacentFields.first() );
4724 } else {
4725 $liWrapper.remove();
4726 }
4727
4728 if ( jQuery( '#frm-show-fields li' ).length === 0 ) {
4729 const formEditorContainer = document.getElementById( 'frm_form_editor_container' );
4730 formEditorContainer.classList.remove( 'frm-has-fields' );
4731 formEditorContainer.classList.add( 'frm-empty-fields' );
4732 } else if ( $section.length ) {
4733 toggleOneSectionHolder( $section );
4734 }
4735
4736 // prevent "More Options" tooltips from staying around after their target field is deleted.
4737 deleteTooltips();
4738 });
4739 }
4740 });
4741 }
4742
4743 function addFieldLogicRow() {
4744 /*jshint validthis:true */
4745 var id = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' ),
4746 formId = thisFormId,
4747 logicRows = document.getElementById( 'frm_logic_row_' + id ).querySelectorAll( '.frm_logic_row' );
4748 jQuery.ajax({
4749 type: 'POST',
4750 url: ajaxurl,
4751 data: {
4752 action: 'frm_add_logic_row',
4753 form_id: formId,
4754 field_id: id,
4755 nonce: frmGlobal.nonce,
4756 meta_name: getNewRowId( logicRows, 'frm_logic_' + id + '_' ),
4757 fields: getFieldList()
4758 },
4759 success: function( html ) {
4760 jQuery( document.getElementById( 'logic_' + id ) ).fadeOut( 'slow', function() {
4761 var logicRow = jQuery( document.getElementById( 'frm_logic_row_' + id ) );
4762 logicRow.append( html );
4763 logicRow.closest( '.frm_logic_rows' ).fadeIn( 'slow' );
4764 });
4765 }
4766 });
4767 return false;
4768 }
4769
4770 function getNewRowId( rows, replace, defaultValue ) {
4771 if ( ! rows.length ) {
4772 return 'undefined' !== typeof defaultValue ? defaultValue : 0;
4773 }
4774 return parseInt( rows[ rows.length - 1 ].id.replace( replace, '' ), 10 ) + 1;
4775 }
4776
4777 function addWatchLookupRow() {
4778 /*jshint validthis:true */
4779 var lastRowId,
4780 id = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' ),
4781 formId = thisFormId,
4782 lookupBlockRows = document.getElementById( 'frm_watch_lookup_block_' + id ).children;
4783 jQuery.ajax({
4784 type: 'POST',
4785 url: ajaxurl,
4786 data: {
4787 action: 'frm_add_watch_lookup_row',
4788 form_id: formId,
4789 field_id: id,
4790 row_key: getNewRowId( lookupBlockRows, 'frm_watch_lookup_' + id + '_' ),
4791 nonce: frmGlobal.nonce
4792 },
4793 success: function( newRow ) {
4794 var watchRowBlock = jQuery( document.getElementById( 'frm_watch_lookup_block_' + id ) );
4795 watchRowBlock.append( newRow );
4796 watchRowBlock.fadeIn( 'slow' );
4797 }
4798 });
4799 return false;
4800 }
4801
4802 function resetOptionTextDetails() {
4803 jQuery( '.frm-single-settings ul input[type="text"][name^="field_options[options_"]' ).filter( '[data-value-on-load]' ).removeAttr( 'data-value-on-load' );
4804 jQuery( 'input[type="hidden"][name^=optionmap]' ).remove();
4805 }
4806
4807 function optionTextAlreadyExists( input ) {
4808 var fieldId = jQuery( input ).closest( '.frm-single-settings' ).attr( 'data-fid' ),
4809 optionInputs = jQuery( input ).closest( 'ul' ).get( 0 ).querySelectorAll( '.field_' + fieldId + '_option' ),
4810 index,
4811 optionInput;
4812
4813 for ( index in optionInputs ) {
4814 optionInput = optionInputs[ index ];
4815 if ( optionInput.id !== input.id && optionInput.value === input.value && optionInput.getAttribute( 'data-duplicate' ) !== 'true' ) {
4816 return true;
4817 }
4818 }
4819
4820 return false;
4821 }
4822
4823 function onOptionTextFocus() {
4824 var input,
4825 fieldId;
4826
4827 if ( this.getAttribute( 'data-value-on-load' ) === null ) {
4828 this.setAttribute( 'data-value-on-load', this.value );
4829
4830 fieldId = jQuery( this ).closest( '.frm-single-settings' ).attr( 'data-fid' );
4831 input = document.createElement( 'input' );
4832 input.value = this.value;
4833 input.setAttribute( 'type', 'hidden' );
4834 input.setAttribute( 'name', 'optionmap[' + fieldId + '][' + this.value + ']' );
4835 this.parentNode.appendChild( input );
4836
4837 if ( typeof optionMap[ fieldId ] === 'undefined' ) {
4838 optionMap[ fieldId ] = {};
4839 }
4840
4841 optionMap[ fieldId ][ this.value ] = input;
4842 }
4843
4844 if ( this.getAttribute( 'data-duplicate' ) === 'true' ) {
4845 this.removeAttribute( 'data-duplicate' );
4846
4847 // we want to use original value if actually still a duplicate
4848 if ( optionTextAlreadyExists( this ) ) {
4849 this.setAttribute( 'data-value-on-focus', this.getAttribute( 'data-value-on-load' ) );
4850 return;
4851 }
4852 }
4853
4854 if ( '' !== this.value || frm_admin_js.new_option !== this.getAttribute( 'data-value-on-focus' ) ) { // eslint-disable-line camelcase
4855 this.setAttribute( 'data-value-on-focus', this.value );
4856 }
4857 }
4858
4859 function onOptionTextBlur() {
4860 var originalValue,
4861 oldValue = this.getAttribute( 'data-value-on-focus' ),
4862 newValue = this.value,
4863 fieldId,
4864 fieldIndex,
4865 logicId,
4866 row,
4867 rowLength,
4868 rowIndex,
4869 valueSelect,
4870 opts,
4871 fieldIds,
4872 settingId,
4873 setting,
4874 optionMatches,
4875 option;
4876
4877 if ( oldValue === newValue ) {
4878 return;
4879 }
4880
4881 fieldId = jQuery( this ).closest( '.frm-single-settings' ).attr( 'data-fid' );
4882 originalValue = this.getAttribute( 'data-value-on-load' );
4883
4884 // check if the newValue is already mapped to another option
4885 // if it is, mark as duplicate and return
4886 if ( optionTextAlreadyExists( this ) ) {
4887 this.setAttribute( 'data-duplicate', 'true' );
4888
4889 if ( typeof optionMap[ fieldId ] !== 'undefined' && typeof optionMap[ fieldId ][ originalValue ] !== 'undefined' ) {
4890 // unmap any other change that may have happened before instead of changing it to something unused
4891 optionMap[ fieldId ][ originalValue ].value = originalValue;
4892 }
4893
4894 return;
4895 }
4896
4897 if ( typeof optionMap[ fieldId ] !== 'undefined' && typeof optionMap[ fieldId ][ originalValue ] !== 'undefined' ) {
4898 optionMap[ fieldId ][ originalValue ].value = newValue;
4899 }
4900
4901 fieldIds = [];
4902 rows = document.getElementById( 'frm_builder_page' ).querySelectorAll( '.frm_logic_row' );
4903 rowLength = rows.length;
4904 for ( rowIndex = 0; rowIndex < rowLength; rowIndex++ ) {
4905 row = rows[ rowIndex ];
4906 opts = row.querySelector( '.frm_logic_field_opts' );
4907
4908 if ( opts.value !== fieldId ) {
4909 continue;
4910 }
4911
4912 logicId = row.id.split( '_' )[ 2 ];
4913 valueSelect = row.querySelector( 'select[name="field_options[hide_opt_' + logicId + '][]"]' );
4914
4915 if ( '' === oldValue ) {
4916 optionMatches = [];
4917 } else {
4918 optionMatches = valueSelect.querySelectorAll( 'option[value="' + oldValue + '"]' );
4919 }
4920
4921 if ( ! optionMatches.length ) {
4922 optionMatches = valueSelect.querySelectorAll( 'option[value="' + newValue + '"]' );
4923
4924 if ( ! optionMatches.length ) {
4925 option = document.createElement( 'option' );
4926 valueSelect.appendChild( option );
4927 }
4928 }
4929
4930 if ( optionMatches.length ) {
4931 option = optionMatches[ optionMatches.length - 1 ];
4932 }
4933
4934 option.setAttribute( 'value', newValue );
4935 option.textContent = newValue;
4936
4937 if ( fieldIds.indexOf( logicId ) === -1 ) {
4938 fieldIds.push( logicId );
4939 }
4940 }
4941
4942 for ( fieldIndex in fieldIds ) {
4943 settingId = fieldIds[ fieldIndex ];
4944 setting = document.getElementById( 'frm-single-settings-' + settingId );
4945 moveFieldSettings( setting );
4946 }
4947 }
4948
4949 function updateGetValueFieldSelection() {
4950 /*jshint validthis:true */
4951 var fieldID = this.id.replace( 'get_values_form_', '' );
4952 var fieldSelect = document.getElementById( 'get_values_field_' + fieldID );
4953 var fieldType = this.getAttribute( 'data-fieldtype' );
4954
4955 if ( this.value === '' ) {
4956 fieldSelect.options.length = 1;
4957 } else {
4958 var formID = this.value;
4959 jQuery.ajax({
4960 type: 'POST', url: ajaxurl,
4961 data: {
4962 action: 'frm_get_options_for_get_values_field',
4963 form_id: formID,
4964 field_type: fieldType,
4965 nonce: frmGlobal.nonce
4966 },
4967 success: function( fields ) {
4968 fieldSelect.innerHTML = fields;
4969 }
4970 });
4971 }
4972 }
4973
4974 // Clear the Watch Fields option when Lookup field switches to "Text" option
4975 function maybeClearWatchFields() {
4976 /*jshint validthis:true */
4977 var link, lookupBlock,
4978 fieldID = this.name.replace( 'field_options[data_type_', '' ).replace( ']', '' );
4979
4980 link = document.getElementById( 'frm_add_watch_lookup_link_' + fieldID );
4981 if ( ! link ) {
4982 return;
4983 }
4984 link = link.parentNode;
4985
4986 if ( this.value === 'text' ) {
4987 lookupBlock = document.getElementById( 'frm_watch_lookup_block_' + fieldID );
4988 if ( lookupBlock !== null ) {
4989 // Clear and hide the Watch Fields option
4990 lookupBlock.innerHTML = '';
4991 link.classList.add( 'frm_hidden' );
4992
4993 // Hide the Watch Fields row
4994 link.previousElementSibling.style.display = 'none';
4995 link.previousElementSibling.previousElementSibling.style.display = 'none';
4996 link.previousElementSibling.previousElementSibling.previousElementSibling.style.display = 'none';
4997 }
4998 } else {
4999 // Show the Watch Fields option
5000 link.classList.remove( 'frm_hidden' );
5001 }
5002
5003 toggleMultiSelect( fieldID, this.value );
5004 }
5005
5006 // Number the pages and hide/show the first page as needed.
5007 function renumberPageBreaks() {
5008 var i, containerClass,
5009 pages = document.getElementsByClassName( 'frm-page-num' );
5010
5011 if ( pages.length > 1 ) {
5012 document.getElementById( 'frm-fake-page' ).style.display = 'block';
5013 for ( i = 0; i < pages.length; i++ ) {
5014 containerClass = pages[i].parentNode.parentNode.parentNode.classList;
5015 if ( i === 1 ) {
5016 // Hide previous button on page 1
5017 containerClass.add( 'frm-first-page' );
5018 } else {
5019 containerClass.remove( 'frm-first-page' );
5020 }
5021 pages[i].textContent = ( i + 1 );
5022 }
5023 } else {
5024 document.getElementById( 'frm-fake-page' ).style.display = 'none';
5025 }
5026 }
5027
5028 // The fake field works differently than real fields.
5029 function maybeCollapsePage() {
5030 /*jshint validthis:true */
5031 var field = jQuery( this ).closest( '.frm_field_box[data-ftype=break]' );
5032 if ( field.length ) {
5033 toggleCollapsePage( field );
5034 } else {
5035 toggleCollapseFakePage();
5036 }
5037 }
5038
5039 // Find all fields in a page and hide/show them
5040 function toggleCollapsePage( field ) {
5041 var toCollapse = getAllFieldsForPage( field.get( 0 ).parentNode.closest( 'li.frm_field_box' ).nextElementSibling );
5042 togglePage( field, toCollapse );
5043 }
5044
5045 function toggleCollapseFakePage() {
5046 var topLevel = document.getElementById( 'frm-fake-page' ),
5047 firstField = document.getElementById( 'frm-show-fields' ).firstElementChild,
5048 toCollapse = getAllFieldsForPage( firstField );
5049
5050 if ( firstField.getAttribute( 'data-ftype' ) === 'break' ) {
5051 // Don't collapse if the first field is a page break.
5052 return;
5053 }
5054
5055 togglePage( jQuery( topLevel ), toCollapse );
5056 }
5057
5058 function getAllFieldsForPage( firstWrapper ) {
5059 var $fieldsForPage, currentWrapper;
5060
5061 $fieldsForPage = jQuery();
5062
5063 if ( null === firstWrapper ) {
5064 return $fieldsForPage;
5065 }
5066
5067 currentWrapper = firstWrapper;
5068
5069 do {
5070 if ( null !== currentWrapper.querySelector( '.edit_field_type_break' ) ) {
5071 break;
5072 }
5073 $fieldsForPage = $fieldsForPage.add( jQuery( currentWrapper ) );
5074 currentWrapper = currentWrapper.nextElementSibling;
5075 } while ( null !== currentWrapper );
5076
5077 return $fieldsForPage;
5078 }
5079
5080 function togglePage( field, toCollapse ) {
5081 var i,
5082 fieldCount = toCollapse.length,
5083 slide = Math.min( fieldCount, 3 );
5084
5085 if ( field.hasClass( 'frm-page-collapsed' ) ) {
5086 field.removeClass( 'frm-page-collapsed' );
5087 toCollapse.removeClass( 'frm-is-collapsed' );
5088 for ( i = 0; i < slide; i++ ) {
5089 if ( i === slide - 1 ) {
5090 jQuery( toCollapse[ i ]).slideDown( 150, function() {
5091 toCollapse.show();
5092 });
5093 } else {
5094 jQuery( toCollapse[ i ]).slideDown( 150 );
5095 }
5096 }
5097 } else {
5098 field.addClass( 'frm-page-collapsed' );
5099 toCollapse.addClass( 'frm-is-collapsed' );
5100 for ( i = 0; i < slide; i++ ) {
5101 if ( i === slide - 1 ) {
5102 jQuery( toCollapse[ i ]).slideUp( 150, function() {
5103 toCollapse.css( 'cssText', 'display:none !important;' );
5104 });
5105 } else {
5106 jQuery( toCollapse[ i ]).slideUp( 150 );
5107 }
5108 }
5109 }
5110 }
5111
5112 function maybeCollapseSection() {
5113 /*jshint validthis:true */
5114 var parentCont = this.parentNode.parentNode.parentNode.parentNode;
5115
5116 parentCont.classList.toggle( 'frm-section-collapsed' );
5117 }
5118
5119 function maybeCollapseSettings() {
5120 /*jshint validthis:true */
5121 this.classList.toggle( 'frm-collapsed' );
5122
5123 // Toggles the "aria-expanded" attribute
5124 let expanded = this.getAttribute( 'aria-expanded' ) === 'true' || false;
5125 this.setAttribute( 'aria-expanded', ! expanded );
5126 }
5127
5128 function clickLabel() {
5129 if ( ! this.id ) {
5130 return;
5131 }
5132
5133 /*jshint validthis:true */
5134 var setting = document.querySelectorAll( '[data-changeme="' + this.id + '"]' )[0],
5135 fieldId = this.id.replace( 'field_label_', '' ),
5136 fieldType = document.getElementById( 'field_options_type_' + fieldId ),
5137 fieldTypeName = fieldType.value;
5138
5139 if ( typeof setting !== 'undefined' ) {
5140 if ( fieldType.tagName === 'SELECT' ) {
5141 fieldTypeName = fieldType.options[ fieldType.selectedIndex ].text.toLowerCase();
5142 } else {
5143 fieldTypeName = fieldTypeName.replace( '_', ' ' );
5144 }
5145
5146 fieldTypeName = normalizeFieldName( fieldTypeName );
5147
5148 setTimeout( function() {
5149 if ( setting.value.toLowerCase() === fieldTypeName ) {
5150 setting.select();
5151 } else {
5152 setting.focus();
5153 }
5154 }, 50 );
5155 }
5156 }
5157
5158 function clickDescription() {
5159 /*jshint validthis:true */
5160 var setting = document.querySelectorAll( '[data-changeme="' + this.id + '"]' )[0];
5161 if ( typeof setting !== 'undefined' ) {
5162 setTimeout( function() {
5163 setting.focus();
5164 autoExpandSettings( setting );
5165 }, 50 );
5166 }
5167 }
5168
5169 function autoExpandSettings( setting ) {
5170 var inSection = setting.closest( '.frm-collapse-me' );
5171 if ( inSection !== null ) {
5172 inSection.previousElementSibling.classList.remove( 'frm-collapsed' );
5173 }
5174 }
5175
5176 function normalizeFieldName( fieldTypeName ) {
5177 if ( fieldTypeName === 'divider' ) {
5178 fieldTypeName = 'section';
5179 } else if ( fieldTypeName === 'range' ) {
5180 fieldTypeName = 'slider';
5181 } else if ( fieldTypeName === 'data' ) {
5182 fieldTypeName = 'dynamic';
5183 } else if ( fieldTypeName === 'form' ) {
5184 fieldTypeName = 'embed form';
5185 }
5186 return fieldTypeName;
5187 }
5188
5189 function clickVis( e ) {
5190 /*jshint validthis:true */
5191 var currentClass, originalList;
5192
5193 currentClass = e.target.classList;
5194
5195 if ( currentClass.contains( 'frm-collapse-page' ) || currentClass.contains( 'frm-sub-label' ) || e.target.closest( '.dropdown' ) !== null ) {
5196 return;
5197 }
5198
5199 if ( this.closest( '.start_divider' ) !== null ) {
5200 e.stopPropagation();
5201 }
5202
5203 if ( this.classList.contains( 'edit_field_type_divider' ) ) {
5204 originalList = e.originalEvent.target.closest( 'ul.frm_sorting' );
5205 if ( null !== originalList ) {
5206 // prevent section click if clicking a field group within a section.
5207 if ( originalList.classList.contains( 'edit_field_type_divider' ) || originalList.parentNode.parentNode.classList.contains( 'start_divider' ) ) {
5208 return;
5209 }
5210 }
5211 }
5212
5213 clickAction( this );
5214 }
5215
5216 /**
5217 * Open Advanced settings on double click.
5218 */
5219 function openAdvanced() {
5220 var fieldId = this.getAttribute( 'data-fid' );
5221 autoExpandSettings( document.getElementById( 'field_options_field_key_' + fieldId ) );
5222 }
5223
5224 function toggleRepeatButtons() {
5225 /*jshint validthis:true */
5226 var $thisField = jQuery( this ).closest( '.frm_field_box' );
5227 $thisField.find( '.repeat_icon_links' ).removeClass( 'repeat_format repeat_formatboth repeat_formattext' ).addClass( 'repeat_format' + this.value );
5228 if ( this.value === 'text' || this.value === 'both' ) {
5229 $thisField.find( '.frm_repeat_text' ).show();
5230 $thisField.find( '.repeat_icon_links a' ).addClass( 'frm_button' );
5231 } else {
5232 $thisField.find( '.frm_repeat_text' ).hide();
5233 $thisField.find( '.repeat_icon_links a' ).removeClass( 'frm_button' );
5234 }
5235 }
5236
5237 function checkRepeatLimit() {
5238 /*jshint validthis:true */
5239 var val = this.value;
5240 if ( val !== '' && ( val < 2 || val > 200 ) ) {
5241 infoModal( frm_admin_js.repeat_limit_min ); // eslint-disable-line camelcase
5242 this.value = '';
5243 }
5244 }
5245
5246 function checkCheckboxSelectionsLimit() {
5247 /*jshint validthis:true */
5248 var val = this.value;
5249 if ( val !== '' && ( val < 1 || val > 200 ) ) {
5250 infoModal( frm_admin_js.checkbox_limit ); // eslint-disable-line camelcase
5251 this.value = '';
5252 }
5253 }
5254
5255 function updateRepeatText( obj, addRemove ) {
5256 var $thisField = jQuery( obj ).closest( '.frm_field_box' );
5257 $thisField.find( '.frm_' + addRemove + '_form_row .frm_repeat_label' ).text( obj.value );
5258 }
5259
5260 function fieldsInSection( id ) {
5261 var children = [];
5262 jQuery( document.getElementById( 'frm_field_id_' + id ) ).find( 'li.frm_field_box:not(.no_repeat_section .edit_field_type_end_divider)' ).each( function() {
5263 children.push( jQuery( this ).data( 'fid' ) );
5264 });
5265 return children;
5266 }
5267
5268 function toggleFormTax() {
5269 /*jshint validthis:true */
5270 var id = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
5271 var val = this.value;
5272 var $showFields = document.getElementById( 'frm_show_selected_fields_' + id );
5273 var $showForms = document.getElementById( 'frm_show_selected_forms_' + id );
5274
5275 jQuery( $showForms ).find( 'select' ).val( '' );
5276 if ( val === 'form' ) {
5277 $showForms.style.display = 'inline';
5278 empty( $showFields );
5279 } else {
5280 $showFields.style.display = 'none';
5281 $showForms.style.display = 'none';
5282 getTaxOrFieldSelection( val, id );
5283 }
5284
5285 }
5286
5287 function resetOptOnChange() {
5288 /*jshint validthis:true */
5289 var field, thisOpt;
5290
5291 field = getFieldKeyFromOpt( this );
5292 if ( ! field ) {
5293 return;
5294 }
5295
5296 thisOpt = jQuery( this ).closest( '.frm_single_option' );
5297
5298 resetSingleOpt( field.fieldId, field.fieldKey, thisOpt );
5299 }
5300
5301 function getFieldKeyFromOpt( object ) {
5302 var allOpts, fieldId, fieldKey;
5303
5304 allOpts = jQuery( object ).closest( '.frm_sortable_field_opts' );
5305 if ( ! allOpts.length ) {
5306 return false;
5307 }
5308
5309 fieldId = allOpts.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_opts', '' );
5310 fieldKey = allOpts.data( 'key' );
5311
5312 return {
5313 fieldId: fieldId,
5314 fieldKey: fieldKey
5315 };
5316 }
5317
5318 function resetSingleOpt( fieldId, fieldKey, thisOpt ) {
5319 var saved, text, defaultVal, previewInput, labelForDisplay, optContainer,
5320 optKey = thisOpt.data( 'optkey' ),
5321 separateValues = usingSeparateValues( fieldId ),
5322 single = jQuery( 'label[for="field_' + fieldKey + '-' + optKey + '"]' ),
5323 baseName = 'field_options[options_' + fieldId + '][' + optKey + ']',
5324 label = jQuery( 'input[name="' + baseName + '[label]"]' );
5325
5326 if ( single.length < 1 ) {
5327 resetDisplayedOpts( fieldId );
5328
5329 // Set the default value.
5330 defaultVal = thisOpt.find( 'input[name^="default_value_"]' );
5331 if ( defaultVal.is( ':checked' ) && label.length > 0 ) {
5332 jQuery( 'select[name^="item_meta[' + fieldId + ']"]' ).val( label.val() );
5333 }
5334 return;
5335 }
5336
5337 previewInput = single.children( 'input' );
5338
5339 if ( label.length < 1 ) {
5340 // Check for other label.
5341 label = jQuery( 'input[name="' + baseName + '"]' );
5342 saved = label.val();
5343 } else if ( separateValues ) {
5344 saved = jQuery( 'input[name="' + baseName + '[value]"]' ).val();
5345 } else {
5346 saved = label.val();
5347 }
5348
5349 if ( label.length < 1 ) {
5350 return;
5351 }
5352
5353 // Set the displayed value.
5354 text = single[0].childNodes;
5355
5356 if ( imagesAsOptions( fieldId ) ) {
5357 labelForDisplay = getImageDisplayValue( thisOpt, fieldId, label );
5358 optContainer = single.find( '.frm_image_option_container' );
5359
5360 if ( optContainer.length > 0 ) {
5361 optContainer.replaceWith( labelForDisplay );
5362 } else {
5363 text[ text.length - 1 ].nodeValue = '';
5364 single.append( labelForDisplay );
5365 }
5366 } else {
5367 let firstInputIndex = false;
5368 text.forEach( ( node, index ) => {
5369 if ( firstInputIndex === false ) {
5370 if ( node.tagName === 'INPUT' ) {
5371 firstInputIndex = index;
5372 }
5373 } else {
5374 if ( index === firstInputIndex + 1 ) {
5375 let nodeValue = '';
5376
5377 if ( buttonsAsOptions( fieldId ) ) {
5378 nodeValue = div({ className: 'frm_label_button_container', text: ' ' + label.val() });
5379 single[0].replaceChild( nodeValue, node );
5380 } else {
5381 node.nodeValue = ' ' + label.val();
5382 }
5383 } else {
5384 single[0].removeChild( node );
5385 }
5386 }
5387 });
5388 }
5389
5390 // Set saved value.
5391 previewInput.val( saved );
5392
5393 // Set the default value.
5394 defaultVal = thisOpt.find( 'input[name^="default_value_"]' );
5395 previewInput.prop( 'checked', defaultVal.is( ':checked' ) ? true : false );
5396 }
5397
5398 function buttonsAsOptions( fieldId ) {
5399 const fields = document.getElementsByName( 'field_options[image_options_' + fieldId + ']' );
5400 const result = Array.from( fields ).find( field => field.checked && ( 'buttons' === field.value ) );
5401
5402 return typeof result !== 'undefined';
5403 }
5404
5405 /**
5406 * Set the displayed value for an image option.
5407 */
5408 function getImageDisplayValue( thisOpt, fieldId, label ) {
5409 var image, imageUrl, showLabelWithImage, fieldType;
5410
5411 image = thisOpt.find( 'img' );
5412 if ( image ) {
5413 imageUrl = image.attr( 'src' );
5414 }
5415
5416 showLabelWithImage = showingLabelWithImage( fieldId );
5417 fieldType = radioOrCheckbox( fieldId );
5418 return getImageLabel( label.val(), showLabelWithImage, imageUrl, fieldType );
5419 }
5420
5421 function getImageOptionSize( fieldId ) {
5422 var val,
5423 field = document.getElementById( 'field_options_image_size_' + fieldId ),
5424 size = '';
5425
5426 if ( field !== null ) {
5427 val = field.value;
5428 if ( val !== '' ) {
5429 size = val;
5430 }
5431 }
5432
5433 return size;
5434 }
5435
5436 function resetDisplayedOpts( fieldId ) {
5437 var i, opts, type, placeholder, fieldInfo,
5438 input = jQuery( '[name^="item_meta[' + fieldId + ']"]' );
5439
5440 if ( input.length < 1 ) {
5441 return;
5442 }
5443
5444 if ( input.is( 'select' ) ) {
5445 placeholder = document.getElementById( 'frm_placeholder_' + fieldId );
5446 if ( placeholder !== null && placeholder.value === '' ) {
5447 fillDropdownOpts( input[0], { sourceID: fieldId });
5448 } else {
5449 fillDropdownOpts( input[0], {
5450 sourceID: fieldId,
5451 placeholder: placeholder.value
5452 });
5453 }
5454 } else {
5455 opts = getMultipleOpts( fieldId );
5456 type = input.attr( 'type' );
5457 jQuery( '#field_' + fieldId + '_inner_container > .frm_form_fields' ).html( '' );
5458 fieldInfo = getFieldKeyFromOpt( jQuery( '#frm_delete_field_' + fieldId + '-000_container' ) );
5459
5460 var container = jQuery( '#field_' + fieldId + '_inner_container > .frm_form_fields' ),
5461 hasImageOptions = imagesAsOptions( fieldId ),
5462 imageSize = hasImageOptions ? getImageOptionSize( fieldId ) : '',
5463 imageOptionClass = hasImageOptions ? ( 'frm_image_option frm_image_' + imageSize + ' ' ) : '',
5464 isProduct = isProductField( fieldId );
5465
5466 for ( i = 0; i < opts.length; i++ ) {
5467 container.append( addRadioCheckboxOpt( type, opts[ i ], fieldId, fieldInfo.fieldKey, isProduct, imageOptionClass ) );
5468 }
5469 }
5470
5471 adjustConditionalLogicOptionOrders( fieldId );
5472 }
5473
5474 function adjustConditionalLogicOptionOrders( fieldId, type ) {
5475 var row, opts, logicId, valueSelect, optionLength, optionIndex, expectedOption, optionMatch, fieldOptions,
5476 rows = document.getElementById( 'frm_builder_page' ).querySelectorAll( '.frm_logic_row' ),
5477 rowLength = rows.length;
5478
5479 fieldOptions = wp.hooks.applyFilters( 'frm_conditional_logic_field_options', getFieldOptions( fieldId ), { type, fieldId });
5480 optionLength = fieldOptions.length;
5481
5482 for ( rowIndex = 0; rowIndex < rowLength; rowIndex++ ) {
5483 row = rows[ rowIndex ];
5484 opts = row.querySelector( '.frm_logic_field_opts' );
5485
5486 if ( opts.value != fieldId ) {
5487 continue;
5488 }
5489
5490 logicId = row.id.split( '_' )[ 2 ];
5491 valueSelect = row.querySelector( 'select[name="field_options[hide_opt_' + logicId + '][]"]' );
5492
5493 for ( optionIndex = optionLength - 1; optionIndex >= 0; optionIndex-- ) {
5494 expectedOption = fieldOptions[ optionIndex ];
5495 optionMatch = valueSelect.querySelector( 'option[value="' + expectedOption + '"]' );
5496
5497 if ( optionMatch === null ) {
5498 optionMatch = document.createElement( 'option' );
5499 optionMatch.setAttribute( 'value', expectedOption );
5500 optionMatch.textContent = expectedOption;
5501 }
5502
5503 valueSelect.prepend( optionMatch );
5504 }
5505
5506 optionMatch = valueSelect.querySelector( 'option[value=""]' );
5507 if ( optionMatch !== null ) {
5508 valueSelect.prepend( optionMatch );
5509 }
5510 }
5511 }
5512
5513 function getFieldOptions( fieldId ) {
5514 var index, input, li, listItems, optsContainer, length,
5515 options = [];
5516 optsContainer = document.getElementById( 'frm_field_' + fieldId + '_opts' );
5517
5518 if ( ! optsContainer ) {
5519 return options;
5520 }
5521 listItems = optsContainer.querySelectorAll( '.frm_single_option' );
5522 length = listItems.length;
5523
5524 for ( index = 0; index < length; index++ ) {
5525 li = listItems[ index ];
5526
5527 if ( li.classList.contains( 'frm_hidden' ) ) {
5528 continue;
5529 }
5530
5531 input = li.querySelector( '.field_' + fieldId + '_option' );
5532 options.push( input.value );
5533 }
5534 return options;
5535 }
5536
5537 function addRadioCheckboxOpt( type, opt, fieldId, fieldKey, isProduct, classes ) {
5538 var other, single,
5539 isOther = opt.key.indexOf( 'other' ) !== -1,
5540 id = 'field_' + fieldKey + '-' + opt.key,
5541 inputType = type === 'scale' ? 'radio' : type;
5542
5543 other = '<input type="text" id="field_' + fieldKey + '-' + opt.key + '-otext" class="frm_other_input frm_pos_none" name="item_meta[other][' + fieldId + '][' + opt.key + ']" value="" />';
5544
5545 single = '<div class="frm_' + type + ' ' + type + ' ' + classes + '" id="frm_' + type + '_' + fieldId + '-' + opt.key + '"><label for="' + id +
5546 '"><input type="' + inputType +
5547 '" name="item_meta[' + fieldId + ']' + ( type === 'checkbox' ? '[]' : '' ) +
5548 '" value="' + purifyHtml( opt.saved ) + '" id="' + id + '"' + ( isProduct ? ' data-price="' + opt.price + '"' : '' ) + ( opt.checked ? ' checked="checked"' : '' ) + '> ' + purifyHtml( opt.label ) + '</label>' +
5549 ( isOther ? other : '' ) +
5550 '</div>';
5551
5552 return single;
5553 }
5554
5555 function fillDropdownOpts( field, atts ) {
5556 if ( field === null ) {
5557 return;
5558 }
5559 var sourceID = atts.sourceID,
5560 placeholder = atts.placeholder,
5561 isProduct = isProductField( sourceID ),
5562 showOther = atts.other;
5563
5564 removeDropdownOpts( field );
5565 var opts = getMultipleOpts( sourceID ),
5566 hasPlaceholder = ( typeof placeholder !== 'undefined' );
5567
5568 for ( var i = 0; i < opts.length; i++ ) {
5569 var label = opts[ i ].label,
5570 isOther = opts[ i ].key.indexOf( 'other' ) !== -1;
5571
5572 if ( hasPlaceholder && label !== '' ) {
5573 addBlankSelectOption( field, placeholder );
5574 } else if ( hasPlaceholder ) {
5575 label = placeholder;
5576 }
5577 hasPlaceholder = false;
5578
5579 if ( ! isOther || showOther ) {
5580 var opt = document.createElement( 'option' );
5581 opt.value = opts[ i ].saved;
5582 opt.innerHTML = purifyHtml( label );
5583
5584 if ( isProduct ) {
5585 opt.setAttribute( 'data-price', opts[ i ].price );
5586 }
5587
5588 field.appendChild( opt );
5589 }
5590 }
5591 }
5592
5593 function addBlankSelectOption( field, placeholder ) {
5594 var opt = document.createElement( 'option' ),
5595 firstChild = field.firstChild;
5596
5597 opt.value = '';
5598 opt.innerHTML = placeholder;
5599 if ( firstChild !== null ) {
5600 field.insertBefore( opt, firstChild );
5601 field.selectedIndex = 0;
5602 } else {
5603 field.appendChild( opt );
5604 }
5605 }
5606
5607 function getMultipleOpts( fieldId ) {
5608 let i, saved, labelName, label, key, optObj,
5609 fieldType,
5610 checked = false,
5611 opts = [],
5612 imageUrl = '';
5613
5614 const optVals = jQuery( 'input[name^="field_options[options_' + fieldId + ']"]' );
5615 const isProduct = isProductField( fieldId );
5616 const showLabelWithImage = showingLabelWithImage( fieldId );
5617 const hasImageOptions = imagesAsOptions( fieldId );
5618 const separateValues = usingSeparateValues( fieldId );
5619
5620 for ( i = 0; i < optVals.length; i++ ) {
5621 if ( optVals[ i ].name.indexOf( '[000]' ) > 0 || optVals[ i ].name.indexOf( '[value]' ) > 0 || optVals[ i ].name.indexOf( '[image]' ) > 0 || optVals[ i ].name.indexOf( '[price]' ) > 0 ) {
5622 continue;
5623 }
5624
5625 saved = optVals[ i ].value;
5626 label = saved;
5627 key = optVals[ i ].name.replace( 'field_options[options_' + fieldId + '][', '' ).replace( '[label]', '' ).replace( ']', '' );
5628
5629 if ( separateValues ) {
5630 labelName = optVals[ i ].name.replace( '[label]', '[value]' );
5631 saved = jQuery( 'input[name="' + labelName + '"]' ).val();
5632 }
5633
5634 if ( hasImageOptions ) {
5635 imageUrl = getImageUrlFromInput( optVals[i]);
5636 fieldType = radioOrCheckbox( fieldId );
5637 label = getImageLabel( label, showLabelWithImage, imageUrl, fieldType );
5638 }
5639
5640 /**
5641 * @since 5.0.04
5642 */
5643 label = frmAdminBuild.hooks.applyFilters( 'frm_choice_field_label', label, fieldId, optVals[ i ], hasImageOptions );
5644
5645 checked = getChecked( optVals[ i ].id );
5646
5647 optObj = {
5648 saved: saved,
5649 label: label,
5650 checked: checked,
5651 key: key
5652 };
5653
5654 if ( isProduct ) {
5655 labelName = optVals[ i ].name.replace( '[label]', '[price]' );
5656 optObj.price = jQuery( 'input[name="' + labelName + '"]' ).val();
5657 }
5658
5659 opts.push( optObj );
5660 }
5661
5662 return opts;
5663 }
5664
5665 function radioOrCheckbox( fieldId ) {
5666 var settings = document.getElementById( 'frm-single-settings-' + fieldId );
5667 if ( settings === null ) {
5668 return 'radio';
5669 }
5670
5671 return settings.classList.contains( 'frm-type-checkbox' ) ? 'checkbox' : 'radio';
5672 }
5673
5674 function getImageUrlFromInput( optVal ) {
5675 var img,
5676 wrapper = jQuery( optVal ).siblings( '.frm_image_preview_wrapper' );
5677
5678 if ( ! wrapper.length ) {
5679 return '';
5680 }
5681
5682 img = wrapper.find( 'img' );
5683 if ( ! img.length ) {
5684 return '';
5685 }
5686
5687 return img.attr( 'src' );
5688 }
5689
5690 function purifyHtml( html ) {
5691 if ( html instanceof Element || html instanceof Document ) {
5692 html = html.outerHTML;
5693 }
5694
5695 const clean = jQuery.parseHTML( html ).reduce(
5696 ( total, currentNode ) => {
5697 const cleanNode = frmDom.cleanNode( currentNode );
5698
5699 if ( '#text' === cleanNode.nodeName ) {
5700 return total += cleanNode.textContent;
5701 }
5702
5703 return total + cleanNode.outerHTML;
5704 },
5705 ''
5706 );
5707
5708 if ( clean !== html ) {
5709 // Clean it until nothing changes, in case the stripped result is now unsafe.
5710 return purifyHtml( clean );
5711 }
5712
5713 return clean;
5714 }
5715
5716 function getImageLabel( label, showLabelWithImage, imageUrl, fieldType ) {
5717 var imageLabelClass,
5718 originalLabel = label,
5719 shape = fieldType === 'checkbox' ? 'square' : 'circle',
5720 labelImage,
5721 labelNode,
5722 imageLabel;
5723
5724 originalLabel = purifyHtml( originalLabel );
5725
5726 if ( imageUrl ) {
5727 labelImage = img({ src: imageUrl, alt: originalLabel });
5728 } else {
5729 labelImage = div({ className: 'frm_empty_url' });
5730 labelImage.innerHTML = frm_admin_js.image_placeholder_icon; // eslint-disable-line camelcase
5731 }
5732
5733 imageLabelClass = showLabelWithImage ? ' frm_label_with_image' : '';
5734
5735 imageLabel = tag( 'span', { className: 'frm_text_label_for_image_inner' });
5736
5737 imageLabel.innerHTML = originalLabel;
5738 labelNode = tag(
5739 'span',
5740 {
5741 className: 'frm_image_option_container' + imageLabelClass,
5742 children: [
5743 tag( 'div', { className: 'frm_selected_checkmark', child: svg({ href: '#frm_checkmark_' + shape + '_icon' }) }),
5744 labelImage,
5745 tag( 'span', { className: 'frm_text_label_for_image', child: imageLabel })
5746 ]
5747 }
5748 );
5749
5750 return labelNode;
5751 }
5752
5753 function getChecked( id ) {
5754 field = jQuery( '#' + id );
5755
5756 if ( field.length === 0 ) {
5757 return false;
5758 }
5759
5760 checkbox = field.siblings( 'input[type=checkbox]' );
5761
5762 return checkbox.length && checkbox.prop( 'checked' );
5763 }
5764
5765 function removeDropdownOpts( field ) {
5766 var i;
5767 if ( typeof field.options === 'undefined' ) {
5768 return;
5769 }
5770
5771 for ( i = field.options.length - 1; i >= 0; i-- ) {
5772 field.remove( i );
5773 }
5774 }
5775
5776 /**
5777 * Is the box checked to use separate values?
5778 */
5779 function usingSeparateValues( fieldId ) {
5780 return isChecked( 'separate_value_' + fieldId );
5781 }
5782
5783 /**
5784 * Is the box checked to use images as options?
5785 */
5786 function imagesAsOptions( fieldId ) {
5787 var checked = false,
5788 field = document.getElementsByName( 'field_options[image_options_' + fieldId + ']' );
5789
5790 for ( var i = 0; i < field.length; i++ ) {
5791 if ( field[ i ].checked ) {
5792 checked = '0' !== field[ i ].value;
5793 }
5794 }
5795
5796 /**
5797 * @since 5.0.04
5798 */
5799 return frmAdminBuild.hooks.applyFilters( 'frm_choice_field_images_as_options', checked, fieldId );
5800 }
5801
5802 function showingLabelWithImage( fieldId ) {
5803 const isShowing = ! isChecked( 'hide_image_text_' + fieldId );
5804
5805 /**
5806 * @since 5.0.04
5807 */
5808 return frmAdminBuild.hooks.applyFilters( 'frm_choice_field_showing_label_with_image', isShowing, fieldId );
5809 }
5810
5811 function isChecked( id ) {
5812 var field = document.getElementById( id );
5813 if ( field === null ) {
5814 return false;
5815 } else {
5816 return field.checked;
5817 }
5818 }
5819
5820 function checkUniqueOpt( targetInput ) {
5821 const settingsContainer = targetInput.closest( '.frm-single-settings' );
5822 const fieldId = settingsContainer.getAttribute( 'data-fid' );
5823 const areValuesSeparate = settingsContainer.querySelector( '[name="field_options[separate_value_' + fieldId + ']"]' ).checked;
5824
5825 if ( areValuesSeparate && ! targetInput.name.endsWith( '[value]' ) ) {
5826 return;
5827 }
5828
5829 const container = document.getElementById( 'frm_field_' + fieldId + '_opts' );
5830 const conflicts = Array.from( container.querySelectorAll( 'input[type="text"]' ) ).filter(
5831 input => input.id !== targetInput.id &&
5832 areValuesSeparate === input.name.endsWith( '[value]' ) &&
5833 input.value === targetInput.value
5834 );
5835
5836 if ( conflicts.length ) {
5837 infoModal( __( 'Duplicate option value "%s" detected', 'formidable' ).replace( '%s', purifyHtml( targetInput.value ) ) );
5838 }
5839 }
5840
5841 function setStarValues() {
5842 /*jshint validthis:true */
5843 var fieldID = this.id.replace( 'radio_maxnum_', '' );
5844 var container = jQuery( '#field_' + fieldID + '_inner_container .frm-star-group' );
5845 var fieldKey = document.getElementsByName( 'field_options[field_key_' + fieldID + ']' )[0].value;
5846 container.html( '' );
5847
5848 var min = 1;
5849 var max = this.value;
5850 if ( min > max ) {
5851 max = min;
5852 }
5853
5854 for ( var i = min; i <= max; i++ ) {
5855 container.append( '<input type="hidden" name="field_options[options_' + fieldID + '][' + i + ']" value="' + i + '"><input type="radio" name="item_meta[' + fieldID + ']" id="field_' + fieldKey + '-' + i + '" value="' + i + '" /><label for="field_' + fieldKey + '-' + i + '" class="star-rating"></label>' );
5856 }
5857 }
5858
5859 function getFieldValues() {
5860 /*jshint validthis:true */
5861 var isTaxonomy,
5862 val = this.value;
5863
5864 if ( val ) {
5865 var parentIDs = this.parentNode.id.replace( 'frm_logic_', '' ).split( '_' );
5866 var fieldID = parentIDs[0];
5867 var metaKey = parentIDs[1];
5868 var valueField = document.getElementById( 'frm_field_id_' + val );
5869 var valueFieldType = valueField.getAttribute( 'data-ftype' );
5870 var fill = document.getElementById( 'frm_show_selected_values_' + fieldID + '_' + metaKey );
5871 var optionName = 'field_options[hide_opt_' + fieldID + '][]';
5872 var optionID = 'frm_field_logic_opt_' + fieldID;
5873 var input = false;
5874 var showSelect = ( valueFieldType === 'select' || valueFieldType === 'checkbox' || valueFieldType === 'radio' );
5875 var showText = ( valueFieldType === 'text' || valueFieldType === 'email' || valueFieldType === 'phone' || valueFieldType === 'url' || valueFieldType === 'number' );
5876
5877 if ( showSelect ) {
5878 isTaxonomy = document.getElementById( 'frm_has_hidden_options_' + val );
5879 if ( isTaxonomy !== null ) {
5880 // get the category options with ajax
5881 showSelect = false;
5882 }
5883 }
5884
5885 if ( showSelect || showText ) {
5886 fill.innerHTML = '';
5887 if ( showSelect ) {
5888 input = document.createElement( 'select' );
5889 } else {
5890 input = document.createElement( 'input' );
5891 input.type = 'text';
5892 }
5893 input.name = optionName;
5894 input.id = optionID + '_' + metaKey;
5895 fill.appendChild( input );
5896
5897 if ( showSelect ) {
5898 var fillField = document.getElementById( input.id );
5899 fillDropdownOpts( fillField, {
5900 sourceID: val,
5901 placeholder: '',
5902 other: true
5903 });
5904 }
5905 } else {
5906 var thisType = this.getAttribute( 'data-type' );
5907 frmGetFieldValues( val, fieldID, metaKey, thisType );
5908 }
5909 }
5910 }
5911
5912 function getFieldSelection() {
5913 /*jshint validthis:true */
5914 var formId = this.value;
5915 if ( formId ) {
5916 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
5917 getTaxOrFieldSelection( formId, fieldId );
5918 }
5919 }
5920
5921 function getTaxOrFieldSelection( formId, fieldId ) {
5922 if ( formId ) {
5923 jQuery.ajax({
5924 type: 'POST',
5925 url: ajaxurl,
5926 data: {
5927 action: 'frm_get_field_selection',
5928 field_id: fieldId,
5929 form_id: formId,
5930 nonce: frmGlobal.nonce
5931 },
5932 success: function( msg ) {
5933 jQuery( '#frm_show_selected_fields_' + fieldId ).html( msg ).show();
5934 }
5935 });
5936 }
5937 }
5938
5939 function updateFieldOrder() {
5940 var fields, fieldId, field, currentOrder, newOrder;
5941 renumberPageBreaks();
5942 jQuery( '#frm-show-fields' ).each( function( i ) {
5943 fields = jQuery( 'li.frm_field_box', this );
5944 for ( i = 0; i < fields.length; i++ ) {
5945 fieldId = fields[ i ].getAttribute( 'data-fid' );
5946 field = jQuery( 'input[name="field_options[field_order_' + fieldId + ']"]' );
5947 currentOrder = field.val();
5948 newOrder = i + 1;
5949
5950 if ( currentOrder != newOrder ) {
5951 field.val( newOrder );
5952 singleField = document.getElementById( 'frm-single-settings-' + fieldId );
5953
5954 moveFieldSettings( singleField );
5955 fieldUpdated();
5956 }
5957 }
5958 });
5959 }
5960
5961 function toggleSectionHolder() {
5962 document.querySelectorAll( '.start_divider' ).forEach(
5963 function( divider ) {
5964 toggleOneSectionHolder( jQuery( divider ) );
5965 }
5966 );
5967 }
5968
5969 function toggleOneSectionHolder( $section ) {
5970 var noSectionFields, $rows, length, index, sectionHasFields;
5971
5972 if ( ! $section.length ) {
5973 return;
5974 }
5975
5976 $rows = $section.find( 'ul.frm_sorting' );
5977 sectionHasFields = false;
5978 length = $rows.length;
5979 for ( index = 0; index < length; ++index ) {
5980 if ( 0 !== getFieldsInRow( jQuery( $rows.get( index ) ) ).length ) {
5981 sectionHasFields = true;
5982 break;
5983 }
5984 }
5985
5986 noSectionFields = $section.parent().children( '.frm_no_section_fields' ).get( 0 );
5987 noSectionFields.classList.toggle( 'frm_block', ! sectionHasFields );
5988 }
5989
5990 function handleShowPasswordLiveUpdate() {
5991 frmDom.util.documentOn( 'change', '.frm_show_password_setting_input', event => {
5992 const fieldId = event.target.getAttribute( 'data-fid' );
5993 const fieldEl = document.getElementById( 'frm_field_id_' + fieldId );
5994 if ( ! fieldEl ) {
5995 return;
5996 }
5997
5998 fieldEl.classList.toggle( 'frm_disabled_show_password', ! event.target.checked );
5999 });
6000 }
6001
6002 function slideDown() {
6003 /*jshint validthis:true */
6004 var id = jQuery( this ).data( 'slidedown' );
6005 var $thisId = jQuery( document.getElementById( id ) );
6006 if ( $thisId.is( ':hidden' ) ) {
6007 $thisId.slideDown( 'fast' );
6008 this.style.display = 'none';
6009 }
6010 return false;
6011 }
6012
6013 function slideUp() {
6014 /*jshint validthis:true */
6015 var id = jQuery( this ).data( 'slideup' );
6016 var $thisId = jQuery( document.getElementById( id ) );
6017 $thisId.slideUp( 'fast' );
6018 $thisId.siblings( 'a' ).show();
6019 return false;
6020 }
6021
6022 function adjustVisibilityValuesForEveryoneValues( element, option ) {
6023 if ( '' === option.getAttribute( 'value' ) ) {
6024 onEveryoneOptionSelected( jQuery( this ) );
6025 } else {
6026 unselectEveryoneOptionIfSelected( jQuery( this ) );
6027 }
6028 }
6029
6030 function onEveryoneOptionSelected( $select ) {
6031 $select.val( '' );
6032 $select.next( '.btn-group' ).find( '.multiselect-container input[value!=""]' ).prop( 'checked', false );
6033 }
6034
6035 function unselectEveryoneOptionIfSelected( $select ) {
6036 var selectedValues = $select.val(),
6037 index;
6038
6039 if ( selectedValues === null ) {
6040 $select.next( '.btn-group' ).find( '.multiselect-container input[value=""]' ).prop( 'checked', true );
6041 onEveryoneOptionSelected( $select );
6042 return;
6043 }
6044
6045 index = selectedValues.indexOf( '' );
6046 if ( index >= 0 ) {
6047 selectedValues.splice( index, 1 );
6048 $select.val( selectedValues );
6049 $select.next( '.btn-group' ).find( '.multiselect-container input[value=""]' ).prop( 'checked', false );
6050 }
6051 }
6052
6053 /**
6054 * Get rid of empty container that inserts extra space.
6055 */
6056 function hideEmptyEle() {
6057 jQuery( '.frm-hide-empty' ).each( function() {
6058 if ( jQuery( this ).text().trim().length === 0 ) {
6059 jQuery( this ).remove();
6060 }
6061 });
6062 }
6063
6064 /* Change the classes in the builder */
6065 function changeFieldClass( field, setting ) {
6066 var classes, replace, alignField,
6067 replaceWith = ' ' + setting.value,
6068 fieldId = field.getAttribute( 'data-fid' );
6069
6070 // Include classes from multiple settings.
6071 if ( typeof fieldId !== 'undefined' ) {
6072 if ( setting.classList.contains( 'field_options_align' ) ) {
6073 replaceWith += ' ' + document.getElementById( 'frm_classes_' + fieldId ).value;
6074 } else if ( setting.classList.contains( 'frm_classes' ) ) {
6075 alignField = document.getElementById( 'field_options_align_' + fieldId );
6076 if ( alignField !== null ) {
6077 replaceWith += ' ' + alignField.value;
6078 }
6079 }
6080 }
6081 replaceWith += ' ';
6082
6083 // Allow for the column number dropdown.
6084 replaceWith = replaceWith.replace( ' block ', ' ' ).replace( ' inline ', ' horizontal_radio ' );
6085
6086 classes = field.className.split( ' frmstart ' )[1];
6087 classes = 0 === classes.indexOf( 'frmend ' ) ? '' : classes.split( ' frmend ' )[0];
6088
6089 if ( classes.trim() === '' ) {
6090 replace = ' frmstart frmend ';
6091 if ( -1 === field.className.indexOf( replace ) ) {
6092 replace = ' frmstart frmend ';
6093 }
6094 replaceWith = ' frmstart ' + replaceWith.trim() + ' frmend ';
6095 } else {
6096 replace = classes.trim();
6097 replaceWith = replaceWith.trim();
6098 }
6099
6100 field.className = field.className.replace( replace, replaceWith );
6101 }
6102
6103 function maybeShowInlineModal( e ) {
6104 /*jshint validthis:true */
6105 e.preventDefault();
6106 showInlineModal( this );
6107 }
6108
6109 function showInlineModal( icon, input ) {
6110 var box = document.getElementById( icon.getAttribute( 'data-open' ) ),
6111 container = jQuery( icon ).closest( 'p' ),
6112 inputTrigger = ( typeof input !== 'undefined' );
6113
6114 if ( container.hasClass( 'frm-open' ) ) {
6115 container.removeClass( 'frm-open' );
6116 box.classList.add( 'frm_hidden' );
6117 } else {
6118 if ( ! inputTrigger ) {
6119 input = getInputForIcon( icon );
6120 }
6121 if ( input !== null ) {
6122 if ( ! inputTrigger ) {
6123 input.focus();
6124 }
6125 container.after( box );
6126 box.setAttribute( 'data-fills', input.id );
6127
6128 if ( box.id.indexOf( 'frm-calc-box' ) === 0 ) {
6129 popCalcFields( box, true );
6130 }
6131 }
6132
6133 container.addClass( 'frm-open' );
6134 box.classList.remove( 'frm_hidden' );
6135
6136 /**
6137 * @since 6.4.1
6138 */
6139 wp.hooks.doAction( 'frm_show_inline_modal', box, icon );
6140 }
6141 }
6142
6143 function dismissInlineModal( e ) {
6144 /*jshint validthis:true */
6145 e.preventDefault();
6146 this.parentNode.classList.add( 'frm_hidden' );
6147 jQuery( '.frm-open [data-open="' + this.parentNode.id + '"]' ).closest( '.frm-open' ).removeClass( 'frm-open' );
6148 }
6149
6150 function changeInputtedValue() {
6151 /*jshint validthis:true */
6152 var i,
6153 action = this.getAttribute( 'data-frmchange' ).split( ',' );
6154
6155 for ( i = 0; i < action.length; i++ ) {
6156 if ( action[i] === 'updateOption' ) {
6157 changeHiddenSeparateValue( this );
6158 } else if ( action[i] === 'updateDefault' ) {
6159 changeDefaultRadioValue( this );
6160 } else if ( action[i] === 'checkUniqueOpt' ) {
6161 checkUniqueOpt( this );
6162 } else {
6163 this.value = this.value[ action[i] ]();
6164 }
6165 }
6166 }
6167
6168 /**
6169 * When the saved value is changed, update the default value radio.
6170 */
6171 function changeDefaultRadioValue( input ) {
6172 var parentLi = getOptionParent( input ),
6173 key = parentLi.getAttribute( 'data-optkey' ),
6174 fieldId = getOptionFieldId( parentLi, key ),
6175 defaultRadio = parentLi.querySelector( 'input[name="default_value_' + fieldId + '"]' );
6176
6177 if ( defaultRadio !== null ) {
6178 defaultRadio.value = input.value;
6179 }
6180 }
6181
6182 /**
6183 * If separate values are not enabled, change the saved value when
6184 * the displayed value is changed.
6185 */
6186 function changeHiddenSeparateValue( input ) {
6187 var savedVal,
6188 parentLi = getOptionParent( input ),
6189 key = parentLi.getAttribute( 'data-optkey' ),
6190 fieldId = getOptionFieldId( parentLi, key ),
6191 sep = document.getElementById( 'separate_value_' + fieldId );
6192
6193 if ( sep !== null && sep.checked === false ) {
6194 // If separate values are not turned on.
6195 savedVal = document.getElementById( 'field_key_' + fieldId + '-' + key );
6196 savedVal.value = input.value;
6197 changeDefaultRadioValue( savedVal );
6198 }
6199 }
6200
6201 function getOptionParent( input ) {
6202 var parentLi = input.parentNode;
6203 if ( parentLi.tagName !== 'LI' ) {
6204 parentLi = parentLi.parentNode;
6205 }
6206 return parentLi;
6207 }
6208
6209 function getOptionFieldId( li, key ) {
6210 var liId = li.id;
6211
6212 return liId.replace( 'frm_delete_field_', '' ).replace( '-' + key + '_container', '' );
6213 }
6214
6215 function submitBuild() {
6216 /*jshint validthis:true */
6217 var $thisEle = this;
6218
6219 if ( showNameYourFormModal() ) {
6220 return;
6221 }
6222
6223 preFormSave( this );
6224
6225 var $form = jQuery( builderForm );
6226 var v = JSON.stringify( $form.serializeArray() );
6227
6228 jQuery( document.getElementById( 'frm_compact_fields' ) ).val( v );
6229 jQuery.ajax({
6230 type: 'POST',
6231 url: ajaxurl,
6232 data: {action: 'frm_save_form', 'frm_compact_fields': v, nonce: frmGlobal.nonce},
6233 success: function( msg ) {
6234 afterFormSave( $thisEle );
6235
6236 var $postStuff = document.getElementById( 'post-body-content' );
6237 var $html = document.createElement( 'div' );
6238 $html.setAttribute( 'class', 'frm_updated_message' );
6239 $html.innerHTML = msg;
6240 $postStuff.insertBefore( $html, $postStuff.firstChild );
6241 reloadIfAddonActivatedAjaxSubmitOnly();
6242 },
6243 error: function() {
6244 triggerSubmit( document.getElementById( 'frm_js_build_form' ) );
6245 }
6246 });
6247 }
6248
6249 function triggerSubmit( form ) {
6250 var button = form.ownerDocument.createElement( 'input' );
6251 button.style.display = 'none';
6252 button.type = 'submit';
6253 form.appendChild( button ).click();
6254 form.removeChild( button );
6255 }
6256
6257 function triggerChange( element ) {
6258 jQuery( element ).trigger( 'change' );
6259 }
6260
6261 function submitNoAjax() {
6262 /*jshint validthis:true */
6263 var form;
6264
6265 if ( showNameYourFormModal() ) {
6266 return;
6267 }
6268
6269 preFormSave( this );
6270 form = jQuery( builderForm );
6271 jQuery( document.getElementById( 'frm_compact_fields' ) ).val( JSON.stringify( form.serializeArray() ) );
6272 triggerSubmit( document.getElementById( 'frm_js_build_form' ) );
6273 }
6274
6275 /**
6276 * Display a modal dialog for naming a new form template, if applicable.
6277 *
6278 * @return {boolean} True if the modal is successfully initialized and displayed; false otherwise.
6279 */
6280 function showNameYourFormModal() {
6281 // Exit early if the 'new_template' URL parameter is not set to 'true'
6282 if ( 'true' !== urlParams.get( 'new_template' ) ) {
6283 return false;
6284 }
6285
6286 const modalWidget = initModal( '#frm-form-templates-modal', '440px' );
6287 if ( ! modalWidget ) {
6288 return false;
6289 }
6290
6291 // Set the vertical offset for the modal and open it
6292 offsetModalY( modalWidget, '72px' );
6293 modalWidget.dialog( 'open' );
6294
6295 return true;
6296 }
6297
6298 /**
6299 * Manages event handling for the 'Name your form' modal.
6300 *
6301 * Attaches click and keydown event listeners to the save button and input field.
6302 *
6303 * @return {void}
6304 */
6305 function addFormNameModalEvents() {
6306 const saveFormNameButton = document.getElementById( 'frm-save-form-name-button' );
6307 const newFormNameInput = document.getElementById( 'frm_new_form_name_input' );
6308
6309 // Attach click event listener
6310 onClickPreventDefault( saveFormNameButton, onSaveFormNameButton );
6311
6312 // Attach keydown event listener
6313 newFormNameInput.addEventListener( 'keydown', function( event ) {
6314 if ( event.key === 'Enter' ) {
6315 onSaveFormNameButton.call( this, event );
6316 }
6317 });
6318 }
6319
6320 /**
6321 * Handles the click event on the save form name button.
6322 *
6323 * @param {Event} event The click event object.
6324 * @return {void}
6325 */
6326 const onSaveFormNameButton = ( event ) => {
6327 const newFormName = document.getElementById( 'frm_new_form_name_input' ).value.trim();
6328
6329 // Prepare FormData for the POST request
6330 const formData = new FormData();
6331 formData.append( 'form_id', urlParams.get( 'id' ) );
6332 formData.append( 'form_name', newFormName );
6333
6334 // Perform the POST request
6335 doJsonPost( 'rename_form', formData ).then( data => {
6336 // Remove the 'new_template' parameter from the URL and update the browser history
6337 urlParams.delete( 'new_template' );
6338 currentURL.search = urlParams.toString();
6339 history.replaceState({}, '', currentURL.toString() );
6340
6341 if ( null !== document.getElementById( 'frm_notification_settings' ) ) {
6342 document.getElementById( 'frm_form_name' ).value = newFormName;
6343 document.getElementById( 'frm_form_key' ).value = data.form_key;
6344 }
6345
6346 // Trigger the 'Save' button click using jQuery
6347 jQuery( '#frm-publishing' ).find( '.frm_button_submit' ).click();
6348 });
6349 };
6350
6351 function preFormSave( b ) {
6352 removeWPUnload();
6353 if ( jQuery( 'form.inplace_form' ).length ) {
6354 jQuery( '.inplace_save, .postbox' ).trigger( 'click' );
6355 }
6356
6357 if ( b.classList.contains( 'frm_button_submit' ) ) {
6358 b.classList.add( 'frm_loading_form' );
6359 } else {
6360 b.classList.add( 'frm_loading_button' );
6361 }
6362 b.setAttribute( 'aria-busy', 'true' );
6363 }
6364
6365 function afterFormSave( button ) {
6366 button.classList.remove( 'frm_loading_form' );
6367 button.classList.remove( 'frm_loading_button' );
6368 resetOptionTextDetails();
6369 fieldsUpdated = 0;
6370 button.setAttribute( 'aria-busy', 'false' );
6371
6372 setTimeout( function() {
6373 jQuery( '.frm_updated_message' ).fadeOut( 'slow', function() {
6374 this.parentNode.removeChild( this );
6375 });
6376 }, 5000 );
6377 }
6378
6379 function initUpgradeModal() {
6380 const $info = initModal( '#frm_upgrade_modal' );
6381 if ( $info === false ) {
6382 return;
6383 }
6384
6385 document.addEventListener( 'click', handleUpgradeClick );
6386
6387 function handleUpgradeClick( event ) {
6388 let element, link, content;
6389
6390 element = event.target;
6391
6392 if ( ! element.classList ) {
6393 return;
6394 }
6395
6396 const showExpiredModal = element.classList.contains( 'frm_show_expired_modal' ) || null !== element.querySelector( '.frm_show_expired_modal' ) || element.closest( '.frm_show_expired_modal' );
6397
6398 if ( ! element.dataset.upgrade ) {
6399 let parent = element.closest( '[data-upgrade]' );
6400 if ( ! parent ) {
6401 parent = element.closest( '.frm_field_box' );
6402 if ( ! parent ) {
6403 return;
6404 }
6405 // Fake it if it's missing to avoid error.
6406 element.dataset.upgrade = '';
6407 }
6408 element = parent;
6409 }
6410
6411 if ( showExpiredModal ) {
6412 const hookName = 'frm_show_expired_modal';
6413 wp.hooks.doAction( hookName, element );
6414 return;
6415 }
6416
6417 const upgradeLabel = element.dataset.upgrade;
6418 if ( ! upgradeLabel || element.classList.contains( 'frm_show_upgrade_tab' ) ) {
6419 return;
6420 }
6421
6422 event.preventDefault();
6423
6424 const modal = $info.get( 0 );
6425 const lockIcon = modal.querySelector( '.frm_lock_icon' );
6426
6427 if ( lockIcon ) {
6428 lockIcon.style.display = 'block';
6429 lockIcon.classList.remove( 'frm_lock_open_icon' );
6430 lockIcon.querySelector( 'use' ).setAttribute( 'href', '#frm_lock_icon' );
6431 }
6432
6433 const upgradeImageId = 'frm_upgrade_modal_image';
6434 const oldImage = document.getElementById( upgradeImageId );
6435 if ( oldImage ) {
6436 oldImage.remove();
6437 }
6438
6439 if ( element.dataset.image ) {
6440 if ( lockIcon ) {
6441 lockIcon.style.display = 'none';
6442 }
6443 lockIcon.parentNode.insertBefore( img({ id: upgradeImageId, src: frmGlobal.url + '/images/' + element.dataset.image }), lockIcon );
6444 }
6445
6446 const level = modal.querySelector( '.license-level' );
6447 if ( level ) {
6448 level.textContent = getRequiredLicenseFromTrigger( element );
6449 }
6450
6451 // If one click upgrade, hide other content
6452 addOneClick( element, 'modal', upgradeLabel );
6453
6454 modal.querySelector( '.frm_are_not_installed' ).style.display = element.dataset.image ? 'none' : 'inline-block';
6455 modal.querySelector( '.frm_feature_label' ).textContent = upgradeLabel;
6456 modal.querySelector( 'h2' ).style.display = 'block';
6457
6458 $info.dialog( 'open' );
6459
6460 // set the utm medium
6461 const button = modal.querySelector( '.button-primary:not(.frm-oneclick-button)' );
6462 link = button.getAttribute( 'href' ).replace( /(medium=)[a-z_-]+/ig, '$1' + element.getAttribute( 'data-medium' ) );
6463 content = element.getAttribute( 'data-content' );
6464 if ( content === null ) {
6465 content = '';
6466 }
6467 link = link.replace( /(content=)[a-z_-]+/ig, '$1' + content );
6468 button.setAttribute( 'href', link );
6469 }
6470 }
6471
6472 function getRequiredLicenseFromTrigger( element ) {
6473 if ( element.dataset.requires ) {
6474 return element.dataset.requires;
6475 }
6476 return 'Pro';
6477 }
6478
6479 function populateUpgradeTab( element ) {
6480 const title = element.dataset.upgrade;
6481
6482 const tab = element.getAttribute( 'href' ).replace( '#', '' );
6483 const container = document.querySelector( '.frm_' + tab ) || document.querySelector( '.' + tab );
6484
6485 if ( ! container ) {
6486 return;
6487 }
6488
6489 if ( container.querySelector( '.frm-upgrade-message' ) ) {
6490 // Tab has already been populated.
6491 return;
6492 }
6493
6494 const h2 = container.querySelector( 'h2' );
6495 h2.style.borderBottom = 'none';
6496
6497 /* translators: %s: Form Setting section name (ie Form Permissions, Form Scheduling). */
6498 h2.textContent = __( '%s are not installed' ).replace( '%s', title );
6499
6500 container.classList.add( 'frmcenter' );
6501
6502 const upgradeModal = document.getElementById( 'frm_upgrade_modal' );
6503 appendClonedModalElementToContainer( 'frm-oneclick' );
6504 appendClonedModalElementToContainer( 'frm-addon-status' );
6505
6506 // Borrow the call to action from the Upgrade upgradeModal which should exist on the settings page (it is still used for other upgrades including Actions).
6507 const upgradeModalLink = upgradeModal.querySelector( '.frm-upgrade-link' );
6508 if ( upgradeModalLink ) {
6509 const upgradeButton = upgradeModalLink.cloneNode( true );
6510 const level = upgradeButton.querySelector( '.license-level' );
6511
6512 if ( level ) {
6513 level.textContent = getRequiredLicenseFromTrigger( element );
6514 }
6515
6516 container.appendChild( upgradeButton );
6517
6518 // Maybe append the secondary "Already purchased?" link from the upgradeModal as well.
6519 if ( upgradeModalLink.nextElementSibling && upgradeModalLink.nextElementSibling.querySelector( '.frm-link-secondary' ) ) {
6520 container.appendChild( upgradeModalLink.nextElementSibling.cloneNode( true ) );
6521 }
6522
6523 appendClonedModalElementToContainer( 'frm-oneclick-button' );
6524 }
6525
6526 appendClonedModalElementToContainer( 'frm-upgrade-message' );
6527
6528 let upgradeLabel = element.dataset.message;
6529
6530 if ( upgradeLabel === undefined ) {
6531 upgradeLabel = element.dataset.upgrade;
6532 }
6533 addOneClick( element, 'tab', upgradeLabel );
6534
6535 if ( element.dataset.screenshot ) {
6536 container.appendChild( getScreenshotWrapper( element.dataset.screenshot ) );
6537 }
6538
6539 function appendClonedModalElementToContainer( className ) {
6540 container.appendChild( upgradeModal.querySelector( '.' + className ).cloneNode( true ) );
6541 }
6542 }
6543
6544 function getScreenshotWrapper( screenshot ) {
6545 const folderUrl = frmGlobal.url + '/images/screenshots/';
6546 const wrapper = div({
6547 className: 'frm-settings-screenshot-wrapper',
6548 children: [
6549 getToolbar(),
6550 div({ child: img({ src: folderUrl + screenshot }) })
6551 ]
6552 });
6553
6554 function getToolbar() {
6555 const children = getColorIcons();
6556 children.push( img({ src: frmGlobal.url + '/images/tab.svg' }) );
6557 return div({
6558 className: 'frm-settings-screenshot-toolbar',
6559 children
6560 });
6561 }
6562
6563 function getColorIcons() {
6564 return [ '#ED8181', '#EDE06A', '#80BE30' ].map(
6565 color => {
6566 const circle = div({ className: 'frm-minmax-icon' });
6567 circle.style.backgroundColor = color;
6568 return circle;
6569 }
6570 );
6571 }
6572
6573 return wrapper;
6574 }
6575
6576 /**
6577 * Allow addons to be installed from the upgrade modal.
6578 *
6579 * @param {Element} link
6580 * @param {String} context Either 'modal' or 'tab'.
6581 * @param {String|undefined} upgradeLabel
6582 */
6583 function addOneClick( link, context, upgradeLabel ) {
6584 let container;
6585
6586 if ( 'modal' === context ) {
6587 container = document.getElementById( 'frm_upgrade_modal' );
6588 } else if ( 'tab' === context ) {
6589 container = document.getElementById( link.getAttribute( 'href' ).substr( 1 ) );
6590 } else {
6591 return;
6592 }
6593
6594 const oneclickMessage = container.querySelector( '.frm-oneclick' );
6595 const upgradeMessage = container.querySelector( '.frm-upgrade-message' );
6596 const showLink = container.querySelector( '.frm-upgrade-link' );
6597 const button = container.querySelector( '.frm-oneclick-button' );
6598 const addonStatus = container.querySelector( '.frm-addon-status' );
6599
6600 let oneclick = link.getAttribute( 'data-oneclick' );
6601 let newMessage = link.getAttribute( 'data-message' );
6602 let showIt = 'block';
6603 let showMsg = 'block';
6604 let hideIt = 'none';
6605
6606 // If one click upgrade, hide other content.
6607 if ( oneclickMessage !== null && typeof oneclick !== 'undefined' && oneclick ) {
6608 if ( newMessage === null ) {
6609 showMsg = 'none';
6610 }
6611 showIt = 'none';
6612 hideIt = 'block';
6613 oneclick = JSON.parse( oneclick );
6614
6615 button.className = button.className.replace( ' frm-install-addon', '' ).replace( ' frm-activate-addon', '' );
6616 button.className = button.className + ' ' + oneclick.class;
6617 button.textContent = __( 'Activate', 'formidable' );
6618 button.rel = oneclick.url;
6619 }
6620
6621 if ( ! newMessage ) {
6622 newMessage = upgradeMessage.getAttribute( 'data-default' );
6623 }
6624 if ( undefined !== upgradeLabel ) {
6625 newMessage = newMessage.replace( '<span class="frm_feature_label"></span>', upgradeLabel );
6626 }
6627
6628 upgradeMessage.innerHTML = newMessage;
6629
6630 // Either set the link or use the default.
6631 showLink.href = getShowLinkHrefValue( link, showLink );
6632
6633 addonStatus.style.display = 'none';
6634
6635 oneclickMessage.style.display = hideIt;
6636 button.style.display = hideIt === 'block' ? 'inline-block' : hideIt;
6637 upgradeMessage.style.display = showMsg;
6638 showLink.style.display = showIt === 'block' ? 'inline-block' : showIt;
6639 }
6640
6641 function getShowLinkHrefValue( link, showLink ) {
6642 let customLink = link.getAttribute( 'data-link' );
6643 if ( customLink === null || typeof customLink === 'undefined' || customLink === '' ) {
6644 customLink = showLink.getAttribute( 'data-default' );
6645 }
6646 return customLink;
6647 }
6648
6649 /* Form settings */
6650
6651 function showInputIcon( parentClass ) {
6652 if ( typeof parentClass === 'undefined' ) {
6653 parentClass = '';
6654 }
6655 maybeAddFieldSelection( parentClass );
6656 jQuery( parentClass + ' .frm_has_shortcodes:not(.frm-with-right-icon) input,' + parentClass + ' .frm_has_shortcodes:not(.frm-with-right-icon) textarea' ).wrap( '<span class="frm-with-right-icon"></span>' ).before( '<svg class="frmsvg frm-show-box"><use xlink:href="#frm_more_horiz_solid_icon"/></svg>' );
6657 }
6658
6659 /**
6660 * For reverse compatibility. Check for fields that were
6661 * using the old sidebar.
6662 */
6663 function maybeAddFieldSelection( parentClass ) {
6664 var i,
6665 missingClass = jQuery( parentClass + ' :not(.frm_has_shortcodes) .frm_not_email_message, ' + parentClass + ' :not(.frm_has_shortcodes) .frm_not_email_to, ' + parentClass + ' :not(.frm_has_shortcodes) .frm_not_email_subject' );
6666 for ( i = 0; i < missingClass.length; i++ ) {
6667 missingClass[i].parentNode.classList.add( 'frm_has_shortcodes' );
6668 }
6669 }
6670
6671 function showSuccessOpt() {
6672 /*jshint validthis:true */
6673 var c = 'success';
6674 if ( this.name === 'options[edit_action]' ) {
6675 c = 'edit';
6676 }
6677 var v = jQuery( this ).val();
6678 jQuery( '.' + c + '_action_box' ).hide();
6679 if ( v === 'redirect' ) {
6680 jQuery( '.' + c + '_action_redirect_box.' + c + '_action_box' ).fadeIn( 'slow' );
6681 } else if ( v === 'page' ) {
6682 jQuery( '.' + c + '_action_page_box.' + c + '_action_box' ).fadeIn( 'slow' );
6683 } else {
6684 jQuery( '.' + c + '_action_message_box.' + c + '_action_box' ).fadeIn( 'slow' );
6685 }
6686 }
6687
6688 function copyFormAction( event ) {
6689 if ( waitForActionToLoadBeforeCopy( event.target ) ) {
6690 return;
6691 }
6692
6693 const targetSettings = event.target.closest( '.frm_form_action_settings' );
6694 const wysiwyg = targetSettings.querySelector( '.wp-editor-area' );
6695 if ( wysiwyg ) {
6696 // Temporary remove TinyMCE before cloning to avoid TinyMCE conflicts.
6697 tinymce.EditorManager.execCommand( 'mceRemoveEditor', true, wysiwyg.id );
6698 }
6699
6700 const $action = jQuery( targetSettings ).clone();
6701 const currentID = $action.attr( 'id' ).replace( 'frm_form_action_', '' );
6702 const newID = newActionId( currentID );
6703
6704 $action.find( '.frm_action_id, .frm-btn-group' ).remove();
6705 $action.find( 'input[name$="[' + currentID + '][ID]"]' ).val( '' );
6706 $action.find( '.widget-inside' ).hide();
6707
6708 // the .html() gets original values, so they need to be set
6709 $action.find( 'input[type=text], textarea, input[type=number]' ).prop( 'defaultValue', function() {
6710 return this.value;
6711 });
6712
6713 $action.find( 'input[type=checkbox], input[type=radio]' ).prop( 'defaultChecked', function() {
6714 return this.checked;
6715 });
6716
6717 const rename = new RegExp( '\\[' + currentID + '\\]', 'g' );
6718 const reid = new RegExp( '_' + currentID + '"', 'g' );
6719 const reclass = new RegExp( '-' + currentID + '"', 'g' );
6720 const revalue = new RegExp( '"' + currentID + '"', 'g' ); // if a field id matches, this could cause trouble
6721
6722 let html = $action.html().replace( rename, '[' + newID + ']' ).replace( reid, '_' + newID + '"' );
6723 html = html.replace( reclass, '-' + newID + '"' ).replace( revalue, '"' + newID + '"' );
6724
6725 const newAction = div({
6726 id: 'frm_form_action_' + newID,
6727 className: $action.get( 0 ).className
6728 });
6729 newAction.setAttribute( 'data-actionkey', newID );
6730 newAction.innerHTML = html;
6731 newAction.querySelectorAll( '.wp-editor-wrap, .wp-editor-wrap *' ).forEach(
6732 element => {
6733 if ( 'string' === typeof element.className ) {
6734 element.className = element.className.replace( currentID, newID );
6735 }
6736 element.id = element.id.replace( currentID, newID );
6737 }
6738 );
6739 newAction.classList.remove( 'open' );
6740 document.getElementById( 'frm_notification_settings' ).appendChild( newAction );
6741
6742 if ( wysiwyg ) {
6743 // Re-initialize the original wysiwyg which was removed before cloning.
6744 frmDom.wysiwyg.init( wysiwyg );
6745 frmDom.wysiwyg.init( newAction.querySelector( '.wp-editor-area' ) );
6746 }
6747
6748 if ( newAction.classList.contains( 'frm_single_on_submit_settings' ) ) {
6749 const autocompleteInput = newAction.querySelector( 'input.frm-page-search' );
6750 if ( autocompleteInput ) {
6751 frmDom.autocomplete.initAutocomplete( 'page', newAction );
6752 }
6753 }
6754
6755 initiateMultiselect();
6756
6757 const hookName = 'frm_after_duplicate_action';
6758 wp.hooks.doAction( hookName, newAction );
6759 }
6760
6761 function waitForActionToLoadBeforeCopy( element ) {
6762 var $trigger = jQuery( element ),
6763 $original = $trigger.closest( '.frm_form_action_settings' ),
6764 $inside = $original.find( '.widget-inside' ),
6765 $top;
6766
6767 if ( $inside.find( 'p, div, table' ).length ) {
6768 return false;
6769 }
6770
6771 $top = $original.find( '.widget-top' );
6772 $top.on( 'frm-action-loaded', function() {
6773 $trigger.trigger( 'click' );
6774 $original.removeClass( 'open' );
6775 $inside.hide();
6776 });
6777 $top.trigger( 'click' );
6778 return true;
6779 }
6780
6781 function newActionId( currentID ) {
6782 var newID = parseInt( currentID, 10 ) + 11;
6783 var exists = document.getElementById( 'frm_form_action_' + newID );
6784 if ( exists !== null ) {
6785 newID++;
6786 newID = newActionId( newID );
6787 }
6788 return newID;
6789 }
6790
6791 function addFormAction() {
6792 /*jshint validthis:true */
6793 const type = jQuery( this ).data( 'actiontype' );
6794
6795 if ( isAtLimitForActionType( type ) ) {
6796 return;
6797 }
6798
6799 const actionId = getNewActionId();
6800 const formId = thisFormId;
6801
6802 const placeholderSetting = document.createElement( 'div' );
6803 placeholderSetting.classList.add( 'frm_single_' + type + '_settings' );
6804
6805 const actionsList = document.getElementById( 'frm_notification_settings' );
6806 actionsList.appendChild( placeholderSetting );
6807
6808 jQuery.ajax({
6809 type: 'POST',
6810 url: ajaxurl,
6811 data: {
6812 action: 'frm_add_form_action',
6813 type: type,
6814 list_id: actionId,
6815 form_id: formId,
6816 nonce: frmGlobal.nonce
6817 },
6818 success: handleAddFormActionSuccess
6819 });
6820
6821 function handleAddFormActionSuccess( html ) {
6822 fieldUpdated();
6823 placeholderSetting.remove();
6824
6825 closeOpenActions();
6826
6827 const newActionContainer = div();
6828 newActionContainer.innerHTML = html;
6829
6830 const widgetTop = newActionContainer.querySelector( '.widget-top' );
6831 Array.from( newActionContainer.children ).forEach( child => actionsList.appendChild( child ) );
6832
6833 jQuery( '.frm_form_action_settings' ).fadeIn( 'slow' );
6834
6835 const newAction = document.getElementById( 'frm_form_action_' + actionId );
6836
6837 newAction.classList.add( 'open' );
6838 document.getElementById( 'post-body-content' ).scroll({
6839 top: newAction.offsetTop + 10,
6840 left: 0,
6841 behavior: 'smooth'
6842 });
6843
6844 // Check if icon should be active
6845 checkActiveAction( type );
6846 showInputIcon( '#frm_form_action_' + actionId );
6847
6848 initiateMultiselect();
6849 frmDom.autocomplete.initAutocomplete( 'page', newAction );
6850
6851 if ( widgetTop ) {
6852 jQuery( widgetTop ).trigger( 'frm-action-loaded' );
6853 }
6854
6855 /**
6856 * Fires after added a new form action.
6857 *
6858 * @since 5.5.4
6859 *
6860 * @param {HTMLElement} formAction Form action element.
6861 */
6862 frmAdminBuild.hooks.doAction( 'frm_added_form_action', newAction );
6863 }
6864 }
6865
6866 function closeOpenActions() {
6867 document.querySelectorAll( '.frm_form_action_settings.open' ).forEach(
6868 setting => setting.classList.remove( 'open' )
6869 );
6870 }
6871
6872 function toggleActionGroups() {
6873 /*jshint validthis:true */
6874 var actions = document.getElementById( 'frm_email_addon_menu' ).classList,
6875 search = document.getElementById( 'actions-search-input' );
6876
6877 if ( actions.contains( 'frm-all-actions' ) ) {
6878 actions.remove( 'frm-all-actions' );
6879 actions.add( 'frm-limited-actions' );
6880 } else {
6881 actions.add( 'frm-all-actions' );
6882 actions.remove( 'frm-limited-actions' );
6883 }
6884
6885 // Reset search.
6886 search.value = '';
6887 triggerEvent( search, 'input' );
6888 }
6889
6890 function getNewActionId() {
6891 var actionSettings = document.querySelectorAll( '.frm_form_action_settings' ),
6892 len = getNewRowId( actionSettings, 'frm_form_action_' );
6893 if ( typeof document.getElementById( 'frm_form_action_' + len ) !== 'undefined' ) {
6894 len = len + 100;
6895 }
6896 if ( lastNewActionIdReturned >= len ) {
6897 len = lastNewActionIdReturned + 1;
6898 }
6899 lastNewActionIdReturned = len;
6900 return len;
6901 }
6902
6903 function clickAction( obj ) {
6904 var $thisobj = jQuery( obj );
6905
6906 if ( obj.className.indexOf( 'selected' ) !== -1 ) {
6907 return;
6908 }
6909 if ( obj.className.indexOf( 'edit_field_type_end_divider' ) !== -1 && $thisobj.closest( '.edit_field_type_divider' ).hasClass( 'no_repeat_section' ) ) {
6910 return;
6911 }
6912
6913 deselectFields();
6914 $thisobj.addClass( 'selected' );
6915 showFieldOptions( obj );
6916 }
6917
6918 /**
6919 * When a field is selected, show the field settings in the sidebar.
6920 */
6921 function showFieldOptions( obj ) {
6922 var i, singleField,
6923 fieldId = obj.getAttribute( 'data-fid' ),
6924 fieldType = obj.getAttribute( 'data-type' ),
6925 allFieldSettings = document.querySelectorAll( '.frm-single-settings:not(.frm_hidden)' );
6926
6927 for ( i = 0; i < allFieldSettings.length; i++ ) {
6928 allFieldSettings[i].classList.add( 'frm_hidden' );
6929 }
6930
6931 singleField = document.getElementById( 'frm-single-settings-' + fieldId );
6932 moveFieldSettings( singleField );
6933
6934 if ( fieldType && 'quantity' === fieldType ) {
6935 popProductFields( jQuery( singleField ).find( '.frmjs_prod_field_opt' )[0]);
6936 }
6937
6938 singleField.classList.remove( 'frm_hidden' );
6939 document.getElementById( 'frm-options-panel-tab' ).click();
6940
6941 const editor = singleField.querySelector( '.wp-editor-area' );
6942 if ( editor ) {
6943 frmDom.wysiwyg.init(
6944 editor,
6945 { setupCallback: setupTinyMceEventHandlers }
6946 );
6947 }
6948 }
6949
6950 function setupTinyMceEventHandlers( editor ) {
6951 editor.on( 'Change', function() {
6952 handleTinyMceChange( editor );
6953 });
6954 }
6955
6956 function handleTinyMceChange( editor ) {
6957 if ( ! isTinyMceActive() || tinyMCE.activeEditor.isHidden() ) {
6958 return;
6959 }
6960
6961 editor.targetElm.value = editor.getContent();
6962 jQuery( editor.targetElm ).trigger( 'change' );
6963 }
6964
6965 function isTinyMceActive() {
6966 var activeSettings, wrapper;
6967
6968 activeSettings = document.querySelector( '.frm-single-settings:not(.frm_hidden)' );
6969 if ( ! activeSettings ) {
6970 return false;
6971 }
6972
6973 wrapper = activeSettings.querySelector( '.wp-editor-wrap' );
6974 return null !== wrapper && wrapper.classList.contains( 'tmce-active' );
6975 }
6976
6977 /**
6978 * Move the settings to the sidebar the first time they are changed or selected.
6979 * Keep the end marker at the end of the form.
6980 */
6981 function moveFieldSettings( singleField ) {
6982 if ( singleField === null ) {
6983 // The field may have not been loaded yet via ajax.
6984 return;
6985 }
6986
6987 var classes = singleField.parentElement.classList;
6988 if ( classes.contains( 'frm_field_box' ) || classes.contains( 'divider_section_only' ) ) {
6989 var endMarker = document.getElementById( 'frm-end-form-marker' );
6990 builderForm.insertBefore( singleField, endMarker );
6991 }
6992 }
6993
6994 function showEmailRow() {
6995 /*jshint validthis:true */
6996 var actionKey = jQuery( this ).closest( '.frm_form_action_settings' ).data( 'actionkey' );
6997 var rowType = this.getAttribute( 'data-emailrow' );
6998
6999 jQuery( '#frm_form_action_' + actionKey + ' .frm_' + rowType + '_row' ).fadeIn( 'slow' );
7000 jQuery( this ).fadeOut( 'slow' );
7001 }
7002
7003 function hideEmailRow() {
7004 /*jshint validthis:true */
7005 var actionBox = jQuery( this ).closest( '.frm_form_action_settings' ),
7006 rowType = this.getAttribute( 'data-emailrow' ),
7007 emailRowSelector = '.frm_' + rowType + '_row',
7008 emailButtonSelector = '.frm_' + rowType + '_button';
7009
7010 jQuery( actionBox ).find( emailButtonSelector ).fadeIn( 'slow' );
7011 jQuery( actionBox ).find( emailRowSelector ).fadeOut( 'slow', function() {
7012 jQuery( actionBox ).find( emailRowSelector + ' input' ).val( '' );
7013 });
7014 }
7015
7016 function showEmailWarning() {
7017 /*jshint validthis:true */
7018 var actionBox = jQuery( this ).closest( '.frm_form_action_settings' ),
7019 emailRowSelector = '.frm_from_to_match_row',
7020 fromVal = actionBox.find( 'input[name$="[post_content][from]"]' ).val(),
7021 toVal = actionBox.find( 'input[name$="[post_content][email_to]"]' ).val();
7022
7023 if ( fromVal === toVal ) {
7024 jQuery( actionBox ).find( emailRowSelector ).fadeIn( 'slow' );
7025 } else {
7026 jQuery( actionBox ).find( emailRowSelector ).fadeOut( 'slow' );
7027 }
7028 }
7029
7030 function checkActiveAction( type ) {
7031 const actionTriggers = document.querySelectorAll( '.frm_' + type + '_action' );
7032
7033 if ( isAtLimitForActionType( type ) ) {
7034 const addAlreadyUsedClass = getLimitForActionType( type ) > 0;
7035 markActionTriggersInactive( actionTriggers, addAlreadyUsedClass );
7036 return;
7037 }
7038
7039 markActionTriggersActive( actionTriggers );
7040 }
7041
7042 function markActionTriggersActive( triggers ) {
7043 triggers.forEach(
7044 trigger => {
7045 if ( trigger.querySelector( '.frm_show_upgrade' ) ) {
7046 // Prevent disabled action becoming active.
7047 return;
7048 }
7049
7050 trigger.classList.remove( 'frm_inactive_action', 'frm_already_used' );
7051 trigger.classList.add( 'frm_active_action' );
7052 }
7053 );
7054 }
7055
7056 function markActionTriggersInactive( triggers, addAlreadyUsedClass ) {
7057 triggers.forEach(
7058 trigger => {
7059 trigger.classList.remove( 'frm_active_action' );
7060 trigger.classList.add( 'frm_inactive_action' );
7061 if ( addAlreadyUsedClass ) {
7062 trigger.classList.add( 'frm_already_used' );
7063 }
7064 }
7065 );
7066 }
7067
7068 function isAtLimitForActionType( type ) {
7069 let atLimit = getNumberOfActionsForType( type ) >= getLimitForActionType( type );
7070
7071 const hookName = 'frm_action_at_limit';
7072 const hookArgs = { type };
7073 atLimit = wp.hooks.applyFilters( hookName, atLimit, hookArgs );
7074
7075 return atLimit;
7076 }
7077
7078 function getLimitForActionType( type ) {
7079 return parseInt( jQuery( '.frm_' + type + '_action' ).data( 'limit' ), 10 );
7080 }
7081
7082 function getNumberOfActionsForType( type ) {
7083 return jQuery( '.frm_single_' + type + '_settings' ).length;
7084 }
7085
7086 function onlyOneActionMessage() {
7087 infoModal( frm_admin_js.only_one_action ); // eslint-disable-line camelcase
7088 }
7089
7090 function addFormLogicRow() {
7091 /*jshint validthis:true */
7092 var id = jQuery( this ).data( 'emailkey' ),
7093 type = jQuery( this ).closest( '.frm_form_action_settings' ).find( '.frm_action_name' ).val(),
7094 formId = document.getElementById( 'form_id' ).value,
7095 logicRows = document.getElementById( 'frm_form_action_' + id ).querySelectorAll( '.frm_logic_row' );
7096 jQuery.ajax({
7097 type: 'POST', url: ajaxurl,
7098 data: {
7099 action: 'frm_add_form_logic_row',
7100 email_id: id,
7101 form_id: formId,
7102 meta_name: getNewRowId( logicRows, 'frm_logic_' + id + '_' ),
7103 type: type,
7104 nonce: frmGlobal.nonce
7105 },
7106 success: function( html ) {
7107 jQuery( document.getElementById( 'logic_link_' + id ) ).fadeOut( 'slow', function() {
7108 var $logicRow = jQuery( document.getElementById( 'frm_logic_row_' + id ) );
7109 $logicRow.append( html );
7110 $logicRow.parent( '.frm_logic_rows' ).fadeIn( 'slow' );
7111 });
7112 }
7113 });
7114 return false;
7115 }
7116
7117 function toggleSubmitLogic() {
7118 /*jshint validthis:true */
7119 if ( this.checked ) {
7120 addSubmitLogic();
7121 } else {
7122 jQuery( '.frm_logic_row_submit' ).remove();
7123 document.getElementById( 'frm_submit_logic_rows' ).style.display = 'none';
7124 }
7125 }
7126
7127 /**
7128 * Adds submit button Conditional Logic row and reveals submit button Conditional Logic
7129 *
7130 * @returns {boolean}
7131 */
7132 function addSubmitLogic() {
7133 /*jshint validthis:true */
7134 var formId = thisFormId,
7135 logicRows = document.getElementById( 'frm_submit_logic_row' ).querySelectorAll( '.frm_logic_row' );
7136 jQuery.ajax({
7137 type: 'POST',
7138 url: ajaxurl,
7139 data: {
7140 action: 'frm_add_submit_logic_row',
7141 form_id: formId,
7142 meta_name: getNewRowId( logicRows, 'frm_logic_submit_' ),
7143 nonce: frmGlobal.nonce
7144 },
7145 success: function( html ) {
7146 var $logicRow = jQuery( document.getElementById( 'frm_submit_logic_row' ) );
7147 $logicRow.append( html );
7148 $logicRow.parent( '.frm_submit_logic_rows' ).fadeIn( 'slow' );
7149 }
7150 });
7151 return false;
7152 }
7153
7154 /**
7155 * When the user selects a field for a submit condition, update corresponding options field accordingly.
7156 */
7157 function addSubmitLogicOpts() {
7158 var fieldOpt = jQuery( this );
7159 var fieldId = fieldOpt.find( ':selected' ).val();
7160
7161 if ( fieldId ) {
7162 var row = fieldOpt.data( 'row' );
7163 frmGetFieldValues( fieldId, 'submit', row, '', 'options[submit_conditions][hide_opt][]' );
7164 }
7165 }
7166
7167 function formatEmailSetting() {
7168 /*jshint validthis:true */
7169 /*var val = jQuery( this ).val();
7170 var email = val.match( /(\s[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi );
7171 if(email !== null && email.length) {
7172 //has email
7173 //TODO: add < > if they aren't there
7174 }*/
7175 }
7176
7177 function checkDupPost() {
7178 /*jshint validthis:true */
7179 var postField = jQuery( 'select.frm_single_post_field' );
7180 postField.css( 'border-color', '' );
7181 var $t = this;
7182 var v = jQuery( $t ).val();
7183 if ( v === '' || v === 'checkbox' ) {
7184 return false;
7185 }
7186 postField.each( function() {
7187 if ( jQuery( this ).val() === v && this.name !== $t.name ) {
7188 this.style.borderColor = 'red';
7189 jQuery( $t ).val( '' );
7190 infoModal( frm_admin_js.field_already_used ); // eslint-disable-line camelcase
7191 return false;
7192 }
7193 });
7194 }
7195
7196 function togglePostContent() {
7197 /*jshint validthis:true */
7198 var v = jQuery( this ).val();
7199 if ( '' === v ) {
7200 jQuery( '.frm_post_content_opt, select.frm_dyncontent_opt' ).hide().val( '' );
7201 jQuery( '.frm_dyncontent_opt' ).hide();
7202 } else if ( 'post_content' === v ) {
7203 jQuery( '.frm_post_content_opt' ).show();
7204 jQuery( '.frm_dyncontent_opt' ).hide();
7205 jQuery( 'select.frm_dyncontent_opt' ).val( '' );
7206 } else {
7207 jQuery( '.frm_post_content_opt' ).hide().val( '' );
7208 jQuery( 'select.frm_dyncontent_opt, .frm_form_field.frm_dyncontent_opt' ).show();
7209 }
7210 }
7211
7212 function fillDyncontent() {
7213 /*jshint validthis:true */
7214 var v = jQuery( this ).val();
7215 var $dyn = jQuery( document.getElementById( 'frm_dyncontent' ) );
7216 if ( '' === v || 'new' === v ) {
7217 $dyn.val( '' );
7218 jQuery( '.frm_dyncontent_opt' ).show();
7219 } else {
7220 jQuery.ajax({
7221 type: 'POST', url: ajaxurl,
7222 data: {action: 'frm_display_get_content', id: v, nonce: frmGlobal.nonce},
7223 success: function( val ) {
7224 $dyn.val( val );
7225 jQuery( '.frm_dyncontent_opt' ).show();
7226 }
7227 });
7228 }
7229 }
7230
7231 function switchPostType() {
7232 /*jshint validthis:true */
7233 // update all rows of categories/taxonomies
7234 var curSelect, newSelect,
7235 catRows = document.getElementById( 'frm_posttax_rows' ).childNodes,
7236 postParentField = document.querySelector( '.frm_post_parent_field' ),
7237 postMenuOrderField = document.querySelector( '.frm_post_menu_order_field' ),
7238 postType = this.value;
7239
7240 // Get new category/taxonomy options
7241 jQuery.ajax({
7242 type: 'POST',
7243 url: ajaxurl,
7244 data: {
7245 action: 'frm_replace_posttax_options',
7246 post_type: postType,
7247 nonce: frmGlobal.nonce
7248 },
7249 success: function( html ) {
7250
7251 // Loop through each category row, and replace the first dropdown
7252 for ( i = 0; i < catRows.length; i++ ) {
7253 // Check if current element is a div
7254 if ( catRows[i].tagName !== 'DIV' ) {
7255 continue;
7256 }
7257
7258 // Get current category select
7259 curSelect = catRows[i].getElementsByTagName( 'select' )[0];
7260
7261 // Set up new select
7262 newSelect = document.createElement( 'select' );
7263 newSelect.innerHTML = html;
7264 newSelect.className = curSelect.className;
7265 newSelect.name = curSelect.name;
7266
7267 // Replace the old select with the new select
7268 catRows[i].replaceChild( newSelect, curSelect );
7269 }
7270 }
7271 });
7272
7273 // Get new post parent option.
7274 if ( postParentField ) {
7275 getActionOption(
7276 postParentField,
7277 postType,
7278 'frm_get_post_parent_option',
7279 function( response, optName ) {
7280 // The replaced string is declared in FrmProFormActionController::ajax_get_post_menu_order_option() in the pro version.
7281 postParentField.querySelector( '.frm_post_parent_opt_wrapper' ).innerHTML = response.replaceAll( 'REPLACETHISNAME', optName );
7282 frmDom.autocomplete.initAutocomplete( 'page', postParentField );
7283 }
7284 );
7285 }
7286
7287 if ( postMenuOrderField ) {
7288 getActionOption( postMenuOrderField, postType, 'frm_should_use_post_menu_order_option' );
7289 }
7290 }
7291
7292 function getActionOption( field, postType, action, successHandler ) {
7293 const opt = field.querySelector( '.frm_autocomplete_value_input' ) || field.querySelector( 'select' ),
7294 optName = opt.getAttribute( 'name' );
7295
7296 jQuery.ajax({
7297 url: ajaxurl,
7298 method: 'POST',
7299 data: {
7300 action: action,
7301 post_type: postType,
7302 _wpnonce: frmGlobal.nonce
7303 },
7304 success: response => {
7305 if ( 'string' !== typeof response ) {
7306 console.error( response );
7307 return;
7308 }
7309
7310 if ( '0' === response ) {
7311 // This post type does not support this field.
7312 field.classList.add( 'frm_hidden' );
7313 field.value = '';
7314 return;
7315 }
7316
7317 field.classList.remove( 'frm_hidden' );
7318
7319 if ( 'function' === typeof successHandler ) {
7320 successHandler( response, optName );
7321 }
7322 },
7323 error: response => console.error( response )
7324 });
7325 }
7326
7327 function addPosttaxRow() {
7328 /*jshint validthis:true */
7329 addPostRow( 'tax', this );
7330 }
7331
7332 function addPostmetaRow() {
7333 /*jshint validthis:true */
7334 addPostRow( 'meta', this );
7335 }
7336
7337 function addPostRow( type, button ) {
7338 var name,
7339 id = jQuery( 'input[name="id"]' ).val(),
7340 settings = jQuery( button ).closest( '.frm_form_action_settings' ),
7341 key = settings.data( 'actionkey' ),
7342 postType = settings.find( '.frm_post_type' ).val(),
7343 metaName = 0,
7344 postTypeRows = document.querySelectorAll( '.frm_post' + type + '_row' );
7345
7346 if ( postTypeRows.length ) {
7347 name = postTypeRows[ postTypeRows.length - 1 ].id.replace( 'frm_post' + type + '_', '' );
7348 if ( isNumeric( name ) ) {
7349 metaName = 1 + parseInt( name, 10 );
7350 } else {
7351 metaName = 1;
7352 }
7353 }
7354
7355 jQuery.ajax({
7356 type: 'POST', url: ajaxurl,
7357 data: {
7358 action: 'frm_add_post' + type + '_row',
7359 form_id: id,
7360 meta_name: metaName,
7361 tax_key: metaName,
7362 post_type: postType,
7363 action_key: key,
7364 nonce: frmGlobal.nonce
7365 },
7366 success: function( html ) {
7367 var cfOpts, optIndex;
7368 jQuery( document.getElementById( 'frm_post' + type + '_rows' ) ).append( html );
7369 jQuery( '.frm_add_post' + type + '_row.button' ).hide();
7370
7371 if ( type === 'meta' ) {
7372 jQuery( '.frm_name_value' ).show();
7373 cfOpts = document.querySelectorAll( '.frm_toggle_cf_opts' );
7374 for ( optIndex = 0; optIndex < cfOpts.length - 1; ++optIndex ) {
7375 cfOpts[ optIndex ].style.display = 'none';
7376 }
7377 } else if ( type === 'tax' ) {
7378 jQuery( '.frm_posttax_labels' ).show();
7379 }
7380 }
7381 });
7382 }
7383
7384 function isNumeric( value ) {
7385 return ! isNaN( parseFloat( value ) ) && isFinite( value );
7386 }
7387
7388 function getMetaValue( id, metaName ) {
7389 var newMeta = metaName;
7390 if ( jQuery( document.getElementById( id + metaName ) ).length > 0 ) {
7391 newMeta = getMetaValue( id, metaName + 1 );
7392 }
7393 return newMeta;
7394 }
7395
7396 function changePosttaxRow() {
7397 /*jshint validthis:true */
7398 if ( ! jQuery( this ).closest( '.frm_posttax_row' ).find( '.frm_posttax_opt_list' ).length ) {
7399 return;
7400 }
7401
7402 jQuery( this ).closest( '.frm_posttax_row' ).find( '.frm_posttax_opt_list' ).html( '<div class="spinner frm_spinner" style="display:block"></div>' );
7403
7404 var postType = jQuery( this ).closest( '.frm_form_action_settings' ).find( 'select[name$="[post_content][post_type]"]' ).val(),
7405 actionKey = jQuery( this ).closest( '.frm_form_action_settings' ).data( 'actionkey' ),
7406 taxKey = jQuery( this ).closest( '.frm_posttax_row' ).attr( 'id' ).replace( 'frm_posttax_', '' ),
7407 metaName = jQuery( this ).val(),
7408 showExclude = jQuery( document.getElementById( taxKey + '_show_exclude' ) ).is( ':checked' ) ? 1 : 0,
7409 fieldId = jQuery( 'select[name$="[post_category][' + taxKey + '][field_id]"]' ).val(),
7410 id = jQuery( 'input[name="id"]' ).val();
7411
7412 jQuery.ajax({
7413 type: 'POST',
7414 url: ajaxurl,
7415 data: {
7416 action: 'frm_add_posttax_row',
7417 form_id: id,
7418 post_type: postType,
7419 tax_key: taxKey,
7420 action_key: actionKey,
7421 meta_name: metaName,
7422 field_id: fieldId,
7423 show_exclude: showExclude,
7424 nonce: frmGlobal.nonce
7425 },
7426 success: function( html ) {
7427 var $tax = jQuery( document.getElementById( 'frm_posttax_' + taxKey ) );
7428 $tax.replaceWith( html );
7429 }
7430 });
7431 }
7432
7433 function toggleCfOpts() {
7434 /*jshint validthis:true */
7435 var row = jQuery( this ).closest( '.frm_postmeta_row' );
7436 var cancel = row.find( '.frm_cancelnew' );
7437 var select = row.find( '.frm_enternew' );
7438 if ( row.find( 'select.frm_cancelnew' ).is( ':visible' ) ) {
7439 cancel.hide();
7440 select.show();
7441 } else {
7442 cancel.show();
7443 select.hide();
7444 }
7445
7446 row.find( 'input.frm_enternew, select.frm_cancelnew' ).val( '' );
7447 return false;
7448 }
7449
7450 function toggleFormOpts() {
7451 /*jshint validthis:true */
7452 var changedOpt = jQuery( this );
7453 var val = changedOpt.val();
7454 if ( changedOpt.attr( 'type' ) === 'checkbox' ) {
7455 if ( this.checked === false ) {
7456 val = '';
7457 }
7458 }
7459
7460 var toggleClass = changedOpt.data( 'toggleclass' );
7461 if ( val === '' ) {
7462 jQuery( '.' + toggleClass ).hide();
7463 } else {
7464 jQuery( '.' + toggleClass ).show();
7465 jQuery( '.hide_' + toggleClass + '_' + val ).hide();
7466 }
7467 }
7468
7469 function submitSettings() {
7470 if ( showNameYourFormModal() ) {
7471 return;
7472 }
7473
7474 /*jshint validthis:true */
7475 preFormSave( this );
7476 triggerSubmit( document.querySelector( '.frm_form_settings' ) );
7477 }
7478
7479 /* View Functions */
7480 function showCount() {
7481 /*jshint validthis:true */
7482 var value = jQuery( this ).val();
7483
7484 var $cont = document.getElementById( 'date_select_container' );
7485 var tab = document.getElementById( 'frm_listing_tab' );
7486 var label = tab.getAttribute( 'data-label' );
7487 if ( value === 'calendar' ) {
7488 jQuery( '.hide_dyncontent, .hide_single_content' ).removeClass( 'frm_hidden' );
7489 jQuery( '.limit_container' ).addClass( 'frm_hidden' );
7490 $cont.style.display = 'block';
7491 } else if ( value === 'dynamic' ) {
7492 jQuery( '.hide_dyncontent, .limit_container, .hide_single_content' ).removeClass( 'frm_hidden' );
7493 } else if ( value === 'one' ) {
7494 label = tab.getAttribute( 'data-one' );
7495 jQuery( '.hide_dyncontent, .limit_container, .hide_single_content' ).addClass( 'frm_hidden' );
7496 } else {
7497 jQuery( '.hide_dyncontent' ).addClass( 'frm_hidden' );
7498 jQuery( '.limit_container, .hide_single_content' ).removeClass( 'frm_hidden' );
7499 }
7500
7501 if ( value !== 'calendar' ) {
7502 $cont.style.display = 'none';
7503 }
7504 tab.innerHTML = label;
7505 }
7506
7507 function displayFormSelected() {
7508 /*jshint validthis:true */
7509 var formId = jQuery( this ).val();
7510 thisFormId = formId; // set the global form id
7511 if ( formId === '' ) {
7512 return;
7513 }
7514
7515 jQuery.ajax({
7516 type: 'POST',
7517 url: ajaxurl,
7518 data: {
7519 action: 'frm_get_cd_tags_box',
7520 form_id: formId,
7521 nonce: frmGlobal.nonce
7522 },
7523 success: function( html ) {
7524 jQuery( '#frm_adv_info .categorydiv' ).html( html );
7525 }
7526 });
7527
7528 jQuery.ajax({
7529 type: 'POST',
7530 url: ajaxurl,
7531 data: {
7532 action: 'frm_get_date_field_select',
7533 form_id: formId,
7534 nonce: frmGlobal.nonce
7535 },
7536 success: function( html ) {
7537 jQuery( document.getElementById( 'date_select_container' ) ).html( html );
7538 }
7539 });
7540 }
7541
7542 function clickTabsAfterAjax() {
7543 /*jshint validthis:true */
7544 var t = jQuery( this ).attr( 'href' );
7545 jQuery( this ).parent().addClass( 'tabs' ).siblings( 'li' ).removeClass( 'tabs' );
7546 jQuery( t ).show().siblings( '.tabs-panel' ).hide();
7547 return false;
7548 }
7549
7550 function clickContentTab() {
7551 /*jshint validthis:true */
7552 link = jQuery( this );
7553 var t = link.attr( 'href' );
7554 if ( typeof t === 'undefined' ) {
7555 return false;
7556 }
7557
7558 var c = t.replace( '#', '.' );
7559 link.closest( '.nav-tab-wrapper' ).find( 'a' ).removeClass( 'nav-tab-active' );
7560 link.addClass( 'nav-tab-active' );
7561 jQuery( '.nav-menu-content' ).not( t ).not( c ).hide();
7562 jQuery( t + ',' + c ).show();
7563
7564 return false;
7565 }
7566
7567 function addOrderRow() {
7568 var logicRows = document.getElementById( 'frm_order_options' ).querySelectorAll( '.frm_logic_rows div' );
7569 jQuery.ajax({
7570 type: 'POST',
7571 url: ajaxurl,
7572 data: {
7573 action: 'frm_add_order_row',
7574 form_id: thisFormId,
7575 order_key: getNewRowId( logicRows, 'frm_order_field_', 1 ),
7576 nonce: frmGlobal.nonce
7577 },
7578 success: function( html ) {
7579 jQuery( '#frm_order_options .frm_logic_rows' ).append( html ).show().prev( '.frm_add_order_row' ).hide();
7580 }
7581 });
7582 }
7583
7584 function addWhereRow() {
7585 var rowDivs = document.getElementById( 'frm_where_options' ).querySelectorAll( '.frm_logic_rows div' );
7586 jQuery.ajax({
7587 type: 'POST',
7588 url: ajaxurl,
7589 data: {
7590 action: 'frm_add_where_row',
7591 form_id: thisFormId,
7592 where_key: getNewRowId( rowDivs, 'frm_where_field_', 1 ),
7593 nonce: frmGlobal.nonce
7594 },
7595 success: function( html ) {
7596 jQuery( '#frm_where_options .frm_logic_rows' ).append( html ).show().prev( '.frm_add_where_row' ).hide();
7597 }
7598 });
7599 }
7600
7601 function insertWhereOptions() {
7602 /*jshint validthis:true */
7603 var value = this.value,
7604 whereKey = jQuery( this ).closest( '.frm_where_row' ).attr( 'id' ).replace( 'frm_where_field_', '' );
7605
7606 jQuery.ajax({
7607 type: 'POST',
7608 url: ajaxurl,
7609 data: {
7610 action: 'frm_add_where_options',
7611 where_key: whereKey,
7612 field_id: value,
7613 nonce: frmGlobal.nonce
7614 },
7615 success: function( html ) {
7616 jQuery( document.getElementById( 'where_field_options_' + whereKey ) ).html( html );
7617 }
7618 });
7619 }
7620
7621 function hideWhereOptions() {
7622 /*jshint validthis:true */
7623 var value = this.value,
7624 whereKey = jQuery( this ).closest( '.frm_where_row' ).attr( 'id' ).replace( 'frm_where_field_', '' );
7625
7626 if ( value === 'group_by' || value === 'group_by_newest' ) {
7627 document.getElementById( 'where_field_options_' + whereKey ).style.display = 'none';
7628 } else {
7629 document.getElementById( 'where_field_options_' + whereKey ).style.display = 'inline-block';
7630 }
7631 }
7632
7633 function setDefaultPostStatus() {
7634 var urlQuery = window.location.search.substring( 1 );
7635 if ( urlQuery.indexOf( 'action=edit' ) === -1 ) {
7636 document.getElementById( 'post-visibility-display' ).textContent = frm_admin_js.private_label; // eslint-disable-line camelcase
7637 document.getElementById( 'hidden-post-visibility' ).value = 'private';
7638 document.getElementById( 'visibility-radio-private' ).checked = true;
7639 }
7640 }
7641
7642 /* Customization Panel */
7643 function insertCode( e ) {
7644 /*jshint validthis:true */
7645 e.preventDefault();
7646 insertFieldCode( jQuery( this ), this.getAttribute( 'data-code' ) );
7647 return false;
7648 }
7649
7650 function insertFieldCode( element, variable ) {
7651 var rich = false,
7652 elementId = element;
7653 if ( typeof element === 'object' ) {
7654 if ( element.hasClass( 'frm_noallow' ) ) {
7655 return;
7656 }
7657
7658 elementId = jQuery( element ).closest( '[data-fills]' ).attr( 'data-fills' );
7659 if ( typeof elementId === 'undefined' ) {
7660 elementId = element.closest( 'div' ).attr( 'class' );
7661 if ( typeof elementId !== 'undefined' ) {
7662 elementId = elementId.split( ' ' )[1];
7663 }
7664 }
7665 }
7666
7667 if ( typeof elementId === 'undefined' ) {
7668 var active = document.activeElement;
7669 if ( active.type === 'search' ) {
7670 // If the search field has focus, find the correct field.
7671 elementId = active.id.replace( '-search-input', '' );
7672 if ( elementId.match( /\d/gi ) === null ) {
7673 active = jQuery( '.frm-single-settings:visible .' + elementId );
7674 elementId = active.attr( 'id' );
7675 }
7676 } else {
7677 elementId = active.id;
7678 }
7679 }
7680
7681 if ( elementId ) {
7682 rich = jQuery( '#wp-' + elementId + '-wrap.wp-editor-wrap' ).length > 0;
7683 }
7684
7685 var contentBox = jQuery( document.getElementById( elementId ) );
7686 if ( typeof element.attr( 'data-shortcode' ) === 'undefined' && ( ! contentBox.length || typeof contentBox.attr( 'data-shortcode' ) === 'undefined' ) ) {
7687 // this helps to exclude those that don't want shortcode-like inserted content e.g. frm-pro's summary field
7688 var doShortcode = element.parents( 'ul.frm_code_list' ).attr( 'data-shortcode' );
7689 if ( doShortcode === 'undefined' || doShortcode !== 'no' ) {
7690 variable = '[' + variable + ']';
7691 }
7692 }
7693
7694 if ( rich ) {
7695 wpActiveEditor = elementId;
7696 }
7697
7698 if ( ! contentBox.length ) {
7699 return false;
7700 }
7701
7702 if ( variable === '[default-html]' || variable === '[default-plain]' ) {
7703 var p = 0;
7704 if ( variable === '[default-plain]' ) {
7705 p = 1;
7706 }
7707 jQuery.ajax({
7708 type: 'POST', url: ajaxurl,
7709 data: {
7710 action: 'frm_get_default_html',
7711 form_id: jQuery( 'input[name="id"]' ).val(),
7712 plain_text: p,
7713 nonce: frmGlobal.nonce
7714 },
7715 elementId: elementId,
7716 success: function( msg ) {
7717 if ( rich ) {
7718 let p = document.createElement( 'p' );
7719 p.innerText = msg;
7720 send_to_editor( p.innerHTML );
7721 } else {
7722 insertContent( contentBox, msg );
7723 }
7724 }
7725 });
7726 } else {
7727 variable = maybeAddSanitizeUrlToShortcodeVariable( variable, element, contentBox );
7728 if ( rich ) {
7729 send_to_editor( variable );
7730 } else {
7731 insertContent( contentBox, variable );
7732 }
7733 }
7734 return false;
7735 }
7736
7737 function maybeAddSanitizeUrlToShortcodeVariable( variable, element, contentBox ) {
7738 if ( 'object' !== typeof element || ! ( element instanceof jQuery ) || 0 !== contentBox[0].id.indexOf( 'success_url_' ) ) {
7739 return variable;
7740 }
7741
7742 element = element[0];
7743 if ( ! element.closest( '#frm-insert-fields-box' ) ) {
7744 // Only add sanitize_url=1 to field shortcodes.
7745 return variable;
7746 }
7747
7748 if ( ! element.parentNode.classList.contains( 'frm_insert_url' ) ) {
7749 variable = variable.replace( ']', ' sanitize_url=1]' );
7750 }
7751
7752 return variable;
7753 }
7754
7755 function insertContent( contentBox, variable ) {
7756 if ( document.selection ) {
7757 contentBox[0].focus();
7758 document.selection.createRange().text = variable;
7759 } else {
7760 obj = contentBox[0];
7761 var e = obj.selectionEnd;
7762
7763 variable = maybeFormatInsertedContent( contentBox, variable, obj.selectionStart, e );
7764
7765 obj.value = obj.value.substr( 0, obj.selectionStart ) + variable + obj.value.substr( obj.selectionEnd, obj.value.length );
7766 var s = e + variable.length;
7767 obj.focus();
7768 obj.setSelectionRange( s, s );
7769 }
7770 triggerChange( contentBox );
7771 }
7772
7773 function maybeFormatInsertedContent( input, textToInsert, selectionStart, selectionEnd ) {
7774 var separator = input.data( 'sep' );
7775 if ( undefined === separator ) {
7776 return textToInsert;
7777 }
7778
7779 var value = input.val();
7780
7781 if ( ! value.trim().length ) {
7782 return textToInsert;
7783 }
7784
7785 var startPattern = new RegExp( separator + '\\s*$' );
7786 var endPattern = new RegExp( '^\\s*' + separator );
7787
7788 if ( value.substr( 0, selectionStart ).trim().length && false === startPattern.test( value.substr( 0, selectionStart ) ) ) {
7789 textToInsert = separator + textToInsert;
7790 }
7791
7792 if ( value.substr( selectionEnd, value.length ).trim().length && false === endPattern.test( value.substr( selectionEnd, value.length ) ) ) {
7793 textToInsert += separator;
7794 }
7795
7796 return textToInsert;
7797 }
7798
7799 function resetLogicBuilder() {
7800 /*jshint validthis:true */
7801 var id = document.getElementById( 'frm-id-condition' ),
7802 key = document.getElementById( 'frm-key-condition' );
7803
7804 if ( this.checked ) {
7805 id.classList.remove( 'frm_hidden' );
7806 key.classList.add( 'frm_hidden' );
7807 triggerEvent( key, 'change' );
7808 } else {
7809 id.classList.add( 'frm_hidden' );
7810 key.classList.remove( 'frm_hidden' );
7811 triggerEvent( id, 'change' );
7812 }
7813 }
7814
7815 function setLogicExample() {
7816 var field, code,
7817 idKey = document.getElementById( 'frm-id-key-condition' ).checked ? 'frm-id-condition' : 'frm-key-condition',
7818 is = document.getElementById( 'frm-is-condition' ).value,
7819 text = document.getElementById( 'frm-text-condition' ).value,
7820 result = document.getElementById( 'frm-insert-condition' );
7821
7822 idKey = document.getElementById( idKey );
7823 field = idKey.options[idKey.selectedIndex].value;
7824 code = 'if ' + field + ' ' + is + '="' + text + '"]';
7825 result.setAttribute( 'data-code', code + frm_admin_js.conditional_text + '[/if ' + field ); // eslint-disable-line camelcase
7826 result.innerHTML = '[' + code + '[/if ' + field + ']';
7827 }
7828
7829 function showBuilderModal() {
7830 /*jshint validthis:true */
7831 var moreIcon = getIconForInput( this );
7832 showInlineModal( moreIcon, this );
7833 }
7834
7835 function maybeShowModal( input ) {
7836 var moreIcon;
7837 if ( input.parentNode.parentNode.classList.contains( 'frm_has_shortcodes' ) ) {
7838 hideShortcodes();
7839 moreIcon = getIconForInput( input );
7840 if ( moreIcon.tagName === 'use' ) {
7841 moreIcon = moreIcon.firstElementChild;
7842 if ( moreIcon.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ).indexOf( 'frm_close_icon' ) === -1 ) {
7843 showShortcodeBox( moreIcon, 'nofocus' );
7844 }
7845 } else if ( ! moreIcon.classList.contains( 'frm_close_icon' ) ) {
7846 showShortcodeBox( moreIcon, 'nofocus' );
7847 }
7848 }
7849 }
7850
7851 function showShortcodes( e ) {
7852 /*jshint validthis:true */
7853 e.preventDefault();
7854 e.stopPropagation();
7855
7856 showShortcodeBox( this );
7857 }
7858
7859 function showShortcodeBox( moreIcon, shouldFocus ) {
7860 var pos = moreIcon.getBoundingClientRect(),
7861 input = getInputForIcon( moreIcon ),
7862 box = document.getElementById( 'frm_adv_info' ),
7863 classes = moreIcon.className,
7864 parentPos = box.parentElement.getBoundingClientRect();
7865
7866 if ( moreIcon.tagName === 'svg' ) {
7867 moreIcon = moreIcon.firstElementChild;
7868 }
7869 if ( moreIcon.tagName === 'use' ) {
7870 classes = moreIcon.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
7871 }
7872
7873 if ( classes.indexOf( 'frm_close_icon' ) !== -1 ) {
7874 hideShortcodes( box );
7875 } else {
7876 box.style.top = ( pos.top - parentPos.top + 32 ) + 'px';
7877 box.style.left = ( pos.left - parentPos.left - 280 ) + 'px';
7878
7879 jQuery( '.frm_code_list a' ).removeClass( 'frm_noallow' );
7880 if ( input.classList.contains( 'frm_not_email_to' ) ) {
7881 jQuery( '#frm-insert-fields-box .frm_code_list li:not(.show_frm_not_email_to) a' ).addClass( 'frm_noallow' );
7882 } else if ( input.classList.contains( 'frm_not_email_subject' ) ) {
7883 jQuery( '.frm_code_list li.hide_frm_not_email_subject a' ).addClass( 'frm_noallow' );
7884 }
7885
7886 box.setAttribute( 'data-fills', input.id );
7887 box.style.display = 'block';
7888
7889 if ( moreIcon.tagName === 'use' ) {
7890 moreIcon.setAttributeNS( 'http://www.w3.org/1999/xlink', 'href', '#frm_close_icon' );
7891 } else {
7892 moreIcon.className = classes.replace( 'frm_more_horiz_solid_icon', 'frm_close_icon' );
7893 }
7894
7895 if ( shouldFocus !== 'nofocus' ) {
7896 if ( 'none' !== input.style.display ) {
7897 input.focus();
7898 } else {
7899 jQuery( tinymce.get( input.id ) ).trigger( 'focus' );
7900 }
7901 }
7902 }
7903 }
7904
7905 function fieldUpdated() {
7906 if ( ! fieldsUpdated ) {
7907 fieldsUpdated = 1;
7908 window.addEventListener( 'beforeunload', confirmExit );
7909 }
7910 }
7911
7912 function buildSubmittedNoAjax() {
7913 // set fieldsUpdated to 0 to avoid the unsaved changes pop up
7914 fieldsUpdated = 0;
7915 }
7916
7917 function settingsSubmitted() {
7918 // set fieldsUpdated to 0 to avoid the unsaved changes pop up
7919 fieldsUpdated = 0;
7920 }
7921
7922 function saveAndReloadSettings() {
7923 var page, form;
7924 page = document.getElementById( 'form_settings_page' );
7925 if ( null !== page ) {
7926 form = page.querySelector( 'form.frm_form_settings' );
7927 if ( null !== form ) {
7928 fieldsUpdated = 0;
7929 form.submit();
7930 }
7931 }
7932 }
7933
7934 function reloadIfAddonActivatedAjaxSubmitOnly() {
7935 const submitButton = document.getElementById( 'frm_submit_side_top' );
7936 if ( submitButton.hasAttribute( 'data-new-addon-installed' ) && 'true' === submitButton.getAttribute( 'data-new-addon-installed' ) ) {
7937 submitButton.removeAttribute( 'data-new-addon-installed' );
7938 window.location.reload();
7939 }
7940
7941 }
7942
7943 function saveAndReloadFormBuilder() {
7944 const submitButton = document.getElementById( 'frm_submit_side_top' );
7945 if ( submitButton.classList.contains( 'frm_submit_ajax' ) ) {
7946 submitButton.setAttribute( 'data-new-addon-installed', true );
7947 }
7948 submitButton.click();
7949 }
7950
7951 function confirmExit( event ) {
7952 if ( fieldsUpdated ) {
7953 event.preventDefault();
7954 event.returnValue = '';
7955 }
7956 }
7957
7958 function bindClickForDialogClose( $modal ) {
7959 const closeModal = function() {
7960 $modal.dialog( 'close' );
7961 };
7962 jQuery( '.ui-widget-overlay' ).on( 'click', closeModal );
7963 $modal.on( 'click', 'a.dismiss', closeModal );
7964 }
7965
7966 function offsetModalY( $modal, amount ) {
7967 const position = {
7968 my: 'top',
7969 at: 'top+' + amount,
7970 of: window
7971 };
7972 $modal.dialog( 'option', 'position', position );
7973 }
7974
7975 /**
7976 * Get the input box for the selected ... icon.
7977 */
7978 function getInputForIcon( moreIcon ) {
7979 var input = moreIcon.nextElementSibling;
7980
7981 while ( input !== null && input.tagName !== 'INPUT' && input.tagName !== 'TEXTAREA' ) {
7982 input = getInputForIcon( input );
7983 }
7984
7985 return input;
7986 }
7987
7988 /**
7989 * Get the ... icon for the selected input box.
7990 */
7991 function getIconForInput( input ) {
7992 var moreIcon = input.previousElementSibling;
7993
7994 while ( moreIcon !== null && moreIcon.tagName !== 'I' && moreIcon.tagName !== 'svg' ) {
7995 moreIcon = getIconForInput( moreIcon );
7996 }
7997
7998 return moreIcon;
7999 }
8000
8001 function hideShortcodes( box ) {
8002 var i, u, closeIcons, closeSvg;
8003 if ( typeof box === 'undefined' ) {
8004 box = document.getElementById( 'frm_adv_info' );
8005 if ( box === null ) {
8006 return;
8007 }
8008 }
8009
8010 if ( document.getElementById( 'frm_dyncontent' ) !== null ) {
8011 // Don't run when in the sidebar.
8012 return;
8013 }
8014
8015 box.style.display = 'none';
8016
8017 closeIcons = document.querySelectorAll( '.frm-show-box.frm_close_icon' );
8018 for ( i = 0; i < closeIcons.length; i++ ) {
8019 closeIcons[i].classList.remove( 'frm_close_icon' );
8020 closeIcons[i].classList.add( 'frm_more_horiz_solid_icon' );
8021 }
8022
8023 closeSvg = document.querySelectorAll( '.frm_has_shortcodes use' );
8024 for ( u = 0; u < closeSvg.length; u++ ) {
8025 if ( closeSvg[u].getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) === '#frm_close_icon' ) {
8026 closeSvg[u].setAttributeNS( 'http://www.w3.org/1999/xlink', 'href', '#frm_more_horiz_solid_icon' );
8027 }
8028 }
8029 }
8030
8031 function initToggleShortcodes() {
8032 if ( typeof tinymce !== 'object' ) {
8033 return;
8034 }
8035
8036 DOM = tinymce.DOM;
8037 if ( typeof DOM.events !== 'undefined' && typeof DOM.events.add !== 'undefined' ) {
8038 DOM.events.add( DOM.select( '.wp-editor-wrap' ), 'mouseover', function() {
8039 if ( jQuery( '*:focus' ).length > 0 ) {
8040 return;
8041 }
8042 if ( this.id ) {
8043 toggleAllowedShortcodes( this.id.slice( 3, -5 ), 'focusin' );
8044 }
8045 });
8046 DOM.events.add( DOM.select( '.wp-editor-wrap' ), 'mouseout', function() {
8047 if ( jQuery( '*:focus' ).length > 0 ) {
8048 return;
8049 }
8050 if ( this.id ) {
8051 toggleAllowedShortcodes( this.id.slice( 3, -5 ), 'focusin' );
8052 }
8053 });
8054 } else {
8055 jQuery( '#frm_dyncontent' ).on( 'mouseover mouseout', '.wp-editor-wrap', function() {
8056 if ( jQuery( '*:focus' ).length > 0 ) {
8057 return;
8058 }
8059 if ( this.id ) {
8060 toggleAllowedShortcodes( this.id.slice( 3, -5 ), 'focusin' );
8061 }
8062 });
8063 }
8064 }
8065
8066 function toggleAllowedShortcodes( id ) {
8067 var c, clickedID;
8068 if ( typeof id === 'undefined' ) {
8069 id = '';
8070 }
8071 c = id;
8072
8073 if ( id.indexOf( '-search-input' ) !== -1 ) {
8074 return;
8075 }
8076
8077 if ( id !== '' ) {
8078 var $ele = jQuery( document.getElementById( id ) );
8079 if ( $ele.attr( 'class' ) && id !== 'wpbody-content' && id !== 'content' && id !== 'dyncontent' && id !== 'success_msg' ) {
8080 var d = $ele.attr( 'class' ).split( ' ' )[0];
8081 if ( d === 'frm_long_input' || d === 'frm_98_width' || typeof d === 'undefined' ) {
8082 d = '';
8083 } else {
8084 id = d.trim();
8085 }
8086 c = c + ' ' + d;
8087 c = c.replace( 'widefat', '' ).replace( 'frm_with_left_label', '' );
8088 }
8089 }
8090
8091 jQuery( '#frm-insert-fields-box,#frm-conditionals,#frm-adv-info-tab,#frm-dynamic-values' ).attr( 'data-fills', c.trim() );
8092 var a = [
8093 'content', 'wpbody-content', 'dyncontent', 'success_url',
8094 'success_msg', 'edit_msg', 'frm_dyncontent', 'frm_not_email_message',
8095 'frm_not_email_subject'
8096 ];
8097 var b = [
8098 'before_content', 'after_content', 'frm_not_email_to',
8099 'dyn_default_value'
8100 ];
8101
8102 if ( jQuery.inArray( id, a ) >= 0 ) {
8103 jQuery( '.frm_code_list a' ).removeClass( 'frm_noallow' ).addClass( 'frm_allow' );
8104 jQuery( '.frm_code_list a.hide_' + id ).addClass( 'frm_noallow' ).removeClass( 'frm_allow' );
8105 } else if ( jQuery.inArray( id, b ) >= 0 ) {
8106 jQuery( '.frm_code_list:not(.frm-dropdown-menu) a:not(.show_' + id + ')' ).addClass( 'frm_noallow' ).removeClass( 'frm_allow' );
8107 jQuery( '.frm_code_list a.show_' + id ).removeClass( 'frm_noallow' ).addClass( 'frm_allow' );
8108 } else {
8109 jQuery( '.frm_code_list:not(.frm-dropdown-menu) a' ).addClass( 'frm_noallow' ).removeClass( 'frm_allow' );
8110 }
8111
8112 // Automatically select a tab.
8113 if ( id === 'dyn_default_value' ) {
8114 clickedID = 'frm_dynamic_values';
8115 document.getElementById( clickedID + '_tab' ).click();
8116 jQuery( '#' + clickedID.replace( /_/g, '-' ) + ' .frm_show_inactive' ).addClass( 'frm_hidden' );
8117 jQuery( '#' + clickedID.replace( /_/g, '-' ) + ' .frm_show_active' ).removeClass( 'frm_hidden' );
8118 }
8119 }
8120
8121 function toggleAllowedHTML( input ) {
8122 var b,
8123 id = input.id;
8124 if ( typeof id === 'undefined' || id.indexOf( '-search-input' ) !== -1 ) {
8125 return;
8126 }
8127
8128 jQuery( '#frm-adv-info-tab' ).attr( 'data-fills', id.trim() );
8129 if ( input.classList.contains( 'field_custom_html' ) ) {
8130 id = 'field_custom_html';
8131 }
8132
8133 b = [ 'after_html', 'before_html', 'submit_html', 'field_custom_html' ];
8134 if ( jQuery.inArray( id, b ) >= 0 ) {
8135 jQuery( '.frm_code_list li:not(.show_' + id + ')' ).addClass( 'frm_hidden' );
8136 jQuery( '.frm_code_list li.show_' + id ).removeClass( 'frm_hidden' );
8137 }
8138 }
8139
8140 function toggleKeyID( switchTo, e ) {
8141 e.stopPropagation();
8142 jQuery( '.frm_code_list .frmids, .frm_code_list .frmkeys' ).addClass( 'frm_hidden' );
8143 jQuery( '.frm_code_list .' + switchTo ).removeClass( 'frm_hidden' );
8144 jQuery( '.frmids, .frmkeys' ).removeClass( 'current' );
8145 jQuery( '.' + switchTo ).addClass( 'current' );
8146 }
8147
8148 function onActionLoaded( event ) {
8149 const settings = event.target.closest( '.frm_form_action_settings' );
8150 if ( settings && ( settings.classList.contains( 'frm_single_email_settings' ) || settings.classList.contains( 'frm_single_on_submit_settings' ) ) ) {
8151 initWysiwygOnActionLoaded( settings );
8152 }
8153 }
8154
8155 function initWysiwygOnActionLoaded( settings ) {
8156 const wysiwyg = settings.querySelector( '.wp-editor-area' );
8157 if ( wysiwyg ) {
8158 frmDom.wysiwyg.init(
8159 wysiwyg,
8160 { height: 160, addFocusEvents: true }
8161 );
8162 }
8163 }
8164
8165 /* Global settings page */
8166 function loadSettingsTab( anchor ) {
8167 var holder = anchor.replace( '#', '' );
8168 var holderContainer = jQuery( '.frm_' + holder + '_ajax' );
8169 if ( holderContainer.length ) {
8170 jQuery.ajax({
8171 type: 'POST', url: ajaxurl,
8172 data: {
8173 'action': 'frm_settings_tab',
8174 'tab': holder.replace( '_settings', '' ),
8175 'nonce': frmGlobal.nonce
8176 },
8177 success: function( html ) {
8178 holderContainer.replaceWith( html );
8179 }
8180 });
8181 }
8182 }
8183
8184 function uninstallNow() {
8185 /*jshint validthis:true */
8186 if ( confirmLinkClick( this ) === true ) {
8187 jQuery( '.frm_uninstall .frm-wait' ).css( 'visibility', 'visible' );
8188 jQuery.ajax({
8189 type: 'POST',
8190 url: ajaxurl,
8191 data: 'action=frm_uninstall&nonce=' + frmGlobal.nonce,
8192 success: function( msg ) {
8193 jQuery( '.frm_uninstall' ).fadeOut( 'slow' );
8194 window.location = msg;
8195 }
8196 });
8197 }
8198 return false;
8199 }
8200
8201 function saveAddonLicense() {
8202 /*jshint validthis:true */
8203 var button = jQuery( this );
8204 var buttonName = this.name;
8205 var pluginSlug = this.getAttribute( 'data-plugin' );
8206 var action = buttonName.replace( 'edd_' + pluginSlug + '_license_', '' );
8207 var license = document.getElementById( 'edd_' + pluginSlug + '_license_key' ).value;
8208 jQuery.ajax({
8209 type: 'POST', url: ajaxurl, dataType: 'json',
8210 data: {action: 'frm_addon_' + action, license: license, plugin: pluginSlug, nonce: frmGlobal.nonce},
8211 success: function( msg ) {
8212 var thisRow = button.closest( '.edd_frm_license_row' );
8213 if ( action === 'deactivate' ) {
8214 license = '';
8215 document.getElementById( 'edd_' + pluginSlug + '_license_key' ).value = '';
8216 }
8217 thisRow.find( '.edd_frm_license' ).html( license );
8218 if ( msg.success === true ) {
8219 thisRow.find( '.frm_icon_font' ).removeClass( 'frm_hidden' );
8220 thisRow.find( 'div.alignleft' ).toggleClass( 'frm_hidden', 1000 );
8221 }
8222
8223 var messageBox = thisRow.find( '.frm_license_msg' );
8224 messageBox.html( msg.message );
8225 if ( msg.message !== '' ) {
8226 setTimeout( function() {
8227 messageBox.html( '' );
8228 }, 15000 );
8229 }
8230 }
8231 });
8232 }
8233
8234 /* Import/Export page */
8235
8236 function startFormMigration( event ) {
8237 event.preventDefault();
8238
8239 var checkedBoxes = jQuery( event.target ).find( 'input:checked' );
8240 if ( ! checkedBoxes.length ) {
8241 return;
8242 }
8243
8244 var ids = [];
8245 checkedBoxes.each( function( i ) {
8246 ids[i] = this.value;
8247 });
8248
8249 // Begin the import process.
8250 importForms( ids, event.target );
8251 }
8252
8253 /**
8254 * Begins the process of importing the forms.
8255 */
8256 function importForms( forms, targetForm ) {
8257
8258 // Hide the form select section.
8259 var $form = jQuery( targetForm ),
8260 $processSettings = $form.next( '.frm-importer-process' );
8261
8262 // Display total number of forms we have to import.
8263 $processSettings.find( '.form-total' ).text( forms.length );
8264 $processSettings.find( '.form-current' ).text( '1' );
8265
8266 $form.hide();
8267
8268 // Show processing status.
8269 // '.process-completed' might have been shown earlier during a previous import, so hide now.
8270 $processSettings.find( '.process-completed' ).hide();
8271 $processSettings.show();
8272
8273 // Create global import queue.
8274 s.importQueue = forms;
8275 s.imported = 0;
8276
8277 // Import the first form in the queue.
8278 importForm( $processSettings );
8279 }
8280
8281 /**
8282 * Imports a single form from the import queue.
8283 */
8284 function importForm( $processSettings ) {
8285 var formID = s.importQueue[0],
8286 provider = jQuery( '#welcome-panel' ).find( 'input[name="slug"]' ).val(),
8287 data = {
8288 action: 'frm_import_' + provider,
8289 form_id: formID,
8290 nonce: frmGlobal.nonce
8291 };
8292
8293 // Trigger AJAX import for this form.
8294 jQuery.post( ajaxurl, data, function( res ) {
8295
8296 if ( res.success ) {
8297 var statusUpdate;
8298
8299 if ( res.data.error ) {
8300 statusUpdate = '<p>' + res.data.name + ': ' + res.data.msg + '</p>';
8301 } else {
8302 statusUpdate = '<p>Imported <a href="' + res.data.link + '" target="_blank">' + res.data.name + '</a></p>';
8303 }
8304
8305 $processSettings.find( '.status' ).prepend( statusUpdate );
8306 $processSettings.find( '.status' ).show();
8307
8308 // Remove this form ID from the queue.
8309 s.importQueue = jQuery.grep( s.importQueue, function( value ) {
8310 return value != formID;
8311 });
8312 s.imported++;
8313
8314 if ( s.importQueue.length === 0 ) {
8315 $processSettings.find( '.process-count' ).hide();
8316 $processSettings.find( '.forms-completed' ).text( s.imported );
8317 $processSettings.find( '.process-completed' ).show();
8318 } else {
8319 // Import next form in the queue.
8320 $processSettings.find( '.form-current' ).text( s.imported + 1 );
8321 importForm( $processSettings );
8322 }
8323 }
8324 });
8325 }
8326
8327 function validateExport( e ) {
8328 /*jshint validthis:true */
8329 e.preventDefault();
8330
8331 var s = false;
8332 var $exportForms = jQuery( 'input[name="frm_export_forms[]"]' );
8333
8334 if ( ! jQuery( 'input[name="frm_export_forms[]"]:checked' ).val() ) {
8335 $exportForms.closest( '.frm-table-box' ).addClass( 'frm_blank_field' );
8336 s = 'stop';
8337 }
8338
8339 var $exportType = jQuery( 'input[name="type[]"]' );
8340 if ( ! jQuery( 'input[name="type[]"]:checked' ).val() && $exportType.attr( 'type' ) === 'checkbox' ) {
8341 $exportType.closest( 'p' ).addClass( 'frm_blank_field' );
8342 s = 'stop';
8343 }
8344
8345 if ( s === 'stop' ) {
8346 return false;
8347 }
8348
8349 e.stopPropagation();
8350 this.submit();
8351 }
8352
8353 function removeExportError() {
8354 /*jshint validthis:true */
8355 var t = jQuery( this ).closest( '.frm_blank_field' );
8356 if ( typeof t === 'undefined' ) {
8357 return;
8358 }
8359
8360 var $thisName = this.name;
8361 if ( $thisName === 'type[]' && jQuery( 'input[name="type[]"]:checked' ).val() ) {
8362 t.removeClass( 'frm_blank_field' );
8363 } else if ( $thisName === 'frm_export_forms[]' && jQuery( this ).val() ) {
8364 t.removeClass( 'frm_blank_field' );
8365 }
8366
8367 }
8368
8369 function checkCSVExtension() {
8370 /*jshint validthis:true */
8371 var f = jQuery( this ).val();
8372 var re = /\.csv$/i;
8373 if ( f.match( re ) !== null ) {
8374 jQuery( '.show_csv' ).fadeIn();
8375 } else {
8376 jQuery( '.show_csv' ).fadeOut();
8377 }
8378 }
8379
8380 function getExportOption() {
8381 const exportFormatSelect = document.querySelector( 'select[name="format"]' );
8382 if ( exportFormatSelect ) {
8383 return exportFormatSelect.value;
8384 } else {
8385 return '';
8386 }
8387 }
8388
8389 function exportTypeChanged( event ) {
8390 const value = event.target.value;
8391 showOrHideRepeaters( value );
8392 checkExportTypes.call( event.target );
8393 checkSelectedAllFormsCheckbox( value );
8394 }
8395
8396 function checkSelectedAllFormsCheckbox( exportType ) {
8397 const selectAllCheckbox = document.getElementById( 'frm-export-select-all' );
8398 if ( exportType === 'csv' ) {
8399 selectAllCheckbox.checked = false;
8400 selectAllCheckbox.disabled = true;
8401 } else {
8402 selectAllCheckbox.disabled = false;
8403 }
8404 }
8405
8406 function checkExportTypes() {
8407 /*jshint validthis:true */
8408 var $dropdown = jQuery( this );
8409 var $selected = $dropdown.find( ':selected' );
8410 var s = $selected.data( 'support' );
8411
8412 var multiple = s.indexOf( '|' );
8413 jQuery( 'input[name="type[]"]' ).each( function() {
8414 this.checked = false;
8415 if ( s.indexOf( this.value ) >= 0 ) {
8416 this.disabled = false;
8417 if ( multiple === -1 ) {
8418 this.checked = true;
8419 }
8420 } else {
8421 this.disabled = true;
8422 }
8423 });
8424
8425 if ( $dropdown.val() === 'csv' ) {
8426 jQuery( '.csv_opts' ).show();
8427 jQuery( '.xml_opts' ).hide();
8428 } else {
8429 jQuery( '.csv_opts' ).hide();
8430 jQuery( '.xml_opts' ).show();
8431 }
8432
8433 var c = $selected.data( 'count' );
8434 var exportField = jQuery( 'input[name="frm_export_forms[]"]' );
8435 if ( c === 'single' ) {
8436 exportField.prop( 'multiple', false );
8437 exportField.prop( 'checked', false );
8438 } else {
8439 exportField.prop( 'multiple', true );
8440 exportField.prop( 'disabled', false );
8441 }
8442 $dropdown.trigger( 'change' );
8443 }
8444
8445 function showOrHideRepeaters( exportOption ) {
8446 if ( exportOption === '' ) {
8447 return;
8448 }
8449
8450 const repeaters = document.querySelectorAll( '.frm-is-repeater' );
8451 if ( ! repeaters.length ) {
8452 return;
8453 }
8454
8455 if ( exportOption === 'csv' ) {
8456 repeaters.forEach( form => {
8457 form.classList.remove( 'frm_hidden' );
8458 });
8459 } else {
8460 repeaters.forEach( form => {
8461 form.classList.add( 'frm_hidden' );
8462 });
8463 }
8464
8465 searchContent.call( document.querySelector( '.frm-auto-search' ) );
8466 }
8467
8468 function preventMultipleExport() {
8469 var type = jQuery( 'select[name=format]' ),
8470 selected = type.find( ':selected' ),
8471 count = selected.data( 'count' ),
8472 exportField = jQuery( 'input[name="frm_export_forms[]"]' );
8473
8474 if ( count === 'single' ) {
8475 // Disable all other fields to prevent multiple selections.
8476 if ( this.checked ) {
8477 exportField.prop( 'disabled', true );
8478 this.removeAttribute( 'disabled' );
8479 } else {
8480 exportField.prop( 'disabled', false );
8481 }
8482 } else {
8483 exportField.prop( 'disabled', false );
8484 }
8485 }
8486
8487 function initiateMultiselect() {
8488 jQuery( '.frm_multiselect' ).hide().each( frmDom.bootstrap.multiselect.init );
8489 }
8490
8491 /* Addons page */
8492 function installMultipleAddons( e ) {
8493 e.preventDefault();
8494 installOrActivate( this, 'frm_multiple_addons' );
8495 }
8496
8497 function activateAddon( e ) {
8498 e.preventDefault();
8499 installOrActivate( this, 'frm_activate_addon' );
8500 }
8501
8502 function installAddon( e ) {
8503 e.preventDefault();
8504 installOrActivate( this, 'frm_install_addon' );
8505 }
8506
8507 function installOrActivate( clicked, action ) {
8508 let button, plugin, el, message;
8509
8510 // Remove any leftover error messages, output an icon and get the plugin basename that needs to be activated.
8511 jQuery( '.frm-addon-error' ).remove();
8512 button = jQuery( clicked );
8513 plugin = button.attr( 'rel' );
8514 el = button.parent();
8515 message = el.parent().find( '.addon-status-label' );
8516
8517 button.addClass( 'frm_loading_button' );
8518
8519 // Process the Ajax to perform the activation.
8520 jQuery.ajax({
8521 url: ajaxurl,
8522 type: 'POST',
8523 async: true,
8524 cache: false,
8525 dataType: 'json',
8526 data: {
8527 action: action,
8528 nonce: frmGlobal.nonce,
8529 plugin: plugin
8530 },
8531 success: function( response ) {
8532 let saveAndReload;
8533
8534 if ( 'string' !== typeof response && 'string' === typeof response.message ) {
8535 if ( 'undefined' !== typeof response.saveAndReload ) {
8536 saveAndReload = response.saveAndReload;
8537 }
8538 response = response.message;
8539 }
8540
8541 const error = extractErrorFromAddOnResponse( response );
8542 if ( error ) {
8543 addonError( error, el, button );
8544 return;
8545 }
8546
8547 afterAddonInstall( response, button, message, el, saveAndReload );
8548 },
8549 error: function() {
8550 button.removeClass( 'frm_loading_button' );
8551 }
8552 });
8553 }
8554
8555 function installAddonWithCreds( e ) {
8556 // Prevent the default action, let the user know we are attempting to install again and go with it.
8557 e.preventDefault();
8558
8559 // Now let's make another Ajax request once the user has submitted their credentials.
8560 const proceed = jQuery( this );
8561 const el = proceed.parent().parent();
8562 const plugin = proceed.attr( 'rel' );
8563
8564 proceed.addClass( 'frm_loading_button' );
8565
8566 jQuery.ajax({
8567 url: ajaxurl,
8568 type: 'POST',
8569 async: true,
8570 cache: false,
8571 dataType: 'json',
8572 data: {
8573 action: 'frm_install_addon',
8574 nonce: frm_admin_js.nonce, // eslint-disable-line camelcase
8575 plugin: plugin,
8576 hostname: el.find( '#hostname' ).val(),
8577 username: el.find( '#username' ).val(),
8578 password: el.find( '#password' ).val()
8579 },
8580 success: function( response ) {
8581 const error = extractErrorFromAddOnResponse( response );
8582 if ( error ) {
8583 addonError( error, el, proceed );
8584 return;
8585 }
8586
8587 afterAddonInstall( response, proceed, message, el );
8588 },
8589 error: function() {
8590 proceed.removeClass( 'frm_loading_button' );
8591 }
8592 });
8593 }
8594
8595 function afterAddonInstall( response, button, message, el, saveAndReload ) {
8596 const addonStatuses = document.querySelectorAll( '.frm-addon-status' );
8597 addonStatuses.forEach(
8598 addonStatus => {
8599 addonStatus.textContent = response;
8600 addonStatus.style.display = 'block';
8601 }
8602 );
8603
8604 // The Ajax request was successful, so let's update the output.
8605 button.css({ opacity: '0' });
8606 message.text( frm_admin_js.active ); // eslint-disable-line camelcase
8607
8608 document.querySelectorAll( '.frm-oneclick' ).forEach(
8609 oneClick => {
8610 oneClick.style.display = 'none';
8611 }
8612 );
8613
8614 jQuery( '#frm_upgrade_modal h2' ).hide();
8615 jQuery( '#frm_upgrade_modal .frm_lock_icon' ).addClass( 'frm_lock_open_icon' );
8616 jQuery( '#frm_upgrade_modal .frm_lock_icon use' ).attr( 'xlink:href', '#frm_lock_open_icon' );
8617
8618 // Proceed with CSS changes
8619 el.parent().removeClass( 'frm-addon-not-installed frm-addon-installed' ).addClass( 'frm-addon-active' );
8620 button.removeClass( 'frm_loading_button' );
8621
8622 // Maybe refresh import and SMTP pages
8623 const refreshPage = document.querySelectorAll( '.frm-admin-page-import, #frm-admin-smtp, #frm-welcome' );
8624 if ( refreshPage.length > 0 ) {
8625 window.location.reload();
8626 return;
8627 }
8628
8629 if ([ 'settings', 'form_builder' ].includes( saveAndReload ) ) {
8630 addonStatuses.forEach(
8631 addonStatus => {
8632 const inModal = null !== addonStatus.closest( '#frm_upgrade_modal' );
8633 addonStatus.appendChild( getSaveAndReloadSettingsOptions( saveAndReload, inModal ) );
8634 }
8635 );
8636 }
8637 }
8638
8639 function getSaveAndReloadSettingsOptions( saveAndReload, inModal ) {
8640 const className = 'frm-save-and-reload-options';
8641 const children = [ saveAndReloadSettingsButton( saveAndReload ) ];
8642 if ( inModal ) {
8643 children.push( closePopupButton() );
8644 }
8645 return div({ className, children });
8646 }
8647
8648 function saveAndReloadSettingsButton( saveAndReload ) {
8649 var button = document.createElement( 'button' );
8650 button.classList.add( 'frm-save-and-reload', 'button', 'button-primary', 'frm-button-primary' );
8651 button.textContent = __( 'Save and Reload', 'formidable' );
8652 button.addEventListener( 'click', () => {
8653 if ( saveAndReload === 'form_builder' ) {
8654 saveAndReloadFormBuilder();
8655 } else if ( saveAndReload === 'settings' ) {
8656 saveAndReloadSettings();
8657 }
8658 });
8659 return button;
8660 }
8661
8662 function closePopupButton() {
8663 var a = document.createElement( 'a' );
8664 a.setAttribute( 'href', '#' );
8665 a.classList.add( 'button', 'button-secondary', 'frm-button-secondary', 'dismiss' );
8666 a.textContent = __( 'Close', 'formidable' );
8667 return a;
8668 }
8669
8670 function extractErrorFromAddOnResponse( response ) {
8671 if ( typeof response !== 'string' ) {
8672 if ( typeof response.success !== 'undefined' && response.success ) {
8673 return false;
8674 }
8675
8676 if ( response.form ) {
8677 if ( jQuery( response.form ).is( '#message' ) ) {
8678 return {
8679 message: jQuery( response.form ).find( 'p' ).html()
8680 };
8681 }
8682 }
8683
8684 return response;
8685 }
8686
8687 return false;
8688 }
8689
8690 function addonError( response, el, button ) {
8691 if ( response.form ) {
8692 jQuery( '.frm-inline-error' ).remove();
8693 button.closest( '.frm-card' )
8694 .html( response.form )
8695 .css({ padding: 5 })
8696 .find( '#upgrade' )
8697 .attr( 'rel', button.attr( 'rel' ) )
8698 .on( 'click', installAddonWithCreds );
8699 } else {
8700 el.append( '<div class="frm-addon-error frm_error_style"><p><strong>' + response.message + '</strong></p></div>' );
8701 button.removeClass( 'frm_loading_button' );
8702 jQuery( '.frm-addon-error' ).delay( 4000 ).fadeOut();
8703 }
8704 }
8705
8706 /* Templates */
8707 function showActiveCampaignForm() {
8708 loadApiEmailForm();
8709 }
8710
8711 function handleApiFormError( inputId, errorId, type, message ) {
8712 const $error = jQuery( errorId );
8713 $error.removeClass( 'frm_hidden' ).attr( 'frm-error', type );
8714
8715 if ( typeof message !== 'undefined' ) {
8716 $error.find( 'span[frm-error="' + type + '"]' ).text( message );
8717 }
8718
8719 jQuery( inputId ).one( 'keyup', function() {
8720 $error.addClass( 'frm_hidden' );
8721 });
8722 }
8723
8724 function handleEmailAddressError( type ) {
8725 handleApiFormError( '#frm_leave_email', '#frm_leave_email_error', type );
8726 }
8727
8728 function loadApiEmailForm() {
8729 const formContainer = document.getElementById( 'frmapi-email-form' );
8730 jQuery.ajax({
8731 dataType: 'json',
8732 url: formContainer.getAttribute( 'data-url' ),
8733 success: function( json ) {
8734 var form = json.renderedHtml;
8735 form = form.replace( /<link\b[^>]*(formidableforms.css|action=frmpro_css)[^>]*>/gi, '' );
8736 formContainer.innerHTML = form;
8737 }
8738 });
8739 }
8740 function initSelectionAutocomplete() {
8741 frmDom.autocomplete.initSelectionAutocomplete();
8742 }
8743
8744 function nextInstallStep( thisStep ) {
8745 thisStep.classList.add( 'frm_grey' );
8746 thisStep.nextElementSibling.classList.remove( 'frm_grey' );
8747 }
8748
8749 function installTemplateFieldset( e ) {
8750 /*jshint validthis:true */
8751 var fieldset = this.parentNode.parentNode,
8752 action = fieldset.elements.type.value,
8753 button = this;
8754 e.preventDefault();
8755 button.classList.add( 'frm_loading_button' );
8756 installNewForm( fieldset, action, button );
8757 }
8758
8759 function installTemplate( e ) {
8760 /*jshint validthis:true */
8761 var action = this.elements.type.value,
8762 button = this.querySelector( 'button' );
8763 e.preventDefault();
8764 button.classList.add( 'frm_loading_button' );
8765 installNewForm( this, action, button );
8766 }
8767
8768 function installNewForm( form, action, button ) {
8769 const formData = formToData( form );
8770 const formName = formData.template_name;
8771 const formDesc = formData.template_desc;
8772 const link = form.elements.link.value;
8773
8774 let data = {
8775 action: action,
8776 xml: link,
8777 name: formName,
8778 desc: formDesc,
8779 form: JSON.stringify( formData ),
8780 nonce: frmGlobal.nonce
8781 };
8782
8783 const hookName = 'frm_before_install_new_form';
8784 const filterArgs = { formData };
8785 data = wp.hooks.applyFilters( hookName, data, filterArgs );
8786
8787 postAjax( data, function( response ) {
8788 if ( typeof response.redirect !== 'undefined' ) {
8789 const redirect = response.redirect;
8790 if ( typeof form.elements.redirect === 'undefined' ) {
8791 window.location = redirect;
8792 } else {
8793 const href = document.getElementById( 'frm-redirect-link' );
8794 if ( typeof link !== 'undefined' && href !== null ) {
8795 // Show the next installation step.
8796 href.setAttribute( 'href', redirect );
8797 href.classList.remove( 'frm_grey', 'disabled' );
8798 nextInstallStep( form.parentNode.parentNode );
8799 button.classList.add( 'frm_grey', 'disabled' );
8800 }
8801 }
8802 } else {
8803 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
8804
8805 // Show response.message
8806 if ( 'string' === typeof response.message ) {
8807 showInstallFormErrorModal( response.message );
8808 }
8809 }
8810 button.classList.remove( 'frm_loading_button' );
8811 });
8812 }
8813
8814 function showInstallFormErrorModal( message ) {
8815 const modalContent = div( message );
8816 modalContent.style.padding = '20px 40px';
8817 const modal = frmDom.modal.maybeCreateModal(
8818 'frmInstallFormErrorModal',
8819 {
8820 title: __( 'Unable to install template', 'formidable' ),
8821 content: modalContent
8822 }
8823 );
8824 modal.classList.add( 'frm_common_modal' );
8825 }
8826
8827 function handleCaptchaTypeChange( e ) {
8828 const thresholdContainer = document.getElementById( 'frm_captcha_threshold_container' );
8829 if ( thresholdContainer ) {
8830 thresholdContainer.classList.toggle( 'frm_hidden', 'v3' !== e.target.value );
8831 }
8832 }
8833
8834 function trashTemplate( e ) {
8835 /*jshint validthis:true */
8836 var id = this.getAttribute( 'data-id' );
8837 e.preventDefault();
8838
8839 data = {
8840 action: 'frm_forms_trash',
8841 id: id,
8842 nonce: frmGlobal.nonce
8843 };
8844 postAjax( data, function() {
8845 var card = document.getElementById( 'frm-template-custom-' + id );
8846 fadeOut( card, function() {
8847 card.parentNode.removeChild( card );
8848 });
8849 });
8850 }
8851
8852 function searchContent() {
8853 /*jshint validthis:true */
8854 var i,
8855 regEx = false,
8856 searchText = this.value.toLowerCase(),
8857 toSearch = this.getAttribute( 'data-tosearch' ),
8858 items = document.getElementsByClassName( toSearch );
8859
8860 if ( this.tagName === 'SELECT' ) {
8861 searchText = selectedOptions( this );
8862 searchText = searchText.join( '|' ).toLowerCase();
8863 regEx = true;
8864 }
8865
8866 if ( toSearch === 'frm-action' && searchText !== '' ) {
8867 var addons = document.getElementById( 'frm_email_addon_menu' ).classList;
8868 addons.remove( 'frm-all-actions' );
8869 addons.add( 'frm-limited-actions' );
8870 }
8871
8872 for ( i = 0; i < items.length; i++ ) {
8873 var innerText = items[i].innerText.toLowerCase();
8874
8875 const itemCanBeShown = ! ( getExportOption() === 'xml' && items[i].classList.contains( 'frm-is-repeater' ) );
8876 if ( searchText === '' ) {
8877 if ( itemCanBeShown ) {
8878 items[i].classList.remove( 'frm_hidden' );
8879 }
8880 items[i].classList.remove( 'frm-search-result' );
8881 } else if ( ( regEx && new RegExp( searchText ).test( innerText ) ) || innerText.indexOf( searchText ) >= 0 ) {
8882 if ( itemCanBeShown ) {
8883 items[i].classList.remove( 'frm_hidden' );
8884 }
8885 items[i].classList.add( 'frm-search-result' );
8886 } else {
8887 items[i].classList.add( 'frm_hidden' );
8888 items[i].classList.remove( 'frm-search-result' );
8889 }
8890 }
8891
8892 // Updates the visibility of category headings based on search results.
8893 updateCatHeadingVisibility();
8894
8895 jQuery( this ).trigger( 'frmAfterSearch' );
8896 }
8897
8898 /**
8899 * Updates the visibility of category headings based on search results.
8900 * If all associated fields are hidden (indicating no search matches),
8901 * the heading is hidden.
8902 *
8903 * @since 6.4.1
8904 */
8905 function updateCatHeadingVisibility() {
8906 const insertFieldsElement = document.querySelector( '#frm-insert-fields' );
8907 if ( ! insertFieldsElement ) {
8908 return;
8909 }
8910
8911 const headingElements = insertFieldsElement.querySelectorAll( ':scope > .frm-with-line' );
8912 headingElements.forEach( heading => {
8913 const fieldsListElement = heading.nextElementSibling;
8914 if ( ! fieldsListElement ) {
8915 return;
8916 }
8917 const listItemElements = fieldsListElement.querySelectorAll( ':scope > li.frmbutton' );
8918 const allHidden = Array.from( listItemElements ).every( li => li.classList.contains( 'frm_hidden' ) );
8919
8920 // Add or remove class based on `allHidden` condition
8921 heading.classList.toggle( 'frm_hidden', allHidden );
8922 });
8923 }
8924
8925 function stopPropagation( e ) {
8926 e.stopPropagation();
8927 }
8928
8929 /* Helpers */
8930
8931 function selectedOptions( select ) {
8932 var opt,
8933 result = [],
8934 options = select && select.options;
8935
8936 for ( var i = 0, iLen = options.length; i < iLen; i++ ) {
8937 opt = options[i];
8938
8939 if ( opt.selected ) {
8940 result.push( opt.value );
8941 }
8942 }
8943 return result;
8944 }
8945
8946 function triggerEvent( element, event ) {
8947 var evt = document.createEvent( 'HTMLEvents' );
8948 evt.initEvent( event, false, true );
8949 element.dispatchEvent( evt );
8950 }
8951
8952 function postAjax( data, success ) {
8953 let response;
8954
8955 const xmlHttp = new XMLHttpRequest();
8956 const params = typeof data === 'string' ? data : Object.keys( data ).map(
8957 function( k ) {
8958 return encodeURIComponent( k ) + '=' + encodeURIComponent( data[k]);
8959 }
8960 ).join( '&' );
8961
8962 xmlHttp.open( 'post', ajaxurl, true );
8963 xmlHttp.onreadystatechange = function() {
8964 if ( xmlHttp.readyState > 3 && xmlHttp.status == 200 ) {
8965 response = xmlHttp.responseText;
8966 try {
8967 response = JSON.parse( response );
8968 } catch ( e ) {
8969 // The response may not be JSON, so just return it.
8970 }
8971 success( response );
8972 }
8973 };
8974 xmlHttp.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
8975 xmlHttp.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );
8976 xmlHttp.send( params );
8977 return xmlHttp;
8978 }
8979
8980 function fadeOut( element, success ) {
8981 element.classList.add( 'frm-fade' );
8982 setTimeout( success, 1000 );
8983 }
8984
8985 function invisible( classes ) {
8986 jQuery( classes ).css( 'visibility', 'hidden' );
8987 }
8988
8989 function visible( classes ) {
8990 jQuery( classes ).css( 'visibility', 'visible' );
8991 }
8992
8993 function initModal( id, width ) {
8994 const $info = jQuery( id );
8995 if ( ! $info.length ) {
8996 return false;
8997 }
8998
8999 if ( typeof width === 'undefined' ) {
9000 width = '550px';
9001 }
9002
9003 const dialogArgs = {
9004 dialogClass: 'frm-dialog',
9005 modal: true,
9006 autoOpen: false,
9007 closeOnEscape: true,
9008 width: width,
9009 resizable: false,
9010 draggable: false,
9011 open: function() {
9012 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
9013 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
9014 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
9015 $info.removeClass( 'ui-dialog-content ui-widget-content' );
9016 bindClickForDialogClose( $info );
9017 },
9018 close: function() {
9019 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
9020 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
9021
9022 this.removeAttribute( 'data-option-type' );
9023 const optionType = document.getElementById( 'bulk-option-type' );
9024 if ( optionType ) {
9025 optionType.value = '';
9026 }
9027 }
9028 };
9029
9030 $info.dialog( dialogArgs );
9031
9032 return $info;
9033 }
9034
9035 function toggle( cname, id ) {
9036 if ( id === '#' ) {
9037 var cont = document.getElementById( cname );
9038 var hidden = cont.style.display;
9039 if ( hidden === 'none' ) {
9040 cont.style.display = 'block';
9041 } else {
9042 cont.style.display = 'none';
9043 }
9044 } else {
9045 var vis = cname.is( ':visible' );
9046 if ( vis ) {
9047 cname.hide();
9048 } else {
9049 cname.show();
9050 }
9051 }
9052 }
9053
9054 function removeWPUnload() {
9055 window.onbeforeunload = null;
9056 var w = jQuery( window );
9057 w.off( 'beforeunload.widgets' );
9058 w.off( 'beforeunload.edit-post' );
9059 }
9060
9061 function addMultiselectLabelListener() {
9062 const clickListener = ( e ) => {
9063 if ( 'LABEL' !== e.target.nodeName ) {
9064 return;
9065 }
9066
9067 const labelFor = e.target.getAttribute( 'for' );
9068 if ( ! labelFor ) {
9069 return;
9070 }
9071
9072 const input = document.getElementById( labelFor );
9073 if ( ! input || ! input.nextElementSibling ) {
9074 return;
9075 }
9076
9077 const buttonToggle = input.nextElementSibling.querySelector( 'button.dropdown-toggle.multiselect' );
9078 if ( ! buttonToggle ) {
9079 return;
9080 }
9081
9082 const triggerMultiselectClick = () => buttonToggle.click();
9083 setTimeout( triggerMultiselectClick, 0 );
9084 };
9085 document.addEventListener( 'click', clickListener );
9086 }
9087
9088 function maybeChangeEmbedFormMsg() {
9089 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
9090 var fieldItem = document.getElementById( 'frm_field_id_' + fieldId );
9091 if ( null === fieldItem || 'form' !== fieldItem.dataset.type ) {
9092 return;
9093 }
9094
9095 fieldItem = jQuery( fieldItem );
9096
9097 if ( this.options[ this.selectedIndex ].value ) {
9098 fieldItem.find( '.frm-not-set' )[0].classList.add( 'frm_hidden' );
9099 var embedMsg = fieldItem.find( '.frm-embed-message' );
9100 embedMsg.html( embedMsg.data( 'embedmsg' ) + this.options[ this.selectedIndex ].text );
9101 fieldItem.find( '.frm-embed-field-placeholder' )[0].classList.remove( 'frm_hidden' );
9102 } else {
9103 fieldItem.find( '.frm-not-set' )[0].classList.remove( 'frm_hidden' );
9104 fieldItem.find( '.frm-embed-field-placeholder' )[0].classList.add( 'frm_hidden' );
9105 }
9106 }
9107
9108 function toggleProductType() {
9109 var settings = jQuery( this ).closest( '.frm-single-settings' ),
9110 container = settings.find( '.frmjs_product_choices' ),
9111 heading = settings.find( '.frm_prod_options_heading' ),
9112 currentVal = this.options[ this.selectedIndex ].value;
9113
9114 container.removeClass( 'frm_prod_type_single frm_prod_type_user_def' );
9115 heading.removeClass( 'frm_prod_user_def' );
9116
9117 if ( 'single' === currentVal ) {
9118 container.addClass( 'frm_prod_type_single' );
9119 } else if ( 'user_def' === currentVal ) {
9120 container.addClass( 'frm_prod_type_user_def' );
9121 heading.addClass( 'frm_prod_user_def' );
9122 }
9123 }
9124
9125 function isProductField( fieldId ) {
9126 var field = document.getElementById( 'frm_field_id_' + fieldId );
9127 if ( field === null ) {
9128 return false;
9129 } else {
9130 return 'product' === field.getAttribute( 'data-type' );
9131 }
9132 }
9133
9134 /**
9135 * Serialize form data with vanilla JS.
9136 */
9137 function formToData( form ) {
9138 var subKey, i,
9139 object = {},
9140 formData = form.elements;
9141
9142 for ( i = 0; i < formData.length; i++ ) {
9143 var input = formData[i],
9144 key = input.name,
9145 value = input.value,
9146 names = key.match( /(.*)\[(.*)\]/ );
9147
9148 if ( ( input.type === 'radio' || input.type === 'checkbox' ) && ! input.checked ) {
9149 continue;
9150 }
9151
9152 if ( names !== null ) {
9153 key = names[1];
9154 subKey = names[2];
9155 if ( ! Reflect.has( object, key ) ) {
9156 object[key] = {};
9157 }
9158 object[key][subKey] = value;
9159 continue;
9160 }
9161
9162 // Reflect.has in favor of: object.hasOwnProperty(key)
9163 if ( ! Reflect.has( object, key ) ) {
9164 object[key] = value;
9165 continue;
9166 }
9167 if ( ! Array.isArray( object[key]) ) {
9168 object[key] = [ object[key] ];
9169 }
9170 object[key].push( value );
9171 }
9172
9173 return object;
9174 }
9175
9176 /**
9177 * Show, hide, and sort subfields of Name field on form builder.
9178 *
9179 * @since 4.11
9180 */
9181 function handleNameFieldOnFormBuilder() {
9182 /**
9183 * Gets subfield element from cache.
9184 *
9185 * @param {String} fieldId Field ID.
9186 * @param {String} key Cache key.
9187 * @returns {HTMLElement|undefined} Return the element from cache or undefined if not found.
9188 */
9189 const getSubFieldElFromCache = ( fieldId, key ) => {
9190 window.frmCachedSubFields = window.frmCachedSubFields || {};
9191 window.frmCachedSubFields[fieldId] = window.frmCachedSubFields[fieldId] || {};
9192 return window.frmCachedSubFields[fieldId][key];
9193 };
9194
9195 /**
9196 * Sets subfield element to cache.
9197 *
9198 * @param {String} fieldId Field ID.
9199 * @param {String} key Cache key.
9200 * @param {HTMLElement} el Element.
9201 */
9202 const setSubFieldElToCache = ( fieldId, key, el ) => {
9203 window.frmCachedSubFields = window.frmCachedSubFields || {};
9204 window.frmCachedSubFields[fieldId] = window.frmCachedSubFields[fieldId] || {};
9205 window.frmCachedSubFields[fieldId][key] = el;
9206 };
9207
9208 /**
9209 * Gets column class from the number of columns.
9210 *
9211 * @param {Number} colCount Number of columns.
9212 * @returns {string}
9213 */
9214 const getColClass = colCount => 'frm' + parseInt( 12 / colCount );
9215
9216 const colClasses = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ].map( num => 'frm' + num );
9217
9218 const allSubFieldNames = [ 'first', 'middle', 'last' ];
9219
9220 /**
9221 * Handles name layout change.
9222 *
9223 * @param {Event} event Event object.
9224 */
9225 const onChangeLayout = event => {
9226 const value = event.target.value;
9227 const subFieldNames = value.split( '_' );
9228 const fieldId = event.target.dataset.fieldId;
9229
9230 /*
9231 * Live update form on the form builder.
9232 */
9233 const container = document.querySelector( '#field_' + fieldId + '_inner_container .frm_combo_inputs_container' );
9234 const newColClass = getColClass( subFieldNames.length );
9235
9236 // Set all sub field elements to cache and hide all of them first.
9237 allSubFieldNames.forEach( name => {
9238 const subFieldEl = container.querySelector( '[data-sub-field-name="' + name + '"]' );
9239 if ( subFieldEl ) {
9240 subFieldEl.classList.add( 'frm_hidden' );
9241 subFieldEl.classList.remove( ...colClasses );
9242 setSubFieldElToCache( fieldId, name, subFieldEl );
9243 }
9244 });
9245
9246 subFieldNames.forEach( subFieldName => {
9247 const subFieldEl = getSubFieldElFromCache( fieldId, subFieldName );
9248 if ( ! subFieldEl ) {
9249 return;
9250 }
9251
9252 subFieldEl.classList.remove( 'frm_hidden' );
9253 subFieldEl.classList.add( newColClass );
9254
9255 container.append( subFieldEl );
9256 });
9257
9258 /*
9259 * Live update subfield options.
9260 */
9261 // Hide all subfield options.
9262 allSubFieldNames.forEach( name => {
9263 const optionsEl = document.querySelector( '.frm_sub_field_options-' + name + '[data-field-id="' + fieldId + '"]' );
9264 if ( optionsEl ) {
9265 optionsEl.classList.add( 'frm_hidden' );
9266 setSubFieldElToCache( fieldId, name + '_options', optionsEl );
9267 }
9268 });
9269
9270 subFieldNames.forEach( subFieldName => {
9271 const optionsEl = getSubFieldElFromCache( fieldId, subFieldName + '_options' );
9272 if ( ! optionsEl ) {
9273 return;
9274 }
9275 optionsEl.classList.remove( 'frm_hidden' );
9276 });
9277 };
9278
9279 const dropdownSelector = '.frm_name_layout_dropdown';
9280 document.addEventListener( 'change', event => {
9281 if ( event.target.matches( dropdownSelector ) ) {
9282 onChangeLayout( event );
9283 }
9284 }, false );
9285 }
9286
9287 function debounce( func, wait = 100 ) {
9288 return frmDom.util.debounce( func, wait );
9289 }
9290
9291 function addSaveAndDragIconsToOption( fieldId, liObject ) {
9292 let li, useTag, useTagHref;
9293 let hasDragIcon = false;
9294 let hasSaveIcon = false;
9295
9296 if ( liObject.newOption ) {
9297 const parser = new DOMParser();
9298 li = parser.parseFromString( liObject.newOption, 'text/html' ).body.childNodes[0];
9299 } else {
9300 li = liObject;
9301 }
9302
9303 const liIcons = li.querySelectorAll( 'svg' );
9304
9305 liIcons.forEach( ( svg, key ) => {
9306 useTag = svg.getElementsByTagNameNS( 'http://www.w3.org/2000/svg', 'use' )[0];
9307 if ( ! useTag ) {
9308 return;
9309 }
9310 useTagHref = useTag.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) || useTag.getAttribute( 'href' );
9311
9312 if ( useTagHref === '#frm_drag_icon' ) {
9313 hasDragIcon = true;
9314 }
9315
9316 if ( useTagHref === '#frm_save_icon' ) {
9317 hasSaveIcon = true;
9318 }
9319 });
9320
9321 if ( ! hasDragIcon ) {
9322 li.prepend( icons.drag.cloneNode( true ) );
9323 }
9324
9325 if ( li.querySelector( `[id^=field_key_${fieldId}-]` ) && ! hasSaveIcon ) {
9326 li.querySelector( `[id^=field_key_${fieldId}-]` ).after( icons.save.cloneNode( true ) );
9327 }
9328
9329 if ( liObject.newOption ) {
9330 liObject.newOption = li;
9331 }
9332 }
9333
9334 function maybeAddSaveAndDragIcons( fieldId ) {
9335 fieldOptions = document.querySelectorAll( `[id^=frm_delete_field_${fieldId}-]` );
9336 // return if there are no options.
9337 if ( fieldOptions.length < 2 ) {
9338 return;
9339 }
9340
9341 let options = [ ...fieldOptions ].slice( 1 );
9342 options.forEach( ( li, _key ) => {
9343 if ( li.classList.contains( 'frm_other_option' ) ) {
9344 return;
9345 }
9346 addSaveAndDragIconsToOption( fieldId, li );
9347 });
9348 }
9349
9350 function initOnSubmitAction() {
9351 const onChangeType = event => {
9352 if ( ! event.target.checked ) {
9353 return;
9354 }
9355
9356 const actionEl = event.target.closest( '.frm_form_action_settings' );
9357 actionEl.querySelectorAll( '.frm_on_submit_dependent_setting:not(.frm_hidden)' ).forEach( el => {
9358 el.classList.add( 'frm_hidden' );
9359 });
9360
9361 const activeEls = actionEl.querySelectorAll( '.frm_on_submit_dependent_setting[data-show-if-' + event.target.value + ']' );
9362 activeEls.forEach( activeEl => {
9363 activeEl.classList.remove( 'frm_hidden' );
9364 });
9365
9366 actionEl.setAttribute( 'data-on-submit-type', event.target.value );
9367 };
9368
9369 frmDom.util.documentOn( 'change', '.frm_on_submit_type input[type="radio"]', onChangeType );
9370 }
9371
9372 /**
9373 * Listen for click events for an API-loaded email collection form.
9374 *
9375 * This is used for the Active Campaign sign-up form in the inbox page (when there are no messages).
9376 */
9377 function initAddMyEmailAddress() {
9378 jQuery( document ).on(
9379 'click',
9380 '#frm-add-my-email-address',
9381 event => {
9382 event.preventDefault();
9383 addMyEmailAddress();
9384 }
9385 );
9386
9387 const emptyInbox = document.getElementById( 'frm_empty_inbox' );
9388 if ( emptyInbox ) {
9389 const leaveEmailModal = document.getElementById( 'frm-leave-email-modal' );
9390 leaveEmailModal.classList.remove( 'frm_hidden' );
9391 leaveEmailModal.querySelector( '.frm_modal_footer' ).classList.add( 'frm_hidden' );
9392
9393 const leaveEmailIput = document.getElementById( 'frm_leave_email' );
9394 leaveEmailIput.addEventListener(
9395 'keyup',
9396 event => {
9397 if ( 'Enter' === event.key ) {
9398 const button = document.getElementById( 'frm-add-my-email-address' );
9399 if ( button ) {
9400 button.click();
9401 }
9402 }
9403 }
9404 );
9405 }
9406 }
9407
9408 function addMyEmailAddress() {
9409 const email = document.getElementById( 'frm_leave_email' ).value.trim();
9410 if ( '' === email ) {
9411 handleEmailAddressError( 'empty' );
9412 return;
9413 }
9414
9415 const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
9416 if ( regex.test( email ) === false ) {
9417 handleEmailAddressError( 'invalid' );
9418 return;
9419 }
9420
9421 const $hiddenForm = jQuery( '#frmapi-email-form' ).find( 'form' );
9422 const $hiddenEmailField = $hiddenForm.find( '[type="email"]' ).not( '.frm_verify' );
9423 if ( ! $hiddenEmailField.length ) {
9424 return;
9425 }
9426
9427 const emptyInbox = document.getElementById( 'frm_empty_inbox' );
9428 if ( emptyInbox ) {
9429 document.getElementById( 'frm-add-my-email-address' ).remove();
9430
9431 const emailWrapper = document.getElementById( 'frm_leave_email_wrapper' );
9432 if ( emailWrapper ) {
9433 emailWrapper.classList.add( 'frm_hidden' );
9434 const spinner = span({ className: 'frm-wait frm_spinner' });
9435 spinner.style.visibility = 'visible';
9436 spinner.style.float = 'none';
9437 spinner.style.width = 'unset';
9438 emailWrapper.parentElement.insertBefore(
9439 spinner,
9440 emailWrapper.nextElementSibling
9441 );
9442 }
9443 }
9444
9445 $hiddenEmailField.val( email );
9446 jQuery.ajax({
9447 type: 'POST',
9448 url: $hiddenForm.attr( 'action' ),
9449 data: $hiddenForm.serialize() + '&action=frm_forms_preview'
9450 }).done( function( data ) {
9451 const message = jQuery( data ).find( '.frm_message' ).text().trim();
9452 if ( message.indexOf( 'Thanks!' ) === -1 ) {
9453 handleEmailAddressError( 'invalid' );
9454 return;
9455 }
9456
9457 const apiForm = document.getElementById( 'frmapi-email-form' );
9458 const spinner = apiForm.parentElement.querySelector( '.frm_spinner' );
9459 if ( spinner ) {
9460 spinner.remove();
9461 }
9462
9463 // Handle successful form submission.
9464 // handle the Active Campaign form on the inbox page.
9465 document.getElementById( 'frm_leave_email_wrapper' ).replaceWith(
9466 span({ text: __( 'Thank you for signing up!', 'formidable' ) })
9467 );
9468 });
9469 }
9470
9471 /**
9472 * Adds footer links to the admin body content.
9473 *
9474 * @return {void}
9475 */
9476 function addAdminFooterLinks() {
9477 const footerLinks = document.querySelector( '.frm-admin-footer-links' );
9478 const bodyContent = document.querySelector( '#wpbody-content' );
9479
9480 if ( ! footerLinks || ! bodyContent ) {
9481 return;
9482 }
9483
9484 bodyContent.appendChild( footerLinks );
9485 footerLinks.classList.remove( 'frm_hidden' );
9486 }
9487
9488 /**
9489 * Apply zebra striping to a table while ignoring empty rows.
9490 *
9491 * @param {string} tableSelector The CSS selector for the table.
9492 * @param {string} emptyRowClass The class name used to identify empty rows.
9493 */
9494 function applyZebraStriping( tableSelector, emptyRowClass ) {
9495 // Get all non-empty table rows within the specified table
9496 const rows = document.querySelectorAll( `${tableSelector} tr${emptyRowClass ? `:not(.${emptyRowClass})` : ''}` );
9497 if ( rows.length < 1 ) {
9498 return;
9499 }
9500
9501 let isOdd = true;
9502 rows.forEach( row => {
9503 // Clean old "frm-odd" or "frm-even" classes and add the appropriate new class
9504 row.classList.remove( 'frm-odd', 'frm-even' );
9505 row.classList.add( isOdd ? 'frm-odd' : 'frm-even' );
9506
9507 isOdd = ! isOdd;
9508 });
9509
9510 const tables = document.querySelectorAll( tableSelector );
9511 tables.forEach( table => table.classList.add( 'frm-zebra-striping' ) );
9512 };
9513
9514 return {
9515 init: function() {
9516 initAddMyEmailAddress();
9517 addAdminFooterLinks();
9518
9519 s = {};
9520
9521 // Bootstrap dropdown button
9522 jQuery( '.wp-admin' ).on( 'click', function( e ) {
9523 var t = jQuery( e.target );
9524 var $openDrop = jQuery( '.dropdown.open' );
9525 if ( $openDrop.length && ! t.hasClass( 'dropdown' ) && ! t.closest( '.dropdown' ).length ) {
9526 $openDrop.removeClass( 'open' );
9527 }
9528 });
9529 jQuery( '#frm_bs_dropdown:not(.open) a' ).on( 'click', focusSearchBox );
9530
9531 if ( typeof thisFormId === 'undefined' ) {
9532 thisFormId = jQuery( document.getElementById( 'form_id' ) ).val();
9533 }
9534
9535 // Add event listener for dismissible warning messages.
9536 document.querySelectorAll( '.frm-warning-dismiss' ).forEach( ( dismissIcon ) => {
9537 onClickPreventDefault( dismissIcon, dismissWarningMessage );
9538 });
9539
9540 frmAdminBuild.inboxBannerInit();
9541
9542 if ( $newFields.length > 0 ) {
9543 // only load this on the form builder page
9544 frmAdminBuild.buildInit();
9545 } else if ( document.getElementById( 'frm_notification_settings' ) !== null ) {
9546 // only load on form settings page
9547 frmAdminBuild.settingsInit();
9548 } else if ( document.getElementById( 'frm_styling_form' ) !== null ) {
9549 // load styling settings js
9550 frmAdminBuild.styleInit();
9551 } else if ( document.getElementById( 'form_global_settings' ) !== null ) {
9552 // global settings page
9553 frmAdminBuild.globalSettingsInit();
9554 } else if ( document.getElementById( 'frm_export_xml' ) !== null ) {
9555 // import/export page
9556 frmAdminBuild.exportInit();
9557 } else if ( document.getElementById( 'frm_dyncontent' ) !== null ) {
9558 // only load on views settings page
9559 frmAdminBuild.viewInit();
9560 } else if ( document.getElementById( 'frm_inbox_page' ) !== null ) {
9561 // Inbox page
9562 frmAdminBuild.inboxInit();
9563 } else if ( document.getElementById( 'frm-welcome' ) !== null ) {
9564 // Solution install page
9565 frmAdminBuild.solutionInit();
9566 } else {
9567 initSelectionAutocomplete();
9568
9569 jQuery( '[data-frmprint]' ).on( 'click', function() {
9570 window.print();
9571 return false;
9572 });
9573 }
9574
9575 jQuery( document ).on( 'change', 'select[data-toggleclass], input[data-toggleclass]', toggleFormOpts );
9576
9577 var $advInfo = jQuery( document.getElementById( 'frm_adv_info' ) );
9578 if ( $advInfo.length > 0 || jQuery( '.frm_field_list' ).length > 0 ) {
9579 // only load on the form, form settings, and view settings pages
9580 frmAdminBuild.panelInit();
9581 }
9582
9583 loadTooltips();
9584 initUpgradeModal();
9585
9586 // used on build, form settings, and view settings
9587 var $shortCodeDiv = jQuery( document.getElementById( 'frm_shortcodediv' ) );
9588 if ( $shortCodeDiv.length > 0 ) {
9589 jQuery( 'a.edit-frm_shortcode' ).on( 'click', function() {
9590 if ( $shortCodeDiv.is( ':hidden' ) ) {
9591 $shortCodeDiv.slideDown( 'fast' );
9592 this.style.display = 'none';
9593 }
9594 return false;
9595 });
9596
9597 jQuery( '.cancel-frm_shortcode', '#frm_shortcodediv' ).on( 'click', function() {
9598 $shortCodeDiv.slideUp( 'fast' );
9599 $shortCodeDiv.siblings( 'a.edit-frm_shortcode' ).show();
9600 return false;
9601 });
9602 }
9603
9604 // tabs
9605 jQuery( document ).on( 'click', '#frm-nav-tabs a', clickNewTab );
9606 jQuery( '.post-type-frm_display .frm-nav-tabs a, .frm-category-tabs a' ).on( 'click', function() {
9607 const showUpgradeTab = this.classList.contains( 'frm_show_upgrade_tab' );
9608 if ( this.classList.contains( 'frm_noallow' ) && ! showUpgradeTab ) {
9609 return;
9610 }
9611
9612 if ( showUpgradeTab ) {
9613 populateUpgradeTab( this );
9614 }
9615
9616 clickTab( this );
9617 return false;
9618 });
9619 clickTab( jQuery( '.starttab a' ), 'auto' );
9620
9621 // submit the search form with dropdown
9622 jQuery( document ).on( 'click', '#frm-fid-search-menu a', function() {
9623 var val = this.id.replace( 'fid-', '' );
9624 jQuery( 'select[name="fid"]' ).val( val );
9625 triggerSubmit( document.getElementById( 'posts-filter' ) );
9626 return false;
9627 });
9628
9629 jQuery( '.frm_select_box' ).on( 'click focus', function() {
9630 this.select();
9631 });
9632
9633 jQuery( document ).on( 'input search change', '.frm-auto-search:not(#frm-form-templates-page #template-search-input)', searchContent );
9634 jQuery( document ).on( 'focusin click', '.frm-auto-search', stopPropagation );
9635 var autoSearch = jQuery( '.frm-auto-search' );
9636 if ( autoSearch.val() !== '' ) {
9637 autoSearch.trigger( 'keyup' );
9638 }
9639
9640 // Initialize Formidable Connection.
9641 FrmFormsConnect.init();
9642
9643 jQuery( document ).on( 'click', '.frm-install-addon', installAddon );
9644 jQuery( document ).on( 'click', '.frm-activate-addon', activateAddon );
9645 jQuery( document ).on( 'click', '.frm-solution-multiple', installMultipleAddons );
9646
9647 // prevent annoying confirmation message from WordPress
9648 jQuery( 'button, input[type=submit]' ).on( 'click', removeWPUnload );
9649
9650 addMultiselectLabelListener();
9651
9652 frmAdminBuild.hooks.addFilter(
9653 'frm_before_embed_modal',
9654 ( ids, { element, type }) => {
9655 if ( 'form' !== type ) {
9656 return ids;
9657 }
9658
9659 let formId, formKey;
9660 const row = element.closest( 'tr' );
9661
9662 if ( row ) {
9663 // Embed icon on form index.
9664 formId = parseInt( row.querySelector( '.column-id' ).textContent );
9665 formKey = row.querySelector( '.column-form_key' ).textContent;
9666 } else {
9667 // Embed button in form builder / form settings.
9668 formId = document.getElementById( 'form_id' ).value;
9669
9670 const formKeyInput = document.getElementById( 'frm_form_key' );
9671 if ( formKeyInput ) {
9672 formKey = formKeyInput.value;
9673 } else {
9674 const previewDrop = document.getElementById( 'frm-previewDrop' );
9675 if ( previewDrop ) {
9676 formKey = previewDrop.nextElementSibling.querySelector( '.dropdown-item a' ).getAttribute( 'href' ).split( 'form=' )[1];
9677 }
9678 }
9679 }
9680
9681 return [ formId, formKey ];
9682 }
9683 );
9684
9685 document.querySelectorAll( '#frm-show-fields > li, .frm_grid_container li' ).forEach( ( el, _key ) => {
9686 el.addEventListener( 'click', function() {
9687 let fieldId = this.querySelector( 'li' )?.dataset.fid || this.dataset.fid;
9688 maybeAddSaveAndDragIcons( fieldId );
9689 });
9690 });
9691 },
9692
9693 buildInit: function() {
9694 let loadFieldId, $builderForm, builderArea;
9695
9696 debouncedSyncAfterDragAndDrop = debounce( syncAfterDragAndDrop, 10 );
9697 postBodyContent = document.getElementById( 'post-body-content' );
9698 $postBodyContent = jQuery( postBodyContent );
9699
9700 if ( jQuery( '.frm_field_loading' ).length ) {
9701 loadFieldId = jQuery( '.frm_field_loading' ).first().attr( 'id' );
9702 loadFields( loadFieldId );
9703 }
9704
9705 setupSortable( 'ul.frm_sorting' );
9706
9707 document.querySelectorAll( '.field_type_list > li:not(.frm_show_upgrade)' ).forEach( makeDraggable );
9708
9709 jQuery( 'ul.field_type_list, .field_type_list li, ul.frm_code_list, .frm_code_list li, .frm_code_list li a, #frm_adv_info #category-tabs li, #frm_adv_info #category-tabs li a' ).disableSelection();
9710
9711 jQuery( '.frm_submit_ajax' ).on( 'click', submitBuild );
9712 jQuery( '.frm_submit_no_ajax' ).on( 'click', submitNoAjax );
9713
9714 addFormNameModalEvents();
9715
9716 jQuery( 'a.edit-form-status' ).on( 'click', slideDown );
9717 jQuery( '.cancel-form-status' ).on( 'click', slideUp );
9718 jQuery( '.save-form-status' ).on( 'click', function() {
9719 const newStatus = jQuery( document.getElementById( 'form_change_status' ) ).val();
9720 jQuery( 'input[name="new_status"]' ).val( newStatus );
9721 jQuery( document.getElementById( 'form-status-display' ) ).html( newStatus );
9722 jQuery( '.cancel-form-status' ).trigger( 'click' );
9723 return false;
9724 });
9725
9726 jQuery( '.frm_form_builder form' ).first().on( 'submit', function() {
9727 jQuery( '.inplace_field' ).trigger( 'blur' );
9728 });
9729
9730 initiateMultiselect();
9731 renumberPageBreaks();
9732
9733 $builderForm = jQuery( builderForm );
9734 builderArea = document.getElementById( 'frm_form_editor_container' );
9735 $builderForm.on( 'click', '.frm_add_logic_row', addFieldLogicRow );
9736 $builderForm.on( 'click', '.frm_add_watch_lookup_row', addWatchLookupRow );
9737 $builderForm.on( 'change', '.frm_get_values_form', updateGetValueFieldSelection );
9738 $builderForm.on( 'change', '.frm_logic_field_opts', getFieldValues );
9739 $builderForm.on( 'change', '.radio_maxnum', setStarValues );
9740 $builderForm.on( 'frm-multiselect-changed', 'select[name^="field_options[admin_only_"]', adjustVisibilityValuesForEveryoneValues );
9741
9742 jQuery( document.getElementById( 'frm-insert-fields' ) ).on( 'click', '.frm_add_field', addFieldClick );
9743 $newFields.on( 'click', '.frm_clone_field', duplicateField );
9744 $builderForm.on( 'blur', 'input[id^="frm_calc"]', checkCalculationCreatedByUser );
9745 $builderForm.on( 'change', 'input.frm_format_opt, input.frm_max_length_opt', toggleInvalidMsg );
9746 $builderForm.on( 'change click', '[data-changeme]', liveChanges );
9747 $builderForm.on( 'click', 'input.frm_req_field', markRequired );
9748 $builderForm.on( 'click', '.frm_mark_unique', markUnique );
9749
9750 $builderForm.on( 'change', '.frm_repeat_format', toggleRepeatButtons );
9751 $builderForm.on( 'change', '.frm_repeat_limit', checkRepeatLimit );
9752 $builderForm.on( 'change', '.frm_js_checkbox_limit', checkCheckboxSelectionsLimit );
9753 $builderForm.on( 'input', 'input[name^="field_options[add_label_"]', function() {
9754 updateRepeatText( this, 'add' );
9755 });
9756 $builderForm.on( 'input', 'input[name^="field_options[remove_label_"]', function() {
9757 updateRepeatText( this, 'remove' );
9758 });
9759 $builderForm.on( 'change', 'select[name^="field_options[data_type_"]', maybeClearWatchFields );
9760 jQuery( builderArea ).on( 'click', '.frm-collapse-page', maybeCollapsePage );
9761 jQuery( builderArea ).on( 'click', '.frm-collapse-section', maybeCollapseSection );
9762 $builderForm.on( 'click', '.frm-single-settings h3', maybeCollapseSettings );
9763 $builderForm.on( 'keydown', '.frm-single-settings h3', function( event ) {
9764 // If so, only proceed if the key pressed was 'Enter' or 'Space'
9765 if ( event.key === 'Enter' || event.key === ' ' ) {
9766 event.preventDefault();
9767 maybeCollapseSettings.call( this, event );
9768 }
9769 });
9770
9771 jQuery( builderArea ).on( 'show.bs.dropdown hide.bs.dropdown', changeSectionStyle );
9772
9773 $builderForm.on( 'click', '.frm_toggle_sep_values', toggleSepValues );
9774 $builderForm.on( 'click', '.frm_toggle_image_options', toggleImageOptions );
9775 $builderForm.on( 'click', '.frm_remove_image_option', removeImageFromOption );
9776 $builderForm.on( 'click', '.frm_choose_image_box', addImageToOption );
9777 $builderForm.on( 'change', '.frm_hide_image_text', refreshOptionDisplay );
9778 $builderForm.on( 'change', '.frm_field_options_image_size', setImageSize );
9779 $builderForm.on( 'click', '.frm_multiselect_opt', toggleMultiselect );
9780 $newFields.on( 'mousedown', 'input, textarea, select', stopFieldFocus );
9781 $newFields.on( 'click', 'input[type=radio], input[type=checkbox]', stopFieldFocus );
9782 $newFields.on( 'click', '.frm_delete_field', clickDeleteField );
9783 $newFields.on( 'click', '.frm_select_field', clickSelectField );
9784 jQuery( document ).on( 'click', '.frm_delete_field_group', clickDeleteFieldGroup );
9785 jQuery( document ).on( 'click', '.frm_clone_field_group', duplicateFieldGroup );
9786 jQuery( document ).on( 'click', '#frm_field_group_controls > span:first-child', clickFieldGroupLayout );
9787 jQuery( document ).on( 'click', '.frm-row-layout-option', handleFieldGroupLayoutOptionClick );
9788 jQuery( document ).on( 'click', '.frm-merge-fields-into-row .frm-row-layout-option', handleFieldGroupLayoutOptionInsideMergeClick );
9789 jQuery( document ).on( 'click', '.frm-custom-field-group-layout', customFieldGroupLayoutClick );
9790 jQuery( document ).on( 'click', '.frm-merge-fields-into-row .frm-custom-field-group-layout', customFieldGroupLayoutInsideMergeClick );
9791 jQuery( document ).on( 'click', '.frm-break-field-group', breakFieldGroupClick );
9792 $newFields.on( 'click', '#frm_field_group_popup .frm_grid_container input', focusFieldGroupInputOnClick );
9793 jQuery( document ).on( 'click', '.frm-cancel-custom-field-group-layout', cancelCustomFieldGroupClick );
9794 jQuery( document ).on( 'click', '.frm-save-custom-field-group-layout', saveCustomFieldGroupClick );
9795 $newFields.on( 'click', 'ul.frm_sorting', fieldGroupClick );
9796 jQuery( document ).on( 'click', '.frm-merge-fields-into-row', mergeFieldsIntoRowClick );
9797 jQuery( document ).on( 'click', '.frm-delete-field-groups', deleteFieldGroupsClick );
9798 $newFields.on( 'click', '.frm-field-action-icons [data-toggle="dropdown"]', function() {
9799 this.closest( 'li.form-field' ).classList.add( 'frm-field-settings-open' );
9800 jQuery( document ).on( 'click', '#frm_builder_page', handleClickOutsideOfFieldSettings );
9801 });
9802 $newFields.on( 'mousemove', 'ul.frm_sorting', checkForMultiselectKeysOnMouseMove );
9803 $newFields.on( 'show.bs.dropdown', '.frm-field-action-icons', onFieldActionDropdownShow );
9804 jQuery( document ).on( 'show.bs.dropdown', '#frm_field_group_controls', onFieldGroupActionDropdownShow );
9805 $builderForm.on( 'click', '.frm_single_option a[data-removeid]', deleteFieldOption );
9806 $builderForm.on( 'mousedown', '.frm_single_option input[type=radio]', maybeUncheckRadio );
9807 $builderForm.on( 'focusin', '.frm_single_option input[type=text]', maybeClearOptText );
9808 $builderForm.on( 'click', '.frm_add_opt', addFieldOption );
9809 $builderForm.on( 'change', '.frm_single_option input', resetOptOnChange );
9810 $builderForm.on( 'change', '.frm_image_id', resetOptOnChange );
9811 $builderForm.on( 'change', '.frm_toggle_mult_sel', toggleMultSel );
9812 $builderForm.on( 'focusin', '.frm_classes', showBuilderModal );
9813
9814 $newFields.on( 'click', '.frm_primary_label', clickLabel );
9815 $newFields.on( 'click', '.frm_description', clickDescription );
9816 $newFields.on( 'click', 'li.ui-state-default:not(.frm_noallow)', clickVis );
9817 $newFields.on( 'dblclick', 'li.ui-state-default', openAdvanced );
9818 $builderForm.on( 'change', '.frm_tax_form_select', toggleFormTax );
9819 $builderForm.on( 'change', 'select.conf_field', addConf );
9820
9821 $builderForm.on( 'change', '.frm_get_field_selection', getFieldSelection );
9822
9823 $builderForm.on( 'click', '.frm-show-inline-modal', maybeShowInlineModal );
9824
9825 $builderForm.on( 'click', '.frm-inline-modal .dismiss', dismissInlineModal );
9826 jQuery( document ).on( 'change', '[data-frmchange]', changeInputtedValue );
9827
9828 $builderForm.on( 'change', '.frm_include_extras_field', rePopCalcFieldsForSummary );
9829 $builderForm.on( 'change', 'select[name^="field_options[form_select_"]', maybeChangeEmbedFormMsg );
9830
9831 jQuery( document ).on( 'submit', '#frm_js_build_form', buildSubmittedNoAjax );
9832 jQuery( document ).on( 'change', '#frm_builder_page input:not(.frm-search-input):not(.frm-custom-grid-size-input), #frm_builder_page select, #frm_builder_page textarea', fieldUpdated );
9833
9834 popAllProductFields();
9835
9836 jQuery( document ).on( 'change', '.frmjs_prod_data_type_opt', toggleProductType );
9837
9838 jQuery( document ).on( 'focus', '.frm-single-settings ul input[type="text"][name^="field_options[options_"]', onOptionTextFocus );
9839 jQuery( document ).on( 'blur', '.frm-single-settings ul input[type="text"][name^="field_options[options_"]', onOptionTextBlur );
9840
9841 initBulkOptionsOverlay();
9842 hideEmptyEle();
9843 maybeHideQuantityProductFieldOption();
9844 handleNameFieldOnFormBuilder();
9845 toggleSectionHolder();
9846 handleShowPasswordLiveUpdate();
9847 },
9848
9849 settingsInit: function() {
9850 const $formActions = jQuery( document.getElementById( 'frm_notification_settings' ) );
9851
9852 let formSettings, $loggedIn, $cookieExp, $editable;
9853
9854 // BCC, CC, and Reply To button functionality
9855 $formActions.on( 'click', '.frm_email_buttons', showEmailRow );
9856 $formActions.on( 'click', '.frm_remove_field', hideEmailRow );
9857 $formActions.on( 'change', '.frm_to_row, .frm_from_row', showEmailWarning );
9858 $formActions.on( 'change', '.frm_tax_selector', changePosttaxRow );
9859 $formActions.on( 'change', 'select.frm_single_post_field', checkDupPost );
9860 $formActions.on( 'change', 'select.frm_toggle_post_content', togglePostContent );
9861 $formActions.on( 'change', 'select.frm_dyncontent_opt', fillDyncontent );
9862 $formActions.on( 'change', '.frm_post_type', switchPostType );
9863 $formActions.on( 'click', '.frm_add_postmeta_row', addPostmetaRow );
9864 $formActions.on( 'click', '.frm_add_posttax_row', addPosttaxRow );
9865 $formActions.on( 'click', '.frm_toggle_cf_opts', toggleCfOpts );
9866 $formActions.on( 'click', '.frm_duplicate_form_action', copyFormAction );
9867 jQuery( '.frm_actions_list' ).on( 'click', '.frm_active_action', addFormAction );
9868 jQuery( '#frm-show-groups, #frm-hide-groups' ).on( 'click', toggleActionGroups );
9869 initiateMultiselect();
9870
9871 //set actions icons to inactive
9872 jQuery( 'ul.frm_actions_list li' ).each( function() {
9873 checkActiveAction( jQuery( this ).children( 'a' ).data( 'actiontype' ) );
9874
9875 // If the icon is a background image, don't add BG color.
9876 const icon = jQuery( this ).find( 'i' );
9877 if ( icon.css( 'background-image' ) !== 'none' ) {
9878 icon.addClass( 'frm-inverse' );
9879 }
9880 });
9881
9882 jQuery( '.frm_submit_settings_btn' ).on( 'click', submitSettings );
9883
9884 addFormNameModalEvents();
9885
9886 formSettings = jQuery( '.frm_form_settings' );
9887 formSettings.on( 'click', '.frm_add_form_logic', addFormLogicRow );
9888 formSettings.on( 'blur', '.frm_email_blur', formatEmailSetting );
9889 formSettings.on( 'click', '.frm_already_used', onlyOneActionMessage );
9890
9891 formSettings.on( 'change', '#logic_link_submit', toggleSubmitLogic );
9892 formSettings.on( 'click', '.frm_add_submit_logic', addSubmitLogic );
9893 formSettings.on( 'change', '.frm_submit_logic_field_opts', addSubmitLogicOpts );
9894
9895 document.addEventListener(
9896 'click',
9897 function handleImageUploadClickEvents( event ) {
9898 const { target } = event;
9899
9900 if ( ! target.closest( '.frm_image_preview_wrapper' ) ) {
9901 return;
9902 }
9903
9904 if ( target.closest( '.frm_choose_image_box' ) ) {
9905 addImageToOption.bind( target )( event );
9906 return;
9907 }
9908
9909 if ( target.closest( '.frm_remove_image_option' ) ) {
9910 removeImageFromOption.bind( target )( event );
9911 }
9912 }
9913 );
9914
9915 // Close shortcode modal on click.
9916 formSettings.on( 'mouseup', '*:not(.frm-show-box)', function( e ) {
9917 e.stopPropagation();
9918
9919 if ( e.target.classList.contains( 'frm-show-box' ) || e.target.parentElement.classList.contains( 'frm-show-box' ) ) {
9920 return;
9921 }
9922
9923 const sidebar = document.getElementById( 'frm_adv_info' );
9924 if ( ! sidebar ) {
9925 return;
9926 }
9927
9928 if ( sidebar.getAttribute( 'data-fills' ) === e.target.id && typeof e.target.id !== 'undefined' ) {
9929 return;
9930 }
9931
9932 const isChild = jQuery( e.target ).closest( '#frm_adv_info' ).length > 0;
9933
9934 if ( ! isChild && sidebar.display !== 'none' ) {
9935 hideShortcodes( sidebar );
9936 }
9937 });
9938
9939 //Warning when user selects "Do not store entries ..."
9940 jQuery( document.getElementById( 'no_save' ) ).on( 'change', function() {
9941 if ( this.checked ) {
9942 if ( confirm( frm_admin_js.no_save_warning ) !== true ) { // eslint-disable-line camelcase
9943 // Uncheck box if user hits "Cancel"
9944 jQuery( this ).attr( 'checked', false );
9945 }
9946 }
9947 });
9948
9949 jQuery( 'select[name="options[edit_action]"]' ).on( 'change', showSuccessOpt );
9950
9951 $loggedIn = document.getElementById( 'logged_in' );
9952 jQuery( $loggedIn ).on( 'change', function() {
9953 if ( this.checked ) {
9954 visible( '.hide_logged_in' );
9955 } else {
9956 invisible( '.hide_logged_in' );
9957 }
9958 });
9959
9960 $cookieExp = jQuery( document.getElementById( 'frm_cookie_expiration' ) );
9961 jQuery( document.getElementById( 'frm_single_entry_type' ) ).on( 'change', function() {
9962 if ( this.value === 'cookie' ) {
9963 $cookieExp.fadeIn( 'slow' );
9964 } else {
9965 $cookieExp.fadeOut( 'slow' );
9966 }
9967 });
9968
9969 var $singleEntry = document.getElementById( 'single_entry' );
9970 jQuery( $singleEntry ).on( 'change', function() {
9971 if ( this.checked ) {
9972 visible( '.hide_single_entry' );
9973 } else {
9974 invisible( '.hide_single_entry' );
9975 }
9976
9977 if ( this.checked && jQuery( document.getElementById( 'frm_single_entry_type' ) ).val() === 'cookie' ) {
9978 $cookieExp.fadeIn( 'slow' );
9979 } else {
9980 $cookieExp.fadeOut( 'slow' );
9981 }
9982 });
9983
9984 jQuery( '.hide_save_draft' ).hide();
9985
9986 var $saveDraft = jQuery( document.getElementById( 'save_draft' ) );
9987 $saveDraft.on( 'change', function() {
9988 if ( this.checked ) {
9989 jQuery( '.hide_save_draft' ).fadeIn( 'slow' );
9990 } else {
9991 jQuery( '.hide_save_draft' ).fadeOut( 'slow' );
9992 }
9993 });
9994 triggerChange( $saveDraft );
9995
9996 //If Allow editing is checked/unchecked
9997 $editable = document.getElementById( 'editable' );
9998 jQuery( $editable ).on( 'change', function() {
9999 if ( this.checked ) {
10000 jQuery( '.hide_editable' ).fadeIn( 'slow' );
10001 triggerChange( document.getElementById( 'edit_action' ) );
10002 } else {
10003 jQuery( '.hide_editable' ).fadeOut( 'slow' );
10004 jQuery( '.edit_action_message_box' ).fadeOut( 'slow' );//Hide On Update message box
10005 }
10006 });
10007
10008 //If File Protection is checked/unchecked
10009 jQuery( document ).on( 'change', '#protect_files', function() {
10010 if ( this.checked ) {
10011 jQuery( '.hide_protect_files' ).fadeIn( 'slow' );
10012 } else {
10013 jQuery( '.hide_protect_files' ).fadeOut( 'slow' );
10014 }
10015 });
10016
10017 jQuery( document ).on( 'frm-multiselect-changed', '#protect_files_role', adjustVisibilityValuesForEveryoneValues );
10018
10019 jQuery( document ).on( 'submit', '.frm_form_settings', settingsSubmitted );
10020 jQuery( document ).on( 'change', '#form_settings_page input:not(.frm-search-input), #form_settings_page select, #form_settings_page textarea', fieldUpdated );
10021
10022 // Page Selection Autocomplete
10023 initSelectionAutocomplete();
10024
10025 jQuery( document ).on( 'frm-action-loaded', onActionLoaded );
10026
10027 initOnSubmitAction();
10028 },
10029
10030 panelInit: function() {
10031 var customPanel, settingsPage, viewPage, insertFieldsTab;
10032
10033 jQuery( '.frm_wrap, #postbox-container-1' ).on( 'click', '.frm_insert_code', insertCode );
10034 jQuery( document ).on( 'change', '.frm_insert_val', function() {
10035 insertFieldCode( jQuery( this ).data( 'target' ), jQuery( this ).val() );
10036 jQuery( this ).val( '' );
10037 });
10038
10039 jQuery( document ).on( 'click change', '#frm-id-key-condition', resetLogicBuilder );
10040 jQuery( document ).on( 'keyup change', '.frm-build-logic', setLogicExample );
10041
10042 showInputIcon();
10043 jQuery( document ).on( 'frmElementAdded', function( event, parentEle ) {
10044 /* This is here for add-ons to trigger */
10045 showInputIcon( parentEle );
10046 });
10047 jQuery( document ).on( 'mousedown', '.frm-show-box', showShortcodes );
10048
10049 settingsPage = document.getElementById( 'form_settings_page' );
10050 viewPage = document.body.classList.contains( 'post-type-frm_display' );
10051 insertFieldsTab = document.getElementById( 'frm_insert_fields_tab' );
10052
10053 if ( settingsPage !== null || viewPage ) {
10054 jQuery( document ).on( 'focusin', 'form input, form textarea', function( e ) {
10055 var htmlTab;
10056 e.stopPropagation();
10057 maybeShowModal( this );
10058
10059 if ( jQuery( this ).is( ':not(:submit, input[type=button], .frm-search-input, input[type=checkbox])' ) ) {
10060 if ( jQuery( e.target ).closest( '#frm_adv_info' ).length ) {
10061 // Don't trigger for fields inside of the modal.
10062 return;
10063 }
10064
10065 if ( settingsPage !== null ) {
10066 /* form settings page */
10067 htmlTab = jQuery( '#frm_html_tab' );
10068 if ( jQuery( this ).closest( '#html_settings' ).length > 0 ) {
10069 htmlTab.show();
10070 htmlTab.siblings().hide();
10071 jQuery( '#frm_html_tab a' ).trigger( 'click' );
10072 toggleAllowedHTML( this, e.type );
10073 } else {
10074 showElement( jQuery( '.frm-category-tabs li' ) );
10075 insertFieldsTab.click();
10076 htmlTab.hide();
10077 htmlTab.siblings().show();
10078 }
10079 } else if ( viewPage ) {
10080 // Run on view page.
10081 toggleAllowedShortcodes( this.id, e.type );
10082 }
10083 }
10084 });
10085 }
10086
10087 jQuery( '.frm_wrap, #postbox-container-1' ).on( 'mousedown', '#frm_adv_info a, .frm_field_list a', function( e ) {
10088 e.preventDefault();
10089 });
10090
10091 customPanel = jQuery( '#frm_adv_info' );
10092 customPanel.on( 'click', '.subsubsub a.frmids', function( e ) {
10093 toggleKeyID( 'frmids', e );
10094 });
10095 customPanel.on( 'click', '.subsubsub a.frmkeys', function( e ) {
10096 toggleKeyID( 'frmkeys', e );
10097 });
10098 },
10099
10100 viewInit: function() {
10101 var $addRemove,
10102 $advInfo = jQuery( document.getElementById( 'frm_adv_info' ) );
10103 $advInfo.before( '<div id="frm_position_ele"></div>' );
10104 setupMenuOffset();
10105
10106 jQuery( document ).on( 'blur', '#param', checkDetailPageSlug );
10107 jQuery( document ).on( 'blur', 'input[name^="options[where_val]"]', checkFilterParamNames );
10108
10109 // Show loading indicator.
10110 jQuery( '#publish' ).on( 'mousedown', function() {
10111 fieldsUpdated = 0;
10112 this.classList.add( 'frm_loading_button' );
10113 });
10114
10115 // move content tabs
10116 jQuery( '#frm_dyncontent .handlediv' ).before( jQuery( '#frm_dyncontent .nav-menus-php' ) );
10117
10118 // click content tabs
10119 jQuery( '.nav-tab-wrapper a' ).on( 'click', clickContentTab );
10120
10121 // click tabs after panel is replaced with ajax
10122 jQuery( '#side-sortables' ).on( 'click', '.frm_doing_ajax.categorydiv .category-tabs a', clickTabsAfterAjax );
10123
10124 initToggleShortcodes();
10125 jQuery( '.frm_code_list:not(.frm-dropdown-menu) a' ).addClass( 'frm_noallow' );
10126
10127 jQuery( 'input[name="show_count"]' ).on( 'change', showCount );
10128
10129 jQuery( document.getElementById( 'form_id' ) ).on( 'change', displayFormSelected );
10130
10131 $addRemove = jQuery( '.frm_repeat_rows' );
10132 $addRemove.on( 'click', '.frm_add_order_row', addOrderRow );
10133 $addRemove.on( 'click', '.frm_add_where_row', addWhereRow );
10134 $addRemove.on( 'change', '.frm_insert_where_options', insertWhereOptions );
10135 $addRemove.on( 'change', '.frm_where_is_options', hideWhereOptions );
10136
10137 setDefaultPostStatus();
10138 },
10139
10140 inboxInit: function() {
10141 jQuery( '.frm_inbox_dismiss, footer .frm-button-secondary, footer .frm-button-primary' ).on( 'click', function( e ) {
10142 var message = this.parentNode.parentNode,
10143 key = message.getAttribute( 'data-message' ),
10144 href = this.getAttribute( 'href' );
10145
10146 if ( 'free_templates' === key && ! this.classList.contains( 'frm_inbox_dismiss' ) ) {
10147 return;
10148 }
10149
10150 e.preventDefault();
10151
10152 data = {
10153 action: 'frm_inbox_dismiss',
10154 key: key,
10155 nonce: frmGlobal.nonce
10156 };
10157 postAjax( data, function() {
10158 if ( href !== '#' ) {
10159 window.location = href;
10160 return true;
10161 }
10162 fadeOut( message, function() {
10163 message.parentNode.removeChild( message );
10164 });
10165 });
10166 });
10167 jQuery( '#frm-dismiss-inbox' ).on( 'click', function( e ) {
10168 data = {
10169 action: 'frm_inbox_dismiss',
10170 key: 'all',
10171 nonce: frmGlobal.nonce
10172 };
10173 postAjax( data, function() {
10174 fadeOut( document.getElementById( 'frm_message_list' ), function() {
10175 document.getElementById( 'frm_empty_inbox' ).classList.remove( 'frm_hidden' );
10176 showActiveCampaignForm();
10177 });
10178 });
10179 });
10180
10181 if ( ! document.getElementById( 'frm_empty_inbox' ).classList.contains( 'frm_hidden' ) ) {
10182 showActiveCampaignForm();
10183 }
10184 },
10185
10186 solutionInit: function() {
10187 jQuery( document ).on( 'submit', '#frm-new-template', installTemplate );
10188 },
10189
10190 styleInit: function() {
10191 const $previewWrapper = jQuery( '.frm_image_preview_wrapper' );
10192 $previewWrapper.on( 'click', '.frm_choose_image_box', addImageToOption );
10193 $previewWrapper.on( 'click', '.frm_remove_image_option', removeImageFromOption );
10194
10195 wp.hooks.doAction( 'frm_style_editor_init' );
10196 },
10197
10198 customCSSInit: function() {
10199 console.warn( 'Calling frmAdminBuild.customCSSInit is deprecated.' );
10200 },
10201
10202 globalSettingsInit: function() {
10203 var licenseTab;
10204
10205 jQuery( document ).on( 'click', '[data-frmuninstall]', uninstallNow );
10206
10207 initiateMultiselect();
10208
10209 // activate addon licenses
10210 licenseTab = document.getElementById( 'licenses_settings' );
10211 if ( licenseTab !== null ) {
10212 jQuery( licenseTab ).on( 'click', '.edd_frm_save_license', saveAddonLicense );
10213 }
10214
10215 // Solution install page
10216 jQuery( document ).on( 'click', '#frm-new-template button', installTemplateFieldset );
10217
10218 jQuery( '#frm-dismissable-cta .dismiss' ).on( 'click', function( event ) {
10219 event.preventDefault();
10220 jQuery.post( ajaxurl, {
10221 action: 'frm_lite_settings_upgrade'
10222 });
10223 jQuery( '.settings-lite-cta' ).remove();
10224 });
10225
10226 const captchaType = document.getElementById( 'frm_re_type' );
10227 if ( captchaType ) {
10228 captchaType.addEventListener( 'change', handleCaptchaTypeChange );
10229 }
10230
10231 document.querySelector( '.frm_captchas' ).addEventListener( 'change', function() {
10232 document.querySelector( '.captcha_settings .frm_note_style' ).classList.toggle( 'frm_hidden' );
10233 });
10234 },
10235
10236 exportInit: function() {
10237 jQuery( '.frm_form_importer' ).on( 'submit', startFormMigration );
10238 jQuery( document.getElementById( 'frm_export_xml' ) ).on( 'submit', validateExport );
10239 jQuery( '#frm_export_xml input, #frm_export_xml select' ).on( 'change', removeExportError );
10240 jQuery( 'input[name="frm_import_file"]' ).on( 'change', checkCSVExtension );
10241 document.querySelector( 'select[name="format"]' ).addEventListener( 'change', exportTypeChanged );
10242
10243 jQuery( 'input[name="frm_export_forms[]"]' ).on( 'click', preventMultipleExport );
10244 initiateMultiselect();
10245
10246 jQuery( '.frm-feature-banner .dismiss' ).on( 'click', function( event ) {
10247 event.preventDefault();
10248 jQuery.post( ajaxurl, {
10249 action: 'frm_dismiss_migrator',
10250 plugin: this.id,
10251 nonce: frmGlobal.nonce
10252 });
10253 this.parentElement.remove();
10254 });
10255
10256 showOrHideRepeaters( getExportOption() );
10257
10258 document.querySelector( '#frm-export-select-all' ).addEventListener( 'change', event => {
10259 document.querySelectorAll( '[name="frm_export_forms[]"]' ).forEach( cb => cb.checked = event.target.checked );
10260 });
10261 },
10262
10263 inboxBannerInit: function() {
10264 const banner = document.getElementById( 'frm_banner' );
10265 if ( ! banner ) {
10266 return;
10267 }
10268
10269 const dismissButton = banner.querySelector( '.frm-banner-dismiss' );
10270 document.addEventListener(
10271 'click',
10272 function( event ) {
10273 if ( event.target !== dismissButton ) {
10274 return;
10275 }
10276
10277 const data = {
10278 action: 'frm_inbox_dismiss',
10279 key: banner.dataset.key,
10280 nonce: frmGlobal.nonce
10281 };
10282 postAjax(
10283 data,
10284 function() {
10285 jQuery( banner ).fadeOut(
10286 400,
10287 function() {
10288 banner.remove();
10289 }
10290 );
10291 }
10292 );
10293 }
10294 );
10295 },
10296
10297 updateOpts: function( fieldId, opts, modal ) {
10298 var separate = usingSeparateValues( fieldId ),
10299 action = isProductField( fieldId ) ? 'frm_bulk_products' : 'frm_import_options';
10300 jQuery.ajax({
10301 type: 'POST',
10302 url: ajaxurl,
10303 data: {
10304 action: action,
10305 field_id: fieldId,
10306 opts: opts,
10307 separate: separate,
10308 nonce: frmGlobal.nonce
10309 },
10310 success: function( html ) {
10311 document.getElementById( 'frm_field_' + fieldId + '_opts' ).innerHTML = html;
10312 resetDisplayedOpts( fieldId );
10313
10314 if ( typeof modal !== 'undefined' ) {
10315 modal.dialog( 'close' );
10316 document.getElementById( 'frm-update-bulk-opts' ).classList.remove( 'frm_loading_button' );
10317 }
10318 }
10319 });
10320 },
10321
10322 /* remove conditional logic if the field doesn't exist */
10323 triggerRemoveLogic: function( fieldID, metaName ) {
10324 jQuery( '#frm_logic_' + fieldID + '_' + metaName + ' .frm_remove_tag' ).trigger( 'click' );
10325 },
10326
10327 downloadXML: function( controller, ids, isTemplate ) {
10328 var url = ajaxurl + '?action=frm_' + controller + '_xml&ids=' + ids;
10329 if ( isTemplate !== null ) {
10330 url = url + '&is_template=' + isTemplate;
10331 }
10332 location.href = url;
10333 },
10334
10335 /**
10336 * @since 5.0.04
10337 */
10338 hooks: {
10339 applyFilters: function( hookName, ...args ) {
10340 return wp.hooks.applyFilters( hookName, ...args );
10341 },
10342 addFilter: function( hookName, callback, priority ) {
10343 return wp.hooks.addFilter( hookName, 'formidable', callback, priority );
10344 },
10345 doAction: function( hookName, ...args ) {
10346 return wp.hooks.doAction( hookName, ...args );
10347 },
10348 addAction: function( hookName, callback, priority ) {
10349 return wp.hooks.addAction( hookName, 'formidable', callback, priority );
10350 }
10351 },
10352
10353 applyZebraStriping,
10354 initModal,
10355 infoModal,
10356 offsetModalY,
10357 adjustConditionalLogicOptionOrders,
10358 addRadioCheckboxOpt,
10359 installNewForm
10360 };
10361 }
10362
10363 frmAdminBuild = frmAdminBuildJS();
10364
10365 jQuery( document ).ready(
10366 () => {
10367 frmAdminBuild.init();
10368
10369 frmDom.bootstrap.setupBootstrapDropdowns( convertOldBootstrapDropdownsToBootstrap4 );
10370 document.querySelector( '.preview.dropdown .frm-dropdown-toggle' )?.setAttribute( 'data-toggle', 'dropdown' );
10371
10372 function convertOldBootstrapDropdownsToBootstrap4( frmDropdownMenu ) {
10373 const toggle = frmDropdownMenu.querySelector( '.frm-dropdown-toggle' );
10374 if ( toggle ) {
10375 if ( ! toggle.hasAttribute( 'role' ) ) {
10376 toggle.setAttribute( 'role', 'button' );
10377 }
10378 if ( ! toggle.hasAttribute( 'tabindex' ) ) {
10379 toggle.setAttribute( 'tabindex', 0 );
10380 }
10381 }
10382
10383 // Convert <li> and <ul> tags.
10384 if ( 'UL' === frmDropdownMenu.tagName ) {
10385 convertBootstrapUl( frmDropdownMenu );
10386 }
10387 }
10388
10389 function convertBootstrapUl( ul ) {
10390 let html = ul.outerHTML;
10391 html = html.replace( '<ul ', '<div ' );
10392 html = html.replace( '</ul>', '</div>' );
10393 html = html.replaceAll( '<li>', '<div class="dropdown-item">' );
10394 html = html.replaceAll( '<li class="', '<div class="dropdown-item ' );
10395 html = html.replaceAll( '</li>', '</div>' );
10396 ul.outerHTML = html;
10397 }
10398 }
10399 );
10400
10401 function frm_remove_tag( htmlTag ) { // eslint-disable-line camelcase
10402 console.warn( 'DEPRECATED: function frm_remove_tag in v2.0' );
10403 jQuery( htmlTag ).remove();
10404 }
10405
10406 function frm_show_div( div, value, showIf, classId ) { // eslint-disable-line camelcase
10407 if ( value == showIf ) {
10408 jQuery( classId + div ).fadeIn( 'slow' ).css( 'visibility', 'visible' );
10409 } else {
10410 jQuery( classId + div ).fadeOut( 'slow' );
10411 }
10412 }
10413
10414 function frmCheckAll( checked, n ) {
10415 jQuery( 'input[name^="' + n + '"]' ).prop( 'checked', ! ! checked );
10416 }
10417
10418 function frmCheckAllLevel( checked, n, level ) {
10419 var $kids = jQuery( '.frm_catlevel_' + level ).children( '.frm_checkbox' ).children( 'label' );
10420 $kids.children( 'input[name^="' + n + '"]' ).prop( 'checked', ! ! checked );
10421 }
10422
10423 function frm_add_logic_row( id, formId ) { // eslint-disable-line camelcase
10424 console.warn( 'DEPRECATED: function frm_add_logic_row in v2.0' );
10425 jQuery.ajax({
10426 type: 'POST',
10427 url: ajaxurl,
10428 data: {
10429 action: 'frm_add_logic_row',
10430 form_id: formId,
10431 field_id: id,
10432 meta_name: jQuery( '#frm_logic_row_' + id + ' > div' ).length,
10433 nonce: frmGlobal.nonce
10434 },
10435 success: function( html ) {
10436 jQuery( '#frm_logic_row_' + id ).append( html );
10437 }
10438 });
10439 return false;
10440 }
10441
10442 function frmGetFieldValues( fieldId, cur, rowNumber, fieldType, htmlName ) {
10443
10444 if ( fieldId ) {
10445 jQuery.ajax({
10446 type: 'POST', url: ajaxurl,
10447 data: 'action=frm_get_field_values&current_field=' + cur + '&field_id=' + fieldId + '&name=' + htmlName + '&t=' + fieldType + '&form_action=' + jQuery( 'input[name="frm_action"]' ).val() + '&nonce=' + frmGlobal.nonce,
10448 success: function( msg ) {
10449 document.getElementById( 'frm_show_selected_values_' + cur + '_' + rowNumber ).innerHTML = msg;
10450 }
10451 });
10452 }
10453 }
10454
10455 function frmImportCsv( formID ) {
10456 var urlVars = '';
10457 if ( typeof __FRMURLVARS !== 'undefined' ) {
10458 urlVars = __FRMURLVARS;
10459 }
10460
10461 jQuery.ajax({
10462 type: 'POST', url: ajaxurl,
10463 data: 'action=frm_import_csv&nonce=' + frmGlobal.nonce + '&frm_skip_cookie=1' + urlVars,
10464 success: function( count ) {
10465 var max = jQuery( '.frm_admin_progress_bar' ).attr( 'aria-valuemax' );
10466 var imported = max - count;
10467 var percent = ( imported / max ) * 100;
10468 jQuery( '.frm_admin_progress_bar' ).css( 'width', percent + '%' ).attr( 'aria-valuenow', imported );
10469
10470 if ( parseInt( count, 10 ) > 0 ) {
10471 jQuery( '.frm_csv_remaining' ).html( count );
10472 frmImportCsv( formID );
10473 } else {
10474 jQuery( document.getElementById( 'frm_import_message' ) ).html( frm_admin_js.import_complete ); // eslint-disable-line camelcase
10475 setTimeout( function() {
10476 location.href = '?page=formidable-entries&frm_action=list&form=' + formID + '&import-message=1';
10477 }, 2000 );
10478 }
10479 }
10480 });
10481 }
10482