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

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

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