PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.11.2
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.11.2
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / js / formidable.js

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

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