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

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

1,833 lines 50.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* exported frmRecaptcha, frmAfterRecaptcha, frmUpdateField */
2 /* eslint-disable prefer-const */
3
4 function frmFrontFormJS() {
5 'use strict';
6
7 /*global jQuery:false, frm_js, grecaptcha, hcaptcha, turnstile, frmProForm, tinyMCE */
8 /*global frmThemeOverride_jsErrors, frmThemeOverride_frmPlaceError, frmThemeOverride_frmAfterSubmit */
9
10 let action = '';
11 let jsErrors = [];
12
13 /**
14 * Triggers custom JS event.
15 *
16 * @since 5.5.3
17 *
18 * @param {HTMLElement} el The HTML element.
19 * @param {string} eventName Event name.
20 * @param {mixed} data The passed data.
21 */
22 function triggerCustomEvent( el, eventName, data ) {
23 if ( typeof window.CustomEvent !== 'function' ) {
24 return;
25 }
26
27 const event = new CustomEvent( eventName );
28 event.frmData = data;
29
30 el.dispatchEvent( event );
31 }
32
33 /* Get the ID of the field that changed*/
34 function getFieldId( field, fullID ) {
35 let nameParts, fieldId,
36 isRepeating = false,
37 fieldName = '';
38 if ( field instanceof jQuery ) {
39 fieldName = field.attr( 'name' );
40 } else {
41 fieldName = field.name;
42 }
43
44 if ( typeof fieldName === 'undefined' ) {
45 fieldName = '';
46 }
47
48 if ( fieldName === '' ) {
49 if ( field instanceof jQuery ) {
50 fieldName = field.data( 'name' );
51 } else {
52 fieldName = field.getAttribute( 'data-name' );
53 }
54
55 if ( typeof fieldName === 'undefined' ) {
56 fieldName = '';
57 }
58
59 if ( fieldName !== '' && fieldName ) {
60 return fieldName;
61 }
62 return 0;
63 }
64
65 nameParts = fieldName.replace( 'item_meta[', '' ).replace( '[]', '' ).split( ']' );
66 //TODO: Fix this for checkboxes and address fields
67 if ( nameParts.length < 1 ) {
68 return 0;
69 }
70 nameParts = nameParts.filter( function( n ) {
71 return n !== '';
72 });
73
74 fieldId = nameParts[0];
75
76 if ( nameParts.length === 1 ) {
77 return fieldId;
78 }
79
80 if ( nameParts[1] === '[form' || nameParts[1] === '[row_ids' ) {
81 return 0;
82 }
83
84 // Check if 'this' is in a repeating section
85 if ( jQuery( 'input[name="item_meta[' + fieldId + '][form]"]' ).length ) {
86
87 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
88 fieldId = nameParts[2].replace( '[', '' );
89 isRepeating = true;
90 }
91
92 // Check if 'this' is an other text field and get field ID for it
93 if ( 'other' === fieldId ) {
94 if ( isRepeating ) {
95 // name for other fields: item_meta[370][0][other][414]
96 fieldId = nameParts[3].replace( '[', '' );
97 } else {
98 // Other field name: item_meta[other][370]
99 fieldId = nameParts[1].replace( '[', '' );
100 }
101 }
102
103 if ( fullID === true ) {
104 // For use in the container div id
105 if ( fieldId === nameParts[0]) {
106 fieldId = fieldId + '-' + nameParts[1].replace( '[', '' );
107 } else {
108 fieldId = fieldId + '-' + nameParts[0] + '-' + nameParts[1].replace( '[', '' );
109 }
110 }
111
112 return fieldId;
113 }
114
115 /**
116 * Disable the submit button for a given jQuery form object
117 *
118 * @since 2.03.02
119 *
120 * @param {Object} $form
121 */
122 function disableSubmitButton( $form ) {
123 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).attr( 'disabled', 'disabled' );
124 }
125
126 /**
127 * Enable the submit button for a given jQuery form object
128 *
129 * @since 2.03.02
130 *
131 * @param {Object} $form
132 */
133 function enableSubmitButton( $form ) {
134 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
135 }
136
137 /**
138 * Disable the save draft link for a given jQuery form object
139 *
140 * @since 4.04.03
141 *
142 * @param {Object} $form
143 */
144 function disableSaveDraft( $form ) {
145 $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
146 }
147
148 /**
149 * Enable the save draft link for a given jQuery form object
150 *
151 * @since 4.04.03
152 *
153 * @param {Object} $form
154 */
155 function enableSaveDraft( $form ) {
156 $form.find( 'a.frm_save_draft' ).css( 'pointer-events', '' );
157 }
158
159 function validateForm( object ) {
160 let errors, r, rl, n, nl, fields, field, requiredFields;
161
162 errors = [];
163
164 // Make sure required text field is filled in
165 requiredFields = jQuery( object ).find(
166 '.frm_required_field:visible input, .frm_required_field:visible select, .frm_required_field:visible textarea'
167 ).filter( ':not(.frm_optional)' );
168 if ( requiredFields.length ) {
169 for ( r = 0, rl = requiredFields.length; r < rl; r++ ) {
170 if ( hasClass( requiredFields[r], 'ed_button' ) ) {
171 // skip rich text field buttons.
172 continue;
173 }
174 errors = checkRequiredField( requiredFields[r], errors );
175 }
176 }
177
178 fields = jQuery( object ).find( 'input,select,textarea' );
179 if ( fields.length ) {
180 for ( n = 0, nl = fields.length; n < nl; n++ ) {
181 field = fields[n];
182 if ( '' === field.value ) {
183 if ( 'number' === field.type ) {
184 // A number field will return an empty string when it is invalid.
185 checkValidity( field, errors );
186 }
187 continue;
188 }
189
190 validateFieldValue( field, errors );
191 checkValidity( field, errors );
192 }
193 }
194
195 errors = validateRecaptcha( object, errors );
196
197 return errors;
198 }
199
200 /**
201 * Check the ValidityState interface for the field.
202 * If it is invalid, show an error for it.
203 *
204 * @param {HTMLElement} field
205 * @param {Array} errors
206 * @return {void}
207 */
208 function checkValidity( field, errors ) {
209 let fieldID;
210 if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
211 return;
212 }
213
214 fieldID = getFieldId( field, true );
215 if ( 'undefined' === typeof errors[ fieldID ]) {
216 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
217 }
218
219 if ( 'function' === typeof field.reportValidity ) {
220 // This triggers an error pop up.
221 field.reportValidity();
222 }
223 }
224
225 /**
226 * @since 5.0.10
227 *
228 * @param {Object} element
229 * @param {string} targetClass
230 * @return {boolean} True if the element has the target class.
231 */
232 function hasClass( element, targetClass ) {
233 return element.classList.contains( targetClass );
234 }
235
236 function maybeValidateChange( field ) {
237 if ( field.type === 'url' ) {
238 maybeAddHttpToUrl( field );
239 }
240 if ( jQuery( field ).closest( 'form' ).hasClass( 'frm_js_validate' ) ) {
241 validateField( field );
242 }
243 }
244
245 function maybeAddHttpToUrl( field ) {
246 const url = field.value;
247 const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
248 if ( field.value !== '' && matches === null ) {
249 field.value = 'http://' + url;
250 }
251 }
252
253 function validateField( field ) {
254 let key,
255 errors = [],
256 $fieldCont = jQuery( field ).closest( '.frm_form_field' );
257
258 if ( $fieldCont.hasClass( 'frm_required_field' ) && ! jQuery( field ).hasClass( 'frm_optional' ) ) {
259 errors = checkRequiredField( field, errors );
260 }
261
262 if ( errors.length < 1 ) {
263 validateFieldValue( field, errors );
264 }
265
266 removeFieldError( $fieldCont );
267 if ( Object.keys( errors ).length > 0 ) {
268 for ( key in errors ) {
269 addFieldError( $fieldCont, key, errors );
270 }
271 }
272 }
273
274 function validateFieldValue( field, errors ) {
275 if ( field.type === 'hidden' ) {
276 // don't validate
277 } else if ( field.type === 'number' ) {
278 checkNumberField( field, errors );
279 } else if ( field.type === 'email' ) {
280 checkEmailField( field, errors );
281 } else if ( field.type === 'password' ) {
282 checkPasswordField( field, errors );
283 } else if ( field.type === 'url' ) {
284 checkUrlField( field, errors );
285 } else if ( field.pattern !== null ) {
286 checkPatternField( field, errors );
287 }
288
289 triggerCustomEvent( document, 'frm_validate_field_value', {
290 field: field,
291 errors: errors
292 });
293 }
294
295 function checkRequiredField( field, errors ) {
296 let checkGroup, tempVal, i, placeholder,
297 val = '',
298 fieldID = '',
299 fileID = field.getAttribute( 'data-frmfile' );
300
301 if ( field.type === 'hidden' && fileID === null && ! isAppointmentField( field ) && ! isInlineDatepickerField( field ) ) {
302 return errors;
303 }
304
305 if ( field.type === 'checkbox' || field.type === 'radio' ) {
306 checkGroup = jQuery( 'input[name="' + field.name + '"]' ).closest( '.frm_required_field' ).find( 'input:checked' );
307 jQuery( checkGroup ).each( function() {
308 val = this.value;
309 });
310 } else if ( field.type === 'file' || fileID ) {
311 if ( typeof fileID === 'undefined' ) {
312 fileID = getFieldId( field, true );
313 fileID = fileID.replace( 'file', '' );
314 }
315
316 if ( typeof errors[ fileID ] === 'undefined' ) {
317 val = getFileVals( fileID );
318 }
319 fieldID = fileID;
320 } else {
321 if ( hasClass( field, 'frm_pos_none' ) ) {
322 // skip hidden other fields
323 return errors;
324 }
325
326 val = jQuery( field ).val();
327 if ( val === null ) {
328 val = '';
329 } else if ( typeof val !== 'string' ) {
330 tempVal = val;
331 val = '';
332 for ( i = 0; i < tempVal.length; i++ ) {
333 if ( tempVal[i] !== '' ) {
334 val = tempVal[i];
335 }
336 }
337 }
338
339 if ( hasClass( field, 'frm_other_input' ) ) {
340 fieldID = getFieldId( field, false );
341
342 if ( val === '' ) {
343 field = document.getElementById( field.id.replace( '-otext', '' ) );
344 }
345 } else {
346 fieldID = getFieldId( field, true );
347 }
348
349 if ( hasClass( field, 'frm_time_select' ) ) {
350 // set id for time field
351 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
352 } else if ( isSignatureField( field ) ) {
353 if ( val === '' ) {
354 val = jQuery( field ).closest( '.frm_form_field' ).find( '[name="' + field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) + '"]' ).val();
355 }
356 fieldID = fieldID.replace( '-typed', '' );
357 }
358
359 placeholder = field.getAttribute( 'data-frmplaceholder' );
360 if ( placeholder !== null && val === placeholder ) {
361 val = '';
362 }
363 }
364
365 if ( val === '' ) {
366 if ( fieldID === '' ) {
367 fieldID = getFieldId( field, true );
368 }
369 if ( ! ( fieldID in errors ) ) {
370 errors[ fieldID ] = getFieldValidationMessage( field, 'data-reqmsg' );
371 }
372 }
373
374 return errors;
375 }
376
377 function isSignatureField( field ) {
378 const name = field.getAttribute( 'name' );
379 return 'string' === typeof name && '[typed]' === name.substr( -7 );
380 }
381
382 function isAppointmentField( field ) {
383 return hasClass( field, 'ssa_appointment_form_field_appointment_id' );
384 }
385
386 function isInlineDatepickerField( field ) {
387 return 'hidden' === field.type && '_alt' === field.id.substr( -4 ) && hasClass( field.nextElementSibling, 'frm_date_inline' );
388 }
389
390 function getFileVals( fileID ) {
391 let val = '',
392 fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
393
394 fileFields.each( function() {
395 if ( val === '' ) {
396 val = this.value;
397 }
398 });
399 return val;
400 }
401
402 function checkUrlField( field, errors ) {
403 let fieldID,
404 url = field.value;
405
406 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
407 fieldID = getFieldId( field, true );
408 if ( ! ( fieldID in errors ) ) {
409 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
410 }
411 }
412 }
413
414 function checkEmailField( field, errors ) {
415 const fieldID = getFieldId( field, true ),
416 pattern = /^(([^<>()\[\]\\.,;:\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;
417
418 // validate the current field we're editing first
419 if ( '' !== field.value && pattern.test( field.value ) === false ) {
420 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
421 }
422
423 confirmField( field, errors );
424 }
425
426 function checkPasswordField( field, errors ) {
427 confirmField( field, errors );
428 }
429
430 function confirmField( field, errors ) {
431 let value, confirmValue, firstField,
432 fieldID = getFieldId( field, true ),
433 strippedId = field.id.replace( 'conf_', '' ),
434 strippedFieldID = fieldID.replace( 'conf_', '' ),
435 confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
436
437 if ( confirmField === null || typeof errors[ 'conf_' + strippedFieldID ] !== 'undefined' ) {
438 return;
439 }
440
441 if ( fieldID !== strippedFieldID ) {
442 firstField = document.getElementById( strippedId );
443 value = firstField.value;
444 confirmValue = confirmField.value;
445 if ( value !== confirmValue ) {
446 errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
447 }
448 } else {
449 validateField( confirmField );
450 }
451 }
452
453 function checkNumberField( field, errors ) {
454 let fieldID,
455 number = field.value;
456
457 if ( number !== '' && isNaN( number / 1 ) !== false ) {
458 fieldID = getFieldId( field, true );
459 if ( ! ( fieldID in errors ) ) {
460 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
461 }
462 }
463 }
464
465 function checkPatternField( field, errors ) {
466 let fieldID,
467 text = field.value,
468 format = getFieldValidationMessage( field, 'pattern' );
469
470 if ( format !== '' && text !== '' ) {
471 fieldID = getFieldId( field, true );
472 if ( ! ( fieldID in errors ) ) {
473 format = new RegExp( '^' + format + '$', 'i' );
474 if ( format.test( text ) === false ) {
475 if ( 'object' === typeof window.frmProForm && 'function' === typeof window.frmProForm.isIntlPhoneInput && window.frmProForm.isIntlPhoneInput( field ) ) {
476 if ( ! window.frmProForm.validateIntlPhoneInput( field ) ) {
477 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
478 }
479 } else {
480 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
481 }
482 }
483 }
484 }
485 }
486
487 /**
488 * Set color for select placeholders.
489 *
490 * @since 6.5.1
491 */
492 function setSelectPlaceholderColor() {
493 let selects = document.querySelectorAll( '.form-field select' ),
494 styleElement = document.querySelector( '.with_frm_style' ),
495 textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '',
496 changeSelectColor;
497
498 // Exit if there are no select elements or the textColorDisabled property is missing
499 if ( ! selects.length || ! textColorDisabled ) {
500 return;
501 }
502
503 // Function to change the color of a select element
504 changeSelectColor = function( select ) {
505 if ( select.options[select.selectedIndex] && hasClass( select.options[select.selectedIndex], 'frm-select-placeholder' ) ) {
506 select.style.setProperty( 'color', textColorDisabled, 'important' );
507 } else {
508 select.style.color = '';
509 }
510 };
511
512 // Use a loop to iterate through each select element
513 Array.prototype.forEach.call( selects, function( select ) {
514 // Apply the color change to each select element
515 changeSelectColor( select );
516
517 // Add an event listener for future changes
518 select.addEventListener( 'change', function() {
519 changeSelectColor( select );
520 });
521 });
522 }
523
524 function hasInvisibleRecaptcha( object ) {
525 let recaptcha, recaptchaID, alreadyChecked;
526
527 if ( isGoingToPrevPage( object ) ) {
528 return false;
529 }
530
531 recaptcha = jQuery( object ).find( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
532 if ( recaptcha.length ) {
533 recaptchaID = recaptcha.data( 'rid' );
534 alreadyChecked = grecaptcha.getResponse( recaptchaID );
535 if ( alreadyChecked.length === 0 ) {
536 return recaptcha;
537 }
538 }
539 return false;
540 }
541
542 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
543 const recaptchaID = invisibleRecaptcha.data( 'rid' );
544 grecaptcha.reset( recaptchaID );
545 grecaptcha.execute( recaptchaID );
546 }
547
548 function validateRecaptcha( form, errors ) {
549 let recaptchaID, response, fieldContainer, fieldID,
550 $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
551 if ( $recaptcha.length ) {
552 recaptchaID = $recaptcha.data( 'rid' );
553
554 try {
555 response = grecaptcha.getResponse( recaptchaID );
556 } catch ( e ) {
557 if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
558 return errors;
559 }
560 response = '';
561 }
562
563 if ( response.length === 0 ) {
564 fieldContainer = $recaptcha.closest( '.frm_form_field' );
565 fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
566 errors[ fieldID ] = '';
567 }
568 }
569 return errors;
570 }
571
572 function getFieldValidationMessage( field, messageType ) {
573 let msg, errorHtml;
574
575 msg = field.getAttribute( messageType );
576 if ( null === msg ) {
577 msg = '';
578 }
579
580 if ( '' !== msg && shouldWrapErrorHtmlAroundMessageType( messageType ) ) {
581 errorHtml = field.getAttribute( 'data-error-html' );
582 if ( null !== errorHtml ) {
583 errorHtml = errorHtml.replace( /\+/g, '%20' );
584 msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
585 msg = msg.replace( '[key]', getFieldId( field, false ) );
586 }
587 }
588
589 return msg;
590 }
591
592 function shouldWrapErrorHtmlAroundMessageType( type ) {
593 return 'pattern' !== type;
594 }
595
596 function shouldJSValidate( object ) {
597 let validate = jQuery( object ).hasClass( 'frm_js_validate' );
598 if ( validate && typeof frmProForm !== 'undefined' && ( frmProForm.savingDraft( object ) || frmProForm.goingToPreviousPage( object ) ) ) {
599 validate = false;
600 }
601
602 return validate;
603 }
604
605 function getFormErrors( object, action ) {
606 let fieldset, data, success, error, shouldTriggerEvent;
607
608 if ( typeof action === 'undefined' ) {
609 jQuery( object ).find( 'input[name="frm_action"]' ).val();
610 }
611
612 fieldset = jQuery( object ).find( '.frm_form_field' );
613 fieldset.addClass( 'frm_doing_ajax' );
614
615 data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
616 shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
617
618 success = function( response ) {
619 let defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
620 $fieldCont, key, inCollapsedSection, frmTrigger, newTab;
621
622 defaultResponse = {
623 content: '',
624 errors: {},
625 pass: false
626 };
627
628 if ( response === null ) {
629 response = defaultResponse;
630 }
631
632 response = response.replace( /^\s+|\s+$/g, '' );
633 if ( response.indexOf( '{' ) === 0 ) {
634 response = JSON.parse( response );
635 } else {
636 response = defaultResponse;
637 }
638
639 if ( typeof response.redirect !== 'undefined' ) {
640 if ( shouldTriggerEvent ) {
641 triggerCustomEvent( object, 'frmSubmitEvent' );
642 return;
643 }
644
645 jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ]);
646
647 if ( ! response.openInNewTab ) {
648 // We return here because we're redirecting there is no need to update content.
649 window.location = response.redirect;
650 return;
651 }
652
653 // We don't return here because we're opening in a new tab, the old tab will still update.
654 newTab = window.open( response.redirect, '_blank' );
655 if ( ! newTab && response.fallbackMsg && response.content ) {
656 response.content = response.content.trim().replace( /(<\/div><\/div>)$/, ' ' + response.fallbackMsg + '</div></div>' );
657 }
658 }
659
660 if ( response.content !== '' ) {
661 // the form or success message was returned
662
663 if ( shouldTriggerEvent ) {
664 triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content });
665 return;
666 }
667
668 removeSubmitLoading( jQuery( object ) );
669 if ( frm_js.offset != -1 ) { // eslint-disable-line camelcase
670 frmFrontForm.scrollMsg( jQuery( object ), false );
671 }
672
673 formID = jQuery( object ).find( 'input[name="form_id"]' ).val();
674 response.content = response.content.replace( / frm_pro_form /g, ' frm_pro_form frm_no_hide ' );
675 replaceContent = jQuery( object ).closest( '.frm_forms' );
676 removeAddedScripts( replaceContent, formID );
677 delay = maybeSlideOut( replaceContent, response.content );
678
679 setTimeout(
680 function() {
681 let container, input, previousInput;
682
683 afterFormSubmittedBeforeReplace( object, response );
684
685 replaceContent.replaceWith( response.content );
686
687 addUrlParam( response );
688
689 if ( typeof frmThemeOverride_frmAfterSubmit === 'function' ) { // eslint-disable-line camelcase
690 pageOrder = jQuery( 'input[name="frm_page_order_' + formID + '"]' ).val();
691 formReturned = jQuery( response.content ).find( 'input[name="form_id"]' ).val();
692 frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
693 }
694
695 if ( typeof response.recaptcha !== 'undefined' ) {
696 container = jQuery( '#frm_form_' + formID + '_container' ).find( '.frm_fields_container' );
697 input = '<input type="hidden" name="recaptcha_checked" value="' + response.recaptcha + '">';
698 previousInput = container.find( 'input[name="recaptcha_checked"]' );
699
700 if ( previousInput.length ) {
701 previousInput.replaceWith( input );
702 } else {
703 container.append( input );
704 }
705 }
706
707 afterFormSubmitted( object, response );
708 },
709 delay
710 );
711 } else if ( Object.keys( response.errors ).length ) {
712 // errors were returned
713 removeSubmitLoading( jQuery( object ), 'enable' );
714
715 //show errors
716 contSubmit = true;
717 removeAllErrors();
718
719 $fieldCont = null;
720
721 for ( key in response.errors ) {
722 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
723
724 if ( $fieldCont.length ) {
725 if ( ! $fieldCont.is( ':visible' ) ) {
726 inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' );
727 if ( inCollapsedSection.length ) {
728 frmTrigger = inCollapsedSection.prev();
729 if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) {
730 // If the frmTrigger object is the section description, check to see if the previous element is the trigger
731 frmTrigger = frmTrigger.prev( '.frm_trigger' );
732 }
733 frmTrigger.trigger( 'click' );
734 }
735 }
736
737 if ( $fieldCont.is( ':visible' ) ) {
738 addFieldError( $fieldCont, key, response.errors );
739 contSubmit = false;
740 }
741 }
742 }
743
744 jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).each( function() {
745 const $recaptcha = jQuery( this ),
746 recaptchaID = $recaptcha.data( 'rid' );
747
748 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
749 if ( recaptchaID ) {
750 grecaptcha.reset( recaptchaID );
751 } else {
752 grecaptcha.reset();
753 }
754 }
755 if ( typeof hcaptcha !== 'undefined' && hcaptcha ) {
756 hcaptcha.reset();
757 }
758 });
759
760 jQuery( document ).trigger( 'frmFormErrors', [ object, response ]);
761
762 fieldset.removeClass( 'frm_doing_ajax' );
763 scrollToFirstField( object );
764
765 if ( contSubmit ) {
766 object.submit();
767 } else {
768 jQuery( object ).prepend( response.error_message );
769 checkForErrorsAndMaybeSetFocus();
770 }
771 } else {
772 // there may have been a plugin conflict, or the form is not set to submit with ajax
773
774 showFileLoading( object );
775
776 object.submit();
777 }
778 };
779
780 error = function() {
781 jQuery( object ).find( 'input[type="submit"], input[type="button"]' ).prop( 'disabled', false );
782 object.submit();
783 };
784
785 postToAjaxUrl( object, data, success, error );
786 }
787
788 function postToAjaxUrl( form, data, success, error ) {
789 let ajaxUrl, action, ajaxParams;
790
791 ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
792 action = form.getAttribute( 'action' );
793
794 if ( 'string' === typeof action && -1 !== action.indexOf( '?action=frm_forms_preview' ) ) {
795 ajaxUrl = action.split( '?action=frm_forms_preview' )[0];
796 }
797
798 ajaxParams = {
799 type: 'POST',
800 url: ajaxUrl,
801 data: data,
802 success: success
803 };
804
805 if ( 'function' === typeof error ) {
806 ajaxParams.error = error;
807 }
808
809 jQuery.ajax( ajaxParams );
810 }
811
812 function afterFormSubmitted( object, response ) {
813 const formCompleted = jQuery( response.content ).find( '.frm_message' );
814 if ( formCompleted.length ) {
815 jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
816 } else {
817 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
818 }
819 }
820
821 /**
822 * Trigger an event before the form is replaced with a success message.
823 *
824 * @since 6.9
825 *
826 * @param {HTMLElement} object The form.
827 * @param {Object} response The response from submitting the form with AJAX.
828 * @return {void}
829 */
830 function afterFormSubmittedBeforeReplace( object, response ) {
831 const formCompleted = jQuery( response.content ).find( '.frm_message' );
832 if ( formCompleted.length ) {
833 triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response });
834 }
835 }
836
837 function removeAddedScripts( formContainer, formID ) {
838 const endReplace = jQuery( '.frm_end_ajax_' + formID );
839 if ( endReplace.length ) {
840 formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
841 endReplace.remove();
842 }
843 }
844
845 function maybeSlideOut( oldContent, newContent ) {
846 let c,
847 newClass = 'frm_slideout';
848 if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
849 c = oldContent.children();
850 if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
851 newClass += ' frm_going_back';
852 }
853 c.removeClass( 'frm_going_back' );
854 c.addClass( newClass );
855 return 300;
856 }
857 return 0;
858 }
859
860 function addUrlParam( response ) {
861 let url;
862 if ( history.pushState && typeof response.page !== 'undefined' ) {
863 url = addQueryVar( 'frm_page', response.page );
864 window.history.pushState({ 'html': response.html }, '', '?' + url );
865 }
866 }
867
868 function addQueryVar( key, value ) {
869 let kvp, i, x;
870
871 key = encodeURI( key );
872 value = encodeURI( value );
873
874 kvp = document.location.search.substr( 1 ).split( '&' );
875
876 i = kvp.length;
877 while ( i-- ) {
878 x = kvp[i].split( '=' );
879
880 if ( x[0] == key ) {
881 x[1] = value;
882 kvp[i] = x.join( '=' );
883 break;
884 }
885 }
886
887 if ( i < 0 ) {
888 kvp[ kvp.length ] = [ key, value ].join( '=' );
889 }
890
891 return kvp.join( '&' );
892 }
893
894 function addFieldError( $fieldCont, key, jsErrors ) {
895 let input, id, describedBy, roleString;
896 if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
897 $fieldCont.addClass( 'frm_blank_field' );
898 input = $fieldCont.find( 'input, select, textarea' );
899 id = getErrorElementId( key, input.get( 0 ) );
900
901 describedBy = input.attr( 'aria-describedby' );
902
903 if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
904 frmThemeOverride_frmPlaceError( key, jsErrors );
905 } else {
906 if ( -1 !== jsErrors[key].indexOf( '<div' ) ) {
907 $fieldCont.append(
908 jsErrors[key]
909 );
910 } else {
911 roleString = frm_js.include_alert_role ? 'role="alert"' : ''; // eslint-disable-line camelcase
912 $fieldCont.append( '<div class="frm_error" ' + roleString + ' id="' + id + '">' + jsErrors[key] + '</div>' );
913 }
914
915 if ( typeof describedBy === 'undefined' ) {
916 describedBy = id;
917 } else if ( describedBy.indexOf( id ) === -1 && describedBy.indexOf( 'frm_error_field_' ) === -1 ) {
918 if ( input.data( 'error-first' ) === 0 ) {
919 describedBy = describedBy + ' ' + id;
920 } else {
921 describedBy = id + ' ' + describedBy;
922 }
923 }
924
925 input.attr( 'aria-describedby', describedBy );
926 }
927 input.attr( 'aria-invalid', true );
928
929 jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
930 }
931 }
932
933 /**
934 * Get the ID to use for an error element added when submitting with AJAX.
935 *
936 * @param {string} key
937 * @param {HTMLElement} input
938 * @return {string} The ID to use for the error element.
939 */
940 function getErrorElementId( key, input ) {
941 if ( isNaN( key ) || ! input.id ) {
942 // If key isn't a number, assume it's already in the right format.
943 return 'frm_error_field_' + key;
944 }
945 return 'frm_error_' + input.id;
946 }
947
948 function removeFieldError( $fieldCont ) {
949 let errorMessage = $fieldCont.find( '.frm_error' ),
950 errorId = errorMessage.attr( 'id' ),
951 input = $fieldCont.find( 'input, select, textarea' ),
952 describedBy = input.attr( 'aria-describedby' );
953
954 $fieldCont.removeClass( 'frm_blank_field has-error' );
955 errorMessage.remove();
956 input.attr( 'aria-invalid', false );
957 input.removeAttr( 'aria-describedby' );
958
959 if ( typeof describedBy !== 'undefined' ) {
960 describedBy = describedBy.replace( errorId, '' );
961 input.attr( 'aria-describedby', describedBy );
962 }
963 }
964
965 function removeAllErrors() {
966 jQuery( '.form-field' ).removeClass( 'frm_blank_field has-error' );
967 jQuery( '.form-field .frm_error' ).replaceWith( '' );
968 jQuery( '.frm_error_style' ).remove();
969 }
970
971 function scrollToFirstField( object ) {
972 const field = jQuery( object ).find( '.frm_blank_field' ).first();
973 if ( field.length ) {
974 frmFrontForm.scrollMsg( field, object, true );
975 }
976 }
977
978 function showSubmitLoading( $object ) {
979 showLoadingIndicator( $object );
980 disableSubmitButton( $object );
981 disableSaveDraft( $object );
982 }
983
984 function showLoadingIndicator( $object ) {
985 if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) {
986 addLoadingClass( $object );
987 $object.trigger( 'frmStartFormLoading' );
988 }
989 }
990
991 function addLoadingClass( $object ) {
992 const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
993
994 $object.addClass( loadingClass );
995 }
996
997 function isGoingToPrevPage( $object ) {
998 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
999 }
1000
1001 function removeSubmitLoading( $object, enable, processesRunning ) {
1002 let loadingForm;
1003
1004 if ( processesRunning > 0 ) {
1005 return;
1006 }
1007
1008 loadingForm = jQuery( '.frm_loading_form' );
1009 loadingForm.removeClass( 'frm_loading_form' );
1010 loadingForm.removeClass( 'frm_loading_prev' );
1011
1012 loadingForm.trigger( 'frmEndFormLoading' );
1013
1014 if ( enable === 'enable' ) {
1015 enableSubmitButton( loadingForm );
1016 enableSaveDraft( loadingForm );
1017 }
1018 }
1019
1020 function showFileLoading( object ) {
1021 let fileval,
1022 loading = document.getElementById( 'frm_loading' );
1023 if ( loading !== null ) {
1024 fileval = jQuery( object ).find( 'input[type=file]' ).val();
1025 if ( typeof fileval !== 'undefined' && fileval !== '' ) {
1026 setTimeout( function() {
1027 jQuery( loading ).fadeIn( 'slow' );
1028 }, 2000 );
1029 }
1030 }
1031 }
1032
1033 function clearDefault() {
1034 /*jshint validthis:true */
1035 toggleDefault( jQuery( this ), 'clear' );
1036 }
1037
1038 function replaceDefault() {
1039 /*jshint validthis:true */
1040 toggleDefault( jQuery( this ), 'replace' );
1041 }
1042
1043 function toggleDefault( $thisField, e ) {
1044 // TODO: Fix this for a default value that is a number or array
1045 let thisVal,
1046 v = $thisField.data( 'frmval' ).replace( /(\n|\r\n)/g, '\r' );
1047 if ( v === '' || typeof v === 'undefined' ) {
1048 return false;
1049 }
1050 thisVal = $thisField.val().replace( /(\n|\r\n)/g, '\r' );
1051
1052 if ( 'replace' === e ) {
1053 if ( thisVal === '' ) {
1054 $thisField.addClass( 'frm_default' ).val( v );
1055 }
1056 } else if ( thisVal == v ) {
1057 $thisField.removeClass( 'frm_default' ).val( '' );
1058 }
1059 }
1060
1061 function resendEmail() {
1062 console.warn( 'DEPRECATED: function resendEmail in v6.10 please update to Formidable Pro v6.10' );
1063
1064 /*jshint validthis:true */
1065 let $link = jQuery( this ),
1066 entryId = this.getAttribute( 'data-eid' ),
1067 formId = this.getAttribute( 'data-fid' ),
1068 label = $link.find( '.frm_link_label' );
1069 if ( label.length < 1 ) {
1070 label = $link;
1071 }
1072 label.append( '<span class="frm-wait"></span>' );
1073
1074 jQuery.ajax({
1075 type: 'POST',
1076 url: frm_js.ajax_url, // eslint-disable-line camelcase
1077 data: {
1078 action: 'frm_entries_send_email',
1079 entry_id: entryId,
1080 form_id: formId,
1081 nonce: frm_js.nonce // eslint-disable-line camelcase
1082 },
1083 success: function( msg ) {
1084 const admin = document.getElementById( 'wpbody' );
1085 if ( admin === null ) {
1086 label.html( msg );
1087 } else {
1088 label.html( '' );
1089 $link.after( msg );
1090 }
1091 }
1092 });
1093 return false;
1094 }
1095
1096 /**********************************************
1097 * General Helpers
1098 *********************************************/
1099
1100 function confirmClick() {
1101 /*jshint validthis:true */
1102 const message = jQuery( this ).data( 'frmconfirm' );
1103 return confirm( message );
1104 }
1105
1106 function toggleDiv() {
1107 /*jshint validthis:true */
1108 const div = jQuery( this ).data( 'frmtoggle' );
1109 if ( jQuery( div ).is( ':visible' ) ) {
1110 jQuery( div ).slideUp( 'fast' );
1111 } else {
1112 jQuery( div ).slideDown( 'fast' );
1113 }
1114 return false;
1115 }
1116
1117 /**
1118 * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
1119 * If this is a match, the User is autofilling the input on a Webkit browser.
1120 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1121 */
1122 function onHoneypotFieldChange() {
1123 const css = jQuery( this ).css( 'box-shadow' );
1124 if ( css.match( /inset/ ) ) {
1125 this.parentNode.removeChild( this );
1126 }
1127 }
1128
1129 function maybeMakeHoneypotFieldsUntabbable() {
1130 document.addEventListener( 'keydown', handleKeyUp );
1131
1132 function handleKeyUp( event ) {
1133 let code;
1134
1135 if ( 'undefined' !== typeof event.key ) {
1136 code = event.key;
1137 } else if ( 'undefined' !== typeof event.keyCode && 9 === event.keyCode ) {
1138 code = 'Tab';
1139 }
1140
1141 if ( 'Tab' === code ) {
1142 makeHoneypotFieldsUntabbable();
1143 document.removeEventListener( 'keydown', handleKeyUp );
1144 }
1145 }
1146
1147 function makeHoneypotFieldsUntabbable() {
1148 document.querySelectorAll( '.frm_verify' ).forEach(
1149 function( input ) {
1150 if ( input.id && 0 === input.id.indexOf( 'frm_email_' ) ) {
1151 input.setAttribute( 'tabindex', -1 );
1152 }
1153 }
1154 );
1155 }
1156 }
1157
1158 /**
1159 * Focus on the first sub field when clicking to the primary label of combo field.
1160 *
1161 * @since 4.10.02
1162 */
1163 function changeFocusWhenClickComboFieldLabel() {
1164 let label;
1165
1166 const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1167 comboInputsContainer.forEach( function( inputsContainer ) {
1168 if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1169 return;
1170 }
1171
1172 label = inputsContainer.closest( '.frm_form_field' ).querySelector( '.frm_primary_label' );
1173 if ( ! label ) {
1174 return;
1175 }
1176
1177 label.addEventListener( 'click', function( e ) {
1178 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1179 });
1180 });
1181 }
1182
1183 function checkForErrorsAndMaybeSetFocus() {
1184 let errors, element, timeoutCallback;
1185
1186 if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1187 return;
1188 }
1189
1190 errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1191 if ( ! errors.length ) {
1192 return;
1193 }
1194
1195 element = errors[0];
1196 do {
1197 element = element.previousSibling;
1198 if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1199 element.focus();
1200 break;
1201 }
1202
1203 if ( 'undefined' !== typeof element.classList ) {
1204 if ( element.classList.contains( 'html-active' ) ) {
1205 timeoutCallback = function() {
1206 const textarea = element.querySelector( 'textarea' );
1207 if ( null !== textarea ) {
1208 textarea.focus();
1209 }
1210 };
1211 } else if ( element.classList.contains( 'tmce-active' ) ) {
1212 timeoutCallback = function() {
1213 tinyMCE.activeEditor.focus();
1214 };
1215 }
1216
1217 if ( 'function' === typeof timeoutCallback ) {
1218 setTimeout( timeoutCallback, 0 );
1219 break;
1220 }
1221 }
1222 } while ( element.previousSibling );
1223 }
1224
1225 /**
1226 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
1227 *
1228 * @since 5.4
1229 *
1230 * @param {string} event Event name.
1231 * @param {string} selector Selector.
1232 * @param {Function} handler Handler.
1233 * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1234 */
1235 function documentOn( event, selector, handler, options ) {
1236 if ( 'undefined' === typeof options ) {
1237 options = false;
1238 }
1239
1240 document.addEventListener( event, function( e ) {
1241 let target;
1242
1243 // loop parent nodes from the target to the delegation node.
1244 for ( target = e.target; target && target != this; target = target.parentNode ) {
1245 if ( target && target.matches && target.matches( selector ) ) {
1246 handler.call( target, e );
1247 break;
1248 }
1249 }
1250 }, options );
1251 }
1252
1253 function initFloatingLabels() {
1254 let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1255
1256 selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1257 floatClass = 'frm_label_float_top';
1258
1259 checkFloatLabel = function( input ) {
1260 let container, shouldFloatTop, firstOpt;
1261
1262 container = input.closest( '.frm_inside_container' );
1263 if ( ! container ) {
1264 return;
1265 }
1266
1267 shouldFloatTop = input.value || document.activeElement === input;
1268
1269 container.classList.toggle( floatClass, shouldFloatTop );
1270
1271 if ( 'SELECT' === input.tagName ) {
1272 firstOpt = input.querySelector( 'option:first-child' );
1273
1274 if ( shouldFloatTop ) {
1275 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1276 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1277 firstOpt.removeAttribute( 'data-label' );
1278 }
1279 } else if ( firstOpt.textContent ) {
1280 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1281 firstOpt.textContent = '';
1282 }
1283 }
1284 };
1285
1286 checkDropdownLabel = function() {
1287 document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1288 const firstOpt = input.querySelector( 'option:first-child' );
1289
1290 if ( firstOpt.textContent ) {
1291 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1292 firstOpt.textContent = '';
1293 }
1294 });
1295 };
1296
1297 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1298 documentOn(
1299 eventName,
1300 selector,
1301 function( event ) {
1302 checkFloatLabel( event.target );
1303 },
1304 true
1305 );
1306 });
1307
1308 jQuery( document ).on( 'change', selector, function( event ) {
1309 checkFloatLabel( event.target );
1310 });
1311
1312 runOnLoad = function( firstLoad ) {
1313 if ( firstLoad && document.activeElement && -1 !== [ 'INPUT', 'SELECT', 'TEXTAREA' ].indexOf( document.activeElement.tagName ) ) {
1314 checkFloatLabel( document.activeElement );
1315 } else if ( firstLoad ) {
1316 document.querySelectorAll( '.frm_inside_container' ).forEach(
1317 function( container ) {
1318 const input = container.querySelector( 'input, select, textarea' );
1319 if ( input && '' !== input.value ) {
1320 checkFloatLabel( input );
1321 }
1322 }
1323 );
1324 }
1325
1326 checkDropdownLabel();
1327 };
1328
1329 runOnLoad( true );
1330
1331 jQuery( document ).on( 'frmPageChanged', function( event ) {
1332 runOnLoad();
1333 });
1334
1335 document.addEventListener( 'frm_after_start_over', function( event ) {
1336 runOnLoad();
1337 });
1338 }
1339
1340 function shouldUpdateValidityMessage( target ) {
1341 if ( 'INPUT' !== target.nodeName ) {
1342 return false;
1343 }
1344
1345 if ( ! target.dataset.invmsg ) {
1346 return false;
1347 }
1348
1349 if ( 'text' !== target.getAttribute( 'type' ) ) {
1350 return false;
1351 }
1352
1353 if ( target.classList.contains( 'frm_verify' ) ) {
1354 return false;
1355 }
1356
1357 return true;
1358 }
1359
1360 function maybeClearCustomValidityMessage( event, field ) {
1361 let key,
1362 isInvalid = false;
1363
1364 if ( ! shouldUpdateValidityMessage( field ) ) {
1365 return;
1366 }
1367
1368 for ( key in field.validity ) {
1369 if ( 'customError' === key ) {
1370 continue;
1371 }
1372 if ( 'valid' !== key && field.validity[ key ] === true ) {
1373 isInvalid = true;
1374 break;
1375 }
1376 };
1377
1378 if ( ! isInvalid ) {
1379 field.setCustomValidity( '' );
1380 }
1381 }
1382
1383 function maybeShowNewTabFallbackMessage() {
1384 let messageEl;
1385
1386 if ( ! window.frmShowNewTabFallback ) {
1387 return;
1388 }
1389
1390 messageEl = document.querySelector( '#frm_form_' + frmShowNewTabFallback.formId + '_container .frm_message' );
1391 if ( ! messageEl ) {
1392 return;
1393 }
1394
1395 messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1396 }
1397
1398 function setCustomValidityMessage() {
1399 let forms, length, index;
1400
1401 forms = document.getElementsByClassName( 'frm-show-form' );
1402 length = forms.length;
1403
1404 for ( index = 0; index < length; ++index ) {
1405 forms[ index ].addEventListener(
1406 'invalid',
1407 function( event ) {
1408 const target = event.target;
1409
1410 if ( shouldUpdateValidityMessage( target ) ) {
1411 target.setCustomValidity( target.dataset.invmsg );
1412 }
1413 },
1414 true
1415 );
1416 }
1417 }
1418
1419 function enableSubmitButtonOnBackButtonPress() {
1420 window.addEventListener( 'pageshow', function( event ) {
1421 if ( event.persisted ) {
1422 document.querySelectorAll( '.frm_loading_form' ).forEach(
1423 function( form ) {
1424 enableSubmitButton( jQuery( form ) );
1425 }
1426 );
1427 removeSubmitLoading();
1428 }
1429 });
1430 }
1431
1432 /**
1433 * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
1434 */
1435 function destroyhCaptcha() {
1436 if ( ! window.hasOwnProperty( 'hcaptcha' ) || ! document.querySelector( '.frm-show-form .h-captcha' ) ) {
1437 return;
1438 }
1439 window.hcaptcha = null;
1440 }
1441
1442 return {
1443 init: function() {
1444 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1445 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1446
1447 jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
1448 if ( jQuery( this ).val() === '' ) {
1449 jQuery( this ).trigger( 'blur' );
1450 }
1451 });
1452
1453 jQuery( document ).on( 'focus', '.frm_toggle_default', clearDefault );
1454 jQuery( document ).on( 'blur', '.frm_toggle_default', replaceDefault );
1455 jQuery( '.frm_toggle_default' ).trigger( 'blur' );
1456
1457 if ( frm_js.include_resend_email ) { // eslint-disable-line camelcase
1458 jQuery( document.getElementById( 'frm_resend_email' ) ).on( 'click', resendEmail );
1459 }
1460
1461 jQuery( document ).on( 'change', '.frm-show-form input[name^="item_meta"], .frm-show-form select[name^="item_meta"], .frm-show-form textarea[name^="item_meta"]', frmFrontForm.fieldValueChanged );
1462
1463 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1464 maybeMakeHoneypotFieldsUntabbable();
1465
1466 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1467 jQuery( 'a[data-frmtoggle]' ).on( 'click', toggleDiv );
1468
1469 checkForErrorsAndMaybeSetFocus();
1470
1471 // Focus on the first sub field when clicking to the primary label of combo field.
1472 changeFocusWhenClickComboFieldLabel();
1473
1474 initFloatingLabels();
1475 maybeShowNewTabFallbackMessage();
1476
1477 jQuery( document ).on( 'frmAfterAddRow', setCustomValidityMessage );
1478 setCustomValidityMessage();
1479 jQuery( document ).on( 'frmFieldChanged', maybeClearCustomValidityMessage );
1480
1481 setSelectPlaceholderColor();
1482
1483 // Elementor popup show event. Fix Elementor Popup && FF Captcha field conflicts
1484 jQuery( document ).on( 'elementor/popup/show', frmRecaptcha );
1485
1486 enableSubmitButtonOnBackButtonPress();
1487 jQuery( document ).on(
1488 'frmPageChanged',
1489 destroyhCaptcha
1490 );
1491 },
1492
1493 getFieldId: function( field, fullID ) {
1494 return getFieldId( field, fullID );
1495 },
1496
1497 renderCaptcha: function( captcha, captchaSelector ) {
1498 let formID, captchaID,
1499 size = captcha.getAttribute( 'data-size' ),
1500 rendered = captcha.getAttribute( 'data-rid' ) !== null,
1501 params = {
1502 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1503 'size': size,
1504 'theme': captcha.getAttribute( 'data-theme' )
1505 },
1506 activeCaptcha = getSelectedCaptcha( captchaSelector ),
1507 captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1508
1509 if ( rendered ) {
1510 return;
1511 }
1512
1513 if ( size === 'invisible' ) {
1514 formID = jQuery( captcha ).closest( 'form' ).find( 'input[name="form_id"]' ).val();
1515 jQuery( captcha ).closest( '.frm_form_field .frm_primary_label' ).hide();
1516 params.callback = function( token ) {
1517 frmFrontForm.afterRecaptcha( token, formID );
1518 };
1519 }
1520
1521
1522 captchaID = activeCaptcha.render( captchaContainer, params );
1523
1524 captcha.setAttribute( 'data-rid', captchaID );
1525 },
1526
1527 afterSingleRecaptcha: function() {
1528 const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1529 frmFrontForm.submitFormNow( object );
1530 },
1531
1532 afterRecaptcha: function( token, formID ) {
1533 const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1534 frmFrontForm.submitFormNow( object );
1535 },
1536
1537 submitForm: function( e ) {
1538 frmFrontForm.submitFormManual( e, this );
1539 },
1540
1541 submitFormManual: function( e, object ) {
1542 let isPro, errors,
1543 invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1544 classList = object.className.trim().split( /\s+/gi );
1545
1546 if ( classList && invisibleRecaptcha.length < 1 ) {
1547 isPro = classList.indexOf( 'frm_pro_form' ) > -1;
1548 if ( ! isPro ) {
1549 return;
1550 }
1551 }
1552
1553 if ( jQuery( 'body' ).hasClass( 'wp-admin' ) && jQuery( object ).closest( '.frmapi-form' ).length < 1 ) {
1554 return;
1555 }
1556
1557 e.preventDefault();
1558
1559 if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
1560 return;
1561 }
1562
1563 if ( invisibleRecaptcha.length ) {
1564 showLoadingIndicator( jQuery( object ) );
1565 executeInvisibleRecaptcha( invisibleRecaptcha );
1566 } else {
1567
1568 errors = frmFrontForm.validateFormSubmit( object );
1569
1570 if ( Object.keys( errors ).length === 0 ) {
1571 showSubmitLoading( jQuery( object ) );
1572
1573 frmFrontForm.submitFormNow( object, classList );
1574 }
1575 }
1576 },
1577
1578 submitFormNow: function( object ) {
1579 let hasFileFields, antispamInput,
1580 classList = object.className.trim().split( /\s+/gi );
1581
1582 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
1583 // include the antispam token on form submit.
1584 antispamInput = document.createElement( 'input' );
1585 antispamInput.type = 'hidden';
1586 antispamInput.name = 'antispam_token';
1587 antispamInput.value = object.getAttribute( 'data-token' );
1588 object.appendChild( antispamInput );
1589 }
1590
1591 if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1592 hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1593 return !! this.value;
1594 }).length;
1595 if ( hasFileFields < 1 ) {
1596 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1597 frmFrontForm.checkFormErrors( object, action );
1598 } else {
1599 object.submit();
1600 }
1601 } else {
1602 object.submit();
1603 }
1604 },
1605
1606 validateFormSubmit: function( object ) {
1607 if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1608 tinyMCE.triggerSave();
1609 }
1610
1611 jsErrors = [];
1612
1613 if ( shouldJSValidate( object ) ) {
1614 frmFrontForm.getAjaxFormErrors( object );
1615
1616 if ( Object.keys( jsErrors ).length ) {
1617 frmFrontForm.addAjaxFormErrors( object );
1618 }
1619 }
1620
1621 return jsErrors;
1622 },
1623
1624 getAjaxFormErrors: function( object ) {
1625 let customErrors, key;
1626
1627 jsErrors = validateForm( object );
1628 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1629 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1630 customErrors = frmThemeOverride_jsErrors( action, object );
1631 if ( Object.keys( customErrors ).length ) {
1632 for ( key in customErrors ) {
1633 jsErrors[ key ] = customErrors[ key ];
1634 }
1635 }
1636 }
1637
1638 return jsErrors;
1639 },
1640
1641 addAjaxFormErrors: function( object ) {
1642 let key, $fieldCont;
1643 removeAllErrors();
1644
1645 for ( key in jsErrors ) {
1646 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
1647
1648 if ( $fieldCont.length ) {
1649 addFieldError( $fieldCont, key, jsErrors );
1650 } else {
1651 // we are unable to show the error, so remove it
1652 delete jsErrors[ key ];
1653 }
1654 }
1655
1656 scrollToFirstField( object );
1657 checkForErrorsAndMaybeSetFocus();
1658 },
1659
1660 checkFormErrors: function( object, action ) {
1661 getFormErrors( object, action );
1662 },
1663
1664 checkRequiredField: function( field, errors ) {
1665 return checkRequiredField( field, errors );
1666 },
1667
1668 showSubmitLoading: function( $object ) {
1669 showSubmitLoading( $object );
1670 },
1671
1672 removeSubmitLoading: function( $object, enable, processesRunning ) {
1673 removeSubmitLoading( $object, enable, processesRunning );
1674 },
1675
1676 scrollToID: function( id ) {
1677 const object = jQuery( document.getElementById( id ) );
1678 frmFrontForm.scrollMsg( object, false );
1679 },
1680
1681 scrollMsg: function( id, object, animate ) {
1682 let newPos, m, b, screenTop, screenBottom,
1683 scrollObj = '';
1684 if ( typeof object === 'undefined' ) {
1685 scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
1686 if ( scrollObj.length < 1 ) {
1687 return;
1688 }
1689 } else if ( typeof id === 'string' ) {
1690 scrollObj = jQuery( object ).find( '#frm_field_' + id + '_container' );
1691 } else {
1692 scrollObj = id;
1693 }
1694
1695 jQuery( scrollObj ).trigger( 'focus' );
1696 newPos = scrollObj.offset().top;
1697 if ( ! newPos || frm_js.offset === '-1' ) { // eslint-disable-line camelcase
1698 return;
1699 }
1700 newPos = newPos - frm_js.offset; // eslint-disable-line camelcase
1701
1702 m = jQuery( 'html' ).css( 'margin-top' );
1703 b = jQuery( 'body' ).css( 'margin-top' );
1704 if ( m || b ) {
1705 newPos = newPos - parseInt( m ) - parseInt( b );
1706 }
1707
1708 if ( newPos && window.innerHeight ) {
1709 screenTop = document.documentElement.scrollTop || document.body.scrollTop;
1710 screenBottom = screenTop + window.innerHeight;
1711
1712 if ( newPos > screenBottom || newPos < screenTop ) {
1713 // Not in view
1714 if ( typeof animate === 'undefined' ) {
1715 jQuery( window ).scrollTop( newPos );
1716 } else {
1717 jQuery( 'html,body' ).animate({ scrollTop: newPos }, 500 );
1718 }
1719 return false;
1720 }
1721 }
1722 },
1723
1724 fieldValueChanged: function( e ) {
1725 /*jshint validthis:true */
1726
1727 const fieldId = frmFrontForm.getFieldId( this, false );
1728 if ( ! fieldId || typeof fieldId === 'undefined' ) {
1729 return;
1730 }
1731
1732 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
1733 return;
1734 }
1735
1736 jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ]);
1737
1738 if ( e.selfTriggered !== true ) {
1739 maybeValidateChange( this );
1740 }
1741 },
1742
1743 savingDraft: function( object ) {
1744 console.warn( 'DEPRECATED: function frmFrontForm.savingDraft in v3.0 use frmProForm.savingDraft' );
1745 if ( typeof frmProForm !== 'undefined' ) {
1746 return frmProForm.savingDraft( object );
1747 }
1748 },
1749
1750 escapeHtml: function( text ) {
1751 return text
1752 .replace( /&/g, '&amp;' )
1753 .replace( /</g, '&lt;' )
1754 .replace( />/g, '&gt;' )
1755 .replace( /"/g, '&quot;' )
1756 .replace( /'/g, '&#039;' );
1757 },
1758
1759 invisible: function( classes ) {
1760 jQuery( classes ).css( 'visibility', 'hidden' );
1761 },
1762
1763 visible: function( classes ) {
1764 jQuery( classes ).css( 'visibility', 'visible' );
1765 },
1766
1767 triggerCustomEvent: triggerCustomEvent,
1768 documentOn
1769 };
1770 }
1771
1772 window.frmFrontForm = frmFrontFormJS();
1773
1774 jQuery( document ).ready( function() {
1775 frmFrontForm.init();
1776 });
1777
1778 function frmRecaptcha() {
1779 frmCaptcha( '.frm-g-recaptcha' );
1780 }
1781
1782 function frmTurnstile() {
1783 frmCaptcha( '.cf-turnstile' );
1784 }
1785
1786 function frmCaptcha( captchaSelector ) {
1787 let c;
1788 const captchas = document.querySelectorAll( captchaSelector );
1789 const cl = captchas.length;
1790 for ( c = 0; c < cl; c++ ) {
1791 frmFrontForm.renderCaptcha( captchas[c], captchaSelector );
1792 }
1793 }
1794
1795 function getSelectedCaptcha( captchaSelector ) {
1796 if ( captchaSelector === '.frm-g-recaptcha' ) {
1797 return grecaptcha;
1798 }
1799 if ( document.querySelector( '.cf-turnstile' ) ) {
1800 return turnstile;
1801 }
1802 return hcaptcha;
1803 }
1804
1805 function frmAfterRecaptcha( token ) {
1806 frmFrontForm.afterSingleRecaptcha( token );
1807 }
1808
1809 if ( frm_js.include_update_field ) { // eslint-disable-line camelcase
1810 window.frmUpdateField = function( entryId, fieldId, value, message, num ) {
1811 console.warn( 'DEPRECATED: function frmUpdateField please update to Formidable Pro v6.9.2' );
1812 jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).html( '<span class="frm-loading-img"></span>' );
1813 jQuery.ajax({
1814 type: 'POST',
1815 url: frm_js.ajax_url, // eslint-disable-line camelcase
1816 data: {
1817 action: 'frm_entries_update_field_ajax',
1818 entry_id: entryId,
1819 field_id: fieldId,
1820 value: value,
1821 nonce: frm_js.nonce // eslint-disable-line camelcase
1822 },
1823 success: function() {
1824 if ( message.replace( /^\s+|\s+$/g, '' ) === '' ) {
1825 jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).fadeOut( 'slow' );
1826 } else {
1827 jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).replaceWith( message );
1828 }
1829 }
1830 });
1831 };
1832 }
1833