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

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