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

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

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