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

1,771 lines 48.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* exported frmRecaptcha, frmAfterRecaptcha, frmUpdateField */
2 /* eslint-disable prefer-const */
3
4 function frmFrontFormJS() {
5 'use strict';
6
7 /*global jQuery:false, frm_js, grecaptcha, hcaptcha, turnstile, frmProForm, tinyMCE */
8 /*global frmThemeOverride_jsErrors, frmThemeOverride_frmPlaceError, frmThemeOverride_frmAfterSubmit */
9
10 let action = '';
11 let jsErrors = [];
12
13 /**
14 * Triggers custom JS event.
15 *
16 * @since 5.5.3
17 *
18 * @param {HTMLElement} el The HTML element.
19 * @param {string} eventName Event name.
20 * @param {mixed} data The passed data.
21 */
22 function triggerCustomEvent( el, eventName, data ) {
23 if ( typeof window.CustomEvent !== 'function' ) {
24 return;
25 }
26
27 const event = new CustomEvent( eventName );
28 event.frmData = data;
29
30 el.dispatchEvent( event );
31 }
32
33 /* Get the ID of the field that changed*/
34 function getFieldId( field, fullID ) {
35 let nameParts, fieldId,
36 isRepeating = false,
37 fieldName = '';
38 if ( field instanceof jQuery ) {
39 fieldName = field.attr( 'name' );
40 } else {
41 fieldName = field.name;
42 }
43
44 if ( typeof fieldName === 'undefined' ) {
45 fieldName = '';
46 }
47
48 if ( fieldName === '' ) {
49 if ( field instanceof jQuery ) {
50 fieldName = field.data( 'name' );
51 } else {
52 fieldName = field.getAttribute( 'data-name' );
53 }
54
55 if ( typeof fieldName === 'undefined' ) {
56 fieldName = '';
57 }
58
59 if ( fieldName !== '' && fieldName ) {
60 return fieldName;
61 }
62 return 0;
63 }
64
65 nameParts = fieldName.replace( 'item_meta[', '' ).replace( '[]', '' ).split( ']' );
66 //TODO: Fix this for checkboxes and address fields
67 if ( nameParts.length < 1 ) {
68 return 0;
69 }
70 nameParts = nameParts.filter( function( n ) {
71 return n !== '';
72 });
73
74 fieldId = nameParts[0];
75
76 if ( nameParts.length === 1 ) {
77 return fieldId;
78 }
79
80 if ( nameParts[1] === '[form' || nameParts[1] === '[row_ids' ) {
81 return 0;
82 }
83
84 // Check if 'this' is in a repeating section
85 if ( jQuery( 'input[name="item_meta[' + fieldId + '][form]"]' ).length ) {
86
87 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
88 fieldId = nameParts[2].replace( '[', '' );
89 isRepeating = true;
90 }
91
92 // Check if 'this' is an other text field and get field ID for it
93 if ( 'other' === fieldId ) {
94 if ( isRepeating ) {
95 // name for other fields: item_meta[370][0][other][414]
96 fieldId = nameParts[3].replace( '[', '' );
97 } else {
98 // Other field name: item_meta[other][370]
99 fieldId = nameParts[1].replace( '[', '' );
100 }
101 }
102
103 if ( fullID === true ) {
104 // For use in the container div id
105 if ( fieldId === nameParts[0]) {
106 fieldId = fieldId + '-' + nameParts[1].replace( '[', '' );
107 } else {
108 fieldId = fieldId + '-' + nameParts[0] + '-' + nameParts[1].replace( '[', '' );
109 }
110 }
111
112 return fieldId;
113 }
114
115 /**
116 * Disable the submit button for a given jQuery form object
117 *
118 * @since 2.03.02
119 *
120 * @param {Object} $form
121 */
122 function disableSubmitButton( $form ) {
123 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).attr( 'disabled', 'disabled' );
124 }
125
126 /**
127 * Enable the submit button for a given jQuery form object
128 *
129 * @since 2.03.02
130 *
131 * @param {Object} $form
132 */
133 function enableSubmitButton( $form ) {
134 $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
135 }
136
137 /**
138 * Disable the save draft link for a given jQuery form object
139 *
140 * @since 4.04.03
141 *
142 * @param {Object} $form
143 */
144 function disableSaveDraft( $form ) {
145 $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
146 }
147
148 /**
149 * Enable the save draft link for a given jQuery form object
150 *
151 * @since 4.04.03
152 *
153 * @param {Object} $form
154 */
155 function enableSaveDraft( $form ) {
156 if ( ! $form.length ) {
157 return;
158 }
159 $form[0].querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
160 saveDraftButton.disabled = false;
161 saveDraftButton.style.pointerEvents = '';
162 });
163 }
164
165 function validateForm( object ) {
166 let errors, r, rl, n, nl, fields, field, requiredFields;
167
168 errors = [];
169
170 // Make sure required text field is filled in
171 requiredFields = jQuery( object ).find(
172 '.frm_required_field:visible input, .frm_required_field:visible select, .frm_required_field:visible textarea'
173 ).filter( ':not(.frm_optional)' );
174 if ( requiredFields.length ) {
175 for ( r = 0, rl = requiredFields.length; r < rl; r++ ) {
176 if ( hasClass( requiredFields[r], 'ed_button' ) ) {
177 // skip rich text field buttons.
178 continue;
179 }
180 errors = checkRequiredField( requiredFields[r], errors );
181 }
182 }
183
184 fields = jQuery( object ).find( 'input,select,textarea' );
185 if ( fields.length ) {
186 for ( n = 0, nl = fields.length; n < nl; n++ ) {
187 field = fields[n];
188 if ( '' === field.value ) {
189 if ( 'number' === field.type ) {
190 // A number field will return an empty string when it is invalid.
191 checkValidity( field, errors );
192 }
193 continue;
194 }
195
196 validateFieldValue( field, errors );
197 checkValidity( field, errors );
198 }
199 }
200
201 errors = validateRecaptcha( object, errors );
202
203 return errors;
204 }
205
206 /**
207 * Check the ValidityState interface for the field.
208 * If it is invalid, show an error for it.
209 *
210 * @param {HTMLElement} field
211 * @param {Array} errors
212 * @return {void}
213 */
214 function checkValidity( field, errors ) {
215 let fieldID;
216 if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
217 return;
218 }
219
220 fieldID = getFieldId( field, true );
221 if ( 'undefined' === typeof errors[ fieldID ]) {
222 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
223 }
224
225 if ( 'function' === typeof field.reportValidity ) {
226 // This triggers an error pop up.
227 field.reportValidity();
228 }
229 }
230
231 /**
232 * @since 5.0.10
233 *
234 * @param {Object} element
235 * @param {string} targetClass
236 * @return {boolean} True if the element has the target class.
237 */
238 function hasClass( element, targetClass ) {
239 return element.classList && element.classList.contains( targetClass );
240 }
241
242 function maybeValidateChange( field ) {
243 if ( field.type === 'url' ) {
244 maybeAddHttpToUrl( field );
245 }
246 if ( jQuery( field ).closest( 'form' ).hasClass( 'frm_js_validate' ) ) {
247 validateField( field );
248 }
249 }
250
251 function maybeAddHttpToUrl( field ) {
252 const url = field.value;
253 const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
254 if ( field.value !== '' && matches === null ) {
255 field.value = 'http://' + url;
256 }
257 }
258
259 function validateField( field ) {
260 let key,
261 errors = [],
262 $fieldCont = jQuery( field ).closest( '.frm_form_field' );
263
264 if ( $fieldCont.hasClass( 'frm_required_field' ) && ! jQuery( field ).hasClass( 'frm_optional' ) ) {
265 errors = checkRequiredField( field, errors );
266 }
267
268 if ( errors.length < 1 ) {
269 validateFieldValue( field, errors );
270 }
271
272 removeFieldError( $fieldCont );
273 if ( Object.keys( errors ).length > 0 ) {
274 for ( key in errors ) {
275 addFieldError( $fieldCont, key, errors );
276 }
277 }
278 }
279
280 function validateFieldValue( field, errors ) {
281 if ( field.type === 'hidden' ) {
282 // don't validate
283 } else if ( field.type === 'number' ) {
284 checkNumberField( field, errors );
285 } else if ( field.type === 'email' ) {
286 checkEmailField( field, errors );
287 } else if ( field.type === 'password' ) {
288 checkPasswordField( field, errors );
289 } else if ( field.type === 'url' ) {
290 checkUrlField( field, errors );
291 } else if ( field.pattern !== null ) {
292 checkPatternField( field, errors );
293 }
294
295 triggerCustomEvent( document, 'frm_validate_field_value', {
296 field: field,
297 errors: errors
298 });
299 }
300
301 function checkRequiredField( field, errors ) {
302 let checkGroup, tempVal, i, placeholder,
303 val = '',
304 fieldID = '',
305 fileID = field.getAttribute( 'data-frmfile' );
306
307 if ( field.type === 'hidden' && fileID === null && ! isAppointmentField( field ) && ! isInlineDatepickerField( field ) ) {
308 return errors;
309 }
310
311 if ( field.type === 'checkbox' || field.type === 'radio' ) {
312 checkGroup = jQuery( 'input[name="' + field.name + '"]' ).closest( '.frm_required_field' ).find( 'input:checked' );
313 jQuery( checkGroup ).each( function() {
314 val = this.value;
315 });
316 } else if ( field.type === 'file' || fileID ) {
317 if ( typeof fileID === 'undefined' ) {
318 fileID = getFieldId( field, true );
319 fileID = fileID.replace( 'file', '' );
320 }
321
322 if ( typeof errors[ fileID ] === 'undefined' ) {
323 val = getFileVals( fileID );
324 }
325 fieldID = fileID;
326 } else {
327 if ( hasClass( field, 'frm_pos_none' ) ) {
328 // skip hidden other fields
329 return errors;
330 }
331
332 val = jQuery( field ).val();
333 if ( val === null ) {
334 val = '';
335 } else if ( typeof val !== 'string' ) {
336 tempVal = val;
337 val = '';
338 for ( i = 0; i < tempVal.length; i++ ) {
339 if ( tempVal[i] !== '' ) {
340 val = tempVal[i];
341 }
342 }
343 }
344
345 if ( hasClass( field, 'frm_other_input' ) ) {
346 fieldID = getFieldId( field, false );
347
348 if ( val === '' ) {
349 field = document.getElementById( field.id.replace( '-otext', '' ) );
350 }
351 } else {
352 fieldID = getFieldId( field, true );
353 }
354
355 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 Form object.
1017 * @return {void}
1018 */
1019 function scrollToFirstField( object ) {
1020 const field = object.querySelector( '.frm_blank_field' );
1021 if ( field ) {
1022 frmFrontForm.scrollMsg( jQuery( field ), object, true );
1023 }
1024 }
1025
1026 function showSubmitLoading( $object ) {
1027 showLoadingIndicator( $object );
1028 disableSubmitButton( $object );
1029 disableSaveDraft( $object );
1030 }
1031
1032 function showLoadingIndicator( $object ) {
1033 if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) {
1034 addLoadingClass( $object );
1035 $object.trigger( 'frmStartFormLoading' );
1036 }
1037 }
1038
1039 function addLoadingClass( $object ) {
1040 const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1041
1042 $object.addClass( loadingClass );
1043 }
1044
1045 function isGoingToPrevPage( $object ) {
1046 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
1047 }
1048
1049 function removeSubmitLoading( $object, enable, processesRunning ) {
1050 let loadingForm;
1051
1052 if ( processesRunning > 0 ) {
1053 return;
1054 }
1055
1056 loadingForm = jQuery( '.frm_loading_form' );
1057 loadingForm.removeClass( 'frm_loading_form' );
1058 loadingForm.removeClass( 'frm_loading_prev' );
1059
1060 loadingForm.trigger( 'frmEndFormLoading' );
1061
1062 if ( enable === 'enable' ) {
1063 enableSubmitButton( loadingForm );
1064 enableSaveDraft( loadingForm );
1065 }
1066 }
1067
1068 function showFileLoading( object ) {
1069 let fileval,
1070 loading = document.getElementById( 'frm_loading' );
1071 if ( loading !== null ) {
1072 fileval = jQuery( object ).find( 'input[type=file]' ).val();
1073 if ( typeof fileval !== 'undefined' && fileval !== '' ) {
1074 setTimeout( function() {
1075 jQuery( loading ).fadeIn( 'slow' );
1076 }, 2000 );
1077 }
1078 }
1079 }
1080
1081 /**********************************************
1082 * General Helpers
1083 *********************************************/
1084
1085 function confirmClick() {
1086 /*jshint validthis:true */
1087 const message = jQuery( this ).data( 'frmconfirm' );
1088 return confirm( message );
1089 }
1090
1091 /**
1092 * Check for -webkit-box-shadow css value for input:-webkit-autofill selector.
1093 * If this is a match, the User is autofilling the input on a Webkit browser.
1094 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1095 */
1096 function onHoneypotFieldChange() {
1097 const css = jQuery( this ).css( 'box-shadow' );
1098 if ( css.match( /inset/ ) ) {
1099 this.parentNode.removeChild( this );
1100 }
1101 }
1102
1103 function maybeMakeHoneypotFieldsUntabbable() {
1104 document.addEventListener( 'keydown', handleKeyUp );
1105
1106 function handleKeyUp( event ) {
1107 let code;
1108
1109 if ( 'undefined' !== typeof event.key ) {
1110 code = event.key;
1111 } else if ( 'undefined' !== typeof event.keyCode && 9 === event.keyCode ) {
1112 code = 'Tab';
1113 }
1114
1115 if ( 'Tab' === code ) {
1116 makeHoneypotFieldsUntabbable();
1117 document.removeEventListener( 'keydown', handleKeyUp );
1118 }
1119 }
1120
1121 function makeHoneypotFieldsUntabbable() {
1122 document.querySelectorAll( '.frm_verify' ).forEach(
1123 function( input ) {
1124 if ( input.id && 0 === input.id.indexOf( 'frm_email_' ) ) {
1125 input.setAttribute( 'tabindex', -1 );
1126 }
1127 }
1128 );
1129 }
1130 }
1131
1132 /**
1133 * Focus on the first sub field when clicking to the primary label of combo field.
1134 *
1135 * @since 4.10.02
1136 */
1137 function changeFocusWhenClickComboFieldLabel() {
1138 let label;
1139
1140 const comboInputsContainer = document.querySelectorAll( '.frm_combo_inputs_container' );
1141 comboInputsContainer.forEach( function( inputsContainer ) {
1142 if ( ! inputsContainer.closest( '.frm_form_field' ) ) {
1143 return;
1144 }
1145
1146 label = inputsContainer.closest( '.frm_form_field' ).querySelector( '.frm_primary_label' );
1147 if ( ! label ) {
1148 return;
1149 }
1150
1151 label.addEventListener( 'click', function() {
1152 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1153 });
1154 });
1155 }
1156
1157 function checkForErrorsAndMaybeSetFocus() {
1158 let errors, element, timeoutCallback;
1159
1160 if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1161 return;
1162 }
1163
1164 errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1165 if ( ! errors.length ) {
1166 return;
1167 }
1168
1169 element = errors[0];
1170 do {
1171 element = element.previousSibling;
1172 if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1173 element.focus();
1174 break;
1175 }
1176
1177 if ( 'undefined' !== typeof element.classList ) {
1178 if ( element.classList.contains( 'html-active' ) ) {
1179 timeoutCallback = function() {
1180 const textarea = element.querySelector( 'textarea' );
1181 if ( null !== textarea ) {
1182 textarea.focus();
1183 }
1184 };
1185 } else if ( element.classList.contains( 'tmce-active' ) ) {
1186 timeoutCallback = function() {
1187 tinyMCE.activeEditor.focus();
1188 };
1189 }
1190
1191 if ( 'function' === typeof timeoutCallback ) {
1192 setTimeout( timeoutCallback, 0 );
1193 break;
1194 }
1195 }
1196 } while ( element.previousSibling );
1197 }
1198
1199 /**
1200 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
1201 *
1202 * @since 5.4
1203 *
1204 * @param {string} event Event name.
1205 * @param {string} selector Selector.
1206 * @param {Function} handler Handler.
1207 * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1208 */
1209 function documentOn( event, selector, handler, options ) {
1210 if ( 'undefined' === typeof options ) {
1211 options = false;
1212 }
1213
1214 document.addEventListener( event, function( e ) {
1215 let target;
1216
1217 // loop parent nodes from the target to the delegation node.
1218 for ( target = e.target; target && target != this; target = target.parentNode ) {
1219 if ( target && target.matches && target.matches( selector ) ) {
1220 handler.call( target, e );
1221 break;
1222 }
1223 }
1224 }, options );
1225 }
1226
1227 function initFloatingLabels() {
1228 let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1229
1230 selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1231 floatClass = 'frm_label_float_top';
1232
1233 checkFloatLabel = function( input ) {
1234 let container, shouldFloatTop, firstOpt;
1235
1236 container = input.closest( '.frm_inside_container' );
1237 if ( ! container ) {
1238 return;
1239 }
1240
1241 shouldFloatTop = input.value || document.activeElement === input;
1242
1243 container.classList.toggle( floatClass, shouldFloatTop );
1244
1245 if ( 'SELECT' === input.tagName ) {
1246 firstOpt = input.querySelector( 'option:first-child' );
1247
1248 if ( shouldFloatTop ) {
1249 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1250 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1251 firstOpt.removeAttribute( 'data-label' );
1252 }
1253 } else if ( firstOpt.textContent ) {
1254 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1255 firstOpt.textContent = '';
1256 }
1257 }
1258 };
1259
1260 checkDropdownLabel = function() {
1261 document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1262 const firstOpt = input.querySelector( 'option:first-child' );
1263
1264 if ( firstOpt.textContent ) {
1265 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1266 firstOpt.textContent = '';
1267 }
1268 });
1269 };
1270
1271 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1272 documentOn(
1273 eventName,
1274 selector,
1275 function( event ) {
1276 checkFloatLabel( event.target );
1277 },
1278 true
1279 );
1280 });
1281
1282 jQuery( document ).on( 'change', selector, function( event ) {
1283 checkFloatLabel( event.target );
1284 });
1285
1286 runOnLoad = function( firstLoad ) {
1287 if ( firstLoad && document.activeElement && -1 !== [ 'INPUT', 'SELECT', 'TEXTAREA' ].indexOf( document.activeElement.tagName ) ) {
1288 checkFloatLabel( document.activeElement );
1289 } else if ( firstLoad ) {
1290 document.querySelectorAll( '.frm_inside_container' ).forEach(
1291 function( container ) {
1292 const input = container.querySelector( 'input, select, textarea' );
1293 if ( input && '' !== input.value ) {
1294 checkFloatLabel( input );
1295 }
1296 }
1297 );
1298 }
1299
1300 checkDropdownLabel();
1301 };
1302
1303 runOnLoad( true );
1304
1305 jQuery( document ).on( 'frmPageChanged', function( event ) {
1306 runOnLoad();
1307 });
1308
1309 document.addEventListener( 'frm_after_start_over', function( event ) {
1310 runOnLoad();
1311 });
1312 }
1313
1314 function shouldUpdateValidityMessage( target ) {
1315 if ( 'INPUT' !== target.nodeName ) {
1316 return false;
1317 }
1318
1319 if ( ! target.dataset.invmsg ) {
1320 return false;
1321 }
1322
1323 if ( 'text' !== target.getAttribute( 'type' ) ) {
1324 return false;
1325 }
1326
1327 if ( target.classList.contains( 'frm_verify' ) ) {
1328 return false;
1329 }
1330
1331 return true;
1332 }
1333
1334 function maybeClearCustomValidityMessage( event, field ) {
1335 let key,
1336 isInvalid = false;
1337
1338 if ( ! shouldUpdateValidityMessage( field ) ) {
1339 return;
1340 }
1341
1342 for ( key in field.validity ) {
1343 if ( 'customError' === key ) {
1344 continue;
1345 }
1346 if ( 'valid' !== key && field.validity[ key ] === true ) {
1347 isInvalid = true;
1348 break;
1349 }
1350 };
1351
1352 if ( ! isInvalid ) {
1353 field.setCustomValidity( '' );
1354 }
1355 }
1356
1357 function maybeShowNewTabFallbackMessage() {
1358 let messageEl;
1359
1360 if ( ! window.frmShowNewTabFallback ) {
1361 return;
1362 }
1363
1364 messageEl = document.querySelector( '#frm_form_' + frmShowNewTabFallback.formId + '_container .frm_message' );
1365 if ( ! messageEl ) {
1366 return;
1367 }
1368
1369 messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1370 }
1371
1372 function setCustomValidityMessage() {
1373 let forms, length, index;
1374
1375 forms = document.getElementsByClassName( 'frm-show-form' );
1376 length = forms.length;
1377
1378 for ( index = 0; index < length; ++index ) {
1379 forms[ index ].addEventListener(
1380 'invalid',
1381 function( event ) {
1382 const target = event.target;
1383
1384 if ( shouldUpdateValidityMessage( target ) ) {
1385 target.setCustomValidity( target.dataset.invmsg );
1386 }
1387 },
1388 true
1389 );
1390 }
1391 }
1392
1393 function enableSubmitButtonOnBackButtonPress() {
1394 window.addEventListener( 'pageshow', function( event ) {
1395 if ( event.persisted ) {
1396 document.querySelectorAll( '.frm_loading_form' ).forEach(
1397 function( form ) {
1398 enableSubmitButton( jQuery( form ) );
1399 }
1400 );
1401 removeSubmitLoading();
1402 }
1403 });
1404 }
1405
1406 /**
1407 * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
1408 */
1409 function destroyhCaptcha() {
1410 if ( ! window.hasOwnProperty( 'hcaptcha' ) || ! document.querySelector( '.frm-show-form .h-captcha' ) ) {
1411 return;
1412 }
1413 window.hcaptcha = null;
1414 }
1415
1416 return {
1417 init: function() {
1418 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1419 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1420
1421 jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
1422 if ( jQuery( this ).val() === '' ) {
1423 jQuery( this ).trigger( 'blur' );
1424 }
1425 });
1426
1427 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 );
1428
1429 jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1430 maybeMakeHoneypotFieldsUntabbable();
1431
1432 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1433
1434 checkForErrorsAndMaybeSetFocus();
1435
1436 // Focus on the first sub field when clicking to the primary label of combo field.
1437 changeFocusWhenClickComboFieldLabel();
1438
1439 initFloatingLabels();
1440 maybeShowNewTabFallbackMessage();
1441
1442 jQuery( document ).on( 'frmAfterAddRow', setCustomValidityMessage );
1443 setCustomValidityMessage();
1444 jQuery( document ).on( 'frmFieldChanged', maybeClearCustomValidityMessage );
1445
1446 setSelectPlaceholderColor();
1447
1448 // Elementor popup show event. Fix Elementor Popup && FF Captcha field conflicts
1449 jQuery( document ).on( 'elementor/popup/show', frmRecaptcha );
1450
1451 enableSubmitButtonOnBackButtonPress();
1452 jQuery( document ).on(
1453 'frmPageChanged',
1454 destroyhCaptcha
1455 );
1456 },
1457
1458 getFieldId: function( field, fullID ) {
1459 return getFieldId( field, fullID );
1460 },
1461
1462 renderCaptcha: function( captcha, captchaSelector ) {
1463 let formID, captchaID,
1464 size = captcha.getAttribute( 'data-size' ),
1465 rendered = captcha.getAttribute( 'data-rid' ) !== null,
1466 params = {
1467 'sitekey': captcha.getAttribute( 'data-sitekey' ),
1468 'size': size,
1469 'theme': captcha.getAttribute( 'data-theme' )
1470 },
1471 activeCaptcha = getSelectedCaptcha( captchaSelector ),
1472 captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1473
1474 if ( rendered ) {
1475 return;
1476 }
1477
1478 if ( size === 'invisible' ) {
1479 formID = jQuery( captcha ).closest( 'form' ).find( 'input[name="form_id"]' ).val();
1480 jQuery( captcha ).closest( '.frm_form_field .frm_primary_label' ).hide();
1481 params.callback = function( token ) {
1482 frmFrontForm.afterRecaptcha( token, formID );
1483 };
1484 }
1485
1486
1487 captchaID = activeCaptcha.render( captchaContainer, params );
1488
1489 captcha.setAttribute( 'data-rid', captchaID );
1490 },
1491
1492 afterSingleRecaptcha: function() {
1493 const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1494 frmFrontForm.submitFormNow( object );
1495 },
1496
1497 afterRecaptcha: function( _, formID ) {
1498 const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1499 frmFrontForm.submitFormNow( object );
1500 },
1501
1502 submitForm: function( e ) {
1503 frmFrontForm.submitFormManual( e, this );
1504 },
1505
1506 submitFormManual: function( e, object ) {
1507 let isPro, errors,
1508 invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1509 classList = object.className.trim().split( /\s+/gi );
1510
1511 if ( classList && invisibleRecaptcha.length < 1 ) {
1512 isPro = classList.indexOf( 'frm_pro_form' ) > -1;
1513 if ( ! isPro ) {
1514 return;
1515 }
1516 }
1517
1518 if ( jQuery( 'body' ).hasClass( 'wp-admin' ) && jQuery( object ).closest( '.frmapi-form' ).length < 1 ) {
1519 return;
1520 }
1521
1522 e.preventDefault();
1523
1524 if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
1525 return;
1526 }
1527
1528 if ( invisibleRecaptcha.length ) {
1529 showLoadingIndicator( jQuery( object ) );
1530 executeInvisibleRecaptcha( invisibleRecaptcha );
1531 } else {
1532
1533 errors = frmFrontForm.validateFormSubmit( object );
1534
1535 if ( Object.keys( errors ).length === 0 ) {
1536 showSubmitLoading( jQuery( object ) );
1537
1538 frmFrontForm.submitFormNow( object, classList );
1539 }
1540 }
1541 },
1542
1543 submitFormNow: function( object ) {
1544 let hasFileFields, antispamInput,
1545 classList = object.className.trim().split( /\s+/gi );
1546
1547 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
1548 // include the antispam token on form submit.
1549 antispamInput = document.createElement( 'input' );
1550 antispamInput.type = 'hidden';
1551 antispamInput.name = 'antispam_token';
1552 antispamInput.value = object.getAttribute( 'data-token' );
1553 object.appendChild( antispamInput );
1554 }
1555
1556 if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1557 hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1558 return !! this.value;
1559 }).length;
1560 if ( hasFileFields < 1 ) {
1561 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1562 frmFrontForm.checkFormErrors( object, action );
1563 } else {
1564 object.submit();
1565 }
1566 } else {
1567 object.submit();
1568 }
1569 },
1570
1571 /**
1572 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1573 *
1574 * @return {Array} List of errors.
1575 */
1576 validateFormSubmit: function( object ) {
1577 if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1578 tinyMCE.triggerSave();
1579 }
1580
1581 jsErrors = [];
1582
1583 if ( shouldJSValidate( object ) ) {
1584 frmFrontForm.getAjaxFormErrors( object );
1585
1586 if ( Object.keys( jsErrors ).length ) {
1587 frmFrontForm.addAjaxFormErrors( object );
1588 }
1589 }
1590
1591 return jsErrors;
1592 },
1593
1594 getAjaxFormErrors: function( object ) {
1595 let customErrors, key;
1596
1597 jsErrors = validateForm( object );
1598 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1599 action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1600 customErrors = frmThemeOverride_jsErrors( action, object );
1601 if ( Object.keys( customErrors ).length ) {
1602 for ( key in customErrors ) {
1603 jsErrors[ key ] = customErrors[ key ];
1604 }
1605 }
1606 }
1607
1608 return jsErrors;
1609 },
1610
1611 addAjaxFormErrors: function( object ) {
1612 let key, $fieldCont;
1613 removeAllErrors();
1614
1615 for ( key in jsErrors ) {
1616 $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
1617
1618 if ( $fieldCont.length ) {
1619 addFieldError( $fieldCont, key, jsErrors );
1620 } else {
1621 // we are unable to show the error, so remove it
1622 delete jsErrors[ key ];
1623 }
1624 }
1625
1626 scrollToFirstField( object );
1627 checkForErrorsAndMaybeSetFocus();
1628 },
1629
1630 checkFormErrors: function( object, action ) {
1631 getFormErrors( object, action );
1632 },
1633
1634 checkRequiredField: function( field, errors ) {
1635 return checkRequiredField( field, errors );
1636 },
1637
1638 showSubmitLoading: function( $object ) {
1639 showSubmitLoading( $object );
1640 },
1641
1642 removeSubmitLoading: function( $object, enable, processesRunning ) {
1643 removeSubmitLoading( $object, enable, processesRunning );
1644 },
1645
1646 scrollToID: function( id ) {
1647 const object = jQuery( document.getElementById( id ) );
1648 frmFrontForm.scrollMsg( object, false );
1649 },
1650
1651 scrollMsg: function( id, object, animate ) {
1652 let newPos, m, b, screenTop, screenBottom,
1653 scrollObj = '';
1654 if ( typeof object === 'undefined' ) {
1655 scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
1656 if ( scrollObj.length < 1 ) {
1657 return;
1658 }
1659 } else if ( typeof id === 'string' ) {
1660 scrollObj = jQuery( object ).find( '#frm_field_' + id + '_container' );
1661 } else {
1662 scrollObj = id;
1663 }
1664
1665 jQuery( scrollObj ).trigger( 'focus' );
1666 newPos = scrollObj.offset().top;
1667 if ( ! newPos || frm_js.offset === '-1' ) { // eslint-disable-line camelcase
1668 return;
1669 }
1670 newPos = newPos - frm_js.offset; // eslint-disable-line camelcase
1671
1672 m = jQuery( 'html' ).css( 'margin-top' );
1673 b = jQuery( 'body' ).css( 'margin-top' );
1674 if ( m || b ) {
1675 newPos = newPos - parseInt( m ) - parseInt( b );
1676 }
1677
1678 if ( newPos && window.innerHeight ) {
1679 screenTop = document.documentElement.scrollTop || document.body.scrollTop;
1680 screenBottom = screenTop + window.innerHeight;
1681
1682 if ( newPos > screenBottom || newPos < screenTop ) {
1683 // Not in view
1684 if ( typeof animate === 'undefined' ) {
1685 jQuery( window ).scrollTop( newPos );
1686 } else {
1687 jQuery( 'html,body' ).animate({ scrollTop: newPos }, 500 );
1688 }
1689 return false;
1690 }
1691 }
1692 },
1693
1694 fieldValueChanged: function( e ) {
1695 /*jshint validthis:true */
1696
1697 const fieldId = frmFrontForm.getFieldId( this, false );
1698 if ( ! fieldId || typeof fieldId === 'undefined' ) {
1699 return;
1700 }
1701
1702 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
1703 return;
1704 }
1705
1706 jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ]);
1707
1708 if ( e.selfTriggered !== true ) {
1709 maybeValidateChange( this );
1710 }
1711 },
1712
1713 escapeHtml: function( text ) {
1714 return text
1715 .replace( /&/g, '&amp;' )
1716 .replace( /</g, '&lt;' )
1717 .replace( />/g, '&gt;' )
1718 .replace( /"/g, '&quot;' )
1719 .replace( /'/g, '&#039;' );
1720 },
1721
1722 invisible: function( classes ) {
1723 jQuery( classes ).css( 'visibility', 'hidden' );
1724 },
1725
1726 visible: function( classes ) {
1727 jQuery( classes ).css( 'visibility', 'visible' );
1728 },
1729
1730 triggerCustomEvent: triggerCustomEvent,
1731 documentOn
1732 };
1733 }
1734
1735 window.frmFrontForm = frmFrontFormJS();
1736
1737 jQuery( document ).ready( function() {
1738 frmFrontForm.init();
1739 });
1740
1741 function frmRecaptcha() {
1742 frmCaptcha( '.frm-g-recaptcha' );
1743 }
1744
1745 function frmTurnstile() {
1746 frmCaptcha( '.cf-turnstile' );
1747 }
1748
1749 function frmCaptcha( captchaSelector ) {
1750 let c;
1751 const captchas = document.querySelectorAll( captchaSelector );
1752 const cl = captchas.length;
1753 for ( c = 0; c < cl; c++ ) {
1754 frmFrontForm.renderCaptcha( captchas[c], captchaSelector );
1755 }
1756 }
1757
1758 function getSelectedCaptcha( captchaSelector ) {
1759 if ( captchaSelector === '.frm-g-recaptcha' ) {
1760 return grecaptcha;
1761 }
1762 if ( document.querySelector( '.cf-turnstile' ) ) {
1763 return turnstile;
1764 }
1765 return hcaptcha;
1766 }
1767
1768 function frmAfterRecaptcha( token ) {
1769 frmFrontForm.afterSingleRecaptcha( token );
1770 }
1771