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

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