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

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