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

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