PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.5
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.5
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.5, at js/formidable_admin.js

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