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

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

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