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

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

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