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

1,775 lines 49.1 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 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 /**
623 * Check if JS validation should happen.
624 *
625 * @param {HTMLElement|Object} object Form object.
626 * @return {boolean} True if validation is enabled and we are not saving a draft or going to a previous page.
627 */
628 function shouldJSValidate( object ) {
629 if ( 'function' === typeof object.get ) {
630 // Get the HTMLElement from a jQuery object.
631 object = object.get( 0 );
632 }
633 let validate = hasClass( object, 'frm_js_validate' );
634 if ( validate && typeof frmProForm !== 'undefined' && ( frmProForm.savingDraft( object ) || frmProForm.goingToPreviousPage( object ) ) ) {
635 validate = false;
636 }
637
638 return validate;
639 }
640
641 function getFormErrors( object, action ) {
642 let fieldset, data, success, error, shouldTriggerEvent;
643
644 if ( typeof action === 'undefined' ) {
645 jQuery( object ).find( 'input[name="frm_action"]' ).val();
646 }
647
648 fieldset = jQuery( object ).find( '.frm_form_field' );
649 fieldset.addClass( 'frm_doing_ajax' );
650
651 data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
652 shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
653
654 success = function( response ) {
655 let defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
656 $fieldCont, key, inCollapsedSection, frmTrigger, newTab;
657
658 defaultResponse = {
659 content: '',
660 errors: {},
661 pass: false
662 };
663
664 if ( response === null ) {
665 response = defaultResponse;
666 }
667
668 response = response.replace( /^\s+|\s+$/g, '' );
669 if ( response.indexOf( '{' ) === 0 ) {
670 response = JSON.parse( response );
671 } else {
672 response = defaultResponse;
673 }
674
675 if ( typeof response.redirect !== 'undefined' ) {
676 if ( shouldTriggerEvent ) {
677 triggerCustomEvent( object, 'frmSubmitEvent' );
678 return;
679 }
680
681 jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ]);
682
683 if ( ! response.openInNewTab ) {
684 // We return here because we're redirecting there is no need to update content.
685 window.location = response.redirect;
686 return;
687 }
688
689 // We don't return here because we're opening in a new tab, the old tab will still update.
690 newTab = window.open( response.redirect, '_blank' );
691 if ( ! newTab && response.fallbackMsg && response.content ) {
692 response.content = response.content.trim().replace( /(<\/div><\/div>)$/, ' ' + response.fallbackMsg + '</div></div>' );
693 }
694 }
695
696 if ( response.content !== '' ) {
697 // the form or success message was returned
698
699 if ( shouldTriggerEvent ) {
700 triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content });
701 return;
702 }
703
704 removeSubmitLoading( jQuery( object ) );
705 if ( frm_js.offset != -1 ) { // eslint-disable-line camelcase
706 frmFrontForm.scrollMsg( jQuery( object ), false );
707 }
708
709 formID = jQuery( object ).find( 'input[name="form_id"]' ).val();
710 response.content = response.content.replace( / frm_pro_form /g, ' frm_pro_form frm_no_hide ' );
711 replaceContent = jQuery( object ).closest( '.frm_forms' );
712 removeAddedScripts( replaceContent, formID );
713 delay = maybeSlideOut( replaceContent, response.content );
714
715 setTimeout(
716 function() {
717 let container, input, previousInput;
718
719 afterFormSubmittedBeforeReplace( object, response );
720
721 replaceContent.replaceWith( response.content );
722
723 addUrlParam( response );
724
725 if ( typeof frmThemeOverride_frmAfterSubmit === 'function' ) { // eslint-disable-line camelcase
726 pageOrder = jQuery( 'input[name="frm_page_order_' + formID + '"]' ).val();
727 formReturned = jQuery( response.content ).find( 'input[name="form_id"]' ).val();
728 frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
729 }
730
731 if ( typeof response.recaptcha !== 'undefined' ) {
732 container = jQuery( '#frm_form_' + formID + '_container' ).find( '.frm_fields_container' );
733 input = '<input type="hidden" name="recaptcha_checked" value="' + response.recaptcha + '">';
734 previousInput = container.find( 'input[name="recaptcha_checked"]' );
735
736 if ( previousInput.length ) {
737 previousInput.replaceWith( input );
738 } else {
739 container.append( input );
740 }
741 }
742
743 afterFormSubmitted( object, response );
744 },
745 delay
746 );
747 } else if ( Object.keys( response.errors ).length ) {
748 // errors were returned
749 removeSubmitLoading( jQuery( object ), 'enable' );
750
751 //show errors
752 contSubmit = true;
753 removeAllErrors();
754
755 $fieldCont = null;
756
757 for ( key in response.errors ) {
758 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
759
760 if ( $fieldCont.length ) {
761 if ( ! $fieldCont.is( ':visible' ) ) {
762 inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' );
763 if ( inCollapsedSection.length ) {
764 frmTrigger = inCollapsedSection.prev();
765 if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) {
766 // If the frmTrigger object is the section description, check to see if the previous element is the trigger
767 frmTrigger = frmTrigger.prev( '.frm_trigger' );
768 }
769 frmTrigger.trigger( 'click' );
770 }
771 }
772
773 if ( $fieldCont.is( ':visible' ) ) {
774 addFieldError( $fieldCont, key, response.errors );
775 contSubmit = false;
776 }
777 }
778 }
779
780 jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).each( function() {
781 const $recaptcha = jQuery( this ),
782 recaptchaID = $recaptcha.data( 'rid' );
783
784 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
785 if ( recaptchaID ) {
786 grecaptcha.reset( recaptchaID );
787 } else {
788 grecaptcha.reset();
789 }
790 }
791 if ( typeof hcaptcha !== 'undefined' && hcaptcha ) {
792 hcaptcha.reset();
793 }
794 });
795
796 jQuery( document ).trigger( 'frmFormErrors', [ object, response ]);
797
798 fieldset.removeClass( 'frm_doing_ajax' );
799 scrollToFirstField( object );
800
801 if ( contSubmit ) {
802 object.submit();
803 } else {
804 object.insertAdjacentHTML( 'afterbegin', response.error_message );
805 checkForErrorsAndMaybeSetFocus();
806 }
807 } else {
808 // there may have been a plugin conflict, or the form is not set to submit with ajax
809
810 showFileLoading( object );
811
812 object.submit();
813 }
814 };
815
816 error = function() {
817 jQuery( object ).find( 'input[type="submit"], input[type="button"]' ).prop( 'disabled', false );
818 object.submit();
819 };
820
821 postToAjaxUrl( object, data, success, error );
822 }
823
824 function postToAjaxUrl( form, data, success, error ) {
825 let ajaxUrl, action, ajaxParams;
826
827 ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
828 action = form.getAttribute( 'action' );
829
830 if ( 'string' === typeof action && -1 !== action.indexOf( '?action=frm_forms_preview' ) ) {
831 ajaxUrl = action.split( '?action=frm_forms_preview' )[0];
832 }
833
834 ajaxParams = {
835 type: 'POST',
836 url: ajaxUrl,
837 data: data,
838 success: success
839 };
840
841 if ( 'function' === typeof error ) {
842 ajaxParams.error = error;
843 }
844
845 jQuery.ajax( ajaxParams );
846 }
847
848 function afterFormSubmitted( object, response ) {
849 const formCompleted = jQuery( response.content ).find( '.frm_message' );
850 if ( formCompleted.length ) {
851 jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
852 } else {
853 jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
854 }
855 }
856
857 /**
858 * Trigger an event before the form is replaced with a success message.
859 *
860 * @since 6.9
861 *
862 * @param {HTMLElement} object The form.
863 * @param {Object} response The response from submitting the form with AJAX.
864 * @return {void}
865 */
866 function afterFormSubmittedBeforeReplace( object, response ) {
867 const formCompleted = jQuery( response.content ).find( '.frm_message' );
868 if ( formCompleted.length ) {
869 triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response });
870 }
871 }
872
873 function removeAddedScripts( formContainer, formID ) {
874 const endReplace = jQuery( '.frm_end_ajax_' + formID );
875 if ( endReplace.length ) {
876 formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
877 endReplace.remove();
878 }
879 }
880
881 function maybeSlideOut( oldContent, newContent ) {
882 let c,
883 newClass = 'frm_slideout';
884 if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
885 c = oldContent.children();
886 if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
887 newClass += ' frm_going_back';
888 }
889 c.removeClass( 'frm_going_back' );
890 c.addClass( newClass );
891 return 300;
892 }
893 return 0;
894 }
895
896 function addUrlParam( response ) {
897 let url;
898 if ( history.pushState && typeof response.page !== 'undefined' ) {
899 url = addQueryVar( 'frm_page', response.page );
900 window.history.pushState({ 'html': response.html }, '', '?' + url );
901 }
902 }
903
904 function addQueryVar( key, value ) {
905 let kvp, i, x;
906
907 key = encodeURI( key );
908 value = encodeURI( value );
909
910 kvp = document.location.search.substr( 1 ).split( '&' );
911
912 i = kvp.length;
913 while ( i-- ) {
914 x = kvp[i].split( '=' );
915
916 if ( x[0] == key ) {
917 x[1] = value;
918 kvp[i] = x.join( '=' );
919 break;
920 }
921 }
922
923 if ( i < 0 ) {
924 kvp[ kvp.length ] = [ key, value ].join( '=' );
925 }
926
927 return kvp.join( '&' );
928 }
929
930 function addFieldError( $fieldCont, key, jsErrors ) {
931 let input, id, describedBy, roleString;
932 if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
933 $fieldCont.addClass( 'frm_blank_field' );
934 input = $fieldCont.find( 'input, select, textarea' );
935 id = getErrorElementId( key, input.get( 0 ) );
936
937 describedBy = input.attr( 'aria-describedby' );
938
939 if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
940 frmThemeOverride_frmPlaceError( key, jsErrors );
941 } else {
942 if ( -1 !== jsErrors[key].indexOf( '<div' ) ) {
943 $fieldCont.append(
944 jsErrors[key]
945 );
946 } else {
947 roleString = frm_js.include_alert_role ? 'role="alert"' : ''; // eslint-disable-line camelcase
948 $fieldCont.append( '<div class="frm_error" ' + roleString + ' id="' + id + '">' + jsErrors[key] + '</div>' );
949 }
950
951 if ( typeof describedBy === 'undefined' ) {
952 describedBy = id;
953 } else if ( describedBy.indexOf( id ) === -1 && describedBy.indexOf( 'frm_error_field_' ) === -1 ) {
954 if ( input.data( 'error-first' ) === 0 ) {
955 describedBy = describedBy + ' ' + id;
956 } else {
957 describedBy = id + ' ' + describedBy;
958 }
959 }
960
961 input.attr( 'aria-describedby', describedBy );
962 }
963 input.attr( 'aria-invalid', true );
964
965 jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
966 }
967 }
968
969 /**
970 * Get the ID to use for an error element added when submitting with AJAX.
971 *
972 * @param {string} key
973 * @param {HTMLElement} input
974 * @return {string} The ID to use for the error element.
975 */
976 function getErrorElementId( key, input ) {
977 if ( isNaN( key ) || ! input.id ) {
978 // If key isn't a number, assume it's already in the right format.
979 return 'frm_error_field_' + key;
980 }
981 return 'frm_error_' + input.id;
982 }
983
984 /**
985 * Removes errors before validating with JS.
986 * This prevents issues with stale errors that has since been fixed.
987 *
988 * @param {Object} $fieldCont jQuery object.
989 * @return {void}
990 */
991 function removeFieldError( $fieldCont ) {
992 const errorMessage = $fieldCont.find( '.frm_error' );
993 const errorId = errorMessage.attr( 'id' );
994 const input = $fieldCont.find( 'input, select, textarea' );
995 let describedBy = input.attr( 'aria-describedby' );
996
997 $fieldCont.get( 0 ).classList.remove( 'frm_blank_field', 'has-error' );
998
999 errorMessage.remove();
1000 input.attr( 'aria-invalid', false );
1001 input.removeAttr( 'aria-describedby' );
1002
1003 if ( typeof describedBy !== 'undefined' ) {
1004 describedBy = describedBy.replace( errorId, '' );
1005 input.attr( 'aria-describedby', describedBy );
1006 }
1007 }
1008
1009 function removeAllErrors() {
1010 jQuery( '.form-field' ).removeClass( 'frm_blank_field has-error' );
1011 jQuery( '.form-field .frm_error' ).replaceWith( '' );
1012 jQuery( '.frm_error_style' ).remove();
1013 }
1014
1015 /**
1016 * @param {HTMLElement|Object} object Form object.
1017 * @return {void}
1018 */
1019 function scrollToFirstField( object ) {
1020 if ( 'function' === typeof object.get ) {
1021 // Get the HTMLElement from a jQuery object.
1022 object = object.get( 0 );
1023 }
1024 const field = object.querySelector( '.frm_blank_field' );
1025 if ( field ) {
1026 frmFrontForm.scrollMsg( jQuery( field ), object, true );
1027 }
1028 }
1029
1030 function showSubmitLoading( $object ) {
1031 showLoadingIndicator( $object );
1032 disableSubmitButton( $object );
1033 disableSaveDraft( $object );
1034 }
1035
1036 function showLoadingIndicator( $object ) {
1037 if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) {
1038 addLoadingClass( $object );
1039 $object.trigger( 'frmStartFormLoading' );
1040 }
1041 }
1042
1043 function addLoadingClass( $object ) {
1044 const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1045
1046 $object.addClass( loadingClass );
1047 }
1048
1049 function isGoingToPrevPage( $object ) {
1050 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
1051 }
1052
1053 function removeSubmitLoading( $object, enable, processesRunning ) {
1054 let loadingForm;
1055
1056 if ( processesRunning > 0 ) {
1057 return;
1058 }
1059
1060 loadingForm = jQuery( '.frm_loading_form' );
1061 loadingForm.removeClass( 'frm_loading_form' );
1062 loadingForm.removeClass( 'frm_loading_prev' );
1063
1064 loadingForm.trigger( 'frmEndFormLoading' );
1065
1066 if ( enable === 'enable' ) {
1067 enableSubmitButton( loadingForm );
1068 enableSaveDraft( loadingForm );
1069 }
1070 }
1071
1072 function showFileLoading( object ) {
1073 let fileval,
1074 loading = document.getElementById( 'frm_loading' );
1075 if ( loading !== null ) {
1076 fileval = jQuery( object ).find( 'input[type=file]' ).val();
1077 if ( typeof fileval !== 'undefined' && fileval !== '' ) {
1078 setTimeout( function() {
1079 jQuery( loading ).fadeIn( 'slow' );
1080 }, 2000 );
1081 }
1082 }
1083 }
1084
1085 /**********************************************
1086 * General Helpers
1087 *********************************************/
1088
1089 function confirmClick() {
1090 /*jshint validthis:true */
1091 const message = jQuery( this ).data( 'frmconfirm' );
1092 return confirm( message );
1093 }
1094
1095 /**
1096 * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
1097 * If this is a match, the User is autofilling the input on a Webkit browser.
1098 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1099 */
1100 function onHoneypotFieldChange() {
1101 const css = jQuery( this ).css( 'box-shadow' );
1102 if ( css.match( /inset/ ) ) {
1103 this.parentNode.removeChild( this );
1104 }
1105 }
1106
1107 function maybeMakeHoneypotFieldsUntabbable() {
1108 document.addEventListener( 'keydown', handleKeyUp );
1109
1110 function handleKeyUp( event ) {
1111 let code;
1112
1113 if ( 'undefined' !== typeof event.key ) {
1114 code = event.key;
1115 } else if ( 'undefined' !== typeof event.keyCode && 9 === event.keyCode ) {
1116 code = 'Tab';
1117 }
1118
1119 if ( 'Tab' === code ) {
1120 makeHoneypotFieldsUntabbable();
1121 document.removeEventListener( 'keydown', handleKeyUp );
1122 }
1123 }
1124
1125 function makeHoneypotFieldsUntabbable() {
1126 document.querySelectorAll( '.frm_verify' ).forEach(
1127 function( input ) {
1128 if ( input.id && 0 === input.id.indexOf( 'frm_email_' ) ) {
1129 input.setAttribute( 'tabindex', -1 );
1130 }
1131 }
1132 );
1133 }
1134 }
1135
1136 /**
1137 * Focus on the first sub field when clicking to the primary label of combo field.
1138 *
1139 * @since 4.10.02
1140 */
1141 function changeFocusWhenClickComboFieldLabel() {
1142 let label;
1143
1144 const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1145 comboInputsContainer.forEach( function( inputsContainer ) {
1146 if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1147 return;
1148 }
1149
1150 label = inputsContainer.closest( '.frm_form_field' ).querySelector( '.frm_primary_label' );
1151 if ( ! label ) {
1152 return;
1153 }
1154
1155 label.addEventListener( 'click', function() {
1156 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1157 });
1158 });
1159 }
1160
1161 function checkForErrorsAndMaybeSetFocus() {
1162 let errors, element, timeoutCallback;
1163
1164 if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1165 return;
1166 }
1167
1168 errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1169 if ( ! errors.length ) {
1170 return;
1171 }
1172
1173 element = errors[0];
1174 do {
1175 element = element.previousSibling;
1176 if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1177 element.focus();
1178 break;
1179 }
1180
1181 if ( 'undefined' !== typeof element.classList ) {
1182 if ( element.classList.contains( 'html-active' ) ) {
1183 timeoutCallback = function() {
1184 const textarea = element.querySelector( 'textarea' );
1185 if ( null !== textarea ) {
1186 textarea.focus();
1187 }
1188 };
1189 } else if ( element.classList.contains( 'tmce-active' ) ) {
1190 timeoutCallback = function() {
1191 tinyMCE.activeEditor.focus();
1192 };
1193 }
1194
1195 if ( 'function' === typeof timeoutCallback ) {
1196 setTimeout( timeoutCallback, 0 );
1197 break;
1198 }
1199 }
1200 } while ( element.previousSibling );
1201 }
1202
1203 /**
1204 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
1205 *
1206 * @since 5.4
1207 *
1208 * @param {string} event Event name.
1209 * @param {string} selector Selector.
1210 * @param {Function} handler Handler.
1211 * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1212 */
1213 function documentOn( event, selector, handler, options ) {
1214 if ( 'undefined' === typeof options ) {
1215 options = false;
1216 }
1217
1218 document.addEventListener( event, function( e ) {
1219 let target;
1220
1221 // loop parent nodes from the target to the delegation node.
1222 for ( target = e.target; target && target != this; target = target.parentNode ) {
1223 if ( target && target.matches && target.matches( selector ) ) {
1224 handler.call( target, e );
1225 break;
1226 }
1227 }
1228 }, options );
1229 }
1230
1231 function initFloatingLabels() {
1232 let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1233
1234 selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1235 floatClass = 'frm_label_float_top';
1236
1237 checkFloatLabel = function( input ) {
1238 let container, shouldFloatTop, firstOpt;
1239
1240 container = input.closest( '.frm_inside_container' );
1241 if ( ! container ) {
1242 return;
1243 }
1244
1245 shouldFloatTop = input.value || document.activeElement === input;
1246
1247 container.classList.toggle( floatClass, shouldFloatTop );
1248
1249 if ( 'SELECT' === input.tagName ) {
1250 firstOpt = input.querySelector( 'option:first-child' );
1251
1252 if ( shouldFloatTop ) {
1253 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1254 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1255 firstOpt.removeAttribute( 'data-label' );
1256 }
1257 } else if ( firstOpt.textContent ) {
1258 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1259 firstOpt.textContent = '';
1260 }
1261 }
1262 };
1263
1264 checkDropdownLabel = function() {
1265 document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1266 const firstOpt = input.querySelector( 'option:first-child' );
1267
1268 if ( firstOpt.textContent ) {
1269 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1270 firstOpt.textContent = '';
1271 }
1272 });
1273 };
1274
1275 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1276 documentOn(
1277 eventName,
1278 selector,
1279 function( event ) {
1280 checkFloatLabel( event.target );
1281 },
1282 true
1283 );
1284 });
1285
1286 jQuery( document ).on( 'change', selector, function( event ) {
1287 checkFloatLabel( event.target );
1288 });
1289
1290 runOnLoad = function( firstLoad ) {
1291 if ( firstLoad && document.activeElement && -1 !== [ 'INPUT', 'SELECT', 'TEXTAREA' ].indexOf( document.activeElement.tagName ) ) {
1292 checkFloatLabel( document.activeElement );
1293 } else if ( firstLoad ) {
1294 document.querySelectorAll( '.frm_inside_container' ).forEach(
1295 function( container ) {
1296 const input = container.querySelector( 'input, select, textarea' );
1297 if ( input && '' !== input.value ) {
1298 checkFloatLabel( input );
1299 }
1300 }
1301 );
1302 }
1303
1304 checkDropdownLabel();
1305 };
1306
1307 runOnLoad( true );
1308
1309 jQuery( document ).on( 'frmPageChanged', function( event ) {
1310 runOnLoad();
1311 });
1312
1313 document.addEventListener( 'frm_after_start_over', function( event ) {
1314 runOnLoad();
1315 });
1316 }
1317
1318 function shouldUpdateValidityMessage( target ) {
1319 if ( 'INPUT' !== target.nodeName ) {
1320 return false;
1321 }
1322
1323 if ( ! target.dataset.invmsg ) {
1324 return false;
1325 }
1326
1327 if ( 'text' !== target.getAttribute( 'type' ) ) {
1328 return false;
1329 }
1330
1331 if ( target.classList.contains( 'frm_verify' ) ) {
1332 return false;
1333 }
1334
1335 return true;
1336 }
1337
1338 function maybeClearCustomValidityMessage( event, field ) {
1339 let key,
1340 isInvalid = false;
1341
1342 if ( ! shouldUpdateValidityMessage( field ) ) {
1343 return;
1344 }
1345
1346 for ( key in field.validity ) {
1347 if ( 'customError' === key ) {
1348 continue;
1349 }
1350 if ( 'valid' !== key && field.validity[ key ] === true ) {
1351 isInvalid = true;
1352 break;
1353 }
1354 };
1355
1356 if ( ! isInvalid ) {
1357 field.setCustomValidity( '' );
1358 }
1359 }
1360
1361 function maybeShowNewTabFallbackMessage() {
1362 let messageEl;
1363
1364 if ( ! window.frmShowNewTabFallback ) {
1365 return;
1366 }
1367
1368 messageEl = document.querySelector( '#frm_form_' + frmShowNewTabFallback.formId + '_container .frm_message' );
1369 if ( ! messageEl ) {
1370 return;
1371 }
1372
1373 messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1374 }
1375
1376 function setCustomValidityMessage() {
1377 let forms, length, index;
1378
1379 forms = document.getElementsByClassName( 'frm-show-form' );
1380 length = forms.length;
1381
1382 for ( index = 0; index < length; ++index ) {
1383 forms[ index ].addEventListener(
1384 'invalid',
1385 function( event ) {
1386 const target = event.target;
1387
1388 if ( shouldUpdateValidityMessage( target ) ) {
1389 target.setCustomValidity( target.dataset.invmsg );
1390 }
1391 },
1392 true
1393 );
1394 }
1395 }
1396
1397 function enableSubmitButtonOnBackButtonPress() {
1398 window.addEventListener( 'pageshow', function( event ) {
1399 if ( event.persisted ) {
1400 document.querySelectorAll( '.frm_loading_form' ).forEach(
1401 function( form ) {
1402 enableSubmitButton( jQuery( form ) );
1403 }
1404 );
1405 removeSubmitLoading();
1406 }
1407 });
1408 }
1409
1410 /**
1411 * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
1412 */
1413 function destroyhCaptcha() {
1414 if ( ! window.hasOwnProperty( 'hcaptcha' ) || ! document.querySelector( '.frm-show-form .h-captcha' ) ) {
1415 return;
1416 }
1417 window.hcaptcha = null;
1418 }
1419
1420 return {
1421 init: function() {
1422 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1423 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1424
1425 jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
1426 if ( jQuery( this ).val() === '' ) {
1427 jQuery( this ).trigger( 'blur' );
1428 }
1429 });
1430
1431 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 );
1432
1433 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1434 maybeMakeHoneypotFieldsUntabbable();
1435
1436 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1437
1438 checkForErrorsAndMaybeSetFocus();
1439
1440 // Focus on the first sub field when clicking to the primary label of combo field.
1441 changeFocusWhenClickComboFieldLabel();
1442
1443 initFloatingLabels();
1444 maybeShowNewTabFallbackMessage();
1445
1446 jQuery( document ).on( 'frmAfterAddRow', setCustomValidityMessage );
1447 setCustomValidityMessage();
1448 jQuery( document ).on( 'frmFieldChanged', maybeClearCustomValidityMessage );
1449
1450 setSelectPlaceholderColor();
1451
1452 // Elementor popup show event. Fix Elementor Popup && FF Captcha field conflicts
1453 jQuery( document ).on( 'elementor/popup/show', frmRecaptcha );
1454
1455 enableSubmitButtonOnBackButtonPress();
1456 jQuery( document ).on(
1457 'frmPageChanged',
1458 destroyhCaptcha
1459 );
1460 },
1461
1462 getFieldId: function( field, fullID ) {
1463 return getFieldId( field, fullID );
1464 },
1465
1466 renderCaptcha: function( captcha, captchaSelector ) {
1467 let formID, captchaID,
1468 size = captcha.getAttribute( 'data-size' ),
1469 rendered = captcha.getAttribute( 'data-rid' ) !== null,
1470 params = {
1471 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1472 'size': size,
1473 'theme': captcha.getAttribute( 'data-theme' )
1474 },
1475 activeCaptcha = getSelectedCaptcha( captchaSelector ),
1476 captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1477
1478 if ( rendered ) {
1479 return;
1480 }
1481
1482 if ( size === 'invisible' ) {
1483 formID = jQuery( captcha ).closest( 'form' ).find( 'input[name="form_id"]' ).val();
1484 jQuery( captcha ).closest( '.frm_form_field .frm_primary_label' ).hide();
1485 params.callback = function( token ) {
1486 frmFrontForm.afterRecaptcha( token, formID );
1487 };
1488 }
1489
1490
1491 captchaID = activeCaptcha.render( captchaContainer, params );
1492
1493 captcha.setAttribute( 'data-rid', captchaID );
1494 },
1495
1496 afterSingleRecaptcha: function() {
1497 const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1498 frmFrontForm.submitFormNow( object );
1499 },
1500
1501 afterRecaptcha: function( _, formID ) {
1502 const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1503 frmFrontForm.submitFormNow( object );
1504 },
1505
1506 submitForm: function( e ) {
1507 frmFrontForm.submitFormManual( e, this );
1508 },
1509
1510 submitFormManual: function( e, object ) {
1511 let isPro, errors,
1512 invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1513 classList = object.className.trim().split( /\s+/gi );
1514
1515 if ( classList && invisibleRecaptcha.length < 1 ) {
1516 isPro = classList.indexOf( 'frm_pro_form' ) > -1;
1517 if ( ! isPro ) {
1518 return;
1519 }
1520 }
1521
1522 if ( jQuery( 'body' ).hasClass( 'wp-admin' ) && jQuery( object ).closest( '.frmapi-form' ).length < 1 ) {
1523 return;
1524 }
1525
1526 e.preventDefault();
1527
1528 if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
1529 return;
1530 }
1531
1532 if ( invisibleRecaptcha.length ) {
1533 showLoadingIndicator( jQuery( object ) );
1534 executeInvisibleRecaptcha( invisibleRecaptcha );
1535 } else {
1536
1537 errors = frmFrontForm.validateFormSubmit( object );
1538
1539 if ( Object.keys( errors ).length === 0 ) {
1540 showSubmitLoading( jQuery( object ) );
1541
1542 frmFrontForm.submitFormNow( object, classList );
1543 }
1544 }
1545 },
1546
1547 submitFormNow: function( object ) {
1548 let hasFileFields, antispamInput,
1549 classList = object.className.trim().split( /\s+/gi );
1550
1551 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
1552 // include the antispam token on form submit.
1553 antispamInput = document.createElement( 'input' );
1554 antispamInput.type = 'hidden';
1555 antispamInput.name = 'antispam_token';
1556 antispamInput.value = object.getAttribute( 'data-token' );
1557 object.appendChild( antispamInput );
1558 }
1559
1560 if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1561 hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1562 return !! this.value;
1563 }).length;
1564 if ( hasFileFields < 1 ) {
1565 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1566 frmFrontForm.checkFormErrors( object, action );
1567 } else {
1568 object.submit();
1569 }
1570 } else {
1571 object.submit();
1572 }
1573 },
1574
1575 /**
1576 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1577 *
1578 * @return {Array} List of errors.
1579 */
1580 validateFormSubmit: function( object ) {
1581 if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1582 tinyMCE.triggerSave();
1583 }
1584
1585 jsErrors = [];
1586
1587 if ( shouldJSValidate( object ) ) {
1588 frmFrontForm.getAjaxFormErrors( object );
1589
1590 if ( Object.keys( jsErrors ).length ) {
1591 frmFrontForm.addAjaxFormErrors( object );
1592 }
1593 }
1594
1595 return jsErrors;
1596 },
1597
1598 getAjaxFormErrors: function( object ) {
1599 let customErrors, key;
1600
1601 jsErrors = validateForm( object );
1602 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1603 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1604 customErrors = frmThemeOverride_jsErrors( action, object );
1605 if ( Object.keys( customErrors ).length ) {
1606 for ( key in customErrors ) {
1607 jsErrors[ key ] = customErrors[ key ];
1608 }
1609 }
1610 }
1611
1612 return jsErrors;
1613 },
1614
1615 addAjaxFormErrors: function( object ) {
1616 let key, $fieldCont;
1617 removeAllErrors();
1618
1619 for ( key in jsErrors ) {
1620 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
1621
1622 if ( $fieldCont.length ) {
1623 addFieldError( $fieldCont, key, jsErrors );
1624 } else {
1625 // we are unable to show the error, so remove it
1626 delete jsErrors[ key ];
1627 }
1628 }
1629
1630 scrollToFirstField( object );
1631 checkForErrorsAndMaybeSetFocus();
1632 },
1633
1634 checkFormErrors: function( object, action ) {
1635 getFormErrors( object, action );
1636 },
1637
1638 checkRequiredField: function( field, errors ) {
1639 return checkRequiredField( field, errors );
1640 },
1641
1642 showSubmitLoading: function( $object ) {
1643 showSubmitLoading( $object );
1644 },
1645
1646 removeSubmitLoading: function( $object, enable, processesRunning ) {
1647 removeSubmitLoading( $object, enable, processesRunning );
1648 },
1649
1650 scrollToID: function( id ) {
1651 const object = jQuery( document.getElementById( id ) );
1652 frmFrontForm.scrollMsg( object, false );
1653 },
1654
1655 scrollMsg: function( id, object, animate ) {
1656 let newPos, m, b, screenTop, screenBottom,
1657 scrollObj = '';
1658 if ( typeof object === 'undefined' ) {
1659 scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
1660 if ( scrollObj.length < 1 ) {
1661 return;
1662 }
1663 } else if ( typeof id === 'string' ) {
1664 scrollObj = jQuery( object ).find( '#frm_field_' + id + '_container' );
1665 } else {
1666 scrollObj = id;
1667 }
1668
1669 jQuery( scrollObj ).trigger( 'focus' );
1670 newPos = scrollObj.offset().top;
1671 if ( ! newPos || frm_js.offset === '-1' ) { // eslint-disable-line camelcase
1672 return;
1673 }
1674 newPos = newPos - frm_js.offset; // eslint-disable-line camelcase
1675
1676 m = jQuery( 'html' ).css( 'margin-top' );
1677 b = jQuery( 'body' ).css( 'margin-top' );
1678 if ( m || b ) {
1679 newPos = newPos - parseInt( m ) - parseInt( b );
1680 }
1681
1682 if ( newPos && window.innerHeight ) {
1683 screenTop = document.documentElement.scrollTop || document.body.scrollTop;
1684 screenBottom = screenTop + window.innerHeight;
1685
1686 if ( newPos > screenBottom || newPos < screenTop ) {
1687 // Not in view
1688 if ( typeof animate === 'undefined' ) {
1689 jQuery( window ).scrollTop( newPos );
1690 } else {
1691 jQuery( 'html,body' ).animate({ scrollTop: newPos }, 500 );
1692 }
1693 return false;
1694 }
1695 }
1696 },
1697
1698 fieldValueChanged: function( e ) {
1699 /*jshint validthis:true */
1700
1701 const fieldId = frmFrontForm.getFieldId( this, false );
1702 if ( ! fieldId || typeof fieldId === 'undefined' ) {
1703 return;
1704 }
1705
1706 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
1707 return;
1708 }
1709
1710 jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ]);
1711
1712 if ( e.selfTriggered !== true ) {
1713 maybeValidateChange( this );
1714 }
1715 },
1716
1717 escapeHtml: function( text ) {
1718 return text
1719 .replace( /&/g, '&amp;' )
1720 .replace( /</g, '&lt;' )
1721 .replace( />/g, '&gt;' )
1722 .replace( /"/g, '&quot;' )
1723 .replace( /'/g, '&#039;' );
1724 },
1725
1726 invisible: function( classes ) {
1727 jQuery( classes ).css( 'visibility', 'hidden' );
1728 },
1729
1730 visible: function( classes ) {
1731 jQuery( classes ).css( 'visibility', 'visible' );
1732 },
1733
1734 triggerCustomEvent: triggerCustomEvent,
1735 documentOn
1736 };
1737 }
1738
1739 window.frmFrontForm = frmFrontFormJS();
1740
1741 jQuery( document ).ready( function() {
1742 frmFrontForm.init();
1743 });
1744
1745 function frmRecaptcha() {
1746 frmCaptcha( '.frm-g-recaptcha' );
1747 }
1748
1749 function frmTurnstile() {
1750 frmCaptcha( '.cf-turnstile' );
1751 }
1752
1753 function frmCaptcha( captchaSelector ) {
1754 let c;
1755 const captchas = document.querySelectorAll( captchaSelector );
1756 const cl = captchas.length;
1757 for ( c = 0; c < cl; c++ ) {
1758 frmFrontForm.renderCaptcha( captchas[c], captchaSelector );
1759 }
1760 }
1761
1762 function getSelectedCaptcha( captchaSelector ) {
1763 if ( captchaSelector === '.frm-g-recaptcha' ) {
1764 return grecaptcha;
1765 }
1766 if ( document.querySelector( '.cf-turnstile' ) ) {
1767 return turnstile;
1768 }
1769 return hcaptcha;
1770 }
1771
1772 function frmAfterRecaptcha( token ) {
1773 frmFrontForm.afterSingleRecaptcha( token );
1774 }
1775