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

10,637 lines 318.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* exported frm_add_logic_row, frm_remove_tag, frm_show_div, frmCheckAll, frmCheckAllLevel */
2
3 var frmAdminBuild;
4
5 var FrmFormsConnect = window.FrmFormsConnect || ( function( document, window, $ ) {
6
7 /*global jQuery:false, frm_admin_js, frmGlobal, ajaxurl */
8
9 var el = {
10 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 onlyOneActionMessage() {
7209 infoModal( frmAdminJs.only_one_action );
7210 }
7211
7212 function addFormLogicRow() {
7213 /*jshint validthis:true */
7214 var id = jQuery( this ).data( 'emailkey' ),
7215 type = jQuery( this ).closest( '.frm_form_action_settings' ).find( '.frm_action_name' ).val(),
7216 formId = document.getElementById( 'form_id' ).value,
7217 logicRows = document.getElementById( 'frm_form_action_' + id ).querySelectorAll( '.frm_logic_row' );
7218 jQuery.ajax({
7219 type: 'POST', url: ajaxurl,
7220 data: {
7221 action: 'frm_add_form_logic_row',
7222 email_id: id,
7223 form_id: formId,
7224 meta_name: getNewRowId( logicRows, 'frm_logic_' + id + '_' ),
7225 type: type,
7226 nonce: frmGlobal.nonce
7227 },
7228 success: function( html ) {
7229 jQuery( document.getElementById( 'logic_link_' + id ) ).fadeOut( 'slow', function() {
7230 var $logicRow = jQuery( document.getElementById( 'frm_logic_row_' + id ) );
7231 $logicRow.append( html );
7232 $logicRow.parent( '.frm_logic_rows' ).fadeIn( 'slow' );
7233 });
7234 }
7235 });
7236 return false;
7237 }
7238
7239 function toggleSubmitLogic() {
7240 /*jshint validthis:true */
7241 if ( this.checked ) {
7242 addSubmitLogic();
7243 } else {
7244 jQuery( '.frm_logic_row_submit' ).remove();
7245 document.getElementById( 'frm_submit_logic_rows' ).style.display = 'none';
7246 }
7247 }
7248
7249 /**
7250 * Adds submit button Conditional Logic row and reveals submit button Conditional Logic
7251 *
7252 * @returns {boolean}
7253 */
7254 function addSubmitLogic() {
7255 /*jshint validthis:true */
7256 var formId = thisFormId,
7257 logicRows = document.getElementById( 'frm_submit_logic_row' ).querySelectorAll( '.frm_logic_row' );
7258 jQuery.ajax({
7259 type: 'POST',
7260 url: ajaxurl,
7261 data: {
7262 action: 'frm_add_submit_logic_row',
7263 form_id: formId,
7264 meta_name: getNewRowId( logicRows, 'frm_logic_submit_' ),
7265 nonce: frmGlobal.nonce
7266 },
7267 success: function( html ) {
7268 var $logicRow = jQuery( document.getElementById( 'frm_submit_logic_row' ) );
7269 $logicRow.append( html );
7270 $logicRow.parent( '.frm_submit_logic_rows' ).fadeIn( 'slow' );
7271 }
7272 });
7273 return false;
7274 }
7275
7276 /**
7277 * When the user selects a field for a submit condition, update corresponding options field accordingly.
7278 */
7279 function addSubmitLogicOpts() {
7280 var fieldOpt = jQuery( this );
7281 var fieldId = fieldOpt.find( ':selected' ).val();
7282
7283 if ( fieldId ) {
7284 var row = fieldOpt.data( 'row' );
7285 frmGetFieldValues( fieldId, 'submit', row, '', 'options[submit_conditions][hide_opt][]' );
7286 }
7287 }
7288
7289 function formatEmailSetting() {
7290 /*jshint validthis:true */
7291 /*var val = jQuery( this ).val();
7292 var email = val.match( /(\s[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi );
7293 if(email !== null && email.length) {
7294 //has email
7295 //TODO: add < > if they aren't there
7296 }*/
7297 }
7298
7299 function checkDupPost() {
7300 /*jshint validthis:true */
7301 var postField = jQuery( 'select.frm_single_post_field' );
7302 postField.css( 'border-color', '' );
7303 var $t = this;
7304 var v = jQuery( $t ).val();
7305 if ( v === '' || v === 'checkbox' ) {
7306 return false;
7307 }
7308 postField.each( function() {
7309 if ( jQuery( this ).val() === v && this.name !== $t.name ) {
7310 this.style.borderColor = 'red';
7311 jQuery( $t ).val( '' );
7312 infoModal( frmAdminJs.field_already_used );
7313 return false;
7314 }
7315 });
7316 }
7317
7318 function togglePostContent() {
7319 /*jshint validthis:true */
7320 var v = jQuery( this ).val();
7321 if ( '' === v ) {
7322 jQuery( '.frm_post_content_opt, select.frm_dyncontent_opt' ).hide().val( '' );
7323 jQuery( '.frm_dyncontent_opt' ).hide();
7324 } else if ( 'post_content' === v ) {
7325 jQuery( '.frm_post_content_opt' ).show();
7326 jQuery( '.frm_dyncontent_opt' ).hide();
7327 jQuery( 'select.frm_dyncontent_opt' ).val( '' );
7328 } else {
7329 jQuery( '.frm_post_content_opt' ).hide().val( '' );
7330 jQuery( 'select.frm_dyncontent_opt, .frm_form_field.frm_dyncontent_opt' ).show();
7331 }
7332 }
7333
7334 function fillDyncontent() {
7335 /*jshint validthis:true */
7336 var v = jQuery( this ).val();
7337 var $dyn = jQuery( document.getElementById( 'frm_dyncontent' ) );
7338 if ( '' === v || 'new' === v ) {
7339 $dyn.val( '' );
7340 jQuery( '.frm_dyncontent_opt' ).show();
7341 } else {
7342 jQuery.ajax({
7343 type: 'POST', url: ajaxurl,
7344 data: {action: 'frm_display_get_content', id: v, nonce: frmGlobal.nonce},
7345 success: function( val ) {
7346 $dyn.val( val );
7347 jQuery( '.frm_dyncontent_opt' ).show();
7348 }
7349 });
7350 }
7351 }
7352
7353 function switchPostType() {
7354 /*jshint validthis:true */
7355 // update all rows of categories/taxonomies
7356 var curSelect, newSelect,
7357 catRows = document.getElementById( 'frm_posttax_rows' ).childNodes,
7358 postParentField = document.querySelector( '.frm_post_parent_field' ),
7359 postMenuOrderField = document.querySelector( '.frm_post_menu_order_field' ),
7360 postType = this.value;
7361
7362 // Get new category/taxonomy options
7363 jQuery.ajax({
7364 type: 'POST',
7365 url: ajaxurl,
7366 data: {
7367 action: 'frm_replace_posttax_options',
7368 post_type: postType,
7369 nonce: frmGlobal.nonce
7370 },
7371 success: function( html ) {
7372
7373 // Loop through each category row, and replace the first dropdown
7374 for ( i = 0; i < catRows.length; i++ ) {
7375 // Check if current element is a div
7376 if ( catRows[i].tagName !== 'DIV' ) {
7377 continue;
7378 }
7379
7380 // Get current category select
7381 curSelect = catRows[i].getElementsByTagName( 'select' )[0];
7382
7383 // Set up new select
7384 newSelect = document.createElement( 'select' );
7385 newSelect.innerHTML = html;
7386 newSelect.className = curSelect.className;
7387 newSelect.name = curSelect.name;
7388
7389 // Replace the old select with the new select
7390 catRows[i].replaceChild( newSelect, curSelect );
7391 }
7392 }
7393 });
7394
7395 // Get new post parent option.
7396 if ( postParentField ) {
7397 getActionOption(
7398 postParentField,
7399 postType,
7400 'frm_get_post_parent_option',
7401 function( response, optName ) {
7402 // The replaced string is declared in FrmProFormActionController::ajax_get_post_menu_order_option() in the pro version.
7403 postParentField.querySelector( '.frm_post_parent_opt_wrapper' ).innerHTML = response.replaceAll( 'REPLACETHISNAME', optName );
7404 frmDom.autocomplete.initAutocomplete( 'page', postParentField );
7405 }
7406 );
7407 }
7408
7409 if ( postMenuOrderField ) {
7410 getActionOption( postMenuOrderField, postType, 'frm_should_use_post_menu_order_option' );
7411 }
7412 }
7413
7414 function getActionOption( field, postType, action, successHandler ) {
7415 const opt = field.querySelector( '.frm_autocomplete_value_input' ) || field.querySelector( 'select' ),
7416 optName = opt.getAttribute( 'name' );
7417
7418 jQuery.ajax({
7419 url: ajaxurl,
7420 method: 'POST',
7421 data: {
7422 action: action,
7423 post_type: postType,
7424 _wpnonce: frmGlobal.nonce
7425 },
7426 success: response => {
7427 if ( 'string' !== typeof response ) {
7428 console.error( response );
7429 return;
7430 }
7431
7432 if ( '0' === response ) {
7433 // This post type does not support this field.
7434 field.classList.add( 'frm_hidden' );
7435 field.value = '';
7436 return;
7437 }
7438
7439 field.classList.remove( 'frm_hidden' );
7440
7441 if ( 'function' === typeof successHandler ) {
7442 successHandler( response, optName );
7443 }
7444 },
7445 error: response => console.error( response )
7446 });
7447 }
7448
7449 function addPosttaxRow() {
7450 /*jshint validthis:true */
7451 addPostRow( 'tax', this );
7452 }
7453
7454 function addPostmetaRow() {
7455 /*jshint validthis:true */
7456 addPostRow( 'meta', this );
7457 }
7458
7459 function addPostRow( type, button ) {
7460 var name,
7461 id = jQuery( 'input[name="id"]' ).val(),
7462 settings = jQuery( button ).closest( '.frm_form_action_settings' ),
7463 key = settings.data( 'actionkey' ),
7464 postType = settings.find( '.frm_post_type' ).val(),
7465 metaName = 0,
7466 postTypeRows = document.querySelectorAll( '.frm_post' + type + '_row' );
7467
7468 if ( postTypeRows.length ) {
7469 name = postTypeRows[ postTypeRows.length - 1 ].id.replace( 'frm_post' + type + '_', '' );
7470 if ( isNumeric( name ) ) {
7471 metaName = 1 + parseInt( name, 10 );
7472 } else {
7473 metaName = 1;
7474 }
7475 }
7476
7477 jQuery.ajax({
7478 type: 'POST', url: ajaxurl,
7479 data: {
7480 action: 'frm_add_post' + type + '_row',
7481 form_id: id,
7482 meta_name: metaName,
7483 tax_key: metaName,
7484 post_type: postType,
7485 action_key: key,
7486 nonce: frmGlobal.nonce
7487 },
7488 success: function( html ) {
7489 var cfOpts, optIndex;
7490 jQuery( document.getElementById( 'frm_post' + type + '_rows' ) ).append( html );
7491 jQuery( '.frm_add_post' + type + '_row.button' ).hide();
7492
7493 if ( type === 'meta' ) {
7494 jQuery( '.frm_name_value' ).show();
7495 cfOpts = document.querySelectorAll( '.frm_toggle_cf_opts' );
7496 for ( optIndex = 0; optIndex < cfOpts.length - 1; ++optIndex ) {
7497 cfOpts[ optIndex ].style.display = 'none';
7498 }
7499 } else if ( type === 'tax' ) {
7500 jQuery( '.frm_posttax_labels' ).show();
7501 }
7502 }
7503 });
7504 }
7505
7506 function isNumeric( value ) {
7507 return ! isNaN( parseFloat( value ) ) && isFinite( value );
7508 }
7509
7510 function getMetaValue( id, metaName ) {
7511 var newMeta = metaName;
7512 if ( jQuery( document.getElementById( id + metaName ) ).length > 0 ) {
7513 newMeta = getMetaValue( id, metaName + 1 );
7514 }
7515 return newMeta;
7516 }
7517
7518 function changePosttaxRow() {
7519 /*jshint validthis:true */
7520 if ( ! jQuery( this ).closest( '.frm_posttax_row' ).find( '.frm_posttax_opt_list' ).length ) {
7521 return;
7522 }
7523
7524 jQuery( this ).closest( '.frm_posttax_row' ).find( '.frm_posttax_opt_list' ).html( '<div class="spinner frm_spinner" style="display:block"></div>' );
7525
7526 var postType = jQuery( this ).closest( '.frm_form_action_settings' ).find( 'select[name$="[post_content][post_type]"]' ).val(),
7527 actionKey = jQuery( this ).closest( '.frm_form_action_settings' ).data( 'actionkey' ),
7528 taxKey = jQuery( this ).closest( '.frm_posttax_row' ).attr( 'id' ).replace( 'frm_posttax_', '' ),
7529 metaName = jQuery( this ).val(),
7530 showExclude = jQuery( document.getElementById( taxKey + '_show_exclude' ) ).is( ':checked' ) ? 1 : 0,
7531 fieldId = jQuery( 'select[name$="[post_category][' + taxKey + '][field_id]"]' ).val(),
7532 id = jQuery( 'input[name="id"]' ).val();
7533
7534 jQuery.ajax({
7535 type: 'POST',
7536 url: ajaxurl,
7537 data: {
7538 action: 'frm_add_posttax_row',
7539 form_id: id,
7540 post_type: postType,
7541 tax_key: taxKey,
7542 action_key: actionKey,
7543 meta_name: metaName,
7544 field_id: fieldId,
7545 show_exclude: showExclude,
7546 nonce: frmGlobal.nonce
7547 },
7548 success: function( html ) {
7549 var $tax = jQuery( document.getElementById( 'frm_posttax_' + taxKey ) );
7550 $tax.replaceWith( html );
7551 }
7552 });
7553 }
7554
7555 function toggleCfOpts() {
7556 /*jshint validthis:true */
7557 var row = jQuery( this ).closest( '.frm_postmeta_row' );
7558 var cancel = row.find( '.frm_cancelnew' );
7559 var select = row.find( '.frm_enternew' );
7560 if ( row.find( 'select.frm_cancelnew' ).is( ':visible' ) ) {
7561 cancel.hide();
7562 select.show();
7563 } else {
7564 cancel.show();
7565 select.hide();
7566 }
7567
7568 row.find( 'input.frm_enternew, select.frm_cancelnew' ).val( '' );
7569 return false;
7570 }
7571
7572 function toggleFormOpts() {
7573 /*jshint validthis:true */
7574 var changedOpt = jQuery( this );
7575 var val = changedOpt.val();
7576 if ( changedOpt.attr( 'type' ) === 'checkbox' ) {
7577 if ( this.checked === false ) {
7578 val = '';
7579 }
7580 }
7581
7582 var toggleClass = changedOpt.data( 'toggleclass' );
7583 if ( val === '' ) {
7584 jQuery( '.' + toggleClass ).hide();
7585 } else {
7586 jQuery( '.' + toggleClass ).show();
7587 jQuery( '.hide_' + toggleClass + '_' + val ).hide();
7588 }
7589 }
7590
7591 function submitSettings() {
7592 if ( showNameYourFormModal() ) {
7593 return;
7594 }
7595
7596 /*jshint validthis:true */
7597 preFormSave( this );
7598 triggerSubmit( document.querySelector( '.frm_form_settings' ) );
7599 }
7600
7601 /* View Functions */
7602 function showCount() {
7603 /*jshint validthis:true */
7604 var value = jQuery( this ).val();
7605
7606 var $cont = document.getElementById( 'date_select_container' );
7607 var tab = document.getElementById( 'frm_listing_tab' );
7608 var label = tab.getAttribute( 'data-label' );
7609 if ( value === 'calendar' ) {
7610 jQuery( '.hide_dyncontent, .hide_single_content' ).removeClass( 'frm_hidden' );
7611 jQuery( '.limit_container' ).addClass( 'frm_hidden' );
7612 $cont.style.display = 'block';
7613 } else if ( value === 'dynamic' ) {
7614 jQuery( '.hide_dyncontent, .limit_container, .hide_single_content' ).removeClass( 'frm_hidden' );
7615 } else if ( value === 'one' ) {
7616 label = tab.getAttribute( 'data-one' );
7617 jQuery( '.hide_dyncontent, .limit_container, .hide_single_content' ).addClass( 'frm_hidden' );
7618 } else {
7619 jQuery( '.hide_dyncontent' ).addClass( 'frm_hidden' );
7620 jQuery( '.limit_container, .hide_single_content' ).removeClass( 'frm_hidden' );
7621 }
7622
7623 if ( value !== 'calendar' ) {
7624 $cont.style.display = 'none';
7625 }
7626 tab.innerHTML = label;
7627 }
7628
7629 function displayFormSelected() {
7630 /*jshint validthis:true */
7631 var formId = jQuery( this ).val();
7632 thisFormId = formId; // set the global form id
7633 if ( formId === '' ) {
7634 return;
7635 }
7636
7637 jQuery.ajax({
7638 type: 'POST',
7639 url: ajaxurl,
7640 data: {
7641 action: 'frm_get_cd_tags_box',
7642 form_id: formId,
7643 nonce: frmGlobal.nonce
7644 },
7645 success: function( html ) {
7646 jQuery( '#frm_adv_info .categorydiv' ).html( html );
7647 }
7648 });
7649
7650 jQuery.ajax({
7651 type: 'POST',
7652 url: ajaxurl,
7653 data: {
7654 action: 'frm_get_date_field_select',
7655 form_id: formId,
7656 nonce: frmGlobal.nonce
7657 },
7658 success: function( html ) {
7659 jQuery( document.getElementById( 'date_select_container' ) ).html( html );
7660 }
7661 });
7662 }
7663
7664 function clickTabsAfterAjax() {
7665 /*jshint validthis:true */
7666 var t = jQuery( this ).attr( 'href' );
7667 jQuery( this ).parent().addClass( 'tabs' ).siblings( 'li' ).removeClass( 'tabs' );
7668 jQuery( t ).show().siblings( '.tabs-panel' ).hide();
7669 return false;
7670 }
7671
7672 function clickContentTab() {
7673 /*jshint validthis:true */
7674 link = jQuery( this );
7675 var t = link.attr( 'href' );
7676 if ( typeof t === 'undefined' ) {
7677 return false;
7678 }
7679
7680 var c = t.replace( '#', '.' );
7681 link.closest( '.nav-tab-wrapper' ).find( 'a' ).removeClass( 'nav-tab-active' );
7682 link.addClass( 'nav-tab-active' );
7683 jQuery( '.nav-menu-content' ).not( t ).not( c ).hide();
7684 jQuery( t + ',' + c ).show();
7685
7686 return false;
7687 }
7688
7689 function addOrderRow() {
7690 var logicRows = document.getElementById( 'frm_order_options' ).querySelectorAll( '.frm_logic_rows div' );
7691 jQuery.ajax({
7692 type: 'POST',
7693 url: ajaxurl,
7694 data: {
7695 action: 'frm_add_order_row',
7696 form_id: thisFormId,
7697 order_key: getNewRowId( logicRows, 'frm_order_field_', 1 ),
7698 nonce: frmGlobal.nonce
7699 },
7700 success: function( html ) {
7701 jQuery( '#frm_order_options .frm_logic_rows' ).append( html ).show().prev( '.frm_add_order_row' ).hide();
7702 }
7703 });
7704 }
7705
7706 function addWhereRow() {
7707 var rowDivs = document.getElementById( 'frm_where_options' ).querySelectorAll( '.frm_logic_rows div' );
7708 jQuery.ajax({
7709 type: 'POST',
7710 url: ajaxurl,
7711 data: {
7712 action: 'frm_add_where_row',
7713 form_id: thisFormId,
7714 where_key: getNewRowId( rowDivs, 'frm_where_field_', 1 ),
7715 nonce: frmGlobal.nonce
7716 },
7717 success: function( html ) {
7718 jQuery( '#frm_where_options .frm_logic_rows' ).append( html ).show().prev( '.frm_add_where_row' ).hide();
7719 }
7720 });
7721 }
7722
7723 function insertWhereOptions() {
7724 /*jshint validthis:true */
7725 var value = this.value,
7726 whereKey = jQuery( this ).closest( '.frm_where_row' ).attr( 'id' ).replace( 'frm_where_field_', '' );
7727
7728 jQuery.ajax({
7729 type: 'POST',
7730 url: ajaxurl,
7731 data: {
7732 action: 'frm_add_where_options',
7733 where_key: whereKey,
7734 field_id: value,
7735 nonce: frmGlobal.nonce
7736 },
7737 success: function( html ) {
7738 jQuery( document.getElementById( 'where_field_options_' + whereKey ) ).html( html );
7739 }
7740 });
7741 }
7742
7743 function hideWhereOptions() {
7744 /*jshint validthis:true */
7745 var value = this.value,
7746 whereKey = jQuery( this ).closest( '.frm_where_row' ).attr( 'id' ).replace( 'frm_where_field_', '' );
7747
7748 if ( value === 'group_by' || value === 'group_by_newest' ) {
7749 document.getElementById( 'where_field_options_' + whereKey ).style.display = 'none';
7750 } else {
7751 document.getElementById( 'where_field_options_' + whereKey ).style.display = 'inline-block';
7752 }
7753 }
7754
7755 function setDefaultPostStatus() {
7756 var urlQuery = window.location.search.substring( 1 );
7757 if ( urlQuery.indexOf( 'action=edit' ) === -1 ) {
7758 document.getElementById( 'post-visibility-display' ).textContent = frmAdminJs.private_label;
7759 document.getElementById( 'hidden-post-visibility' ).value = 'private';
7760 document.getElementById( 'visibility-radio-private' ).checked = true;
7761 }
7762 }
7763
7764 /* Customization Panel */
7765 function insertCode( e ) {
7766 /*jshint validthis:true */
7767 e.preventDefault();
7768 insertFieldCode( jQuery( this ), this.getAttribute( 'data-code' ) );
7769 return false;
7770 }
7771
7772 function insertFieldCode( element, variable ) {
7773 var rich = false,
7774 elementId = element;
7775 if ( typeof element === 'object' ) {
7776 if ( element.hasClass( 'frm_noallow' ) ) {
7777 return;
7778 }
7779
7780 elementId = jQuery( element ).closest( '[data-fills]' ).attr( 'data-fills' );
7781 if ( typeof elementId === 'undefined' ) {
7782 elementId = element.closest( 'div' ).attr( 'class' );
7783 if ( typeof elementId !== 'undefined' ) {
7784 elementId = elementId.split( ' ' )[1];
7785 }
7786 }
7787 }
7788
7789 if ( typeof elementId === 'undefined' ) {
7790 var active = document.activeElement;
7791 if ( active.type === 'search' ) {
7792 // If the search field has focus, find the correct field.
7793 elementId = active.id.replace( '-search-input', '' );
7794 if ( elementId.match( /\d/gi ) === null ) {
7795 active = jQuery( '.frm-single-settings:visible .' + elementId );
7796 elementId = active.attr( 'id' );
7797 }
7798 } else {
7799 elementId = active.id;
7800 }
7801 }
7802
7803 if ( elementId ) {
7804 rich = jQuery( '#wp-' + elementId + '-wrap.wp-editor-wrap' ).length > 0;
7805 }
7806
7807 var contentBox = jQuery( document.getElementById( elementId ) );
7808 if ( typeof element.attr( 'data-shortcode' ) === 'undefined' && ( ! contentBox.length || typeof contentBox.attr( 'data-shortcode' ) === 'undefined' ) ) {
7809 // this helps to exclude those that don't want shortcode-like inserted content e.g. frm-pro's summary field
7810 var doShortcode = element.parents( 'ul.frm_code_list' ).attr( 'data-shortcode' );
7811 if ( doShortcode === 'undefined' || doShortcode !== 'no' ) {
7812 variable = '[' + variable + ']';
7813 }
7814 }
7815
7816 if ( rich ) {
7817 wpActiveEditor = elementId;
7818 }
7819
7820 if ( ! contentBox.length ) {
7821 return false;
7822 }
7823
7824 if ( variable === '[default-html]' || variable === '[default-plain]' ) {
7825 var p = 0;
7826 if ( variable === '[default-plain]' ) {
7827 p = 1;
7828 }
7829 jQuery.ajax({
7830 type: 'POST', url: ajaxurl,
7831 data: {
7832 action: 'frm_get_default_html',
7833 form_id: jQuery( 'input[name="id"]' ).val(),
7834 plain_text: p,
7835 nonce: frmGlobal.nonce
7836 },
7837 elementId: elementId,
7838 success: function( msg ) {
7839 if ( rich ) {
7840 let p = document.createElement( 'p' );
7841 p.innerText = msg;
7842 send_to_editor( p.innerHTML );
7843 } else {
7844 insertContent( contentBox, msg );
7845 }
7846 }
7847 });
7848 } else {
7849 variable = maybeAddSanitizeUrlToShortcodeVariable( variable, element, contentBox );
7850 if ( rich ) {
7851 send_to_editor( variable );
7852 } else {
7853 insertContent( contentBox, variable );
7854 }
7855 }
7856 return false;
7857 }
7858
7859 function maybeAddSanitizeUrlToShortcodeVariable( variable, element, contentBox ) {
7860 if ( 'object' !== typeof element || ! ( element instanceof jQuery ) || 0 !== contentBox[0].id.indexOf( 'success_url_' ) ) {
7861 return variable;
7862 }
7863
7864 element = element[0];
7865 if ( ! element.closest( '#frm-insert-fields-box' ) ) {
7866 // Only add sanitize_url=1 to field shortcodes.
7867 return variable;
7868 }
7869
7870 if ( ! element.parentNode.classList.contains( 'frm_insert_url' ) ) {
7871 variable = variable.replace( ']', ' sanitize_url=1]' );
7872 }
7873
7874 return variable;
7875 }
7876
7877 function insertContent( contentBox, variable ) {
7878 if ( document.selection ) {
7879 contentBox[0].focus();
7880 document.selection.createRange().text = variable;
7881 } else {
7882 obj = contentBox[0];
7883 var e = obj.selectionEnd;
7884
7885 variable = maybeFormatInsertedContent( contentBox, variable, obj.selectionStart, e );
7886
7887 obj.value = obj.value.substr( 0, obj.selectionStart ) + variable + obj.value.substr( obj.selectionEnd, obj.value.length );
7888 var s = e + variable.length;
7889 obj.focus();
7890 obj.setSelectionRange( s, s );
7891 }
7892 triggerChange( contentBox );
7893 }
7894
7895 function maybeFormatInsertedContent( input, textToInsert, selectionStart, selectionEnd ) {
7896 var separator = input.data( 'sep' );
7897 if ( undefined === separator ) {
7898 return textToInsert;
7899 }
7900
7901 var value = input.val();
7902
7903 if ( ! value.trim().length ) {
7904 return textToInsert;
7905 }
7906
7907 var startPattern = new RegExp( separator + '\\s*$' );
7908 var endPattern = new RegExp( '^\\s*' + separator );
7909
7910 if ( value.substr( 0, selectionStart ).trim().length && false === startPattern.test( value.substr( 0, selectionStart ) ) ) {
7911 textToInsert = separator + textToInsert;
7912 }
7913
7914 if ( value.substr( selectionEnd, value.length ).trim().length && false === endPattern.test( value.substr( selectionEnd, value.length ) ) ) {
7915 textToInsert += separator;
7916 }
7917
7918 return textToInsert;
7919 }
7920
7921 function resetLogicBuilder() {
7922 /*jshint validthis:true */
7923 var id = document.getElementById( 'frm-id-condition' ),
7924 key = document.getElementById( 'frm-key-condition' );
7925
7926 if ( this.checked ) {
7927 id.classList.remove( 'frm_hidden' );
7928 key.classList.add( 'frm_hidden' );
7929 triggerEvent( key, 'change' );
7930 } else {
7931 id.classList.add( 'frm_hidden' );
7932 key.classList.remove( 'frm_hidden' );
7933 triggerEvent( id, 'change' );
7934 }
7935 }
7936
7937 function setLogicExample() {
7938 var field, code,
7939 idKey = document.getElementById( 'frm-id-key-condition' ).checked ? 'frm-id-condition' : 'frm-key-condition',
7940 is = document.getElementById( 'frm-is-condition' ).value,
7941 text = document.getElementById( 'frm-text-condition' ).value,
7942 result = document.getElementById( 'frm-insert-condition' );
7943
7944 idKey = document.getElementById( idKey );
7945 field = idKey.options[idKey.selectedIndex].value;
7946 code = 'if ' + field + ' ' + is + '="' + text + '"]';
7947 result.setAttribute( 'data-code', code + frmAdminJs.conditional_text + '[/if ' + field );
7948 result.innerHTML = '[' + code + '[/if ' + field + ']';
7949 }
7950
7951 function showBuilderModal() {
7952 /*jshint validthis:true */
7953 var moreIcon = getIconForInput( this );
7954 showInlineModal( moreIcon, this );
7955 }
7956
7957 function maybeShowModal( input ) {
7958 var moreIcon;
7959 if ( input.parentNode.parentNode.classList.contains( 'frm_has_shortcodes' ) ) {
7960 hideShortcodes();
7961 moreIcon = getIconForInput( input );
7962 if ( moreIcon.tagName === 'use' ) {
7963 moreIcon = moreIcon.firstElementChild;
7964 if ( moreIcon.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ).indexOf( 'frm_close_icon' ) === -1 ) {
7965 showShortcodeBox( moreIcon, 'nofocus' );
7966 }
7967 } else if ( ! moreIcon.classList.contains( 'frm_close_icon' ) ) {
7968 showShortcodeBox( moreIcon, 'nofocus' );
7969 }
7970 }
7971 }
7972
7973 function showShortcodes( e ) {
7974 /*jshint validthis:true */
7975 e.preventDefault();
7976 e.stopPropagation();
7977
7978 showShortcodeBox( this );
7979 }
7980
7981 function showShortcodeBox( moreIcon, shouldFocus ) {
7982 var pos = moreIcon.getBoundingClientRect(),
7983 input = getInputForIcon( moreIcon ),
7984 box = document.getElementById( 'frm_adv_info' ),
7985 classes = moreIcon.className,
7986 parentPos = box.parentElement.getBoundingClientRect();
7987
7988 if ( moreIcon.tagName === 'svg' ) {
7989 moreIcon = moreIcon.firstElementChild;
7990 }
7991 if ( moreIcon.tagName === 'use' ) {
7992 classes = moreIcon.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
7993 }
7994
7995 if ( classes.indexOf( 'frm_close_icon' ) !== -1 ) {
7996 hideShortcodes( box );
7997 } else {
7998 box.style.top = ( pos.top - parentPos.top + 32 ) + 'px';
7999 box.style.left = ( pos.left - parentPos.left - 280 ) + 'px';
8000
8001 jQuery( '.frm_code_list a' ).removeClass( 'frm_noallow' );
8002 if ( input.classList.contains( 'frm_not_email_to' ) ) {
8003 jQuery( '#frm-insert-fields-box .frm_code_list li:not(.show_frm_not_email_to) a' ).addClass( 'frm_noallow' );
8004 } else if ( input.classList.contains( 'frm_not_email_subject' ) ) {
8005 jQuery( '.frm_code_list li.hide_frm_not_email_subject a' ).addClass( 'frm_noallow' );
8006 }
8007
8008 box.setAttribute( 'data-fills', input.id );
8009 box.style.display = 'block';
8010
8011 if ( moreIcon.tagName === 'use' ) {
8012 moreIcon.setAttributeNS( 'http://www.w3.org/1999/xlink', 'href', '#frm_close_icon' );
8013 } else {
8014 moreIcon.className = classes.replace( 'frm_more_horiz_solid_icon', 'frm_close_icon' );
8015 }
8016
8017 if ( shouldFocus !== 'nofocus' ) {
8018 if ( 'none' !== input.style.display ) {
8019 input.focus();
8020 } else {
8021 jQuery( tinymce.get( input.id ) ).trigger( 'focus' );
8022 }
8023 }
8024 }
8025 }
8026
8027 function fieldUpdated() {
8028 if ( ! fieldsUpdated ) {
8029 fieldsUpdated = 1;
8030 window.addEventListener( 'beforeunload', confirmExit );
8031 }
8032 }
8033
8034 function buildSubmittedNoAjax() {
8035 // set fieldsUpdated to 0 to avoid the unsaved changes pop up
8036 fieldsUpdated = 0;
8037 }
8038
8039 function settingsSubmitted() {
8040 // set fieldsUpdated to 0 to avoid the unsaved changes pop up
8041 fieldsUpdated = 0;
8042 }
8043
8044 function saveAndReloadSettings() {
8045 var page, form;
8046 page = document.getElementById( 'form_settings_page' );
8047 if ( null !== page ) {
8048 form = page.querySelector( 'form.frm_form_settings' );
8049 if ( null !== form ) {
8050 fieldsUpdated = 0;
8051 form.submit();
8052 }
8053 }
8054 }
8055
8056 function reloadIfAddonActivatedAjaxSubmitOnly() {
8057 const submitButton = document.getElementById( 'frm_submit_side_top' );
8058 if ( submitButton.hasAttribute( 'data-new-addon-installed' ) && 'true' === submitButton.getAttribute( 'data-new-addon-installed' ) ) {
8059 submitButton.removeAttribute( 'data-new-addon-installed' );
8060 window.location.reload();
8061 }
8062
8063 }
8064
8065 function saveAndReloadFormBuilder() {
8066 const submitButton = document.getElementById( 'frm_submit_side_top' );
8067 if ( submitButton.classList.contains( 'frm_submit_ajax' ) ) {
8068 submitButton.setAttribute( 'data-new-addon-installed', true );
8069 }
8070 submitButton.click();
8071 }
8072
8073 function confirmExit( event ) {
8074 if ( fieldsUpdated ) {
8075 event.preventDefault();
8076 event.returnValue = '';
8077 }
8078 }
8079
8080 function bindClickForDialogClose( $modal ) {
8081 const closeModal = function() {
8082 $modal.dialog( 'close' );
8083 };
8084 jQuery( '.ui-widget-overlay' ).on( 'click', closeModal );
8085 $modal.on( 'click', 'a.dismiss', closeModal );
8086 }
8087
8088 function offsetModalY( $modal, amount ) {
8089 const position = {
8090 my: 'top',
8091 at: 'top+' + amount,
8092 of: window
8093 };
8094 $modal.dialog( 'option', 'position', position );
8095 }
8096
8097 /**
8098 * Get the input box for the selected ... icon.
8099 */
8100 function getInputForIcon( moreIcon ) {
8101 var input = moreIcon.nextElementSibling;
8102
8103 while ( input !== null && input.tagName !== 'INPUT' && input.tagName !== 'TEXTAREA' ) {
8104 input = getInputForIcon( input );
8105 }
8106
8107 return input;
8108 }
8109
8110 /**
8111 * Get the ... icon for the selected input box.
8112 */
8113 function getIconForInput( input ) {
8114 var moreIcon = input.previousElementSibling;
8115
8116 while ( moreIcon !== null && moreIcon.tagName !== 'I' && moreIcon.tagName !== 'svg' ) {
8117 moreIcon = getIconForInput( moreIcon );
8118 }
8119
8120 return moreIcon;
8121 }
8122
8123 function hideShortcodes( box ) {
8124 var i, u, closeIcons, closeSvg;
8125 if ( typeof box === 'undefined' ) {
8126 box = document.getElementById( 'frm_adv_info' );
8127 if ( box === null ) {
8128 return;
8129 }
8130 }
8131
8132 if ( document.getElementById( 'frm_dyncontent' ) !== null ) {
8133 // Don't run when in the sidebar.
8134 return;
8135 }
8136
8137 box.style.display = 'none';
8138
8139 closeIcons = document.querySelectorAll( '.frm-show-box.frm_close_icon' );
8140 for ( i = 0; i < closeIcons.length; i++ ) {
8141 closeIcons[i].classList.remove( 'frm_close_icon' );
8142 closeIcons[i].classList.add( 'frm_more_horiz_solid_icon' );
8143 }
8144
8145 closeSvg = document.querySelectorAll( '.frm_has_shortcodes use' );
8146 for ( u = 0; u < closeSvg.length; u++ ) {
8147 if ( closeSvg[u].getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) === '#frm_close_icon' ) {
8148 closeSvg[u].setAttributeNS( 'http://www.w3.org/1999/xlink', 'href', '#frm_more_horiz_solid_icon' );
8149 }
8150 }
8151 }
8152
8153 function initToggleShortcodes() {
8154 if ( typeof tinymce !== 'object' ) {
8155 return;
8156 }
8157
8158 DOM = tinymce.DOM;
8159 if ( typeof DOM.events !== 'undefined' && typeof DOM.events.add !== 'undefined' ) {
8160 DOM.events.add( DOM.select( '.wp-editor-wrap' ), 'mouseover', function() {
8161 if ( jQuery( '*:focus' ).length > 0 ) {
8162 return;
8163 }
8164 if ( this.id ) {
8165 toggleAllowedShortcodes( this.id.slice( 3, -5 ), 'focusin' );
8166 }
8167 });
8168 DOM.events.add( DOM.select( '.wp-editor-wrap' ), 'mouseout', function() {
8169 if ( jQuery( '*:focus' ).length > 0 ) {
8170 return;
8171 }
8172 if ( this.id ) {
8173 toggleAllowedShortcodes( this.id.slice( 3, -5 ), 'focusin' );
8174 }
8175 });
8176 } else {
8177 jQuery( '#frm_dyncontent' ).on( 'mouseover mouseout', '.wp-editor-wrap', function() {
8178 if ( jQuery( '*:focus' ).length > 0 ) {
8179 return;
8180 }
8181 if ( this.id ) {
8182 toggleAllowedShortcodes( this.id.slice( 3, -5 ), 'focusin' );
8183 }
8184 });
8185 }
8186 }
8187
8188 function toggleAllowedShortcodes( id ) {
8189 var c, clickedID;
8190 if ( typeof id === 'undefined' ) {
8191 id = '';
8192 }
8193 c = id;
8194
8195 if ( id.indexOf( '-search-input' ) !== -1 ) {
8196 return;
8197 }
8198
8199 if ( id !== '' ) {
8200 var $ele = jQuery( document.getElementById( id ) );
8201 if ( $ele.attr( 'class' ) && id !== 'wpbody-content' && id !== 'content' && id !== 'dyncontent' && id !== 'success_msg' ) {
8202 var d = $ele.attr( 'class' ).split( ' ' )[0];
8203 if ( d === 'frm_long_input' || d === 'frm_98_width' || typeof d === 'undefined' ) {
8204 d = '';
8205 } else {
8206 id = d.trim();
8207 }
8208 c = c + ' ' + d;
8209 c = c.replace( 'widefat', '' ).replace( 'frm_with_left_label', '' );
8210 }
8211 }
8212
8213 jQuery( '#frm-insert-fields-box,#frm-conditionals,#frm-adv-info-tab,#frm-dynamic-values' ).attr( 'data-fills', c.trim() );
8214 var a = [
8215 'content', 'wpbody-content', 'dyncontent', 'success_url',
8216 'success_msg', 'edit_msg', 'frm_dyncontent', 'frm_not_email_message',
8217 'frm_not_email_subject'
8218 ];
8219 var b = [
8220 'before_content', 'after_content', 'frm_not_email_to',
8221 'dyn_default_value'
8222 ];
8223
8224 if ( jQuery.inArray( id, a ) >= 0 ) {
8225 jQuery( '.frm_code_list a' ).removeClass( 'frm_noallow' ).addClass( 'frm_allow' );
8226 jQuery( '.frm_code_list a.hide_' + id ).addClass( 'frm_noallow' ).removeClass( 'frm_allow' );
8227 } else if ( jQuery.inArray( id, b ) >= 0 ) {
8228 jQuery( '.frm_code_list:not(.frm-dropdown-menu) a:not(.show_' + id + ')' ).addClass( 'frm_noallow' ).removeClass( 'frm_allow' );
8229 jQuery( '.frm_code_list a.show_' + id ).removeClass( 'frm_noallow' ).addClass( 'frm_allow' );
8230 } else {
8231 jQuery( '.frm_code_list:not(.frm-dropdown-menu) a' ).addClass( 'frm_noallow' ).removeClass( 'frm_allow' );
8232 }
8233
8234 // Automatically select a tab.
8235 if ( id === 'dyn_default_value' ) {
8236 clickedID = 'frm_dynamic_values';
8237 document.getElementById( clickedID + '_tab' ).click();
8238 jQuery( '#' + clickedID.replace( /_/g, '-' ) + ' .frm_show_inactive' ).addClass( 'frm_hidden' );
8239 jQuery( '#' + clickedID.replace( /_/g, '-' ) + ' .frm_show_active' ).removeClass( 'frm_hidden' );
8240 }
8241 }
8242
8243 function toggleAllowedHTML( input ) {
8244 var b,
8245 id = input.id;
8246 if ( typeof id === 'undefined' || id.indexOf( '-search-input' ) !== -1 ) {
8247 return;
8248 }
8249
8250 jQuery( '#frm-adv-info-tab' ).attr( 'data-fills', id.trim() );
8251 if ( input.classList.contains( 'field_custom_html' ) ) {
8252 id = 'field_custom_html';
8253 }
8254
8255 b = [ 'after_html', 'before_html', 'submit_html', 'field_custom_html' ];
8256 if ( jQuery.inArray( id, b ) >= 0 ) {
8257 jQuery( '.frm_code_list li:not(.show_' + id + ')' ).addClass( 'frm_hidden' );
8258 jQuery( '.frm_code_list li.show_' + id ).removeClass( 'frm_hidden' );
8259 }
8260 }
8261
8262 function toggleKeyID( switchTo, e ) {
8263 e.stopPropagation();
8264 jQuery( '.frm_code_list .frmids, .frm_code_list .frmkeys' ).addClass( 'frm_hidden' );
8265 jQuery( '.frm_code_list .' + switchTo ).removeClass( 'frm_hidden' );
8266 jQuery( '.frmids, .frmkeys' ).removeClass( 'current' );
8267 jQuery( '.' + switchTo ).addClass( 'current' );
8268 }
8269
8270 function onActionLoaded( event ) {
8271 const settings = event.target.closest( '.frm_form_action_settings' );
8272 if ( settings && ( settings.classList.contains( 'frm_single_email_settings' ) || settings.classList.contains( 'frm_single_on_submit_settings' ) ) ) {
8273 initWysiwygOnActionLoaded( settings );
8274 }
8275 }
8276
8277 function initWysiwygOnActionLoaded( settings ) {
8278 const wysiwyg = settings.querySelector( '.wp-editor-area' );
8279 if ( wysiwyg ) {
8280 frmDom.wysiwyg.init(
8281 wysiwyg,
8282 { height: 160, addFocusEvents: true }
8283 );
8284 }
8285 }
8286
8287 /* Global settings page */
8288 function loadSettingsTab( anchor ) {
8289 var holder = anchor.replace( '#', '' );
8290 var holderContainer = jQuery( '.frm_' + holder + '_ajax' );
8291 if ( holderContainer.length ) {
8292 jQuery.ajax({
8293 type: 'POST', url: ajaxurl,
8294 data: {
8295 'action': 'frm_settings_tab',
8296 'tab': holder.replace( '_settings', '' ),
8297 'nonce': frmGlobal.nonce
8298 },
8299 success: function( html ) {
8300 holderContainer.replaceWith( html );
8301 }
8302 });
8303 }
8304 }
8305
8306 function uninstallNow() {
8307 /*jshint validthis:true */
8308 if ( confirmLinkClick( this ) === true ) {
8309 jQuery( '.frm_uninstall .frm-wait' ).css( 'visibility', 'visible' );
8310 jQuery.ajax({
8311 type: 'POST',
8312 url: ajaxurl,
8313 data: 'action=frm_uninstall&nonce=' + frmGlobal.nonce,
8314 success: function( msg ) {
8315 jQuery( '.frm_uninstall' ).fadeOut( 'slow' );
8316 window.location = msg;
8317 }
8318 });
8319 }
8320 return false;
8321 }
8322
8323 function saveAddonLicense() {
8324 /*jshint validthis:true */
8325 var button = jQuery( this );
8326 var buttonName = this.name;
8327 var pluginSlug = this.getAttribute( 'data-plugin' );
8328 var action = buttonName.replace( 'edd_' + pluginSlug + '_license_', '' );
8329 var license = document.getElementById( 'edd_' + pluginSlug + '_license_key' ).value;
8330 jQuery.ajax({
8331 type: 'POST', url: ajaxurl, dataType: 'json',
8332 data: {action: 'frm_addon_' + action, license: license, plugin: pluginSlug, nonce: frmGlobal.nonce},
8333 success: function( msg ) {
8334 var thisRow = button.closest( '.edd_frm_license_row' );
8335 if ( action === 'deactivate' ) {
8336 license = '';
8337 document.getElementById( 'edd_' + pluginSlug + '_license_key' ).value = '';
8338 }
8339 thisRow.find( '.edd_frm_license' ).html( license );
8340 if ( msg.success === true ) {
8341 thisRow.find( '.frm_icon_font' ).removeClass( 'frm_hidden' );
8342 thisRow.find( 'div.alignleft' ).toggleClass( 'frm_hidden', 1000 );
8343 }
8344
8345 var messageBox = thisRow.find( '.frm_license_msg' );
8346 messageBox.html( msg.message );
8347 if ( msg.message !== '' ) {
8348 setTimeout( function() {
8349 messageBox.html( '' );
8350 }, 15000 );
8351 }
8352 }
8353 });
8354 }
8355
8356 /* Import/Export page */
8357
8358 function startFormMigration( event ) {
8359 event.preventDefault();
8360
8361 var checkedBoxes = jQuery( event.target ).find( 'input:checked' );
8362 if ( ! checkedBoxes.length ) {
8363 return;
8364 }
8365
8366 var ids = [];
8367 checkedBoxes.each( function( i ) {
8368 ids[i] = this.value;
8369 });
8370
8371 // Begin the import process.
8372 importForms( ids, event.target );
8373 }
8374
8375 /**
8376 * Begins the process of importing the forms.
8377 */
8378 function importForms( forms, targetForm ) {
8379
8380 // Hide the form select section.
8381 var $form = jQuery( targetForm ),
8382 $processSettings = $form.next( '.frm-importer-process' );
8383
8384 // Display total number of forms we have to import.
8385 $processSettings.find( '.form-total' ).text( forms.length );
8386 $processSettings.find( '.form-current' ).text( '1' );
8387
8388 $form.hide();
8389
8390 // Show processing status.
8391 // '.process-completed' might have been shown earlier during a previous import, so hide now.
8392 $processSettings.find( '.process-completed' ).hide();
8393 $processSettings.show();
8394
8395 // Create global import queue.
8396 s.importQueue = forms;
8397 s.imported = 0;
8398
8399 // Import the first form in the queue.
8400 importForm( $processSettings );
8401 }
8402
8403 /**
8404 * Imports a single form from the import queue.
8405 */
8406 function importForm( $processSettings ) {
8407 var formID = s.importQueue[0],
8408 provider = jQuery( '#welcome-panel' ).find( 'input[name="slug"]' ).val(),
8409 data = {
8410 action: 'frm_import_' + provider,
8411 form_id: formID,
8412 nonce: frmGlobal.nonce
8413 };
8414
8415 // Trigger AJAX import for this form.
8416 jQuery.post( ajaxurl, data, function( res ) {
8417
8418 if ( res.success ) {
8419 var statusUpdate;
8420
8421 if ( res.data.error ) {
8422 statusUpdate = '<p>' + res.data.name + ': ' + res.data.msg + '</p>';
8423 } else {
8424 statusUpdate = '<p>Imported <a href="' + res.data.link + '" target="_blank">' + res.data.name + '</a></p>';
8425 }
8426
8427 $processSettings.find( '.status' ).prepend( statusUpdate );
8428 $processSettings.find( '.status' ).show();
8429
8430 // Remove this form ID from the queue.
8431 s.importQueue = jQuery.grep( s.importQueue, function( value ) {
8432 return value != formID;
8433 });
8434 s.imported++;
8435
8436 if ( s.importQueue.length === 0 ) {
8437 $processSettings.find( '.process-count' ).hide();
8438 $processSettings.find( '.forms-completed' ).text( s.imported );
8439 $processSettings.find( '.process-completed' ).show();
8440 } else {
8441 // Import next form in the queue.
8442 $processSettings.find( '.form-current' ).text( s.imported + 1 );
8443 importForm( $processSettings );
8444 }
8445 }
8446 });
8447 }
8448
8449 function validateExport( e ) {
8450 /*jshint validthis:true */
8451 e.preventDefault();
8452
8453 var s = false;
8454 var $exportForms = jQuery( 'input[name="frm_export_forms[]"]' );
8455
8456 if ( ! jQuery( 'input[name="frm_export_forms[]"]:checked' ).val() ) {
8457 $exportForms.closest( '.frm-table-box' ).addClass( 'frm_blank_field' );
8458 s = 'stop';
8459 }
8460
8461 var $exportType = jQuery( 'input[name="type[]"]' );
8462 if ( ! jQuery( 'input[name="type[]"]:checked' ).val() && $exportType.attr( 'type' ) === 'checkbox' ) {
8463 $exportType.closest( 'p' ).addClass( 'frm_blank_field' );
8464 s = 'stop';
8465 }
8466
8467 if ( s === 'stop' ) {
8468 return false;
8469 }
8470
8471 e.stopPropagation();
8472 this.submit();
8473 }
8474
8475 function removeExportError() {
8476 /*jshint validthis:true */
8477 var t = jQuery( this ).closest( '.frm_blank_field' );
8478 if ( typeof t === 'undefined' ) {
8479 return;
8480 }
8481
8482 var $thisName = this.name;
8483 if ( $thisName === 'type[]' && jQuery( 'input[name="type[]"]:checked' ).val() ) {
8484 t.removeClass( 'frm_blank_field' );
8485 } else if ( $thisName === 'frm_export_forms[]' && jQuery( this ).val() ) {
8486 t.removeClass( 'frm_blank_field' );
8487 }
8488
8489 }
8490
8491 function checkCSVExtension() {
8492 /*jshint validthis:true */
8493 var f = jQuery( this ).val();
8494 var re = /\.csv$/i;
8495 if ( f.match( re ) !== null ) {
8496 jQuery( '.show_csv' ).fadeIn();
8497 } else {
8498 jQuery( '.show_csv' ).fadeOut();
8499 }
8500 }
8501
8502 function getExportOption() {
8503 const exportFormatSelect = document.querySelector( 'select[name="format"]' );
8504 if ( exportFormatSelect ) {
8505 return exportFormatSelect.value;
8506 } else {
8507 return '';
8508 }
8509 }
8510
8511 function exportTypeChanged( event ) {
8512 const value = event.target.value;
8513 showOrHideRepeaters( value );
8514 checkExportTypes.call( event.target );
8515 checkSelectedAllFormsCheckbox( value );
8516 }
8517
8518 function checkSelectedAllFormsCheckbox( exportType ) {
8519 const selectAllCheckbox = document.getElementById( 'frm-export-select-all' );
8520 if ( exportType === 'csv' ) {
8521 selectAllCheckbox.checked = false;
8522 selectAllCheckbox.disabled = true;
8523 } else {
8524 selectAllCheckbox.disabled = false;
8525 }
8526 }
8527
8528 function checkExportTypes() {
8529 /*jshint validthis:true */
8530 var $dropdown = jQuery( this );
8531 var $selected = $dropdown.find( ':selected' );
8532 var s = $selected.data( 'support' );
8533
8534 var multiple = s.indexOf( '|' );
8535 jQuery( 'input[name="type[]"]' ).each( function() {
8536 this.checked = false;
8537 if ( s.indexOf( this.value ) >= 0 ) {
8538 this.disabled = false;
8539 if ( multiple === -1 ) {
8540 this.checked = true;
8541 }
8542 } else {
8543 this.disabled = true;
8544 }
8545 });
8546
8547 if ( $dropdown.val() === 'csv' ) {
8548 jQuery( '.csv_opts' ).show();
8549 jQuery( '.xml_opts' ).hide();
8550 } else {
8551 jQuery( '.csv_opts' ).hide();
8552 jQuery( '.xml_opts' ).show();
8553 }
8554
8555 var c = $selected.data( 'count' );
8556 var exportField = jQuery( 'input[name="frm_export_forms[]"]' );
8557 if ( c === 'single' ) {
8558 exportField.prop( 'multiple', false );
8559 exportField.prop( 'checked', false );
8560 } else {
8561 exportField.prop( 'multiple', true );
8562 exportField.prop( 'disabled', false );
8563 }
8564 $dropdown.trigger( 'change' );
8565 }
8566
8567 function showOrHideRepeaters( exportOption ) {
8568 if ( exportOption === '' ) {
8569 return;
8570 }
8571
8572 const repeaters = document.querySelectorAll( '.frm-is-repeater' );
8573 if ( ! repeaters.length ) {
8574 return;
8575 }
8576
8577 if ( exportOption === 'csv' ) {
8578 repeaters.forEach( form => {
8579 form.classList.remove( 'frm_hidden' );
8580 });
8581 } else {
8582 repeaters.forEach( form => {
8583 form.classList.add( 'frm_hidden' );
8584 });
8585 }
8586
8587 searchContent.call( document.querySelector( '.frm-auto-search' ) );
8588 }
8589
8590 function preventMultipleExport() {
8591 var type = jQuery( 'select[name=format]' ),
8592 selected = type.find( ':selected' ),
8593 count = selected.data( 'count' ),
8594 exportField = jQuery( 'input[name="frm_export_forms[]"]' );
8595
8596 if ( count === 'single' ) {
8597 // Disable all other fields to prevent multiple selections.
8598 if ( this.checked ) {
8599 exportField.prop( 'disabled', true );
8600 this.removeAttribute( 'disabled' );
8601 } else {
8602 exportField.prop( 'disabled', false );
8603 }
8604 } else {
8605 exportField.prop( 'disabled', false );
8606 }
8607 }
8608
8609 function initiateMultiselect() {
8610 jQuery( '.frm_multiselect' ).hide().each( frmDom.bootstrap.multiselect.init );
8611 }
8612
8613 /* Addons page */
8614 function installMultipleAddons( e ) {
8615 e.preventDefault();
8616 toggleAddonState( this, 'frm_multiple_addons' );
8617 }
8618
8619 function activateAddon( e ) {
8620 e.preventDefault();
8621 toggleAddonState( this, 'frm_activate_addon' );
8622 }
8623
8624 function installAddon( e ) {
8625 e.preventDefault();
8626 toggleAddonState( this, 'frm_install_addon' );
8627 }
8628
8629 function toggleAddonState( clicked, action ) {
8630 let button, plugin, el, message;
8631
8632 // Remove any leftover error messages, output an icon and get the plugin basename that needs to be activated.
8633 jQuery( '.frm-addon-error' ).remove();
8634 button = jQuery( clicked );
8635 plugin = button.attr( 'rel' );
8636 el = button.parent();
8637 message = el.parent().find( '.addon-status-label' );
8638
8639 button.addClass( 'frm_loading_button' );
8640
8641 // Process the Ajax to perform the activation.
8642 jQuery.ajax({
8643 url: ajaxurl,
8644 type: 'POST',
8645 async: true,
8646 cache: false,
8647 dataType: 'json',
8648 data: {
8649 action: action,
8650 nonce: frmGlobal.nonce,
8651 plugin: plugin
8652 },
8653 success: function( response ) {
8654 response = response?.data ?? response;
8655
8656 let saveAndReload;
8657
8658 if ( 'string' !== typeof response && 'string' === typeof response.message ) {
8659 if ( 'undefined' !== typeof response.saveAndReload ) {
8660 saveAndReload = response.saveAndReload;
8661 }
8662 response = response.message;
8663 }
8664
8665 const error = extractErrorFromAddOnResponse( response );
8666 if ( error ) {
8667 addonError( error, el, button );
8668 return;
8669 }
8670
8671 afterAddonInstall( response, button, message, el, saveAndReload, action );
8672 },
8673 error: function() {
8674 button.removeClass( 'frm_loading_button' );
8675 }
8676 });
8677 }
8678
8679 function installAddonWithCreds( e ) {
8680 // Prevent the default action, let the user know we are attempting to install again and go with it.
8681 e.preventDefault();
8682
8683 // Now let's make another Ajax request once the user has submitted their credentials.
8684 const proceed = jQuery( this );
8685 const el = proceed.parent().parent();
8686 const plugin = proceed.attr( 'rel' );
8687
8688 proceed.addClass( 'frm_loading_button' );
8689
8690 jQuery.ajax({
8691 url: ajaxurl,
8692 type: 'POST',
8693 async: true,
8694 cache: false,
8695 dataType: 'json',
8696 data: {
8697 action: 'frm_install_addon',
8698 nonce: frmAdminJs.nonce,
8699 plugin: plugin,
8700 hostname: el.find( '#hostname' ).val(),
8701 username: el.find( '#username' ).val(),
8702 password: el.find( '#password' ).val()
8703 },
8704 success: function( response ) {
8705 response = response?.data ?? response;
8706
8707 const error = extractErrorFromAddOnResponse( response );
8708 if ( error ) {
8709 addonError( error, el, proceed );
8710 return;
8711 }
8712
8713 afterAddonInstall( response, proceed, message, el );
8714 },
8715 error: function() {
8716 proceed.removeClass( 'frm_loading_button' );
8717 }
8718 });
8719 }
8720
8721 function afterAddonInstall( response, button, message, el, saveAndReload, action = 'frm_activate_addon' ) {
8722 const addonStatuses = document.querySelectorAll( '.frm-addon-status' );
8723 addonStatuses.forEach(
8724 addonStatus => {
8725 addonStatus.textContent = response;
8726 addonStatus.style.display = 'block';
8727 }
8728 );
8729
8730 // The Ajax request was successful, so let's update the output.
8731 button.css({ opacity: '0' });
8732
8733 document.querySelectorAll( '.frm-oneclick' ).forEach(
8734 oneClick => {
8735 oneClick.style.display = 'none';
8736 }
8737 );
8738
8739 jQuery( '#frm_upgrade_modal h2' ).hide();
8740 jQuery( '#frm_upgrade_modal .frm_lock_icon' ).addClass( 'frm_lock_open_icon' );
8741 jQuery( '#frm_upgrade_modal .frm_lock_icon use' ).attr( 'xlink:href', '#frm_lock_open_icon' );
8742
8743 // Proceed with CSS changes
8744 const actionMap = {
8745 frm_activate_addon: { class: 'frm-addon-active', message: frmAdminJs.active },
8746 frm_deactivate_addon: { class: 'frm-addon-installed', message: frmAdminJs.installed },
8747 frm_uninstall_addon: { class: 'frm-addon-not-installed', message: frmAdminJs.not_installed }
8748 };
8749 actionMap.frm_install_addon = actionMap.frm_activate_addon;
8750
8751 const messageElement = message[0];
8752 if ( messageElement ) {
8753 messageElement.textContent = actionMap[action].message;
8754 }
8755
8756 const parentElement = el[0].parentElement;
8757 parentElement.classList.remove( 'frm-addon-not-installed', 'frm-addon-installed', 'frm-addon-active' );
8758 parentElement.classList.add( actionMap[action].class );
8759
8760 const buttonElement = button[0];
8761 buttonElement.classList.remove( 'frm_loading_button' );
8762
8763 // Maybe refresh import and SMTP pages
8764 const refreshPage = document.querySelectorAll( '.frm-admin-page-import, #frm-admin-smtp, #frm-welcome' );
8765 if ( refreshPage.length > 0 ) {
8766 window.location.reload();
8767 return;
8768 }
8769
8770 if ([ 'settings', 'form_builder' ].includes( saveAndReload ) ) {
8771 addonStatuses.forEach(
8772 addonStatus => {
8773 const inModal = null !== addonStatus.closest( '#frm_upgrade_modal' );
8774 addonStatus.appendChild( getSaveAndReloadSettingsOptions( saveAndReload, inModal ) );
8775 }
8776 );
8777 }
8778 }
8779
8780 function getSaveAndReloadSettingsOptions( saveAndReload, inModal ) {
8781 const className = 'frm-save-and-reload-options';
8782 const children = [ saveAndReloadSettingsButton( saveAndReload ) ];
8783 if ( inModal ) {
8784 children.push( closePopupButton() );
8785 }
8786 return div({ className, children });
8787 }
8788
8789 function saveAndReloadSettingsButton( saveAndReload ) {
8790 var button = document.createElement( 'button' );
8791 button.classList.add( 'frm-save-and-reload', 'button', 'button-primary', 'frm-button-primary' );
8792 button.textContent = __( 'Save and Reload', 'formidable' );
8793 button.addEventListener( 'click', () => {
8794 if ( saveAndReload === 'form_builder' ) {
8795 saveAndReloadFormBuilder();
8796 } else if ( saveAndReload === 'settings' ) {
8797 saveAndReloadSettings();
8798 }
8799 });
8800 return button;
8801 }
8802
8803 function closePopupButton() {
8804 var a = document.createElement( 'a' );
8805 a.setAttribute( 'href', '#' );
8806 a.classList.add( 'button', 'button-secondary', 'frm-button-secondary', 'dismiss' );
8807 a.textContent = __( 'Close', 'formidable' );
8808 return a;
8809 }
8810
8811 function extractErrorFromAddOnResponse( response ) {
8812 if ( typeof response !== 'string' ) {
8813 if ( typeof response.success !== 'undefined' && response.success ) {
8814 return false;
8815 }
8816
8817 if ( response.form ) {
8818 if ( jQuery( response.form ).is( '#message' ) ) {
8819 return {
8820 message: jQuery( response.form ).find( 'p' ).html()
8821 };
8822 }
8823 }
8824
8825 return response;
8826 }
8827
8828 return false;
8829 }
8830
8831 function addonError( response, el, button ) {
8832 if ( response.form ) {
8833 jQuery( '.frm-inline-error' ).remove();
8834 button.closest( '.frm-card' )
8835 .html( response.form )
8836 .css({ padding: 5 })
8837 .find( '#upgrade' )
8838 .attr( 'rel', button.attr( 'rel' ) )
8839 .on( 'click', installAddonWithCreds );
8840 } else {
8841 el.append( '<div class="frm-addon-error frm_error_style"><p><strong>' + response.message + '</strong></p></div>' );
8842 button.removeClass( 'frm_loading_button' );
8843 jQuery( '.frm-addon-error' ).delay( 4000 ).fadeOut();
8844 }
8845 }
8846
8847 /* Templates */
8848 function showActiveCampaignForm() {
8849 loadApiEmailForm();
8850 }
8851
8852 function handleApiFormError( inputId, errorId, type, message ) {
8853 const $error = jQuery( errorId );
8854 $error.removeClass( 'frm_hidden' ).attr( 'frm-error', type );
8855
8856 if ( typeof message !== 'undefined' ) {
8857 $error.find( 'span[frm-error="' + type + '"]' ).text( message );
8858 }
8859
8860 jQuery( inputId ).one( 'keyup', function() {
8861 $error.addClass( 'frm_hidden' );
8862 });
8863 }
8864
8865 function handleEmailAddressError( type ) {
8866 handleApiFormError( '#frm_leave_email', '#frm_leave_email_error', type );
8867 }
8868
8869 function loadApiEmailForm() {
8870 const formContainer = document.getElementById( 'frmapi-email-form' );
8871 jQuery.ajax({
8872 dataType: 'json',
8873 url: formContainer.getAttribute( 'data-url' ),
8874 success: function( json ) {
8875 var form = json.renderedHtml;
8876 form = form.replace( /<link\b[^>]*(formidableforms.css|action=frmpro_css)[^>]*>/gi, '' );
8877 formContainer.innerHTML = form;
8878 }
8879 });
8880 }
8881 function initSelectionAutocomplete() {
8882 frmDom.autocomplete.initSelectionAutocomplete();
8883 }
8884
8885 function nextInstallStep( thisStep ) {
8886 thisStep.classList.add( 'frm_grey' );
8887 thisStep.nextElementSibling.classList.remove( 'frm_grey' );
8888 }
8889
8890 function installTemplateFieldset( e ) {
8891 /*jshint validthis:true */
8892 var fieldset = this.parentNode.parentNode,
8893 action = fieldset.elements.type.value,
8894 button = this;
8895 e.preventDefault();
8896 button.classList.add( 'frm_loading_button' );
8897 installNewForm( fieldset, action, button );
8898 }
8899
8900 function installTemplate( e ) {
8901 /*jshint validthis:true */
8902 var action = this.elements.type.value,
8903 button = this.querySelector( 'button' );
8904 e.preventDefault();
8905 button.classList.add( 'frm_loading_button' );
8906 installNewForm( this, action, button );
8907 }
8908
8909 function installNewForm( form, action, button ) {
8910 const formData = formToData( form );
8911 const formName = formData.template_name;
8912 const formDesc = formData.template_desc;
8913 const link = form.elements.link.value;
8914
8915 let data = {
8916 action: action,
8917 xml: link,
8918 name: formName,
8919 desc: formDesc,
8920 form: JSON.stringify( formData ),
8921 nonce: frmGlobal.nonce
8922 };
8923
8924 const hookName = 'frm_before_install_new_form';
8925 const filterArgs = { formData };
8926 data = wp.hooks.applyFilters( hookName, data, filterArgs );
8927
8928 postAjax( data, function( response ) {
8929 if ( typeof response.redirect !== 'undefined' ) {
8930 const redirect = response.redirect;
8931 if ( typeof form.elements.redirect === 'undefined' ) {
8932 window.location = redirect;
8933 } else {
8934 const href = document.getElementById( 'frm-redirect-link' );
8935 if ( typeof link !== 'undefined' && href !== null ) {
8936 // Show the next installation step.
8937 href.setAttribute( 'href', redirect );
8938 href.classList.remove( 'frm_grey', 'disabled' );
8939 nextInstallStep( form.parentNode.parentNode );
8940 button.classList.add( 'frm_grey', 'disabled' );
8941 }
8942 }
8943 } else {
8944 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
8945
8946 // Show response.message
8947 if ( 'string' === typeof response.message ) {
8948 showInstallFormErrorModal( response.message );
8949 }
8950 }
8951 button.classList.remove( 'frm_loading_button' );
8952 });
8953 }
8954
8955 function showInstallFormErrorModal( message ) {
8956 const modalContent = div( message );
8957 modalContent.style.padding = '20px 40px';
8958 const modal = frmDom.modal.maybeCreateModal(
8959 'frmInstallFormErrorModal',
8960 {
8961 title: __( 'Unable to install template', 'formidable' ),
8962 content: modalContent
8963 }
8964 );
8965 modal.classList.add( 'frm_common_modal' );
8966 }
8967
8968 function handleCaptchaTypeChange( e ) {
8969 const thresholdContainer = document.getElementById( 'frm_captcha_threshold_container' );
8970 if ( thresholdContainer ) {
8971 thresholdContainer.classList.toggle( 'frm_hidden', 'v3' !== e.target.value );
8972 }
8973 }
8974
8975 function trashTemplate( e ) {
8976 /*jshint validthis:true */
8977 var id = this.getAttribute( 'data-id' );
8978 e.preventDefault();
8979
8980 data = {
8981 action: 'frm_forms_trash',
8982 id: id,
8983 nonce: frmGlobal.nonce
8984 };
8985 postAjax( data, function() {
8986 var card = document.getElementById( 'frm-template-custom-' + id );
8987 fadeOut( card, function() {
8988 card.parentNode.removeChild( card );
8989 });
8990 });
8991 }
8992
8993 function searchContent() {
8994 /*jshint validthis:true */
8995 var i,
8996 regEx = false,
8997 searchText = this.value.toLowerCase(),
8998 toSearch = this.getAttribute( 'data-tosearch' ),
8999 items = document.getElementsByClassName( toSearch );
9000
9001 if ( this.tagName === 'SELECT' ) {
9002 searchText = selectedOptions( this );
9003 searchText = searchText.join( '|' ).toLowerCase();
9004 regEx = true;
9005 }
9006
9007 if ( toSearch === 'frm-action' && searchText !== '' ) {
9008 var addons = document.getElementById( 'frm_email_addon_menu' ).classList;
9009 addons.remove( 'frm-all-actions' );
9010 addons.add( 'frm-limited-actions' );
9011 }
9012
9013 for ( i = 0; i < items.length; i++ ) {
9014 var innerText = items[i].innerText.toLowerCase();
9015
9016 const itemCanBeShown = ! ( getExportOption() === 'xml' && items[i].classList.contains( 'frm-is-repeater' ) );
9017 if ( searchText === '' ) {
9018 if ( itemCanBeShown ) {
9019 items[i].classList.remove( 'frm_hidden' );
9020 }
9021 items[i].classList.remove( 'frm-search-result' );
9022 } else if ( ( regEx && new RegExp( searchText ).test( innerText ) ) || innerText.indexOf( searchText ) >= 0 ) {
9023 if ( itemCanBeShown ) {
9024 items[i].classList.remove( 'frm_hidden' );
9025 }
9026 items[i].classList.add( 'frm-search-result' );
9027 } else {
9028 items[i].classList.add( 'frm_hidden' );
9029 items[i].classList.remove( 'frm-search-result' );
9030 }
9031 }
9032
9033 // Updates the visibility of category headings based on search results.
9034 updateCatHeadingVisibility();
9035
9036 jQuery( this ).trigger( 'frmAfterSearch' );
9037 }
9038
9039 /**
9040 * Updates the visibility of category headings based on search results.
9041 * If all associated fields are hidden (indicating no search matches),
9042 * the heading is hidden.
9043 *
9044 * @since 6.4.1
9045 */
9046 function updateCatHeadingVisibility() {
9047 const insertFieldsElement = document.querySelector( '#frm-insert-fields' );
9048 if ( ! insertFieldsElement ) {
9049 return;
9050 }
9051
9052 const headingElements = insertFieldsElement.querySelectorAll( ':scope > .frm-with-line' );
9053 headingElements.forEach( heading => {
9054 const fieldsListElement = heading.nextElementSibling;
9055 if ( ! fieldsListElement ) {
9056 return;
9057 }
9058 const listItemElements = fieldsListElement.querySelectorAll( ':scope > li.frmbutton' );
9059 const allHidden = Array.from( listItemElements ).every( li => li.classList.contains( 'frm_hidden' ) );
9060
9061 // Add or remove class based on `allHidden` condition
9062 heading.classList.toggle( 'frm_hidden', allHidden );
9063 });
9064 }
9065
9066 function stopPropagation( e ) {
9067 e.stopPropagation();
9068 }
9069
9070 /* Helpers */
9071
9072 function selectedOptions( select ) {
9073 var opt,
9074 result = [],
9075 options = select && select.options;
9076
9077 for ( var i = 0, iLen = options.length; i < iLen; i++ ) {
9078 opt = options[i];
9079
9080 if ( opt.selected ) {
9081 result.push( opt.value );
9082 }
9083 }
9084 return result;
9085 }
9086
9087 function triggerEvent( element, event ) {
9088 var evt = document.createEvent( 'HTMLEvents' );
9089 evt.initEvent( event, false, true );
9090 element.dispatchEvent( evt );
9091 }
9092
9093 function postAjax( data, success ) {
9094 let response;
9095
9096 const xmlHttp = new XMLHttpRequest();
9097 const params = typeof data === 'string' ? data : Object.keys( data ).map(
9098 function( k ) {
9099 return encodeURIComponent( k ) + '=' + encodeURIComponent( data[k]);
9100 }
9101 ).join( '&' );
9102
9103 xmlHttp.open( 'post', ajaxurl, true );
9104 xmlHttp.onreadystatechange = function() {
9105 if ( xmlHttp.readyState > 3 && xmlHttp.status == 200 ) {
9106 response = xmlHttp.responseText;
9107 try {
9108 response = JSON.parse( response );
9109 } catch ( e ) {
9110 // The response may not be JSON, so just return it.
9111 }
9112 success( response );
9113 }
9114 };
9115 xmlHttp.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
9116 xmlHttp.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );
9117 xmlHttp.send( params );
9118 return xmlHttp;
9119 }
9120
9121 function fadeOut( element, success ) {
9122 element.classList.add( 'frm-fade' );
9123 setTimeout( success, 1000 );
9124 }
9125
9126 function invisible( classes ) {
9127 jQuery( classes ).css( 'visibility', 'hidden' );
9128 }
9129
9130 function visible( classes ) {
9131 jQuery( classes ).css( 'visibility', 'visible' );
9132 }
9133
9134 function initModal( id, width ) {
9135 const $info = jQuery( id );
9136 if ( ! $info.length ) {
9137 return false;
9138 }
9139
9140 if ( typeof width === 'undefined' ) {
9141 width = '550px';
9142 }
9143
9144 const dialogArgs = {
9145 dialogClass: 'frm-dialog',
9146 modal: true,
9147 autoOpen: false,
9148 closeOnEscape: true,
9149 width: width,
9150 resizable: false,
9151 draggable: false,
9152 open: function() {
9153 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
9154 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
9155 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
9156 $info.removeClass( 'ui-dialog-content ui-widget-content' );
9157 bindClickForDialogClose( $info );
9158 },
9159 close: function() {
9160 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
9161 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
9162
9163 this.removeAttribute( 'data-option-type' );
9164 const optionType = document.getElementById( 'bulk-option-type' );
9165 if ( optionType ) {
9166 optionType.value = '';
9167 }
9168 }
9169 };
9170
9171 $info.dialog( dialogArgs );
9172
9173 return $info;
9174 }
9175
9176 function toggle( cname, id ) {
9177 if ( id === '#' ) {
9178 var cont = document.getElementById( cname );
9179 var hidden = cont.style.display;
9180 if ( hidden === 'none' ) {
9181 cont.style.display = 'block';
9182 } else {
9183 cont.style.display = 'none';
9184 }
9185 } else {
9186 var vis = cname.is( ':visible' );
9187 if ( vis ) {
9188 cname.hide();
9189 } else {
9190 cname.show();
9191 }
9192 }
9193 }
9194
9195 function removeWPUnload() {
9196 window.onbeforeunload = null;
9197 var w = jQuery( window );
9198 w.off( 'beforeunload.widgets' );
9199 w.off( 'beforeunload.edit-post' );
9200 }
9201
9202 function addMultiselectLabelListener() {
9203 const clickListener = ( e ) => {
9204 if ( 'LABEL' !== e.target.nodeName ) {
9205 return;
9206 }
9207
9208 const labelFor = e.target.getAttribute( 'for' );
9209 if ( ! labelFor ) {
9210 return;
9211 }
9212
9213 const input = document.getElementById( labelFor );
9214 if ( ! input || ! input.nextElementSibling ) {
9215 return;
9216 }
9217
9218 const buttonToggle = input.nextElementSibling.querySelector( 'button.dropdown-toggle.multiselect' );
9219 if ( ! buttonToggle ) {
9220 return;
9221 }
9222
9223 const triggerMultiselectClick = () => buttonToggle.click();
9224 setTimeout( triggerMultiselectClick, 0 );
9225 };
9226 document.addEventListener( 'click', clickListener );
9227 }
9228
9229 function maybeChangeEmbedFormMsg() {
9230 var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
9231 var fieldItem = document.getElementById( 'frm_field_id_' + fieldId );
9232 if ( null === fieldItem || 'form' !== fieldItem.dataset.type ) {
9233 return;
9234 }
9235
9236 fieldItem = jQuery( fieldItem );
9237
9238 if ( this.options[ this.selectedIndex ].value ) {
9239 fieldItem.find( '.frm-not-set' )[0].classList.add( 'frm_hidden' );
9240 var embedMsg = fieldItem.find( '.frm-embed-message' );
9241 embedMsg.html( embedMsg.data( 'embedmsg' ) + this.options[ this.selectedIndex ].text );
9242 fieldItem.find( '.frm-embed-field-placeholder' )[0].classList.remove( 'frm_hidden' );
9243 } else {
9244 fieldItem.find( '.frm-not-set' )[0].classList.remove( 'frm_hidden' );
9245 fieldItem.find( '.frm-embed-field-placeholder' )[0].classList.add( 'frm_hidden' );
9246 }
9247 }
9248
9249 function toggleProductType() {
9250 var settings = jQuery( this ).closest( '.frm-single-settings' ),
9251 container = settings.find( '.frmjs_product_choices' ),
9252 heading = settings.find( '.frm_prod_options_heading' ),
9253 currentVal = this.options[ this.selectedIndex ].value;
9254
9255 container.removeClass( 'frm_prod_type_single frm_prod_type_user_def' );
9256 heading.removeClass( 'frm_prod_user_def' );
9257
9258 if ( 'single' === currentVal ) {
9259 container.addClass( 'frm_prod_type_single' );
9260 } else if ( 'user_def' === currentVal ) {
9261 container.addClass( 'frm_prod_type_user_def' );
9262 heading.addClass( 'frm_prod_user_def' );
9263 }
9264 }
9265
9266 function isProductField( fieldId ) {
9267 var field = document.getElementById( 'frm_field_id_' + fieldId );
9268 if ( field === null ) {
9269 return false;
9270 } else {
9271 return 'product' === field.getAttribute( 'data-type' );
9272 }
9273 }
9274
9275 /**
9276 * Serialize form data with vanilla JS.
9277 */
9278 function formToData( form ) {
9279 var subKey, i,
9280 object = {},
9281 formData = form.elements;
9282
9283 for ( i = 0; i < formData.length; i++ ) {
9284 var input = formData[i],
9285 key = input.name,
9286 value = input.value,
9287 names = key.match( /(.*)\[(.*)\]/ );
9288
9289 if ( ( input.type === 'radio' || input.type === 'checkbox' ) && ! input.checked ) {
9290 continue;
9291 }
9292
9293 if ( names !== null ) {
9294 key = names[1];
9295 subKey = names[2];
9296 if ( ! Reflect.has( object, key ) ) {
9297 object[key] = {};
9298 }
9299 object[key][subKey] = value;
9300 continue;
9301 }
9302
9303 // Reflect.has in favor of: object.hasOwnProperty(key)
9304 if ( ! Reflect.has( object, key ) ) {
9305 object[key] = value;
9306 continue;
9307 }
9308 if ( ! Array.isArray( object[key]) ) {
9309 object[key] = [ object[key] ];
9310 }
9311 object[key].push( value );
9312 }
9313
9314 return object;
9315 }
9316
9317 /**
9318 * Show, hide, and sort subfields of Name field on form builder.
9319 *
9320 * @since 4.11
9321 */
9322 function handleNameFieldOnFormBuilder() {
9323 /**
9324 * Gets subfield element from cache.
9325 *
9326 * @param {String} fieldId Field ID.
9327 * @param {String} key Cache key.
9328 * @returns {HTMLElement|undefined} Return the element from cache or undefined if not found.
9329 */
9330 const getSubFieldElFromCache = ( fieldId, key ) => {
9331 window.frmCachedSubFields = window.frmCachedSubFields || {};
9332 window.frmCachedSubFields[fieldId] = window.frmCachedSubFields[fieldId] || {};
9333 return window.frmCachedSubFields[fieldId][key];
9334 };
9335
9336 /**
9337 * Sets subfield element to cache.
9338 *
9339 * @param {String} fieldId Field ID.
9340 * @param {String} key Cache key.
9341 * @param {HTMLElement} el Element.
9342 */
9343 const setSubFieldElToCache = ( fieldId, key, el ) => {
9344 window.frmCachedSubFields = window.frmCachedSubFields || {};
9345 window.frmCachedSubFields[fieldId] = window.frmCachedSubFields[fieldId] || {};
9346 window.frmCachedSubFields[fieldId][key] = el;
9347 };
9348
9349 /**
9350 * Gets column class from the number of columns.
9351 *
9352 * @param {Number} colCount Number of columns.
9353 * @returns {string}
9354 */
9355 const getColClass = colCount => 'frm' + parseInt( 12 / colCount );
9356
9357 const colClasses = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ].map( num => 'frm' + num );
9358
9359 const allSubFieldNames = [ 'first', 'middle', 'last' ];
9360
9361 /**
9362 * Handles name layout change.
9363 *
9364 * @param {Event} event Event object.
9365 */
9366 const onChangeLayout = event => {
9367 const value = event.target.value;
9368 const subFieldNames = value.split( '_' );
9369 const fieldId = event.target.dataset.fieldId;
9370
9371 /*
9372 * Live update form on the form builder.
9373 */
9374 const container = document.querySelector( '#field_' + fieldId + '_inner_container .frm_combo_inputs_container' );
9375 const newColClass = getColClass( subFieldNames.length );
9376
9377 // Set all sub field elements to cache and hide all of them first.
9378 allSubFieldNames.forEach( name => {
9379 const subFieldEl = container.querySelector( '[data-sub-field-name="' + name + '"]' );
9380 if ( subFieldEl ) {
9381 subFieldEl.classList.add( 'frm_hidden' );
9382 subFieldEl.classList.remove( ...colClasses );
9383 setSubFieldElToCache( fieldId, name, subFieldEl );
9384 }
9385 });
9386
9387 subFieldNames.forEach( subFieldName => {
9388 const subFieldEl = getSubFieldElFromCache( fieldId, subFieldName );
9389 if ( ! subFieldEl ) {
9390 return;
9391 }
9392
9393 subFieldEl.classList.remove( 'frm_hidden' );
9394 subFieldEl.classList.add( newColClass );
9395
9396 container.append( subFieldEl );
9397 });
9398
9399 /*
9400 * Live update subfield options.
9401 */
9402 // Hide all subfield options.
9403 allSubFieldNames.forEach( name => {
9404 const optionsEl = document.querySelector( '.frm_sub_field_options-' + name + '[data-field-id="' + fieldId + '"]' );
9405 if ( optionsEl ) {
9406 optionsEl.classList.add( 'frm_hidden' );
9407 setSubFieldElToCache( fieldId, name + '_options', optionsEl );
9408 }
9409 });
9410
9411 subFieldNames.forEach( subFieldName => {
9412 const optionsEl = getSubFieldElFromCache( fieldId, subFieldName + '_options' );
9413 if ( ! optionsEl ) {
9414 return;
9415 }
9416 optionsEl.classList.remove( 'frm_hidden' );
9417 });
9418 };
9419
9420 const dropdownSelector = '.frm_name_layout_dropdown';
9421 document.addEventListener( 'change', event => {
9422 if ( event.target.matches( dropdownSelector ) ) {
9423 onChangeLayout( event );
9424 }
9425 }, false );
9426 }
9427
9428 function debounce( func, wait = 100 ) {
9429 return frmDom.util.debounce( func, wait );
9430 }
9431
9432 function addSaveAndDragIconsToOption( fieldId, liObject ) {
9433 let li, useTag, useTagHref;
9434 let hasDragIcon = false;
9435 let hasSaveIcon = false;
9436
9437 if ( liObject.newOption ) {
9438 const parser = new DOMParser();
9439 li = parser.parseFromString( liObject.newOption, 'text/html' ).body.childNodes[0];
9440 } else {
9441 li = liObject;
9442 }
9443
9444 const liIcons = li.querySelectorAll( 'svg' );
9445
9446 liIcons.forEach( ( svg, key ) => {
9447 useTag = svg.getElementsByTagNameNS( 'http://www.w3.org/2000/svg', 'use' )[0];
9448 if ( ! useTag ) {
9449 return;
9450 }
9451 useTagHref = useTag.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) || useTag.getAttribute( 'href' );
9452
9453 if ( useTagHref === '#frm_drag_icon' ) {
9454 hasDragIcon = true;
9455 }
9456
9457 if ( useTagHref === '#frm_save_icon' ) {
9458 hasSaveIcon = true;
9459 }
9460 });
9461
9462 if ( ! hasDragIcon ) {
9463 li.prepend( icons.drag.cloneNode( true ) );
9464 }
9465
9466 if ( li.querySelector( `[id^=field_key_${fieldId}-]` ) && ! hasSaveIcon ) {
9467 li.querySelector( `[id^=field_key_${fieldId}-]` ).after( icons.save.cloneNode( true ) );
9468 }
9469
9470 if ( liObject.newOption ) {
9471 liObject.newOption = li;
9472 }
9473 }
9474
9475 function maybeAddSaveAndDragIcons( fieldId ) {
9476 fieldOptions = document.querySelectorAll( `[id^=frm_delete_field_${fieldId}-]` );
9477 // return if there are no options.
9478 if ( fieldOptions.length < 2 ) {
9479 return;
9480 }
9481
9482 let options = [ ...fieldOptions ].slice( 1 );
9483 options.forEach( ( li, _key ) => {
9484 if ( li.classList.contains( 'frm_other_option' ) ) {
9485 return;
9486 }
9487 addSaveAndDragIconsToOption( fieldId, li );
9488 });
9489 }
9490
9491 function initOnSubmitAction() {
9492 const onChangeType = event => {
9493 if ( ! event.target.checked ) {
9494 return;
9495 }
9496
9497 const actionEl = event.target.closest( '.frm_form_action_settings' );
9498 actionEl.querySelectorAll( '.frm_on_submit_dependent_setting:not(.frm_hidden)' ).forEach( el => {
9499 el.classList.add( 'frm_hidden' );
9500 });
9501
9502 const activeEls = actionEl.querySelectorAll( '.frm_on_submit_dependent_setting[data-show-if-' + event.target.value + ']' );
9503 activeEls.forEach( activeEl => {
9504 activeEl.classList.remove( 'frm_hidden' );
9505 });
9506
9507 actionEl.setAttribute( 'data-on-submit-type', event.target.value );
9508 };
9509
9510 frmDom.util.documentOn( 'change', '.frm_on_submit_type input[type="radio"]', onChangeType );
9511 }
9512
9513 /**
9514 * Listen for click events for an API-loaded email collection form.
9515 *
9516 * This is used for the Active Campaign sign-up form in the inbox page (when there are no messages).
9517 */
9518 function initAddMyEmailAddress() {
9519 jQuery( document ).on(
9520 'click',
9521 '#frm-add-my-email-address',
9522 event => {
9523 event.preventDefault();
9524 addMyEmailAddress();
9525 }
9526 );
9527
9528 const emptyInbox = document.getElementById( 'frm_empty_inbox' );
9529 const leaveEmailIput = document.getElementById( 'frm_leave_email' );
9530
9531 if ( emptyInbox && leaveEmailIput ) {
9532 const leaveEmailModal = document.getElementById( 'frm-leave-email-modal' );
9533 leaveEmailModal.classList.remove( 'frm_hidden' );
9534 leaveEmailModal.querySelector( '.frm_modal_footer' ).classList.add( 'frm_hidden' );
9535
9536 leaveEmailIput.addEventListener(
9537 'keyup',
9538 event => {
9539 if ( 'Enter' === event.key ) {
9540 const button = document.getElementById( 'frm-add-my-email-address' );
9541 if ( button ) {
9542 button.click();
9543 }
9544 }
9545 }
9546 );
9547 }
9548 }
9549
9550 function addMyEmailAddress() {
9551 const email = document.getElementById( 'frm_leave_email' ).value.trim();
9552 if ( '' === email ) {
9553 handleEmailAddressError( 'empty' );
9554 return;
9555 }
9556
9557 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;
9558 if ( regex.test( email ) === false ) {
9559 handleEmailAddressError( 'invalid' );
9560 return;
9561 }
9562
9563 const $hiddenForm = jQuery( '#frmapi-email-form' ).find( 'form' );
9564 const $hiddenEmailField = $hiddenForm.find( '[type="email"]' ).not( '.frm_verify' );
9565 if ( ! $hiddenEmailField.length ) {
9566 return;
9567 }
9568
9569 const emptyInbox = document.getElementById( 'frm_empty_inbox' );
9570 if ( emptyInbox ) {
9571 document.getElementById( 'frm-add-my-email-address' ).remove();
9572
9573 const emailWrapper = document.getElementById( 'frm_leave_email_wrapper' );
9574 if ( emailWrapper ) {
9575 emailWrapper.classList.add( 'frm_hidden' );
9576 const spinner = span({ className: 'frm-wait frm_spinner' });
9577 spinner.style.visibility = 'visible';
9578 spinner.style.float = 'none';
9579 spinner.style.width = 'unset';
9580 emailWrapper.parentElement.insertBefore(
9581 spinner,
9582 emailWrapper.nextElementSibling
9583 );
9584 }
9585 }
9586
9587 $hiddenEmailField.val( email );
9588 jQuery.ajax({
9589 type: 'POST',
9590 url: $hiddenForm.attr( 'action' ),
9591 data: $hiddenForm.serialize() + '&action=frm_forms_preview'
9592 }).done( function( data ) {
9593 const message = jQuery( data ).find( '.frm_message' ).text().trim();
9594 if ( message.indexOf( 'Thanks!' ) === -1 ) {
9595 handleEmailAddressError( 'invalid' );
9596 return;
9597 }
9598
9599 const apiForm = document.getElementById( 'frmapi-email-form' );
9600 const spinner = apiForm.parentElement.querySelector( '.frm_spinner' );
9601 if ( spinner ) {
9602 spinner.remove();
9603 }
9604
9605 // Handle successful form submission.
9606 // handle the Active Campaign form on the inbox page.
9607 document.getElementById( 'frm_leave_email_wrapper' ).replaceWith(
9608 span({ text: __( 'Thank you for signing up!', 'formidable' ) })
9609 );
9610 });
9611 }
9612
9613 /**
9614 * Adds footer links to the admin body content.
9615 *
9616 * @return {void}
9617 */
9618 function addAdminFooterLinks() {
9619 const footerLinks = document.querySelector( '.frm-admin-footer-links' );
9620 const container = document.querySelector( '.frm_page_container' ) ?? document.getElementById( 'wpbody-content' );
9621
9622 if ( ! footerLinks || ! container ) {
9623 return;
9624 }
9625
9626 container.appendChild( footerLinks );
9627 footerLinks.classList.remove( 'frm_hidden' );
9628 }
9629
9630 /**
9631 * Apply zebra striping to a table while ignoring empty rows.
9632 *
9633 * @param {string} tableSelector The CSS selector for the table.
9634 * @param {string} emptyRowClass The class name used to identify empty rows.
9635 */
9636 function applyZebraStriping( tableSelector, emptyRowClass ) {
9637 // Get all non-empty table rows within the specified table
9638 const rows = document.querySelectorAll( `${tableSelector} tr${emptyRowClass ? `:not(.${emptyRowClass})` : ''}` );
9639 if ( rows.length < 1 ) {
9640 return;
9641 }
9642
9643 let isOdd = true;
9644 rows.forEach( row => {
9645 // Clean old "frm-odd" or "frm-even" classes and add the appropriate new class
9646 row.classList.remove( 'frm-odd', 'frm-even' );
9647 row.classList.add( isOdd ? 'frm-odd' : 'frm-even' );
9648
9649 isOdd = ! isOdd;
9650 });
9651
9652 const tables = document.querySelectorAll( tableSelector );
9653 tables.forEach( table => table.classList.add( 'frm-zebra-striping' ) );
9654 };
9655
9656 return {
9657 init: function() {
9658 initAddMyEmailAddress();
9659 addAdminFooterLinks();
9660
9661 s = {};
9662
9663 // Bootstrap dropdown button
9664 jQuery( '.wp-admin' ).on( 'click', function( e ) {
9665 var t = jQuery( e.target );
9666 var $openDrop = jQuery( '.dropdown.open' );
9667 if ( $openDrop.length && ! t.hasClass( 'dropdown' ) && ! t.closest( '.dropdown' ).length ) {
9668 $openDrop.removeClass( 'open' );
9669 }
9670 });
9671 jQuery( '#frm_bs_dropdown:not(.open) a' ).on( 'click', focusSearchBox );
9672
9673 if ( typeof thisFormId === 'undefined' ) {
9674 thisFormId = jQuery( document.getElementById( 'form_id' ) ).val();
9675 }
9676
9677 // Add event listener for dismissible warning messages.
9678 document.querySelectorAll( '.frm-warning-dismiss' ).forEach( ( dismissIcon ) => {
9679 onClickPreventDefault( dismissIcon, dismissWarningMessage );
9680 });
9681
9682 frmAdminBuild.inboxBannerInit();
9683
9684 if ( $newFields.length > 0 ) {
9685 // only load this on the form builder page
9686 frmAdminBuild.buildInit();
9687 } else if ( document.getElementById( 'frm_notification_settings' ) !== null ) {
9688 // only load on form settings page
9689 frmAdminBuild.settingsInit();
9690 } else if ( document.getElementById( 'frm_styling_form' ) !== null ) {
9691 // load styling settings js
9692 frmAdminBuild.styleInit();
9693 } else if ( document.getElementById( 'form_global_settings' ) !== null ) {
9694 // global settings page
9695 frmAdminBuild.globalSettingsInit();
9696 } else if ( document.getElementById( 'frm_export_xml' ) !== null ) {
9697 // import/export page
9698 frmAdminBuild.exportInit();
9699 } else if ( document.getElementById( 'frm_dyncontent' ) !== null ) {
9700 // only load on views settings page
9701 frmAdminBuild.viewInit();
9702 } else if ( document.getElementById( 'frm_inbox_page' ) !== null || null !== document.querySelector( '.frm-inbox-wrapper' ) ) {
9703 // Inbox page
9704 frmAdminBuild.inboxInit();
9705 } else if ( document.getElementById( 'frm-welcome' ) !== null ) {
9706 // Solution install page
9707 frmAdminBuild.solutionInit();
9708 } else {
9709 initSelectionAutocomplete();
9710
9711 jQuery( '[data-frmprint]' ).on( 'click', function() {
9712 window.print();
9713 return false;
9714 });
9715 }
9716
9717 jQuery( document ).on( 'change', 'select[data-toggleclass], input[data-toggleclass]', toggleFormOpts );
9718
9719 var $advInfo = jQuery( document.getElementById( 'frm_adv_info' ) );
9720 if ( $advInfo.length > 0 || jQuery( '.frm_field_list' ).length > 0 ) {
9721 // only load on the form, form settings, and view settings pages
9722 frmAdminBuild.panelInit();
9723 }
9724
9725 loadTooltips();
9726 initUpgradeModal();
9727
9728 // used on build, form settings, and view settings
9729 var $shortCodeDiv = jQuery( document.getElementById( 'frm_shortcodediv' ) );
9730 if ( $shortCodeDiv.length > 0 ) {
9731 jQuery( 'a.edit-frm_shortcode' ).on( 'click', function() {
9732 if ( $shortCodeDiv.is( ':hidden' ) ) {
9733 $shortCodeDiv.slideDown( 'fast' );
9734 this.style.display = 'none';
9735 }
9736 return false;
9737 });
9738
9739 jQuery( '.cancel-frm_shortcode', '#frm_shortcodediv' ).on( 'click', function() {
9740 $shortCodeDiv.slideUp( 'fast' );
9741 $shortCodeDiv.siblings( 'a.edit-frm_shortcode' ).show();
9742 return false;
9743 });
9744 }
9745
9746 // tabs
9747 jQuery( document ).on( 'click', '#frm-nav-tabs a', clickNewTab );
9748 jQuery( '.post-type-frm_display .frm-nav-tabs a, .frm-category-tabs a' ).on( 'click', function() {
9749 const showUpgradeTab = this.classList.contains( 'frm_show_upgrade_tab' );
9750 if ( this.classList.contains( 'frm_noallow' ) && ! showUpgradeTab ) {
9751 return;
9752 }
9753
9754 if ( showUpgradeTab ) {
9755 populateUpgradeTab( this );
9756 }
9757
9758 clickTab( this );
9759 return false;
9760 });
9761 clickTab( jQuery( '.starttab a' ), 'auto' );
9762
9763 // submit the search form with dropdown
9764 jQuery( document ).on( 'click', '#frm-fid-search-menu a', function() {
9765 var val = this.id.replace( 'fid-', '' );
9766 jQuery( 'select[name="fid"]' ).val( val );
9767 triggerSubmit( document.getElementById( 'posts-filter' ) );
9768 return false;
9769 });
9770
9771 jQuery( '.frm_select_box' ).on( 'click focus', function() {
9772 this.select();
9773 });
9774
9775 jQuery( document ).on( 'input search change', '.frm-auto-search:not(#frm-form-templates-page #template-search-input)', searchContent );
9776 jQuery( document ).on( 'focusin click', '.frm-auto-search', stopPropagation );
9777 var autoSearch = jQuery( '.frm-auto-search' );
9778 if ( autoSearch.val() !== '' ) {
9779 autoSearch.trigger( 'keyup' );
9780 }
9781
9782 // Initialize Formidable Connection.
9783 FrmFormsConnect.init();
9784
9785 jQuery( document ).on( 'click', '.frm-install-addon', installAddon );
9786 jQuery( document ).on( 'click', '.frm-activate-addon', activateAddon );
9787 jQuery( document ).on( 'click', '.frm-solution-multiple', installMultipleAddons );
9788
9789 // prevent annoying confirmation message from WordPress
9790 jQuery( 'button, input[type=submit]' ).on( 'click', removeWPUnload );
9791
9792 addMultiselectLabelListener();
9793
9794 frmAdminBuild.hooks.addFilter(
9795 'frm_before_embed_modal',
9796 ( ids, { element, type }) => {
9797 if ( 'form' !== type ) {
9798 return ids;
9799 }
9800
9801 let formId, formKey;
9802 const row = element.closest( 'tr' );
9803
9804 if ( row ) {
9805 // Embed icon on form index.
9806 formId = parseInt( row.querySelector( '.column-id' ).textContent );
9807 formKey = row.querySelector( '.column-form_key' ).textContent;
9808 } else {
9809 // Embed button in form builder / form settings.
9810 formId = document.getElementById( 'form_id' ).value;
9811
9812 const formKeyInput = document.getElementById( 'frm_form_key' );
9813 if ( formKeyInput ) {
9814 formKey = formKeyInput.value;
9815 } else {
9816 const previewDrop = document.getElementById( 'frm-previewDrop' );
9817 if ( previewDrop ) {
9818 formKey = previewDrop.nextElementSibling.querySelector( '.dropdown-item a' ).getAttribute( 'href' ).split( 'form=' )[1];
9819 }
9820 }
9821 }
9822
9823 return [ formId, formKey ];
9824 }
9825 );
9826
9827 document.querySelectorAll( '#frm-show-fields > li, .frm_grid_container li' ).forEach( ( el, _key ) => {
9828 el.addEventListener( 'click', function() {
9829 let fieldId = this.querySelector( 'li' )?.dataset.fid || this.dataset.fid;
9830 maybeAddSaveAndDragIcons( fieldId );
9831 });
9832 });
9833 },
9834
9835 buildInit: function() {
9836 let loadFieldId, $builderForm, builderArea;
9837
9838 debouncedSyncAfterDragAndDrop = debounce( syncAfterDragAndDrop, 10 );
9839 postBodyContent = document.getElementById( 'post-body-content' );
9840 $postBodyContent = jQuery( postBodyContent );
9841
9842 if ( jQuery( '.frm_field_loading' ).length ) {
9843 loadFieldId = jQuery( '.frm_field_loading' ).first().attr( 'id' );
9844 loadFields( loadFieldId );
9845 }
9846
9847 setupSortable( 'ul.frm_sorting' );
9848
9849 document.querySelectorAll( '.field_type_list > li:not(.frm_show_upgrade)' ).forEach( makeDraggable );
9850
9851 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();
9852
9853 jQuery( '.frm_submit_ajax' ).on( 'click', submitBuild );
9854 jQuery( '.frm_submit_no_ajax' ).on( 'click', submitNoAjax );
9855
9856 addFormNameModalEvents();
9857
9858 jQuery( 'a.edit-form-status' ).on( 'click', slideDown );
9859 jQuery( '.cancel-form-status' ).on( 'click', slideUp );
9860 jQuery( '.save-form-status' ).on( 'click', function() {
9861 const newStatus = jQuery( document.getElementById( 'form_change_status' ) ).val();
9862 jQuery( 'input[name="new_status"]' ).val( newStatus );
9863 jQuery( document.getElementById( 'form-status-display' ) ).html( newStatus );
9864 jQuery( '.cancel-form-status' ).trigger( 'click' );
9865 return false;
9866 });
9867
9868 jQuery( '.frm_form_builder form' ).first().on( 'submit', function() {
9869 jQuery( '.inplace_field' ).trigger( 'blur' );
9870 });
9871
9872 initiateMultiselect();
9873 renumberPageBreaks();
9874
9875 $builderForm = jQuery( builderForm );
9876 builderArea = document.getElementById( 'frm_form_editor_container' );
9877 $builderForm.on( 'click', '.frm_add_logic_row', addFieldLogicRow );
9878 $builderForm.on( 'click', '.frm_add_watch_lookup_row', addWatchLookupRow );
9879 $builderForm.on( 'change', '.frm_get_values_form', updateGetValueFieldSelection );
9880 $builderForm.on( 'change', '.frm_logic_field_opts', getFieldValues );
9881 $builderForm.on( 'change', '.radio_maxnum', setStarValues );
9882 $builderForm.on( 'frm-multiselect-changed', 'select[name^="field_options[admin_only_"]', adjustVisibilityValuesForEveryoneValues );
9883
9884 jQuery( document.getElementById( 'frm-insert-fields' ) ).on( 'click', '.frm_add_field', addFieldClick );
9885 $newFields.on( 'click', '.frm_clone_field', duplicateField );
9886 $builderForm.on( 'blur', 'input[id^="frm_calc"]', checkCalculationCreatedByUser );
9887 $builderForm.on( 'change', 'input.frm_format_opt, input.frm_max_length_opt', toggleInvalidMsg );
9888 $builderForm.on( 'change click', '[data-changeme]', liveChanges );
9889 $builderForm.on( 'click', 'input.frm_req_field', markRequired );
9890 $builderForm.on( 'click', '.frm_mark_unique', markUnique );
9891
9892 $builderForm.on( 'change', '.frm_repeat_format', toggleRepeatButtons );
9893 $builderForm.on( 'change', '.frm_repeat_limit', checkRepeatLimit );
9894 $builderForm.on( 'change', '.frm_js_checkbox_limit', checkCheckboxSelectionsLimit );
9895 $builderForm.on( 'input', 'input[name^="field_options[add_label_"]', function() {
9896 updateRepeatText( this, 'add' );
9897 });
9898 $builderForm.on( 'input', 'input[name^="field_options[remove_label_"]', function() {
9899 updateRepeatText( this, 'remove' );
9900 });
9901 $builderForm.on( 'change', 'select[name^="field_options[data_type_"]', maybeClearWatchFields );
9902 jQuery( builderArea ).on( 'click', '.frm-collapse-page', maybeCollapsePage );
9903 jQuery( builderArea ).on( 'click', '.frm-collapse-section', maybeCollapseSection );
9904 $builderForm.on( 'click', '.frm-single-settings h3', maybeCollapseSettings );
9905 $builderForm.on( 'keydown', '.frm-single-settings h3', function( event ) {
9906 // If so, only proceed if the key pressed was 'Enter' or 'Space'
9907 if ( event.key === 'Enter' || event.key === ' ' ) {
9908 event.preventDefault();
9909 maybeCollapseSettings.call( this, event );
9910 }
9911 });
9912
9913 jQuery( builderArea ).on( 'show.bs.dropdown hide.bs.dropdown', changeSectionStyle );
9914
9915 $builderForm.on( 'click', '.frm_toggle_sep_values', toggleSepValues );
9916 $builderForm.on( 'click', '.frm_toggle_image_options', toggleImageOptions );
9917 $builderForm.on( 'click', '.frm_remove_image_option', removeImageFromOption );
9918 $builderForm.on( 'click', '.frm_choose_image_box', addImageToOption );
9919 $builderForm.on( 'change', '.frm_hide_image_text', refreshOptionDisplay );
9920 $builderForm.on( 'change', '.frm_field_options_image_size', setImageSize );
9921 $builderForm.on( 'click', '.frm_multiselect_opt', toggleMultiselect );
9922 $newFields.on( 'mousedown', 'input, textarea, select', stopFieldFocus );
9923 $newFields.on( 'click', 'input[type=radio], input[type=checkbox]', stopFieldFocus );
9924 $newFields.on( 'click', '.frm_delete_field', clickDeleteField );
9925 $newFields.on( 'click', '.frm_select_field', clickSelectField );
9926 jQuery( document ).on( 'click', '.frm_delete_field_group', clickDeleteFieldGroup );
9927 jQuery( document ).on( 'click', '.frm_clone_field_group', duplicateFieldGroup );
9928 jQuery( document ).on( 'click', '#frm_field_group_controls > span:first-child', clickFieldGroupLayout );
9929 jQuery( document ).on( 'click', '.frm-row-layout-option', handleFieldGroupLayoutOptionClick );
9930 jQuery( document ).on( 'click', '.frm-merge-fields-into-row .frm-row-layout-option', handleFieldGroupLayoutOptionInsideMergeClick );
9931 jQuery( document ).on( 'click', '.frm-custom-field-group-layout', customFieldGroupLayoutClick );
9932 jQuery( document ).on( 'click', '.frm-merge-fields-into-row .frm-custom-field-group-layout', customFieldGroupLayoutInsideMergeClick );
9933 jQuery( document ).on( 'click', '.frm-break-field-group', breakFieldGroupClick );
9934 $newFields.on( 'click', '#frm_field_group_popup .frm_grid_container input', focusFieldGroupInputOnClick );
9935 jQuery( document ).on( 'click', '.frm-cancel-custom-field-group-layout', cancelCustomFieldGroupClick );
9936 jQuery( document ).on( 'click', '.frm-save-custom-field-group-layout', saveCustomFieldGroupClick );
9937 $newFields.on( 'click', 'ul.frm_sorting', fieldGroupClick );
9938 jQuery( document ).on( 'click', '.frm-merge-fields-into-row', mergeFieldsIntoRowClick );
9939 jQuery( document ).on( 'click', '.frm-delete-field-groups', deleteFieldGroupsClick );
9940 $newFields.on( 'click', '.frm-field-action-icons [data-toggle="dropdown"]', function() {
9941 this.closest( 'li.form-field' ).classList.add( 'frm-field-settings-open' );
9942 jQuery( document ).on( 'click', '#frm_builder_page', handleClickOutsideOfFieldSettings );
9943 });
9944 $newFields.on( 'mousemove', 'ul.frm_sorting', checkForMultiselectKeysOnMouseMove );
9945 $newFields.on( 'show.bs.dropdown', '.frm-field-action-icons', onFieldActionDropdownShow );
9946 jQuery( document ).on( 'show.bs.dropdown', '#frm_field_group_controls', onFieldGroupActionDropdownShow );
9947 $builderForm.on( 'click', '.frm_single_option a[data-removeid]', deleteFieldOption );
9948 $builderForm.on( 'mousedown', '.frm_single_option input[type=radio]', maybeUncheckRadio );
9949 $builderForm.on( 'focusin', '.frm_single_option input[type=text]', maybeClearOptText );
9950 $builderForm.on( 'click', '.frm_add_opt', addFieldOption );
9951 $builderForm.on( 'change', '.frm_single_option input', resetOptOnChange );
9952 $builderForm.on( 'change', '.frm_image_id', resetOptOnChange );
9953 $builderForm.on( 'change', '.frm_toggle_mult_sel', toggleMultSel );
9954 $builderForm.on( 'focusin', '.frm_classes', showBuilderModal );
9955
9956 $newFields.on( 'click', '.frm_primary_label', clickLabel );
9957 $newFields.on( 'click', '.frm_description', clickDescription );
9958 $newFields.on( 'click', 'li.ui-state-default:not(.frm_noallow)', clickVis );
9959 $newFields.on( 'dblclick', 'li.ui-state-default', openAdvanced );
9960 $builderForm.on( 'change', '.frm_tax_form_select', toggleFormTax );
9961 $builderForm.on( 'change', 'select.conf_field', addConf );
9962
9963 $builderForm.on( 'change', '.frm_get_field_selection', getFieldSelection );
9964
9965 $builderForm.on( 'click', '.frm-show-inline-modal', maybeShowInlineModal );
9966
9967 $builderForm.on( 'click', '.frm-inline-modal .dismiss', dismissInlineModal );
9968 jQuery( document ).on( 'change', '[data-frmchange]', changeInputtedValue );
9969
9970 $builderForm.on( 'change', '.frm_include_extras_field', rePopCalcFieldsForSummary );
9971 $builderForm.on( 'change', 'select[name^="field_options[form_select_"]', maybeChangeEmbedFormMsg );
9972
9973 jQuery( document ).on( 'submit', '#frm_js_build_form', buildSubmittedNoAjax );
9974 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 );
9975
9976 popAllProductFields();
9977
9978 jQuery( document ).on( 'change', '.frmjs_prod_data_type_opt', toggleProductType );
9979
9980 jQuery( document ).on( 'focus', '.frm-single-settings ul input[type="text"][name^="field_options[options_"]', onOptionTextFocus );
9981 jQuery( document ).on( 'blur', '.frm-single-settings ul input[type="text"][name^="field_options[options_"]', onOptionTextBlur );
9982
9983 initBulkOptionsOverlay();
9984 hideEmptyEle();
9985 maybeHideQuantityProductFieldOption();
9986 handleNameFieldOnFormBuilder();
9987 toggleSectionHolder();
9988 handleShowPasswordLiveUpdate();
9989 },
9990
9991 settingsInit: function() {
9992 const $formActions = jQuery( document.getElementById( 'frm_notification_settings' ) );
9993
9994 let formSettings, $loggedIn, $cookieExp, $editable;
9995
9996 // BCC, CC, and Reply To button functionality
9997 $formActions.on( 'click', '.frm_email_buttons', showEmailRow );
9998 $formActions.on( 'click', '.frm_remove_field', hideEmailRow );
9999 $formActions.on( 'change', '.frm_to_row, .frm_from_row', showEmailWarning );
10000 $formActions.on( 'change', '.frm_tax_selector', changePosttaxRow );
10001 $formActions.on( 'change', 'select.frm_single_post_field', checkDupPost );
10002 $formActions.on( 'change', 'select.frm_toggle_post_content', togglePostContent );
10003 $formActions.on( 'change', 'select.frm_dyncontent_opt', fillDyncontent );
10004 $formActions.on( 'change', '.frm_post_type', switchPostType );
10005 $formActions.on( 'click', '.frm_add_postmeta_row', addPostmetaRow );
10006 $formActions.on( 'click', '.frm_add_posttax_row', addPosttaxRow );
10007 $formActions.on( 'click', '.frm_toggle_cf_opts', toggleCfOpts );
10008 $formActions.on( 'click', '.frm_duplicate_form_action', copyFormAction );
10009 jQuery( '.frm_actions_list' ).on( 'click', '.frm_active_action', addFormAction );
10010 jQuery( '#frm-show-groups, #frm-hide-groups' ).on( 'click', toggleActionGroups );
10011 initiateMultiselect();
10012
10013 //set actions icons to inactive
10014 jQuery( 'ul.frm_actions_list li' ).each( function() {
10015 checkActiveAction( jQuery( this ).children( 'a' ).data( 'actiontype' ) );
10016
10017 // If the icon is a background image, don't add BG color.
10018 const icon = jQuery( this ).find( 'i' );
10019 if ( icon.css( 'background-image' ) !== 'none' ) {
10020 icon.addClass( 'frm-inverse' );
10021 }
10022 });
10023
10024 jQuery( '.frm_submit_settings_btn' ).on( 'click', submitSettings );
10025
10026 addFormNameModalEvents();
10027
10028 formSettings = jQuery( '.frm_form_settings' );
10029 formSettings.on( 'click', '.frm_add_form_logic', addFormLogicRow );
10030 formSettings.on( 'blur', '.frm_email_blur', formatEmailSetting );
10031 formSettings.on( 'click', '.frm_already_used', onlyOneActionMessage );
10032
10033 formSettings.on( 'change', '#logic_link_submit', toggleSubmitLogic );
10034 formSettings.on( 'click', '.frm_add_submit_logic', addSubmitLogic );
10035 formSettings.on( 'change', '.frm_submit_logic_field_opts', addSubmitLogicOpts );
10036
10037 document.addEventListener(
10038 'click',
10039 function handleImageUploadClickEvents( event ) {
10040 const { target } = event;
10041
10042 if ( ! target.closest( '.frm_image_preview_wrapper' ) ) {
10043 return;
10044 }
10045
10046 if ( target.closest( '.frm_choose_image_box' ) ) {
10047 addImageToOption.bind( target )( event );
10048 return;
10049 }
10050
10051 if ( target.closest( '.frm_remove_image_option' ) ) {
10052 removeImageFromOption.bind( target )( event );
10053 }
10054 }
10055 );
10056
10057 // Close shortcode modal on click.
10058 formSettings.on( 'mouseup', '*:not(.frm-show-box)', function( e ) {
10059 e.stopPropagation();
10060
10061 if ( e.target.classList.contains( 'frm-show-box' ) || e.target.parentElement.classList.contains( 'frm-show-box' ) ) {
10062 return;
10063 }
10064
10065 const sidebar = document.getElementById( 'frm_adv_info' );
10066 if ( ! sidebar ) {
10067 return;
10068 }
10069
10070 if ( sidebar.getAttribute( 'data-fills' ) === e.target.id && typeof e.target.id !== 'undefined' ) {
10071 return;
10072 }
10073
10074 const isChild = jQuery( e.target ).closest( '#frm_adv_info' ).length > 0;
10075
10076 if ( ! isChild && sidebar.display !== 'none' ) {
10077 hideShortcodes( sidebar );
10078 }
10079 });
10080
10081 //Warning when user selects "Do not store entries ..."
10082 jQuery( document.getElementById( 'no_save' ) ).on( 'change', function() {
10083 if ( this.checked ) {
10084 if ( confirm( frmAdminJs.no_save_warning ) !== true ) {
10085 // Uncheck box if user hits "Cancel"
10086 jQuery( this ).attr( 'checked', false );
10087 }
10088 }
10089 });
10090
10091 jQuery( 'select[name="options[edit_action]"]' ).on( 'change', showSuccessOpt );
10092
10093 $loggedIn = document.getElementById( 'logged_in' );
10094 jQuery( $loggedIn ).on( 'change', function() {
10095 if ( this.checked ) {
10096 visible( '.hide_logged_in' );
10097 } else {
10098 invisible( '.hide_logged_in' );
10099 }
10100 });
10101
10102 $cookieExp = jQuery( document.getElementById( 'frm_cookie_expiration' ) );
10103 jQuery( document.getElementById( 'frm_single_entry_type' ) ).on( 'change', function() {
10104 if ( this.value === 'cookie' ) {
10105 $cookieExp.fadeIn( 'slow' );
10106 } else {
10107 $cookieExp.fadeOut( 'slow' );
10108 }
10109 });
10110
10111 var $singleEntry = document.getElementById( 'single_entry' );
10112 jQuery( $singleEntry ).on( 'change', function() {
10113 if ( this.checked ) {
10114 visible( '.hide_single_entry' );
10115 } else {
10116 invisible( '.hide_single_entry' );
10117 }
10118
10119 if ( this.checked && jQuery( document.getElementById( 'frm_single_entry_type' ) ).val() === 'cookie' ) {
10120 $cookieExp.fadeIn( 'slow' );
10121 } else {
10122 $cookieExp.fadeOut( 'slow' );
10123 }
10124 });
10125
10126 jQuery( '.hide_save_draft' ).hide();
10127
10128 var $saveDraft = jQuery( document.getElementById( 'save_draft' ) );
10129 $saveDraft.on( 'change', function() {
10130 if ( this.checked ) {
10131 jQuery( '.hide_save_draft' ).fadeIn( 'slow' );
10132 } else {
10133 jQuery( '.hide_save_draft' ).fadeOut( 'slow' );
10134 }
10135 });
10136 triggerChange( $saveDraft );
10137
10138 //If Allow editing is checked/unchecked
10139 $editable = document.getElementById( 'editable' );
10140 jQuery( $editable ).on( 'change', function() {
10141 if ( this.checked ) {
10142 jQuery( '.hide_editable' ).fadeIn( 'slow' );
10143 triggerChange( document.getElementById( 'edit_action' ) );
10144 } else {
10145 jQuery( '.hide_editable' ).fadeOut( 'slow' );
10146 jQuery( '.edit_action_message_box' ).fadeOut( 'slow' );//Hide On Update message box
10147 }
10148 });
10149
10150 //If File Protection is checked/unchecked
10151 jQuery( document ).on( 'change', '#protect_files', function() {
10152 if ( this.checked ) {
10153 jQuery( '.hide_protect_files' ).fadeIn( 'slow' );
10154 } else {
10155 jQuery( '.hide_protect_files' ).fadeOut( 'slow' );
10156 }
10157 });
10158
10159 jQuery( document ).on( 'frm-multiselect-changed', '#protect_files_role', adjustVisibilityValuesForEveryoneValues );
10160
10161 jQuery( document ).on( 'submit', '.frm_form_settings', settingsSubmitted );
10162 jQuery( document ).on( 'change', '#form_settings_page input:not(.frm-search-input), #form_settings_page select, #form_settings_page textarea', fieldUpdated );
10163
10164 // Page Selection Autocomplete
10165 initSelectionAutocomplete();
10166
10167 jQuery( document ).on( 'frm-action-loaded', onActionLoaded );
10168
10169 initOnSubmitAction();
10170 },
10171
10172 panelInit: function() {
10173 var customPanel, settingsPage, viewPage, insertFieldsTab;
10174
10175 jQuery( '.frm_wrap, #postbox-container-1' ).on( 'click', '.frm_insert_code', insertCode );
10176 jQuery( document ).on( 'change', '.frm_insert_val', function() {
10177 insertFieldCode( jQuery( this ).data( 'target' ), jQuery( this ).val() );
10178 jQuery( this ).val( '' );
10179 });
10180
10181 jQuery( document ).on( 'click change', '#frm-id-key-condition', resetLogicBuilder );
10182 jQuery( document ).on( 'keyup change', '.frm-build-logic', setLogicExample );
10183
10184 showInputIcon();
10185 jQuery( document ).on( 'frmElementAdded', function( event, parentEle ) {
10186 /* This is here for add-ons to trigger */
10187 showInputIcon( parentEle );
10188 });
10189 jQuery( document ).on( 'mousedown', '.frm-show-box', showShortcodes );
10190
10191 settingsPage = document.getElementById( 'form_settings_page' );
10192 viewPage = document.body.classList.contains( 'post-type-frm_display' );
10193 insertFieldsTab = document.getElementById( 'frm_insert_fields_tab' );
10194
10195 if ( settingsPage !== null || viewPage ) {
10196 jQuery( document ).on( 'focusin', 'form input, form textarea', function( e ) {
10197 var htmlTab;
10198 e.stopPropagation();
10199 maybeShowModal( this );
10200
10201 if ( jQuery( this ).is( ':not(:submit, input[type=button], .frm-search-input, input[type=checkbox])' ) ) {
10202 if ( jQuery( e.target ).closest( '#frm_adv_info' ).length ) {
10203 // Don't trigger for fields inside of the modal.
10204 return;
10205 }
10206
10207 if ( settingsPage !== null ) {
10208 /* form settings page */
10209 htmlTab = jQuery( '#frm_html_tab' );
10210 if ( jQuery( this ).closest( '#html_settings' ).length > 0 ) {
10211 htmlTab.show();
10212 htmlTab.siblings().hide();
10213 jQuery( '#frm_html_tab a' ).trigger( 'click' );
10214 toggleAllowedHTML( this, e.type );
10215 } else {
10216 showElement( jQuery( '.frm-category-tabs li' ) );
10217 insertFieldsTab.click();
10218 htmlTab.hide();
10219 htmlTab.siblings().show();
10220 }
10221 } else if ( viewPage ) {
10222 // Run on view page.
10223 toggleAllowedShortcodes( this.id, e.type );
10224 }
10225 }
10226 });
10227 }
10228
10229 jQuery( '.frm_wrap, #postbox-container-1' ).on( 'mousedown', '#frm_adv_info a, .frm_field_list a', function( e ) {
10230 e.preventDefault();
10231 });
10232
10233 customPanel = jQuery( '#frm_adv_info' );
10234 customPanel.on( 'click', '.subsubsub a.frmids', function( e ) {
10235 toggleKeyID( 'frmids', e );
10236 });
10237 customPanel.on( 'click', '.subsubsub a.frmkeys', function( e ) {
10238 toggleKeyID( 'frmkeys', e );
10239 });
10240 },
10241
10242 viewInit: function() {
10243 var $addRemove,
10244 $advInfo = jQuery( document.getElementById( 'frm_adv_info' ) );
10245 $advInfo.before( '<div id="frm_position_ele"></div>' );
10246 setupMenuOffset();
10247
10248 jQuery( document ).on( 'blur', '#param', checkDetailPageSlug );
10249 jQuery( document ).on( 'blur', 'input[name^="options[where_val]"]', checkFilterParamNames );
10250
10251 // Show loading indicator.
10252 jQuery( '#publish' ).on( 'mousedown', function() {
10253 fieldsUpdated = 0;
10254 this.classList.add( 'frm_loading_button' );
10255 });
10256
10257 // move content tabs
10258 jQuery( '#frm_dyncontent .handlediv' ).before( jQuery( '#frm_dyncontent .nav-menus-php' ) );
10259
10260 // click content tabs
10261 jQuery( '.nav-tab-wrapper a' ).on( 'click', clickContentTab );
10262
10263 // click tabs after panel is replaced with ajax
10264 jQuery( '#side-sortables' ).on( 'click', '.frm_doing_ajax.categorydiv .category-tabs a', clickTabsAfterAjax );
10265
10266 initToggleShortcodes();
10267 jQuery( '.frm_code_list:not(.frm-dropdown-menu) a' ).addClass( 'frm_noallow' );
10268
10269 jQuery( 'input[name="show_count"]' ).on( 'change', showCount );
10270
10271 jQuery( document.getElementById( 'form_id' ) ).on( 'change', displayFormSelected );
10272
10273 $addRemove = jQuery( '.frm_repeat_rows' );
10274 $addRemove.on( 'click', '.frm_add_order_row', addOrderRow );
10275 $addRemove.on( 'click', '.frm_add_where_row', addWhereRow );
10276 $addRemove.on( 'change', '.frm_insert_where_options', insertWhereOptions );
10277 $addRemove.on( 'change', '.frm_where_is_options', hideWhereOptions );
10278
10279 setDefaultPostStatus();
10280 },
10281
10282 inboxInit: function() {
10283 jQuery( '.frm_inbox_dismiss, footer .frm-button-secondary, footer .frm-button-primary' ).on( 'click', function( e ) {
10284 var message = this.parentNode.parentNode,
10285 key = message.getAttribute( 'data-message' ),
10286 href = this.getAttribute( 'href' ),
10287 dismissedMessage = message.cloneNode( true );
10288 dismissedMessagesWrapper = document.querySelector( '.frm-dismissed-inbox-messages' );
10289
10290 if ( 'free_templates' === key && ! this.classList.contains( 'frm_inbox_dismiss' ) ) {
10291 return;
10292 }
10293
10294 e.preventDefault();
10295
10296 data = {
10297 action: 'frm_inbox_dismiss',
10298 key: key,
10299 nonce: frmGlobal.nonce
10300 };
10301 postAjax( data, function() {
10302 if ( href !== '#' ) {
10303 window.location = href;
10304 return true;
10305 }
10306 fadeOut( message, function() {
10307 if ( null !== dismissedMessagesWrapper ) {
10308 dismissedMessage.classList.remove( 'frm-fade' );
10309 dismissedMessage.querySelector( '.frm-inbox-message-heading' ).removeChild( dismissedMessage.querySelector( '.frm-inbox-message-heading .frm_inbox_dismiss' ) );
10310 dismissedMessagesWrapper.append( dismissedMessage );
10311 }
10312 if ( 1 === message.parentNode.querySelectorAll( '.frm-inbox-message-container' ).length ) {
10313 document.getElementById( 'frm_empty_inbox' ).classList.remove( 'frm_hidden' );
10314 message.parentNode.closest( '.frm-active' ).classList.add( 'frm-empty-inbox' );
10315 }
10316 message.parentNode.removeChild( message );
10317 });
10318 });
10319 });
10320 jQuery( '#frm-dismiss-inbox' ).on( 'click', function( e ) {
10321 data = {
10322 action: 'frm_inbox_dismiss',
10323 key: 'all',
10324 nonce: frmGlobal.nonce
10325 };
10326 postAjax( data, function() {
10327 fadeOut( document.getElementById( 'frm_message_list' ), function() {
10328 document.getElementById( 'frm_empty_inbox' ).classList.remove( 'frm_hidden' );
10329 showActiveCampaignForm();
10330 });
10331 });
10332 });
10333
10334 if ( ! document.getElementById( 'frm_empty_inbox' ).classList.contains( 'frm_hidden' ) ) {
10335 showActiveCampaignForm();
10336 }
10337 },
10338
10339 solutionInit: function() {
10340 jQuery( document ).on( 'submit', '#frm-new-template', installTemplate );
10341 },
10342
10343 styleInit: function() {
10344 const $previewWrapper = jQuery( '.frm_image_preview_wrapper' );
10345 $previewWrapper.on( 'click', '.frm_choose_image_box', addImageToOption );
10346 $previewWrapper.on( 'click', '.frm_remove_image_option', removeImageFromOption );
10347
10348 wp.hooks.doAction( 'frm_style_editor_init' );
10349 },
10350
10351 customCSSInit: function() {
10352 console.warn( 'Calling frmAdminBuild.customCSSInit is deprecated.' );
10353 },
10354
10355 globalSettingsInit: function() {
10356 var licenseTab;
10357
10358 jQuery( document ).on( 'click', '[data-frmuninstall]', uninstallNow );
10359
10360 initiateMultiselect();
10361
10362 // activate addon licenses
10363 licenseTab = document.getElementById( 'licenses_settings' );
10364 if ( licenseTab !== null ) {
10365 jQuery( licenseTab ).on( 'click', '.edd_frm_save_license', saveAddonLicense );
10366 }
10367
10368 // Solution install page
10369 jQuery( document ).on( 'click', '#frm-new-template button', installTemplateFieldset );
10370
10371 jQuery( '#frm-dismissable-cta .dismiss' ).on( 'click', function( event ) {
10372 event.preventDefault();
10373 jQuery.post( ajaxurl, {
10374 action: 'frm_lite_settings_upgrade'
10375 });
10376 jQuery( '.settings-lite-cta' ).remove();
10377 });
10378
10379 const captchaType = document.getElementById( 'frm_re_type' );
10380 if ( captchaType ) {
10381 captchaType.addEventListener( 'change', handleCaptchaTypeChange );
10382 }
10383
10384 document.querySelector( '.frm_captchas' ).addEventListener( 'change', function() {
10385 document.querySelector( '.captcha_settings .frm_note_style' ).classList.toggle( 'frm_hidden' );
10386 });
10387 },
10388
10389 exportInit: function() {
10390 jQuery( '.frm_form_importer' ).on( 'submit', startFormMigration );
10391 jQuery( document.getElementById( 'frm_export_xml' ) ).on( 'submit', validateExport );
10392 jQuery( '#frm_export_xml input, #frm_export_xml select' ).on( 'change', removeExportError );
10393 jQuery( 'input[name="frm_import_file"]' ).on( 'change', checkCSVExtension );
10394 document.querySelector( 'select[name="format"]' ).addEventListener( 'change', exportTypeChanged );
10395
10396 jQuery( 'input[name="frm_export_forms[]"]' ).on( 'click', preventMultipleExport );
10397 initiateMultiselect();
10398
10399 jQuery( '.frm-feature-banner .dismiss' ).on( 'click', function( event ) {
10400 event.preventDefault();
10401 jQuery.post( ajaxurl, {
10402 action: 'frm_dismiss_migrator',
10403 plugin: this.id,
10404 nonce: frmGlobal.nonce
10405 });
10406 this.parentElement.remove();
10407 });
10408
10409 showOrHideRepeaters( getExportOption() );
10410
10411 document.querySelector( '#frm-export-select-all' ).addEventListener( 'change', event => {
10412 document.querySelectorAll( '[name="frm_export_forms[]"]' ).forEach( cb => cb.checked = event.target.checked );
10413 });
10414 },
10415
10416 inboxBannerInit: function() {
10417 const banner = document.getElementById( 'frm_banner' );
10418 if ( ! banner ) {
10419 return;
10420 }
10421
10422 const dismissButton = banner.querySelector( '.frm-banner-dismiss' );
10423 document.addEventListener(
10424 'click',
10425 function( event ) {
10426 if ( event.target !== dismissButton ) {
10427 return;
10428 }
10429
10430 const data = {
10431 action: 'frm_inbox_dismiss',
10432 key: banner.dataset.key,
10433 nonce: frmGlobal.nonce
10434 };
10435 postAjax(
10436 data,
10437 function() {
10438 jQuery( banner ).fadeOut(
10439 400,
10440 function() {
10441 banner.remove();
10442 }
10443 );
10444 }
10445 );
10446 }
10447 );
10448 },
10449
10450 updateOpts: function( fieldId, opts, modal ) {
10451 var separate = usingSeparateValues( fieldId ),
10452 action = isProductField( fieldId ) ? 'frm_bulk_products' : 'frm_import_options';
10453 jQuery.ajax({
10454 type: 'POST',
10455 url: ajaxurl,
10456 data: {
10457 action: action,
10458 field_id: fieldId,
10459 opts: opts,
10460 separate: separate,
10461 nonce: frmGlobal.nonce
10462 },
10463 success: function( html ) {
10464 document.getElementById( 'frm_field_' + fieldId + '_opts' ).innerHTML = html;
10465 resetDisplayedOpts( fieldId );
10466
10467 if ( typeof modal !== 'undefined' ) {
10468 modal.dialog( 'close' );
10469 document.getElementById( 'frm-update-bulk-opts' ).classList.remove( 'frm_loading_button' );
10470 }
10471 }
10472 });
10473 },
10474
10475 /* remove conditional logic if the field doesn't exist */
10476 triggerRemoveLogic: function( fieldID, metaName ) {
10477 jQuery( '#frm_logic_' + fieldID + '_' + metaName + ' .frm_remove_tag' ).trigger( 'click' );
10478 },
10479
10480 downloadXML: function( controller, ids, isTemplate ) {
10481 var url = ajaxurl + '?action=frm_' + controller + '_xml&ids=' + ids;
10482 if ( isTemplate !== null ) {
10483 url = url + '&is_template=' + isTemplate;
10484 }
10485 location.href = url;
10486 },
10487
10488 /**
10489 * @since 5.0.04
10490 */
10491 hooks: {
10492 applyFilters: function( hookName, ...args ) {
10493 return wp.hooks.applyFilters( hookName, ...args );
10494 },
10495 addFilter: function( hookName, callback, priority ) {
10496 return wp.hooks.addFilter( hookName, 'formidable', callback, priority );
10497 },
10498 doAction: function( hookName, ...args ) {
10499 return wp.hooks.doAction( hookName, ...args );
10500 },
10501 addAction: function( hookName, callback, priority ) {
10502 return wp.hooks.addAction( hookName, 'formidable', callback, priority );
10503 }
10504 },
10505
10506 applyZebraStriping,
10507 initModal,
10508 infoModal,
10509 offsetModalY,
10510 adjustConditionalLogicOptionOrders,
10511 addRadioCheckboxOpt,
10512 installNewForm,
10513 toggleAddonState,
10514 purifyHtml
10515 };
10516 }
10517
10518 frmAdminBuild = frmAdminBuildJS();
10519
10520 jQuery( document ).ready(
10521 () => {
10522 frmAdminBuild.init();
10523
10524 frmDom.bootstrap.setupBootstrapDropdowns( convertOldBootstrapDropdownsToBootstrap4 );
10525 document.querySelector( '.preview.dropdown .frm-dropdown-toggle' )?.setAttribute( 'data-toggle', 'dropdown' );
10526
10527 function convertOldBootstrapDropdownsToBootstrap4( frmDropdownMenu ) {
10528 const toggle = frmDropdownMenu.querySelector( '.frm-dropdown-toggle' );
10529 if ( toggle ) {
10530 if ( ! toggle.hasAttribute( 'role' ) ) {
10531 toggle.setAttribute( 'role', 'button' );
10532 }
10533 if ( ! toggle.hasAttribute( 'tabindex' ) ) {
10534 toggle.setAttribute( 'tabindex', 0 );
10535 }
10536 }
10537
10538 // Convert <li> and <ul> tags.
10539 if ( 'UL' === frmDropdownMenu.tagName ) {
10540 convertBootstrapUl( frmDropdownMenu );
10541 }
10542 }
10543
10544 function convertBootstrapUl( ul ) {
10545 let html = ul.outerHTML;
10546 html = html.replace( '<ul ', '<div ' );
10547 html = html.replace( '</ul>', '</div>' );
10548 html = html.replaceAll( '<li>', '<div class="dropdown-item">' );
10549 html = html.replaceAll( '<li class="', '<div class="dropdown-item ' );
10550 html = html.replaceAll( '</li>', '</div>' );
10551 ul.outerHTML = html;
10552 }
10553 }
10554 );
10555
10556 function frm_remove_tag( htmlTag ) { // eslint-disable-line camelcase
10557 console.warn( 'DEPRECATED: function frm_remove_tag in v2.0' );
10558 jQuery( htmlTag ).remove();
10559 }
10560
10561 function frm_show_div( div, value, showIf, classId ) { // eslint-disable-line camelcase
10562 if ( value == showIf ) {
10563 jQuery( classId + div ).fadeIn( 'slow' ).css( 'visibility', 'visible' );
10564 } else {
10565 jQuery( classId + div ).fadeOut( 'slow' );
10566 }
10567 }
10568
10569 function frmCheckAll( checked, n ) {
10570 jQuery( 'input[name^="' + n + '"]' ).prop( 'checked', ! ! checked );
10571 }
10572
10573 function frmCheckAllLevel( checked, n, level ) {
10574 var $kids = jQuery( '.frm_catlevel_' + level ).children( '.frm_checkbox' ).children( 'label' );
10575 $kids.children( 'input[name^="' + n + '"]' ).prop( 'checked', ! ! checked );
10576 }
10577
10578 function frm_add_logic_row( id, formId ) { // eslint-disable-line camelcase
10579 console.warn( 'DEPRECATED: function frm_add_logic_row in v2.0' );
10580 jQuery.ajax({
10581 type: 'POST',
10582 url: ajaxurl,
10583 data: {
10584 action: 'frm_add_logic_row',
10585 form_id: formId,
10586 field_id: id,
10587 meta_name: jQuery( '#frm_logic_row_' + id + ' > div' ).length,
10588 nonce: frmGlobal.nonce
10589 },
10590 success: function( html ) {
10591 jQuery( '#frm_logic_row_' + id ).append( html );
10592 }
10593 });
10594 return false;
10595 }
10596
10597 function frmGetFieldValues( fieldId, cur, rowNumber, fieldType, htmlName ) {
10598
10599 if ( fieldId ) {
10600 jQuery.ajax({
10601 type: 'POST', url: ajaxurl,
10602 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,
10603 success: function( msg ) {
10604 document.getElementById( 'frm_show_selected_values_' + cur + '_' + rowNumber ).innerHTML = msg;
10605 }
10606 });
10607 }
10608 }
10609
10610 function frmImportCsv( formID ) {
10611 var urlVars = '';
10612 if ( typeof __FRMURLVARS !== 'undefined' ) {
10613 urlVars = __FRMURLVARS;
10614 }
10615
10616 jQuery.ajax({
10617 type: 'POST', url: ajaxurl,
10618 data: 'action=frm_import_csv&nonce=' + frmGlobal.nonce + '&frm_skip_cookie=1' + urlVars,
10619 success: function( count ) {
10620 var max = jQuery( '.frm_admin_progress_bar' ).attr( 'aria-valuemax' );
10621 var imported = max - count;
10622 var percent = ( imported / max ) * 100;
10623 jQuery( '.frm_admin_progress_bar' ).css( 'width', percent + '%' ).attr( 'aria-valuenow', imported );
10624
10625 if ( parseInt( count, 10 ) > 0 ) {
10626 jQuery( '.frm_csv_remaining' ).html( count );
10627 frmImportCsv( formID );
10628 } else {
10629 jQuery( document.getElementById( 'frm_import_message' ) ).html( frm_admin_js.import_complete ); // eslint-disable-line camelcase
10630 setTimeout( function() {
10631 location.href = '?page=formidable-entries&frm_action=list&form=' + formID + '&import-message=1';
10632 }, 2000 );
10633 }
10634 }
10635 });
10636 }
10637