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

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