PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.21
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.21
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
← All changes | js/formidable.js +442 -925 6.306.21 View file →
@@ -1,9 +1,13 @@
1 1 /* exported frmRecaptcha, frmAfterRecaptcha */
2 +/* eslint-disable prefer-const */
2 3
3 4 function frmFrontFormJS() {
4 5 'use strict';
5 6
7 + /*global jQuery:false, frm_js, grecaptcha, hcaptcha, turnstile, frmProForm, tinyMCE */
8 + /*global frmThemeOverride_jsErrors, frmThemeOverride_frmPlaceError, frmThemeOverride_frmAfterSubmit */
9 +
6 10 let jsErrors = [];
7 11
8 12 /**
9 13 * Triggers custom JS event.
@@ -11,9 +15,9 @@
11 15 * @since 5.5.3
12 16 *
13 17 * @param {HTMLElement} el The HTML element.
14 18 * @param {string} eventName Event name.
15 - * @param {*} data The passed data.
19 + * @param {mixed} data The passed data.
16 20 */
17 21 function triggerCustomEvent( el, eventName, data ) {
18 22 if ( typeof window.CustomEvent !== 'function' ) {
19 23 return;
@@ -32,27 +36,29 @@
32 36 * @param {boolean} fullID
33 37 * @return {string|number} Field ID.
34 38 */
35 39 function getFieldId( field, fullID ) {
36 - let nameParts;
37 - let fieldId;
38 - let isRepeating = false;
39 - let fieldName = '';
40 -
40 + let nameParts, fieldId,
41 + isRepeating = false,
42 + fieldName = '';
41 43 if ( field instanceof jQuery ) {
42 - field = field.get( 0 );
44 + fieldName = field.attr( 'name' );
45 + } else {
46 + fieldName = field.name;
43 47 }
44 48
45 - fieldName = field.name;
46 -
47 - if ( fieldName === undefined ) {
49 + if ( typeof fieldName === 'undefined' ) {
48 50 fieldName = '';
49 51 }
50 52
51 53 if ( fieldName === '' ) {
52 - fieldName = field.getAttribute( 'data-name' );
54 + if ( field instanceof jQuery ) {
55 + fieldName = field.data( 'name' );
56 + } else {
57 + fieldName = field.getAttribute( 'data-name' );
58 + }
53 59
54 - if ( fieldName === undefined ) {
60 + if ( typeof fieldName === 'undefined' ) {
55 61 fieldName = '';
56 62 }
57 63
58 64 if ( fieldName !== '' && fieldName ) {
@@ -67,24 +73,25 @@
67 73 return 0;
68 74 }
69 75 nameParts = nameParts.filter( function( n ) {
70 76 return n !== '';
71 - } );
77 + });
72 78
73 - fieldId = nameParts[ 0 ];
79 + fieldId = nameParts[0];
74 80
75 81 if ( nameParts.length === 1 ) {
76 82 return fieldId;
77 83 }
78 84
79 - if ( nameParts[ 1 ] === '[form' || nameParts[ 1 ] === '[row_ids' ) {
85 + if ( nameParts[1] === '[form' || nameParts[1] === '[row_ids' ) {
80 86 return 0;
81 87 }
82 88
83 89 // Check if 'this' is in a repeating section
84 - if ( document.querySelector( `input[name="item_meta[${ fieldId }][form]"]` ) ) {
90 + if ( document.querySelector( 'input[name="item_meta[' + fieldId + '][form]"]' ) ) {
91 +
85 92 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
86 - fieldId = nameParts[ 2 ].replace( '[', '' );
93 + fieldId = nameParts[2].replace( '[', '' );
87 94 isRepeating = true;
88 95 }
89 96
90 97 // Check if 'this' is an other text field and get field ID for it
@@ -90,21 +97,21 @@
90 97 // Check if 'this' is an other text field and get field ID for it
91 98 if ( 'other' === fieldId ) {
92 99 if ( isRepeating ) {
93 100 // name for other fields: item_meta[370][0][other][414]
94 - fieldId = nameParts[ 3 ].replace( '[', '' );
101 + fieldId = nameParts[3].replace( '[', '' );
95 102 } else {
96 103 // Other field name: item_meta[other][370]
97 - fieldId = nameParts[ 1 ].replace( '[', '' );
104 + fieldId = nameParts[1].replace( '[', '' );
98 105 }
99 106 }
100 107
101 108 if ( fullID === true ) {
102 109 // For use in the container div id
103 - if ( fieldId === nameParts[ 0 ] ) {
104 - fieldId = `${ fieldId }-${ nameParts[ 1 ].replace( '[', '' ) }`;
110 + if ( fieldId === nameParts[0]) {
111 + fieldId = fieldId + '-' + nameParts[1].replace( '[', '' );
105 112 } else {
106 - fieldId = `${ fieldId }-${ nameParts[ 0 ] }-${ nameParts[ 1 ].replace( '[', '' ) }`;
113 + fieldId = fieldId + '-' + nameParts[0] + '-' + nameParts[1].replace( '[', '' );
107 114 }
108 115 }
109 116
110 117 return fieldId;
@@ -117,15 +124,9 @@
117 124 *
118 125 * @param {Object} $form
119 126 */
120 127 function disableSubmitButton( $form ) {
121 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
122 - if ( ! form ) {
123 - return;
124 - }
125 - form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).forEach(
126 - button => button.disabled = true
127 - );
128 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).attr( 'disabled', 'disabled' );
128 129 }
129 130
130 131 /**
131 132 * Enable the submit button for a given jQuery form object
@@ -131,16 +132,12 @@
131 132 * Enable the submit button for a given jQuery form object
132 133 *
133 134 * @since 2.03.02
134 135 *
135 - * @param {HTMLElement} form
136 - *
137 - * @return {void}
136 + * @param {Object} $form
138 137 */
139 - function enableSubmitButton( form ) {
140 - form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"]' ).forEach(
141 - button => button.disabled = false
142 - );
138 + function enableSubmitButton( $form ) {
139 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
143 140 }
144 141
145 142 /**
146 143 * Disable the save draft link for a given jQuery form object
@@ -149,33 +146,26 @@
149 146 *
150 147 * @param {Object} $form
151 148 */
152 149 function disableSaveDraft( $form ) {
153 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
154 - if ( ! form ) {
155 - return;
156 - }
157 - form.querySelectorAll( 'a.frm_save_draft' ).forEach(
158 - link => link.style.pointerEvents = 'none'
159 - );
150 + $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
160 151 }
161 152
162 153 /**
163 - * Enable the save draft link for a given form object.
154 + * Enable the save draft link for a given jQuery form object
164 155 *
165 156 * @since 4.04.03
166 157 *
167 - * @param {jQuery|HTMLElement} $form
158 + * @param {jQuery} $form
168 159 */
169 160 function enableSaveDraft( $form ) {
170 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
171 - if ( ! form ) {
161 + if ( ! $form.length ) {
172 162 return;
173 163 }
174 - form.querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
175 - saveDraftButton.disabled = false;
164 + $form[0].querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
165 + saveDraftButton.disabled = false;
176 166 saveDraftButton.style.pointerEvents = '';
177 - } );
167 + });
178 168 }
179 169
180 170 /**
181 171 * Validate form with JS.
@@ -247,14 +237,15 @@
247 237 * @param {Array} errors
248 238 * @return {void}
249 239 */
250 240 function checkValidity( field, errors ) {
241 + let fieldID;
251 242 if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
252 243 return;
253 244 }
254 245
255 - const fieldID = getFieldId( field, true );
256 - if ( errors[ fieldID ] === undefined ) {
246 + fieldID = getFieldId( field, true );
247 + if ( 'undefined' === typeof errors[ fieldID ]) {
257 248 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
258 249 }
259 250
260 251 if ( 'function' === typeof field.reportValidity ) {
@@ -278,9 +269,9 @@
278 269 * @param {HTMLElement} field
279 270 */
280 271 function maybeValidateChange( field ) {
281 272 if ( field.type === 'url' ) {
282 - maybeAddHttpsToUrl( field );
273 + maybeAddHttpToUrl( field );
283 274 }
284 275 const form = field.closest( 'form' );
285 276 if ( form && hasClass( form, 'frm_js_validate' ) ) {
286 277 validateField( field );
@@ -289,13 +280,13 @@
289 280
290 281 /**
291 282 * @param {HTMLElement} field
292 283 */
293 - function maybeAddHttpsToUrl( field ) {
284 + function maybeAddHttpToUrl( field ) {
294 285 const url = field.value;
295 286 const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
296 287 if ( field.value !== '' && matches === null ) {
297 - field.value = `https://${ url }`;
288 + field.value = 'http://' + url;
298 289 }
299 290 }
300 291
301 292 /**
@@ -305,12 +296,11 @@
305 296 *
306 297 * @return {void}
307 298 */
308 299 function validateField( field ) {
309 - let errors;
310 - let key;
300 + let errors, key;
311 301
312 - errors = [];
302 + errors = [];
313 303 const fieldContainer = field.closest( '.frm_form_field' );
314 304
315 305 if ( ! fieldContainer ) {
316 306 // Hidden fields do not have a field container and do not require JS validation.
@@ -324,12 +314,13 @@
324 314 if ( errors.length < 1 ) {
325 315 validateFieldValue( field, errors, false );
326 316 }
327 317
328 - removeFieldError( fieldContainer );
318 + const $fieldCont = jQuery( fieldContainer );
319 + removeFieldError( $fieldCont );
329 320 if ( Object.keys( errors ).length > 0 ) {
330 321 for ( key in errors ) {
331 - addFieldError( fieldContainer, key, errors );
322 + addFieldError( $fieldCont, key, errors );
332 323 }
333 324 }
334 325 }
335 326
@@ -356,20 +347,16 @@
356 347 } else if ( field.pattern !== null ) {
357 348 checkPatternField( field, errors );
358 349 }
359 350
360 - if ( 'tel' === field.type && shouldCheckConfirmField( field, onSubmit ) ) {
361 - confirmField( field, errors );
362 - }
363 -
364 351 /**
365 352 * @since 6.15 Added `onSubmit` to the data.
366 353 */
367 354 triggerCustomEvent( document, 'frm_validate_field_value', {
368 - field,
369 - errors,
370 - onSubmit
371 - } );
355 + field: field,
356 + errors: errors,
357 + onSubmit: onSubmit
358 + });
372 359 }
373 360
374 361 /**
375 362 * @param {HTMLElement} field
@@ -376,14 +363,12 @@
376 363 * @param {Array} errors
377 364 * @return {Array} Errors
378 365 */
379 366 function checkRequiredField( field, errors ) {
380 - let tempVal;
381 - let i;
382 - let placeholder;
383 - let val = '';
384 - let fieldID = '';
385 - let fileID = field.getAttribute( 'data-frmfile' );
367 + let tempVal, i, placeholder,
368 + val = '',
369 + fieldID = '',
370 + fileID = field.getAttribute( 'data-frmfile' );
386 371
387 372 if ( field.type === 'hidden' && fileID === null && ! isAppointmentField( field ) && ! isInlineDatepickerField( field ) ) {
388 373 return errors;
389 374 }
@@ -388,9 +373,9 @@
388 373 return errors;
389 374 }
390 375
391 376 if ( field.type === 'checkbox' || field.type === 'radio' ) {
392 - document.querySelectorAll( `input[name="${ field.name }"]` ).forEach( function( input ) {
377 + document.querySelectorAll( 'input[name="' + field.name + '"]' ).forEach( function( input ) {
393 378 const requiredField = input.closest( '.frm_required_field' );
394 379 if ( ! requiredField ) {
395 380 return;
396 381 }
@@ -400,14 +385,14 @@
400 385 val = checkedInput.value;
401 386 } );
402 387 } );
403 388 } else if ( field.type === 'file' || fileID ) {
404 - if ( fileID === undefined ) {
389 + if ( typeof fileID === 'undefined' ) {
405 390 fileID = getFieldId( field, true );
406 391 fileID = fileID.replace( 'file', '' );
407 392 }
408 393
409 - if ( errors[ fileID ] === undefined ) {
394 + if ( typeof errors[ fileID ] === 'undefined' ) {
410 395 val = getFileVals( fileID );
411 396 }
412 397 fieldID = fileID;
413 398 } else {
@@ -415,10 +400,9 @@
415 400 // skip hidden other fields
416 401 return errors;
417 402 }
418 403
419 - val = jQuery( field ).val(); // eslint-disable-line no-jquery/no-val
420 -
404 + val = jQuery( field ).val();
421 405 if ( val === null ) {
422 406 val = '';
423 407 } else if ( typeof val !== 'string' ) {
424 408 tempVal = val;
@@ -423,10 +407,10 @@
423 407 } else if ( typeof val !== 'string' ) {
424 408 tempVal = val;
425 409 val = '';
426 410 for ( i = 0; i < tempVal.length; i++ ) {
427 - if ( tempVal[ i ] !== '' ) {
428 - val = tempVal[ i ];
411 + if ( tempVal[i] !== '' ) {
412 + val = tempVal[i];
429 413 }
430 414 }
431 415 }
432 416
@@ -450,11 +434,9 @@
450 434 // set id for time field
451 435 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
452 436 } else if ( isSignatureField( field ) ) {
453 437 if ( val === '' ) {
454 - const fieldContainer = field.closest( '.frm_form_field' );
455 - const outputField = fieldContainer ? fieldContainer.querySelector( `[name="${ field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) }"]` ) : null;
456 - val = outputField ? outputField.value : '';
438 + val = jQuery( field ).closest( '.frm_form_field' ).find( '[name="' + field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) + '"]' ).val();
457 439 }
458 440 fieldID = fieldID.replace( '-typed', '' );
459 441 }
460 442
@@ -505,16 +487,16 @@
505 487 * @param {string|number} fileID
506 488 * @return {string} File input value.
507 489 */
508 490 function getFileVals( fileID ) {
509 - let val = '';
510 - const fileFields = document.querySelectorAll( `input[name="file${ fileID }"], input[name="file${ fileID }[]"], input[name^="item_meta[${ fileID }]"]` );
491 + let val = '',
492 + fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
511 493
512 - fileFields.forEach( function( field ) {
494 + fileFields.each( function() {
513 495 if ( val === '' ) {
514 - val = field.value;
496 + val = this.value;
515 497 }
516 - } );
498 + });
517 499 return val;
518 500 }
519 501
520 502 /**
@@ -522,10 +504,10 @@
522 504 * @param {Array} errors
523 505 * @return {void}
524 506 */
525 507 function checkUrlField( field, errors ) {
526 - let fieldID;
527 - const url = field.value;
508 + let fieldID,
509 + url = field.value;
528 510
529 511 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
530 512 fieldID = getFieldId( field, true );
531 513 if ( ! ( fieldID in errors ) ) {
@@ -566,10 +548,10 @@
566 548 * @param {Object} errors Errors data.
567 549 * @param {boolean} onSubmit Is `true` if the form is being submitted.
568 550 */
569 551 function checkEmailField( field, errors, onSubmit ) {
570 - const fieldID = getFieldId( field, true );
571 - const 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;
552 + const fieldID = getFieldId( field, true ),
553 + 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;
572 554
573 555 // validate the current field we're editing first
574 556 if ( '' !== field.value && pattern.test( field.value ) === false ) {
575 557 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
@@ -600,23 +582,24 @@
600 582 * @param {Array} errors
601 583 * @return {void}
602 584 */
603 585 function confirmField( field, errors ) {
604 - const fieldID = getFieldId( field, true );
605 - const strippedId = field.id.replace( 'conf_', '' );
606 - const strippedFieldID = fieldID.replace( 'conf_', '' );
607 - const confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
586 + let value, confirmValue, firstField,
587 + fieldID = getFieldId( field, true ),
588 + strippedId = field.id.replace( 'conf_', '' ),
589 + strippedFieldID = fieldID.replace( 'conf_', '' ),
590 + confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
608 591
609 - if ( ! confirmField || errors[ `conf_${ strippedFieldID }` ] !== undefined ) {
592 + if ( confirmField === null || typeof errors[ 'conf_' + strippedFieldID ] !== 'undefined' ) {
610 593 return;
611 594 }
612 595
613 596 if ( fieldID !== strippedFieldID ) {
614 - const firstField = document.getElementById( strippedId );
615 - const { value } = firstField;
616 - const confirmValue = confirmField.value;
597 + firstField = document.getElementById( strippedId );
598 + value = firstField.value;
599 + confirmValue = confirmField.value;
617 600 if ( value !== confirmValue ) {
618 - errors[ `conf_${ strippedFieldID }` ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
601 + errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
619 602 }
620 603 } else {
621 604 validateField( confirmField );
622 605 }
@@ -627,10 +610,10 @@
627 610 * @param {Array} errors
628 611 * @return {void}
629 612 */
630 613 function checkNumberField( field, errors ) {
631 - let fieldID;
632 - const number = field.value;
614 + let fieldID,
615 + number = field.value;
633 616
634 617 if ( number !== '' && isNaN( number / 1 ) !== false ) {
635 618 fieldID = getFieldId( field, true );
636 619 if ( ! ( fieldID in errors ) ) {
@@ -644,11 +627,11 @@
644 627 * @param {Array} errors
645 628 * @return {void}
646 629 */
647 630 function checkPatternField( field, errors ) {
648 - let fieldID;
649 - const text = field.value;
650 - let format = getFieldValidationMessage( field, 'pattern' );
631 + let fieldID,
632 + text = field.value,
633 + format = getFieldValidationMessage( field, 'pattern' );
651 634
652 635 if ( format !== '' && text !== '' ) {
653 636 fieldID = getFieldId( field, true );
654 637 if ( ! ( fieldID in errors ) ) {
@@ -656,9 +639,9 @@
656 639 if ( ! window.frmProForm.validateIntlPhoneInput( field ) ) {
657 640 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
658 641 }
659 642 } else {
660 - format = new RegExp( `^${ format }$`, 'i' );
643 + format = new RegExp( '^' + format + '$', 'i' );
661 644 if ( format.test( text ) === false ) {
662 645 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
663 646 }
664 647 }
@@ -671,11 +654,13 @@
671 654 *
672 655 * @since 6.5.1
673 656 */
674 657 function setSelectPlaceholderColor() {
675 - const selects = document.querySelectorAll( '.form-field select' );
676 - const styleElement = document.querySelector( '.with_frm_style' );
677 - const textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '';
658 + let selects = document.querySelectorAll( '.form-field select' ),
659 + styleElement = document.querySelector( '.with_frm_style' ),
660 + textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '',
661 + changeSelectColor;
662 +
678 663 // Exit if there are no select elements or the textColorDisabled property is missing
679 664 if ( ! selects.length || ! textColorDisabled ) {
680 665 return;
681 666 }
@@ -680,10 +665,10 @@
680 665 return;
681 666 }
682 667
683 668 // Function to change the color of a select element
684 - const changeSelectColor = function( select ) {
685 - if ( select.options[ select.selectedIndex ] && hasClass( select.options[ select.selectedIndex ], 'frm-select-placeholder' ) ) {
669 + changeSelectColor = function( select ) {
670 + if ( select.options[select.selectedIndex] && hasClass( select.options[select.selectedIndex], 'frm-select-placeholder' ) ) {
686 671 select.style.setProperty( 'color', textColorDisabled, 'important' );
687 672 } else {
688 673 select.style.color = '';
689 674 }
@@ -696,68 +681,57 @@
696 681
697 682 // Add an event listener for future changes
698 683 select.addEventListener( 'change', function() {
699 684 changeSelectColor( select );
700 - } );
701 - } );
685 + });
686 + });
702 687 }
703 688
704 689 /**
705 690 * @param {HTMLElement|jQuery} object
706 - *
707 - * @return {HTMLElement|false} Captcha element if there is an invisible recaptcha.
691 + * @return {boolean} True if there is an invisible recaptcha.
708 692 */
709 693 function hasInvisibleRecaptcha( object ) {
694 + let recaptcha, recaptchaID, alreadyChecked;
695 +
710 696 if ( isGoingToPrevPage( object ) ) {
711 697 return false;
712 698 }
713 699
714 - const form = object instanceof jQuery ? object.get( 0 ) : object;
715 - if ( ! form ) {
716 - return false;
717 - }
718 -
719 - const recaptcha = form.querySelector( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
720 - if ( recaptcha ) {
721 - const recaptchaID = recaptcha.dataset.rid;
722 - const alreadyChecked = grecaptcha.getResponse( recaptchaID );
700 + recaptcha = jQuery( object ).find( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
701 + if ( recaptcha.length ) {
702 + recaptchaID = recaptcha.data( 'rid' );
703 + alreadyChecked = grecaptcha.getResponse( recaptchaID );
723 704 if ( alreadyChecked.length === 0 ) {
724 705 return recaptcha;
725 706 }
726 707 }
727 -
728 708 return false;
729 709 }
730 710
731 711 /**
732 - * @param {HTMLElement} invisibleRecaptcha
733 - *
734 - * @return {void}
712 + * @param {jQuery} invisibleRecaptcha
735 713 */
736 714 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
737 - const recaptchaID = invisibleRecaptcha.dataset.rid;
715 + const recaptchaID = invisibleRecaptcha.data( 'rid' );
738 716 grecaptcha.reset( recaptchaID );
739 717 grecaptcha.execute( recaptchaID );
740 718 }
741 719
742 720 function validateRecaptcha( form, errors ) {
743 - const formEl = form instanceof jQuery ? form.get( 0 ) : form;
744 - if ( ! formEl ) {
745 - return errors;
746 - }
721 + let response;
747 722
748 - const recaptcha = formEl.querySelector( '.frm-g-recaptcha' );
749 - if ( ! recaptcha ) {
723 + const $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
724 + if ( ! $recaptcha.length ) {
750 725 return errors;
751 726 }
752 727
753 - const recaptchaID = recaptcha.dataset.rid;
754 - let response;
728 + const recaptchaID = $recaptcha.data( 'rid' );
755 729
756 730 try {
757 731 response = grecaptcha.getResponse( recaptchaID );
758 732 } catch ( e ) {
759 - if ( formEl.querySelector( 'input[name="recaptcha_checked"]' ) ) {
733 + if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
760 734 return errors;
761 735 }
762 736 response = '';
763 737 }
@@ -762,13 +736,11 @@
762 736 response = '';
763 737 }
764 738
765 739 if ( response.length === 0 ) {
766 - const fieldContainer = recaptcha.closest( '.frm_form_field' );
767 - if ( fieldContainer?.id ) {
768 - const fieldID = fieldContainer.id.replace( 'frm_field_', '' ).replace( '_container', '' );
769 - errors[ fieldID ] = '';
770 - }
740 + const fieldContainer = $recaptcha.closest( '.frm_form_field' );
741 + const fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
742 + errors[ fieldID ] = '';
771 743 }
772 744
773 745 return errors;
774 746 }
@@ -801,15 +773,15 @@
801 773 if ( null === errorHtml ) {
802 774 return msg;
803 775 }
804 776
805 - errorHtml = errorHtml.replace( /\+/g, '%20' );
806 - msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
807 - const fieldId = getFieldId( field, false );
808 - const split = fieldId.split( '-' );
777 + errorHtml = errorHtml.replace( /\+/g, '%20' );
778 + msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
779 + const fieldId = getFieldId( field, false );
780 + const split = fieldId.split( '-' );
809 781 const fieldIdParts = field.id.split( '_' );
810 782 fieldIdParts.shift(); // Drop the "field" value from the front.
811 - split[ 0 ] = fieldIdParts.join( '_' );
783 + split[0] = fieldIdParts.join( '_' );
812 784 const errorKey = split.join( '-' );
813 785 return msg.replace( '[key]', errorKey );
814 786 }
815 787
@@ -841,16 +813,18 @@
841 813 * @param {string} action
842 814 * @return {void}
843 815 */
844 816 function getFormErrors( object, action ) {
845 - const fieldsets = object.querySelectorAll( '.frm_form_field' );
846 - fieldsets.forEach( field => field.classList.add( 'frm_doing_ajax' ) );
817 + let fieldset, data, success, error, shouldTriggerEvent;
847 818
848 - const data = `${ jQuery( object ).serialize() }&action=frm_entries_${ action }&nonce=${ frm_js.nonce }`; // eslint-disable-line no-jquery/no-serialize
849 - const shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
819 + fieldset = jQuery( object ).find( '.frm_form_field' );
820 + fieldset.addClass( 'frm_doing_ajax' );
850 821
822 + data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
823 + shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
824 +
851 825 const doRedirect = response => {
852 - jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ] );
826 + jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ]);
853 827
854 828 if ( ! response.openInNewTab ) {
855 829 // We return here because we're redirecting there is no need to update content.
856 830 window.location = response.redirect;
@@ -859,14 +833,17 @@
859 833
860 834 // We don't return here because we're opening in a new tab, the old tab will still update.
861 835 const newTab = window.open( response.redirect, '_blank' );
862 836 if ( ! newTab && response.fallbackMsg && response.content ) {
863 - response.content = response.content.trim().replace( /(<\/div><\/div>)$/, ` ${ response.fallbackMsg }</div></div>` );
837 + response.content = response.content.trim().replace( /(<\/div><\/div>)$/, ' ' + response.fallbackMsg + '</div></div>' );
864 838 }
865 839 };
866 840
867 - const success = function( response ) {
868 - const defaultResponse = {
841 + success = function( response ) {
842 + let defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
843 + $fieldCont, key, inCollapsedSection, frmTrigger;
844 +
845 + defaultResponse = {
869 846 content: '',
870 847 errors: {},
871 848 pass: false
872 849 };
@@ -882,9 +859,9 @@
882 859 response = defaultResponse;
883 860 }
884 861 }
885 862
886 - if ( response.redirect !== undefined ) {
863 + if ( typeof response.redirect !== 'undefined' ) {
887 864 if ( shouldTriggerEvent ) {
888 865 triggerCustomEvent( object, 'frmSubmitEvent' );
889 866 return;
890 867 }
@@ -901,26 +878,27 @@
901 878 if ( 'string' === typeof response.content && response.content !== '' ) {
902 879 // the form or success message was returned
903 880
904 881 if ( shouldTriggerEvent ) {
905 - triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content } );
882 + triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content });
906 883 return;
907 884 }
908 885
909 886 removeSubmitLoading( jQuery( object ) );
910 - if ( frm_js.offset != -1 ) {
887 + if ( frm_js.offset != -1 ) { // eslint-disable-line camelcase
911 888 frmFrontForm.scrollMsg( jQuery( object ), false );
912 889 }
913 890
914 - const formIdInput = object.querySelector( 'input[name="form_id"]' );
915 - const formID = formIdInput ? formIdInput.value : '';
891 + formID = jQuery( object ).find( 'input[name="form_id"]' ).val();
916 892 response.content = response.content.replace( / frm_pro_form /g, ' frm_pro_form frm_no_hide ' );
917 - const replaceContent = jQuery( object ).closest( '.frm_forms' ); // eslint-disable-line no-jquery/no-closest
893 + replaceContent = jQuery( object ).closest( '.frm_forms' );
918 894 removeAddedScripts( replaceContent, formID );
919 - const delay = maybeSlideOut( replaceContent, response.content );
895 + delay = maybeSlideOut( replaceContent, response.content );
920 896
921 897 setTimeout(
922 898 function() {
899 + let container, input, previousInput;
900 +
923 901 afterFormSubmittedBeforeReplace( object, response );
924 902
925 903 replaceContent.replaceWith( response.content );
926 904
@@ -926,17 +904,25 @@
926 904
927 905 addUrlParam( response );
928 906
929 907 if ( typeof frmThemeOverride_frmAfterSubmit === 'function' ) { // eslint-disable-line camelcase
930 - const pageOrderInput = document.querySelector( `input[name="frm_page_order_${ formID }"]` );
931 - const pageOrder = pageOrderInput ? pageOrderInput.value : '';
932 - const tempDiv = document.createElement( 'div' );
933 - tempDiv.innerHTML = response.content;
934 - const formReturnedInput = tempDiv.querySelector( 'input[name="form_id"]' );
935 - const formReturned = formReturnedInput ? formReturnedInput.value : '';
908 + pageOrder = jQuery( 'input[name="frm_page_order_' + formID + '"]' ).val();
909 + formReturned = jQuery( response.content ).find( 'input[name="form_id"]' ).val();
936 910 frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
937 911 }
938 912
913 + if ( typeof response.recaptcha !== 'undefined' ) {
914 + container = jQuery( '#frm_form_' + formID + '_container' ).find( '.frm_fields_container' );
915 + input = '<input type="hidden" name="recaptcha_checked" value="' + response.recaptcha + '">';
916 + previousInput = container.find( 'input[name="recaptcha_checked"]' );
917 +
918 + if ( previousInput.length ) {
919 + previousInput.replaceWith( input );
920 + } else {
921 + container.append( input );
922 + }
923 + }
924 +
939 925 afterFormSubmitted( object, response );
940 926 },
941 927 delay
942 928 );
@@ -944,23 +930,22 @@
944 930 // errors were returned
945 931 removeSubmitLoading( jQuery( object ), 'enable' );
946 932
947 933 //show errors
948 - let contSubmit = true;
934 + contSubmit = true;
949 935 removeAllErrors();
950 936
951 - let $fieldCont = null;
937 + $fieldCont = null;
952 938
953 - for ( const key in response.errors ) {
954 - const fieldContEl = object.querySelector( `#frm_field_${ key }_container` );
955 - $fieldCont = fieldContEl ? jQuery( fieldContEl ) : jQuery();
939 + for ( key in response.errors ) {
940 + $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
956 941
957 942 if ( $fieldCont.length ) {
958 - if ( ! $fieldCont.is( ':visible' ) ) { // eslint-disable-line no-jquery/no-is
959 - const inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' ); // eslint-disable-line no-jquery/no-closest, formidable/no-jquery-variable-methods
943 + if ( ! $fieldCont.is( ':visible' ) ) {
944 + inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' );
960 945 if ( inCollapsedSection.length ) {
961 - let frmTrigger = inCollapsedSection.prev();
962 - if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) { // eslint-disable-line formidable/no-jquery-variable-methods
946 + frmTrigger = inCollapsedSection.prev();
947 + if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) {
963 948 // If the frmTrigger object is the section description, check to see if the previous element is the trigger
964 949 frmTrigger = frmTrigger.prev( '.frm_trigger' );
965 950 }
966 951 frmTrigger.trigger( 'click' );
@@ -966,9 +951,9 @@
966 951 frmTrigger.trigger( 'click' );
967 952 }
968 953 }
969 954
970 - if ( $fieldCont.is( ':visible' ) ) { // eslint-disable-line no-jquery/no-is
955 + if ( $fieldCont.is( ':visible' ) ) {
971 956 addFieldError( $fieldCont, key, response.errors );
972 957 contSubmit = false;
973 958 }
974 959 }
@@ -973,10 +958,11 @@
973 958 }
974 959 }
975 960 }
976 961
977 - object.querySelectorAll( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).forEach( function( captchaEl ) {
978 - const recaptchaID = captchaEl.dataset.rid;
962 + jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).each( function() {
963 + const $recaptcha = jQuery( this ),
964 + recaptchaID = $recaptcha.data( 'rid' );
979 965
980 966 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
981 967 if ( recaptchaID ) {
982 968 grecaptcha.reset( recaptchaID );
@@ -983,23 +969,22 @@
983 969 } else {
984 970 grecaptcha.reset();
985 971 }
986 972 }
987 -
988 973 if ( typeof hcaptcha !== 'undefined' && hcaptcha ) {
989 974 hcaptcha.reset();
990 975 }
991 - } );
976 + });
992 977
993 978 if ( window.turnstile ) {
994 - object.querySelectorAll( '.frm-cf-turnstile' ).forEach(
979 + object.querySelectorAll( '.cf-turnstile' ).forEach(
995 980 turnstileField => turnstileField.dataset.rid && turnstile.reset( turnstileField.dataset.rid )
996 981 );
997 982 }
998 983
999 - jQuery( document ).trigger( 'frmFormErrors', [ object, response ] );
984 + jQuery( document ).trigger( 'frmFormErrors', [ object, response ]);
1000 985
1001 - fieldsets.forEach( field => field.classList.remove( 'frm_doing_ajax' ) );
986 + fieldset.removeClass( 'frm_doing_ajax' );
1002 987 scrollToFirstField( object );
1003 988
1004 989 if ( contSubmit ) {
1005 990 object.submit();
@@ -1015,12 +1000,10 @@
1015 1000 object.submit();
1016 1001 }
1017 1002 };
1018 1003
1019 - const error = function() {
1020 - object.querySelectorAll( 'input[type="submit"], input[type="button"]' ).forEach(
1021 - button => button.disabled = false
1022 - );
1004 + error = function() {
1005 + jQuery( object ).find( 'input[type="submit"], input[type="button"]' ).prop( 'disabled', false );
1023 1006 object.submit();
1024 1007 };
1025 1008
1026 1009 postToAjaxUrl( object, data, success, error );
@@ -1026,20 +1009,22 @@
1026 1009 postToAjaxUrl( object, data, success, error );
1027 1010 }
1028 1011
1029 1012 function postToAjaxUrl( form, data, success, error ) {
1030 - let ajaxUrl = frm_js.ajax_url;
1031 - const action = form.getAttribute( 'action' );
1013 + let ajaxUrl, action, ajaxParams;
1032 1014
1033 - if ( 'string' === typeof action && action.includes( '?action=frm_forms_preview' ) ) {
1034 - ajaxUrl = action.split( '?action=frm_forms_preview' )[ 0 ];
1015 + ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
1016 + action = form.getAttribute( 'action' );
1017 +
1018 + if ( 'string' === typeof action && -1 !== action.indexOf( '?action=frm_forms_preview' ) ) {
1019 + ajaxUrl = action.split( '?action=frm_forms_preview' )[0];
1035 1020 }
1036 1021
1037 - const ajaxParams = {
1022 + ajaxParams = {
1038 1023 type: 'POST',
1039 1024 url: ajaxUrl,
1040 - data,
1041 - success
1025 + data: data,
1026 + success: success
1042 1027 };
1043 1028
1044 1029 if ( 'function' === typeof error ) {
1045 1030 ajaxParams.error = error;
@@ -1044,19 +1029,17 @@
1044 1029 if ( 'function' === typeof error ) {
1045 1030 ajaxParams.error = error;
1046 1031 }
1047 1032
1048 - jQuery.ajax( ajaxParams ); // eslint-disable-line no-jquery/no-ajax
1033 + jQuery.ajax( ajaxParams );
1049 1034 }
1050 1035
1051 1036 function afterFormSubmitted( object, response ) {
1052 - const tempDiv = document.createElement( 'div' );
1053 - tempDiv.innerHTML = response.content;
1054 - const formCompleted = tempDiv.querySelector( '.frm_message' );
1055 - if ( formCompleted ) {
1056 - jQuery( document ).trigger( 'frmFormComplete', [ object, response ] );
1037 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
1038 + if ( formCompleted.length ) {
1039 + jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
1057 1040 } else {
1058 - jQuery( document ).trigger( 'frmPageChanged', [ object, response ] );
1041 + jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
1059 1042 }
1060 1043 }
1061 1044
1062 1045 /**
@@ -1068,30 +1051,28 @@
1068 1051 * @param {Object} response The response from submitting the form with AJAX.
1069 1052 * @return {void}
1070 1053 */
1071 1054 function afterFormSubmittedBeforeReplace( object, response ) {
1072 - const tempDiv = document.createElement( 'div' );
1073 - tempDiv.innerHTML = response.content;
1074 - const formCompleted = tempDiv.querySelector( '.frm_message' );
1075 - if ( formCompleted ) {
1076 - triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response } );
1055 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
1056 + if ( formCompleted.length ) {
1057 + triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response });
1077 1058 }
1078 1059 }
1079 1060
1080 1061 function removeAddedScripts( formContainer, formID ) {
1081 - const endReplace = document.querySelectorAll( `.frm_end_ajax_${ formID }` );
1062 + const endReplace = jQuery( '.frm_end_ajax_' + formID );
1082 1063 if ( endReplace.length ) {
1083 - formContainer.nextUntil( `.frm_end_ajax_${ formID }` ).remove();
1084 - endReplace.forEach( el => el.remove() );
1064 + formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
1065 + endReplace.remove();
1085 1066 }
1086 1067 }
1087 1068
1088 1069 function maybeSlideOut( oldContent, newContent ) {
1089 - let c;
1090 - let newClass = 'frm_slideout';
1091 - if ( newContent.includes( ' frm_slide' ) ) {
1070 + let c,
1071 + newClass = 'frm_slideout';
1072 + if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
1092 1073 c = oldContent.children();
1093 - if ( newContent.includes( ' frm_going_back' ) ) {
1074 + if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
1094 1075 newClass += ' frm_going_back';
1095 1076 }
1096 1077 c.removeClass( 'frm_going_back' );
1097 1078 c.addClass( newClass );
@@ -1101,27 +1082,29 @@
1101 1082 }
1102 1083
1103 1084 function addUrlParam( response ) {
1104 1085 let url;
1105 - if ( history.pushState && response.page !== undefined ) {
1086 + if ( history.pushState && typeof response.page !== 'undefined' ) {
1106 1087 url = addQueryVar( 'frm_page', response.page );
1107 - window.history.pushState( { html: response.html }, '', `?${ url }` );
1088 + window.history.pushState({ 'html': response.html }, '', '?' + url );
1108 1089 }
1109 1090 }
1110 1091
1111 1092 function addQueryVar( key, value ) {
1093 + let kvp, i, x;
1094 +
1112 1095 key = encodeURI( key );
1113 1096 value = encodeURI( value );
1114 1097
1115 - const kvp = document.location.search.substr( 1 ).split( '&' );
1098 + kvp = document.location.search.substr( 1 ).split( '&' );
1116 1099
1117 - let i = kvp.length;
1100 + i = kvp.length;
1118 1101 while ( i-- ) {
1119 - const x = kvp[ i ].split( '=' );
1102 + x = kvp[i].split( '=' );
1120 1103
1121 - if ( x[ 0 ] == key ) {
1122 - x[ 1 ] = value;
1123 - kvp[ i ] = x.join( '=' );
1104 + if ( x[0] == key ) {
1105 + x[1] = value;
1106 + kvp[i] = x.join( '=' );
1124 1107 break;
1125 1108 }
1126 1109 }
1127 1110
@@ -1132,59 +1115,44 @@
1132 1115 return kvp.join( '&' );
1133 1116 }
1134 1117
1135 1118 function addFieldError( $fieldCont, key, jsErrors ) {
1136 - const container = $fieldCont instanceof jQuery ? $fieldCont.get( 0 ) : $fieldCont;
1119 + let input, id, describedBy, roleString;
1120 + if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
1121 + $fieldCont.addClass( 'frm_blank_field' );
1122 + input = $fieldCont.find( 'input, select, textarea' );
1123 + id = getErrorElementId( key, input.get( 0 ) );
1137 1124
1138 - if ( ! container || container.offsetParent === null ) {
1139 - return;
1140 - }
1125 + describedBy = input.attr( 'aria-describedby' );
1141 1126
1142 - container.classList.add( 'frm_blank_field' );
1143 - const input = container.querySelector( 'input, select, textarea' );
1144 - const id = getErrorElementId( key, input );
1145 -
1146 - let describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
1147 -
1148 - if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
1149 - frmThemeOverride_frmPlaceError( key, jsErrors );
1150 - } else {
1151 - let errorHtml;
1152 - if ( jsErrors[ key ].includes( '<div' ) ) {
1153 - errorHtml = jsErrors[ key ];
1127 + if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
1128 + frmThemeOverride_frmPlaceError( key, jsErrors );
1154 1129 } else {
1155 - const roleString = frm_js.include_alert_role ? 'role="alert"' : '';
1156 - errorHtml = `<div class="frm_error" ${ roleString } id="${ id }">${ jsErrors[ key ] }</div>`;
1157 - }
1158 - container.insertAdjacentHTML( 'beforeend', errorHtml );
1130 + if ( -1 !== jsErrors[key].indexOf( '<div' ) ) {
1131 + $fieldCont.append(
1132 + jsErrors[key]
1133 + );
1134 + } else {
1135 + roleString = frm_js.include_alert_role ? 'role="alert"' : ''; // eslint-disable-line camelcase
1136 + $fieldCont.append( '<div class="frm_error" ' + roleString + ' id="' + id + '">' + jsErrors[key] + '</div>' );
1137 + }
1159 1138
1160 - if ( input ) {
1161 - if ( ! describedBy ) {
1139 + if ( typeof describedBy === 'undefined' ) {
1162 1140 describedBy = id;
1163 - } else if ( ! describedBy.includes( id ) && ! describedBy.includes( 'frm_error_field_' ) ) {
1164 - const { errorFirst } = input.dataset;
1165 - if ( errorFirst === '0' ) {
1166 - describedBy = `${ describedBy } ${ id }`;
1141 + } else if ( describedBy.indexOf( id ) === -1 && describedBy.indexOf( 'frm_error_field_' ) === -1 ) {
1142 + if ( input.data( 'error-first' ) === 0 ) {
1143 + describedBy = describedBy + ' ' + id;
1167 1144 } else {
1168 - describedBy = `${ id } ${ describedBy }`;
1145 + describedBy = id + ' ' + describedBy;
1169 1146 }
1170 1147 }
1171 - input.setAttribute( 'aria-describedby', describedBy );
1148 +
1149 + input.attr( 'aria-describedby', describedBy );
1172 1150 }
1173 - }
1151 + input.attr( 'aria-invalid', true );
1174 1152
1175 - if ( input ) {
1176 - if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
1177 - const group = input.closest( '[role="radiogroup"], [role="group"]' );
1178 - if ( group ) {
1179 - group.setAttribute( 'aria-invalid', 'true' );
1180 - }
1181 - } else {
1182 - input.setAttribute( 'aria-invalid', 'true' );
1183 - }
1153 + jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
1184 1154 }
1185 -
1186 - jQuery( document ).trigger( 'frmAddFieldError', [ jQuery( container ), key, jsErrors ] );
1187 1155 }
1188 1156
1189 1157 /**
1190 1158 * Get the ID to use for an error element added when submitting with AJAX.
@@ -1195,11 +1163,11 @@
1195 1163 */
1196 1164 function getErrorElementId( key, input ) {
1197 1165 if ( isNaN( key ) || ! input || ! input.id ) {
1198 1166 // If key isn't a number, assume it's already in the right format.
1199 - return `frm_error_field_${ key }`;
1167 + return 'frm_error_field_' + key;
1200 1168 }
1201 - return `frm_error_${ input.id }`;
1169 + return 'frm_error_' + input.id;
1202 1170 }
1203 1171
1204 1172 /**
1205 1173 * Removes errors before validating with JS.
@@ -1204,56 +1172,36 @@
1204 1172 /**
1205 1173 * Removes errors before validating with JS.
1206 1174 * This prevents issues with stale errors that has since been fixed.
1207 1175 *
1208 - * @param {HTMLElement|jQuery} fieldCont Field container element.
1176 + * @param {Object} $fieldCont jQuery object.
1209 1177 * @return {void}
1210 1178 */
1211 - function removeFieldError( fieldCont ) {
1212 - const container = fieldCont instanceof jQuery ? fieldCont.get( 0 ) : fieldCont;
1213 - if ( ! container ) {
1214 - return;
1215 - }
1179 + function removeFieldError( $fieldCont ) {
1180 + const errorMessage = $fieldCont.find( '.frm_error' );
1181 + const errorId = errorMessage.attr( 'id' );
1182 + const input = $fieldCont.find( 'input, select, textarea' );
1183 + let describedBy = input.attr( 'aria-describedby' );
1216 1184
1217 - const errorMessage = container.querySelector( '.frm_error' );
1218 - const errorId = errorMessage ? errorMessage.id : '';
1219 - const input = container.querySelector( 'input, select, textarea' );
1220 - let describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
1221 -
1222 - container.classList.remove( 'frm_blank_field', 'has-error' );
1223 -
1224 - if ( input ) {
1225 - if ( 'true' === input.getAttribute( 'aria-invalid' ) ) {
1226 - input.setAttribute( 'aria-invalid', 'false' );
1227 - } else if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
1228 - const group = input.closest( '[role="radiogroup"], [role="group"]' );
1229 - if ( group ) {
1230 - group.setAttribute( 'aria-invalid', 'false' );
1231 - }
1232 - }
1185 + const fieldContainer = $fieldCont.get( 0 );
1186 + if ( fieldContainer && fieldContainer.classList ) {
1187 + fieldContainer.classList.remove( 'frm_blank_field', 'has-error' );
1233 1188 }
1234 1189
1235 - if ( errorMessage ) {
1236 - errorMessage.remove();
1237 - }
1190 + errorMessage.remove();
1191 + input.attr( 'aria-invalid', false );
1192 + input.removeAttr( 'aria-describedby' );
1238 1193
1239 - if ( input ) {
1240 - input.removeAttribute( 'aria-describedby' );
1241 - if ( describedBy ) {
1242 - describedBy = describedBy.replace( errorId, '' ).trim();
1243 - if ( describedBy ) {
1244 - input.setAttribute( 'aria-describedby', describedBy );
1245 - }
1246 - }
1194 + if ( typeof describedBy !== 'undefined' ) {
1195 + describedBy = describedBy.replace( errorId, '' );
1196 + input.attr( 'aria-describedby', describedBy );
1247 1197 }
1248 1198 }
1249 1199
1250 1200 function removeAllErrors() {
1251 - document.querySelectorAll( '.form-field' ).forEach( field => {
1252 - field.classList.remove( 'frm_blank_field', 'has-error' );
1253 - } );
1254 - document.querySelectorAll( '.form-field .frm_error' ).forEach( error => error.remove() );
1255 - document.querySelectorAll( '.frm_error_style' ).forEach( error => error.remove() );
1201 + jQuery( '.form-field' ).removeClass( 'frm_blank_field has-error' );
1202 + jQuery( '.form-field .frm_error' ).replaceWith( '' );
1203 + jQuery( '.frm_error_style' ).remove();
1256 1204 }
1257 1205
1258 1206 /**
1259 1207 * @param {HTMLElement|Object} object Form object.
@@ -1276,9 +1224,9 @@
1276 1224 disableSaveDraft( $object );
1277 1225 }
1278 1226
1279 1227 function showLoadingIndicator( $object ) {
1280 - if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) { // eslint-disable-line no-jquery/no-class, formidable/no-jquery-variable-methods
1228 + if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) {
1281 1229 addLoadingClass( $object );
1282 1230 $object.trigger( 'frmStartFormLoading' );
1283 1231 }
1284 1232 }
@@ -1285,44 +1233,45 @@
1285 1233
1286 1234 function addLoadingClass( $object ) {
1287 1235 const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1288 1236
1289 - $object.addClass( loadingClass ); // eslint-disable-line no-jquery/no-class, formidable/no-jquery-variable-methods
1237 + $object.addClass( loadingClass );
1290 1238 }
1291 1239
1292 1240 function isGoingToPrevPage( $object ) {
1293 - return typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object );
1241 + return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
1294 1242 }
1295 1243
1296 1244 function removeSubmitLoading( _, enable, processesRunning ) {
1245 + let loadingForm;
1246 +
1297 1247 if ( processesRunning > 0 ) {
1298 1248 return;
1299 1249 }
1300 1250
1301 - document.querySelectorAll( '.frm_loading_form' ).forEach( function( form ) {
1302 - form.classList.remove( 'frm_loading_form', 'frm_loading_prev' );
1303 - jQuery( form ).trigger( 'frmEndFormLoading' );
1251 + loadingForm = jQuery( '.frm_loading_form' );
1252 + loadingForm.removeClass( 'frm_loading_form' );
1253 + loadingForm.removeClass( 'frm_loading_prev' );
1304 1254
1305 - if ( enable === 'enable' ) {
1306 - enableSubmitButton( form );
1307 - enableSaveDraft( form );
1308 - }
1309 - } );
1255 + loadingForm.trigger( 'frmEndFormLoading' );
1256 +
1257 + if ( enable === 'enable' ) {
1258 + enableSubmitButton( loadingForm );
1259 + enableSaveDraft( loadingForm );
1260 + }
1310 1261 }
1311 1262
1312 1263 function showFileLoading( object ) {
1313 - const loading = document.getElementById( 'frm_loading' );
1314 - if ( ! loading ) {
1315 - return;
1264 + let fileval,
1265 + loading = document.getElementById( 'frm_loading' );
1266 + if ( loading !== null ) {
1267 + fileval = jQuery( object ).find( 'input[type=file]' ).val();
1268 + if ( typeof fileval !== 'undefined' && fileval !== '' ) {
1269 + setTimeout( function() {
1270 + jQuery( loading ).fadeIn( 'slow' );
1271 + }, 2000 );
1272 + }
1316 1273 }
1317 -
1318 - const fileInput = object.querySelector( 'input[type=file]' );
1319 - const fileval = fileInput ? fileInput.value : '';
1320 - if ( fileval !== '' ) {
1321 - setTimeout( function() {
1322 - jQuery( loading ).fadeIn( 'slow' ); // eslint-disable-line no-jquery/no-fade
1323 - }, 2000 );
1324 - }
1325 1274 }
1326 1275
1327 1276 /**********************************************
1328 1277 * General Helpers
@@ -1329,9 +1278,9 @@
1329 1278 *********************************************/
1330 1279
1331 1280 function confirmClick() {
1332 1281 /*jshint validthis:true */
1333 - const message = this.dataset.frmconfirm;
1282 + const message = jQuery( this ).data( 'frmconfirm' );
1334 1283 return confirm( message );
1335 1284 }
1336 1285
1337 1286 /**
@@ -1339,12 +1288,11 @@
1339 1288 * If this is a match, the User is autofilling the input on a Webkit browser.
1340 1289 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1341 1290 */
1342 1291 function onHoneypotFieldChange() {
1343 - /*jshint validthis:true */
1344 - const css = window.getComputedStyle( this ).boxShadow;
1345 - if ( css?.match( /inset/ ) ) {
1346 - this.remove();
1292 + const css = jQuery( this ).css( 'box-shadow' );
1293 + if ( css.match( /inset/ ) ) {
1294 + this.parentNode.removeChild( this );
1347 1295 }
1348 1296 }
1349 1297
1350 1298 /**
@@ -1367,10 +1315,10 @@
1367 1315 }
1368 1316
1369 1317 label.addEventListener( 'click', function() {
1370 1318 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1371 - } );
1372 - } );
1319 + });
1320 + });
1373 1321 }
1374 1322
1375 1323 /**
1376 1324 * Sets focus on a the first subfield of a combo field that has an error.
@@ -1395,22 +1343,23 @@
1395 1343 return false;
1396 1344 }
1397 1345
1398 1346 function checkForErrorsAndMaybeSetFocus() {
1399 - if ( ! frm_js.focus_first_error ) {
1347 + let errors, element, timeoutCallback;
1348 +
1349 + if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1400 1350 return;
1401 1351 }
1402 1352
1403 - const errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1353 + errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1404 1354 if ( ! errors.length ) {
1405 1355 return;
1406 1356 }
1407 1357
1408 - let element = errors[ 0 ];
1409 - let timeoutCallback;
1358 + element = errors[0];
1410 1359 do {
1411 1360 element = element.previousSibling;
1412 - if ( [ 'input', 'select', 'textarea' ].includes( element.nodeName.toLowerCase() ) ) {
1361 + if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1413 1362 focusInput( element );
1414 1363 break;
1415 1364 }
1416 1365
@@ -1417,9 +1366,9 @@
1417 1366 if ( maybeFocusOnComboSubField( element ) ) {
1418 1367 break;
1419 1368 }
1420 1369
1421 - if ( element.classList !== undefined ) {
1370 + if ( 'undefined' !== typeof element.classList ) {
1422 1371 if ( element.classList.contains( 'html-active' ) ) {
1423 1372 timeoutCallback = function() {
1424 1373 const textarea = element.querySelector( 'textarea' );
1425 1374 if ( null !== textarea ) {
@@ -1429,14 +1378,8 @@
1429 1378 } else if ( element.classList.contains( 'tmce-active' ) ) {
1430 1379 timeoutCallback = function() {
1431 1380 tinyMCE.activeEditor.focus();
1432 1381 };
1433 - } else if ( element.classList.contains( 'frm_opt_container' ) ) {
1434 - const firstInput = element.querySelector( 'input' );
1435 - if ( firstInput ) {
1436 - focusInput( firstInput );
1437 - break;
1438 - }
1439 1382 }
1440 1383
1441 1384 if ( 'function' === typeof timeoutCallback ) {
1442 1385 setTimeout( timeoutCallback, 0 );
@@ -1457,9 +1400,9 @@
1457 1400 function focusInput( input ) {
1458 1401 if ( input.offsetParent !== null ) {
1459 1402 input.focus();
1460 1403 } else {
1461 - triggerCustomEvent( document, 'frmMaybeDelayFocus', { input } );
1404 + triggerCustomEvent( document, 'frmMaybeDelayFocus', { input });
1462 1405 }
1463 1406 }
1464 1407
1465 1408 /**
@@ -1472,9 +1415,9 @@
1472 1415 * @param {Function} handler Handler.
1473 1416 * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1474 1417 */
1475 1418 function documentOn( event, selector, handler, options ) {
1476 - if ( options === undefined ) {
1419 + if ( 'undefined' === typeof options ) {
1477 1420 options = false;
1478 1421 }
1479 1422
1480 1423 document.addEventListener( event, function( e ) {
@@ -1481,9 +1424,9 @@
1481 1424 let target;
1482 1425
1483 1426 // loop parent nodes from the target to the delegation node.
1484 1427 for ( target = e.target; target && target != this; target = target.parentNode ) {
1485 - if ( target.matches && target.matches( selector ) ) {
1428 + if ( target && target.matches && target.matches( selector ) ) {
1486 1429 handler.call( target, e );
1487 1430 break;
1488 1431 }
1489 1432 }
@@ -1490,23 +1433,27 @@
1490 1433 }, options );
1491 1434 }
1492 1435
1493 1436 function initFloatingLabels() {
1494 - const selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1495 - const floatClass = 'frm_label_float_top';
1437 + let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1496 1438
1497 - const checkFloatLabel = function( input ) {
1498 - const container = input.closest( '.frm_inside_container' );
1439 + selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1440 + floatClass = 'frm_label_float_top';
1441 +
1442 + checkFloatLabel = function( input ) {
1443 + let container, shouldFloatTop, firstOpt;
1444 +
1445 + container = input.closest( '.frm_inside_container' );
1499 1446 if ( ! container ) {
1500 1447 return;
1501 1448 }
1502 1449
1503 - const shouldFloatTop = input.value || document.activeElement === input;
1450 + shouldFloatTop = input.value || document.activeElement === input;
1504 1451
1505 1452 container.classList.toggle( floatClass, shouldFloatTop );
1506 1453
1507 1454 if ( 'SELECT' === input.tagName ) {
1508 - const firstOpt = input.querySelector( 'option:first-child' );
1455 + firstOpt = input.querySelector( 'option:first-child' );
1509 1456
1510 1457 if ( shouldFloatTop ) {
1511 1458 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1512 1459 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
@@ -1518,10 +1465,10 @@
1518 1465 }
1519 1466 }
1520 1467 };
1521 1468
1522 - const checkDropdownLabel = function() {
1523 - document.querySelectorAll( `.frm-show-form .frm_inside_container:not(.${ floatClass }) select` ).forEach( function( input ) {
1469 + checkDropdownLabel = function() {
1470 + document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1524 1471 const firstOpt = input.querySelector( 'option:first-child' );
1525 1472
1526 1473 if ( firstOpt.textContent ) {
1527 1474 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
@@ -1526,9 +1473,9 @@
1526 1473 if ( firstOpt.textContent ) {
1527 1474 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1528 1475 firstOpt.textContent = '';
1529 1476 }
1530 - } );
1477 + });
1531 1478 };
1532 1479
1533 1480 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1534 1481 documentOn(
@@ -1538,12 +1485,16 @@
1538 1485 checkFloatLabel( event.target );
1539 1486 },
1540 1487 true
1541 1488 );
1542 - } );
1489 + });
1543 1490
1544 - const runOnLoad = function( firstLoad ) {
1545 - if ( firstLoad && document.activeElement && [ 'INPUT', 'SELECT', 'TEXTAREA' ].includes( document.activeElement.tagName ) ) {
1491 + jQuery( document ).on( 'change', selector, function( event ) {
1492 + checkFloatLabel( event.target );
1493 + });
1494 +
1495 + runOnLoad = function( firstLoad ) {
1496 + if ( firstLoad && document.activeElement && -1 !== [ 'INPUT', 'SELECT', 'TEXTAREA' ].indexOf( document.activeElement.tagName ) ) {
1546 1497 checkFloatLabel( document.activeElement );
1547 1498 } else if ( firstLoad ) {
1548 1499 document.querySelectorAll( '.frm_inside_container' ).forEach(
1549 1500 function( container ) {
@@ -1555,9 +1506,8 @@
1555 1506 );
1556 1507 }
1557 1508
1558 1509 checkDropdownLabel();
1559 - calcProductsTotal();
1560 1510 };
1561 1511
1562 1512 runOnLoad( true );
1563 1513
@@ -1562,13 +1512,13 @@
1562 1512 runOnLoad( true );
1563 1513
1564 1514 jQuery( document ).on( 'frmPageChanged', function( event ) {
1565 1515 runOnLoad();
1566 - } );
1516 + });
1567 1517
1568 1518 document.addEventListener( 'frm_after_start_over', function( event ) {
1569 1519 runOnLoad();
1570 - } );
1520 + });
1571 1521 }
1572 1522
1573 1523 function shouldUpdateValidityMessage( target ) {
1574 1524 if ( 'INPUT' !== target.nodeName ) {
@@ -1590,10 +1540,10 @@
1590 1540 return true;
1591 1541 }
1592 1542
1593 1543 function maybeClearCustomValidityMessage( event, field ) {
1594 - let key;
1595 - let isInvalid = false;
1544 + let key,
1545 + isInvalid = false;
1596 1546
1597 1547 if ( ! shouldUpdateValidityMessage( field ) ) {
1598 1548 return;
1599 1549 }
@@ -1605,9 +1555,9 @@
1605 1555 if ( 'valid' !== key && field.validity[ key ] === true ) {
1606 1556 isInvalid = true;
1607 1557 break;
1608 1558 }
1609 - }
1559 + };
1610 1560
1611 1561 if ( ! isInvalid ) {
1612 1562 field.setCustomValidity( '' );
1613 1563 }
@@ -1613,29 +1563,33 @@
1613 1563 }
1614 1564 }
1615 1565
1616 1566 function maybeShowNewTabFallbackMessage() {
1567 + let messageEl;
1568 +
1617 1569 if ( ! window.frmShowNewTabFallback ) {
1618 1570 return;
1619 1571 }
1620 1572
1621 - const messageEl = document.querySelector( `#frm_form_${ frmShowNewTabFallback.formId }_container .frm_message` );
1573 + messageEl = document.querySelector( '#frm_form_' + frmShowNewTabFallback.formId + '_container .frm_message' );
1622 1574 if ( ! messageEl ) {
1623 1575 return;
1624 1576 }
1625 1577
1626 - messageEl.insertAdjacentHTML( 'beforeend', ` ${ frmShowNewTabFallback.message }` );
1578 + messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1627 1579 }
1628 1580
1629 1581 function setCustomValidityMessage() {
1630 - const forms = document.getElementsByClassName( 'frm-show-form' );
1631 - const { length } = forms;
1582 + let forms, length, index;
1632 1583
1633 - for ( let index = 0; index < length; ++index ) {
1584 + forms = document.getElementsByClassName( 'frm-show-form' );
1585 + length = forms.length;
1586 +
1587 + for ( index = 0; index < length; ++index ) {
1634 1588 forms[ index ].addEventListener(
1635 1589 'invalid',
1636 1590 function( event ) {
1637 - const { target } = event;
1591 + const target = event.target;
1638 1592
1639 1593 if ( shouldUpdateValidityMessage( target ) ) {
1640 1594 target.setCustomValidity( target.dataset.invmsg );
1641 1595 }
@@ -1649,14 +1603,14 @@
1649 1603 window.addEventListener( 'pageshow', function( event ) {
1650 1604 if ( event.persisted ) {
1651 1605 document.querySelectorAll( '.frm_loading_form' ).forEach(
1652 1606 function( form ) {
1653 - enableSubmitButton( form );
1607 + enableSubmitButton( jQuery( form ) );
1654 1608 }
1655 1609 );
1656 1610 removeSubmitLoading();
1657 1611 }
1658 - } );
1612 + });
1659 1613 }
1660 1614
1661 1615 /**
1662 1616 * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
@@ -1677,9 +1631,9 @@
1677 1631 const uniqueKey = Array.from( window.crypto.getRandomValues( new Uint8Array( 8 ) ) )
1678 1632 .map( b => b.toString( 16 ).padStart( 2, '0' ) )
1679 1633 .join( '' );
1680 1634 const timestamp = Date.now().toString( 16 );
1681 - return `${ uniqueKey }-${ timestamp }`;
1635 + return uniqueKey + '-' + timestamp;
1682 1636 }
1683 1637
1684 1638 /**
1685 1639 * Animates the scroll position of the document.
@@ -1696,463 +1650,32 @@
1696 1650 document.documentElement.scrollTop = end;
1697 1651 return;
1698 1652 }
1699 1653
1654 + /* eslint-disable compat/compat */
1700 1655 const startTime = performance.now();
1701 - const step = currentTime => {
1656 + const step = ( currentTime ) => {
1702 1657 const progress = Math.min( ( currentTime - startTime ) / duration, 1 );
1703 - document.documentElement.scrollTop = start + ( ( end - start ) * progress );
1658 + document.documentElement.scrollTop = start + ( end - start ) * progress;
1704 1659 if ( progress < 1 ) {
1705 1660 requestAnimationFrame( step );
1706 1661 }
1707 1662 };
1708 1663 requestAnimationFrame( step );
1664 + /* eslint-enable compat/compat */
1709 1665 }
1710 1666
1711 - /**
1712 - * Make sure that the captcha label for a reCAPTCHA or Turnstile field matches the response input ID.
1713 - * This is determined dynamically, so we check for the ID after the input is rendered.
1714 - * hCaptcha is handled separately, in the frmCaptcha function as it is not rendered explicitly.
1715 - *
1716 - * @since 6.25.1
1717 - *
1718 - * @param {HTMLElement} captcha
1719 - * @return {void}
1720 - */
1721 - function maybeFixCaptchaLabel( captcha ) {
1722 - const form = captcha.closest( 'form' );
1723 - if ( ! form ) {
1724 - return;
1725 - }
1726 -
1727 - const label = form.querySelector( 'label[for="g-recaptcha-response"], label[for="cf-turnstile-response"]' );
1728 - const captchaResponse = form.querySelector( '[name="g-recaptcha-response"], [name="cf-turnstile-response"]' );
1729 -
1730 - if ( label && captchaResponse ) {
1731 - label.htmlFor = captchaResponse.id;
1732 - }
1733 - }
1734 -
1735 - /**
1736 - * Check to make sure the quantity field value is within the min and max values.
1737 - *
1738 - * @param {HTMLElement} input
1739 - * @return {number} The quantity value.
1740 - */
1741 - function checkQuantityFieldMinMax( input ) {
1742 - if ( '' === input.value ) {
1743 - // Leave the value if it is empty.
1744 - return 0;
1745 - }
1746 -
1747 - const val = parseFloat( input.value ? input.value.trim() : 0 );
1748 - if ( isNaN( val ) ) {
1749 - return 0;
1750 - }
1751 -
1752 - let max = input.hasAttribute( 'max' ) ? parseFloat( input.getAttribute( 'max' ) ) : 0;
1753 - let min = input.hasAttribute( 'min' ) ? parseFloat( input.getAttribute( 'min' ) ) : 0;
1754 -
1755 - max = isNaN( max ) ? 0 : max;
1756 - min = isNaN( min ) ? 0 : Math.max( 0, min );
1757 -
1758 - if ( val < min ) {
1759 - input.value = min;
1760 - return min;
1761 - }
1762 -
1763 - if ( 0 !== max && val > max ) {
1764 - input.value = max;
1765 - return max;
1766 - }
1767 -
1768 - return val;
1769 - }
1770 -
1771 - function triggerChange( input, fieldKey ) {
1772 - if ( fieldKey === undefined ) {
1773 - fieldKey = 'dependent';
1774 - }
1775 -
1776 - jQuery( input ).trigger( { type: 'change', selfTriggered: true, frmTriggered: fieldKey } );
1777 - }
1778 -
1779 - /**
1780 - * Calculates the total price.
1781 - *
1782 - * @param {Event|undefined} e The event object.
1783 - * @return {void}
1784 - */
1785 - function calcProductsTotal( e ) {
1786 - if ( 'object' === typeof frmProForm ) {
1787 - // Pro is installed, use the Pro JS.
1788 - return;
1789 - }
1790 -
1791 - if ( typeof __FRMCURR === 'undefined' ) {
1792 - return;
1793 - }
1794 -
1795 - const totalFields = document.querySelectorAll( '[data-frmtotal]' );
1796 - if ( ! totalFields.length ) {
1797 - return;
1798 - }
1799 -
1800 - const formTotals = [];
1801 -
1802 - totalFields.forEach( totalField => {
1803 - let total = 0;
1804 - const form = totalField.closest( 'form' );
1805 -
1806 - if ( ! form ) {
1807 - return;
1808 - }
1809 -
1810 - const formId = form.querySelector( 'input[name="form_id"]' ).value;
1811 - const currency = getCurrency( formId );
1812 -
1813 - if ( undefined !== formTotals[ formId ] ) {
1814 - total = formTotals[ formId ];
1815 - } else {
1816 - form.querySelectorAll( 'input[data-frmprice],select:has([data-frmprice])' ).forEach( function( input ) {
1817 - let quantity = 0;
1818 - let price = 0;
1819 - const isSingle = 'hidden' === input.type;
1820 -
1821 - if ( input.tagName === 'SELECT' ) {
1822 - if ( input.selectedIndex !== -1 ) {
1823 - price = input.options[ input.selectedIndex ].getAttribute( 'data-frmprice' );
1824 - }
1825 - } else {
1826 - if ( ! isSingle && ! input.matches( ':checked' ) ) {
1827 - return;
1828 - }
1829 - price = input.getAttribute( 'data-frmprice' );
1830 - }
1831 -
1832 - if ( ! price ) {
1833 - price = 0;
1834 - } else {
1835 - price = preparePrice( price, currency );
1836 - quantity = getQuantity( input );
1837 - price = parseFloat( quantity ) * parseFloat( price );
1838 - }
1839 -
1840 - if ( 'true' === input.getAttribute( 'data-frmdiscount' ) ) {
1841 - price = price * -1;
1842 - }
1843 -
1844 - total += price;
1845 - } );
1846 -
1847 - formTotals[ formId ] = total;
1848 - }
1849 -
1850 - total = isNaN( total ) ? 0 : total;
1851 -
1852 - // Set a decimal separator for currency if no default for it
1853 - currency.decimal_separator = currency.decimal_separator.trim(); // first remove unnecessary space(s)
1854 - if ( ! currency.decimal_separator.length ) {
1855 - currency.decimal_separator = '.';
1856 - }
1857 -
1858 - totalField.value = total;
1859 - total = normalizeTotal( total, currency );
1860 -
1861 - // because of e.g. fields that might be using this field for calculations
1862 - triggerChange( totalField );
1863 -
1864 - total = formatCurrency( total, currency );
1865 - const formatted = totalField.previousElementSibling;
1866 - if ( formatted?.matches( '.frm_total_formatted' ) ) {
1867 - // Use innerHTML so that currency symbols like Euros can render and not their encoded string value.
1868 - formatted.innerHTML = total;
1869 - return;
1870 - }
1871 -
1872 - const formattedEls = totalField.closest( '.frm_form_field' ).querySelectorAll( '.frm_total_formatted' );
1873 - formattedEls.forEach( formattedEl => {
1874 - // Use innerHTML so that currency symbols like Euros can render and not their encoded string value.
1875 - formattedEl.innerHTML = total;
1876 - } );
1877 - } );
1878 - }
1879 -
1880 - /**
1881 - * Round total and maybe add trailing zeros so formatCurrency has a proper format to work with.
1882 - *
1883 - * @param {number} total The total amount to normalize.
1884 - * @param {Object} currency The currency object containing decimal information.
1885 - * @return {string} The normalized total amount.
1886 - */
1887 - function normalizeTotal( total, currency ) {
1888 - const isLargeTotal = total > Number.MAX_SAFE_INTEGER;
1889 -
1890 - if ( ! isLargeTotal ) {
1891 - const { decimals } = currency;
1892 - total = decimals > 0 ? round10( total, decimals ) : Math.ceil( total );
1893 - }
1894 -
1895 - return maybeAddTrailingZeroToPrice( total, currency, isLargeTotal );
1896 - }
1897 -
1898 - function round10( value, decimals ) {
1899 - return Number( `${ Math.round( `${ value }e${ decimals }` ) }e-${ decimals }` );
1900 - }
1901 -
1902 - /**
1903 - * Format a numeric value according to the specified currency format settings.
1904 - *
1905 - * @param {string} total The numeric value to format.
1906 - * @param {Object} currency The currency formatting configuration.
1907 - * @return {string} The formatted currency string.
1908 - */
1909 - function formatCurrency( total, currency ) {
1910 - total = maybeAddTrailingZeroToPrice( total, currency );
1911 - if ( total.length && ( total[ total.length - 1 ] === '.' || total[ total.length - 1 ] === currency.decimal_separator ) ) {
1912 - total = total.substr( 0, total.length - 1 );
1913 - }
1914 -
1915 - total = maybeRemoveTrailingZerosFromPrice( total, currency );
1916 - total = addThousands( total, currency );
1917 -
1918 - const leftSymbol = currency.symbol_left ? ( currency.symbol_left + currency.symbol_padding ) : '';
1919 - const rightSymbol = currency.symbol_right ? ( currency.symbol_padding + currency.symbol_right ) : '';
1920 -
1921 - return `${ leftSymbol }${ total }${ rightSymbol }`;
1922 - }
1923 -
1924 - /**
1925 - * Gets currency from form id.
1926 - *
1927 - * @param {number} formId Form ID.
1928 - * @return {Object} Currency object.
1929 - */
1930 - function getCurrency( formId ) {
1931 - if ( undefined !== window.__FRMCURR && undefined !== window.__FRMCURR[ formId ] ) {
1932 - return window.__FRMCURR[ formId ];
1933 - }
1934 -
1935 - return {
1936 - symbol_left: '$',
1937 - symbol_right: '',
1938 - symbol_padding: '',
1939 - thousand_separator: ',',
1940 - decimal_separator: '.',
1941 - decimals: 2,
1942 - };
1943 - }
1944 -
1945 - /**
1946 - * Gets quantity.
1947 - *
1948 - * @param {HTMLElement} field The field element.
1949 - * @return {number} The quantity.
1950 - */
1951 - function getQuantity( field ) {
1952 - const fieldID = frmFrontForm.getFieldId( field, false );
1953 - if ( ! fieldID ) {
1954 - return 0;
1955 - }
1956 -
1957 - const quantityField = getQuantityField( field, fieldID );
1958 - if ( ! quantityField ) {
1959 - // If there is no quantity field, assume 1.
1960 - return 1;
1961 - }
1962 -
1963 - return checkQuantityFieldMinMax( quantityField );
1964 - }
1965 -
1966 - /**
1967 - * Gets quantity field.
1968 - *
1969 - * @param {HTMLElement} element The element.
1970 - * @param {number} fieldID The field ID.
1971 - * @return {HTMLElement|null} The quantity field.
1972 - */
1973 - function getQuantityField( element, fieldID ) {
1974 - const quantityFields = element.closest( 'form' ).querySelectorAll( '[data-frmproduct]' );
1975 - if ( ! quantityFields.length ) {
1976 - return null;
1977 - }
1978 -
1979 - fieldID = fieldID.toString();
1980 -
1981 - return Array.from( quantityFields ).find( element => {
1982 - let ids;
1983 -
1984 - ids = JSON.parse( element.getAttribute( 'data-frmproduct' ).trim() );
1985 - if ( '' === ids ) {
1986 - return false;
1987 - }
1988 -
1989 - // Convert to array if necessary because of existing fields that are already using single product fields.
1990 - ids = 'string' === typeof ids ? [ ids ] : ids;
1991 - return ids.includes( fieldID );
1992 - } );
1993 - }
1994 -
1995 - /**
1996 - * Prepare a price for calculation.
1997 - *
1998 - * @param {number|string} price The price to prepare.
1999 - * @param {Object} currency The currency object containing decimal information.
2000 - * @return {string} The prepared price.
2001 - */
2002 - function preparePrice( price, currency ) {
2003 - if ( ! price ) {
2004 - return 0;
2005 - }
2006 - price = `${ price }`; // convert to string just to be sure
2007 -
2008 - const regex = getRegexForPrice( currency );
2009 -
2010 - const matches = price.match( regex );
2011 - if ( null === matches ) {
2012 - return 0;
2013 - }
2014 -
2015 - price = matches.length ? matches[ matches.length - 1 ] : 0;
2016 - price = price.trim();
2017 -
2018 - // Fix issues with parsing Fr.15.00. The regex catches .15.00.
2019 - // This checks for the leading decimal and removes it.
2020 - if ( currency.decimal_separator === '.' && 3 === price.split( '.' ).length && price[ 0 ] === '.' ) {
2021 - price = price.substr( 1 );
2022 - }
2023 -
2024 - if ( price ) {
2025 - price = maybeUseDecimal( price, currency );
2026 - price = price.split( currency.thousand_separator ).join( '' ).replace( currency.decimal_separator, '.' );
2027 - }
2028 -
2029 - return price;
2030 - }
2031 -
2032 - /**
2033 - * @param {Object} currency The currency object.
2034 - * @return {RegExp} The regular expression object.
2035 - */
2036 - function getRegexForPrice( currency ) {
2037 - let regexString = '[0-9,.';
2038 -
2039 - if ( currency.thousand_separator !== '.' && currency.thousand_separator !== ',' ) {
2040 - regexString += currency.thousand_separator;
2041 - }
2042 - if ( currency.decimal_separator !== '.' && currency.decimal_separator !== ',' ) {
2043 - regexString += currency.decimal_separator;
2044 - }
2045 -
2046 - regexString += ']*\\.?\\,?[0-9]+';
2047 -
2048 - return new RegExp( regexString, 'g' );
2049 - }
2050 -
2051 - /**
2052 - * Maybe replace the decimal separator with the currency's decimal separator.
2053 - *
2054 - * @param {string} price The price string.
2055 - * @param {Object} currency The currency object.
2056 - * @return {string} The modified price string.
2057 - */
2058 - function maybeUseDecimal( price, currency ) {
2059 - let usedForDecimal;
2060 - let priceParts;
2061 - if ( '.' === currency.thousand_separator ) {
2062 - priceParts = price.split( '.' );
2063 - usedForDecimal = 2 === priceParts.length && 2 === priceParts[ 1 ].length;
2064 - if ( usedForDecimal ) {
2065 - price = price.replace( '.', currency.decimal_separator );
2066 - }
2067 - }
2068 - return price;
2069 - }
2070 -
2071 - /**
2072 - * Add trailing zeros to a price if necessary and replace the decimal separator.
2073 - *
2074 - * @param {number|string} price The price to format.
2075 - * @param {Object} currency The currency object containing the decimal separator.
2076 - * @param {boolean} [force=false] Whether to force processing even if the price is not a number.
2077 - * @return {string} The formatted price string.
2078 - */
2079 - function maybeAddTrailingZeroToPrice( price, currency, force = false ) {
2080 - if ( 'number' !== typeof price && ! force ) {
2081 - return price;
2082 - }
2083 -
2084 - price = String( price ); // first convert to string
2085 - const pos = price.indexOf( '.' );
2086 -
2087 - if ( pos === -1 ) {
2088 - price = `${ price }.`;
2089 -
2090 - for ( let n = 0; n < currency.decimals; ++n ) {
2091 - price += '0';
2092 - }
2093 - } else {
2094 - const decimalsString = price.substring( pos + 1 );
2095 - if ( decimalsString.length < currency.decimals ) {
2096 - if ( decimalsString.length < 2 ) {
2097 - price += '0';
2098 - }
2099 -
2100 - for ( let n = 2; n < currency.decimals; ++n ) {
2101 - price += '0';
2102 - }
2103 - }
2104 - }
2105 -
2106 - return price.replace( '.', currency.decimal_separator );
2107 - }
2108 -
2109 - /**
2110 - * Format a numeric string by adding thousand separators.
2111 - *
2112 - * @param {string|number} price The numeric value to format.
2113 - * @param {Object} options Formatting options.
2114 - * @param {string} options.decimal_separator Character used as decimal separator.
2115 - * @param {string} options.thousand_separator Character used as thousand separator.
2116 - *
2117 - * @return {string} The price string with thousand separators.
2118 - */
2119 - function addThousands( price, options ) {
2120 - const split = options.decimal_separator === ''
2121 - ? [ price.toString() ]
2122 - : price.split( options.decimal_separator );
2123 -
2124 - if ( options.thousand_separator ) {
2125 - split[ 0 ] = split[ 0 ].replace( /\B(?=(\d{3})+(?!\d))/g, options.thousand_separator );
2126 - }
2127 -
2128 - return split.join( options.decimal_separator );
2129 - }
2130 -
2131 - /**
2132 - * Maybe remove trailing zeros from a price string.
2133 - *
2134 - * @param {string} price The price string.
2135 - * @param {Object} currency The currency data.
2136 - *
2137 - * @return {string} The price string with trailing zeros removed.
2138 - */
2139 - function maybeRemoveTrailingZerosFromPrice( price, currency ) {
2140 - const split = price.split( currency.decimal_separator );
2141 - if ( 2 !== split.length || split[ 1 ].length <= currency.decimals ) {
2142 - return price;
2143 - }
2144 - if ( 0 === currency.decimals ) {
2145 - return split[ 0 ];
2146 - }
2147 - return `${ split[ 0 ] }${ currency.decimal_separator }${ split[ 1 ].substr( 0, currency.decimals ) }`;
2148 - }
2149 -
2150 1667 return {
2151 - init() {
1668 + init: function() {
2152 1669 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
2153 1670 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
2154 1671
1672 + jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
1673 + if ( jQuery( this ).val() === '' ) {
1674 + jQuery( this ).trigger( 'blur' );
1675 + }
1676 + });
1677 +
2155 1678 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 );
2156 1679
2157 1680 jQuery( document ).on( 'change', '.frm_verify[id^=field_]', onHoneypotFieldChange );
2158 1681
@@ -2179,13 +1702,8 @@
2179 1702 jQuery( document ).on(
2180 1703 'frmPageChanged',
2181 1704 destroyhCaptcha
2182 1705 );
2183 -
2184 - jQuery( document ).on( 'frmAfterAddRow frmAfterRemoveRow', calcProductsTotal );
2185 - jQuery( document ).on( 'change', '[type="checkbox"][data-frmprice],[type="radio"][data-frmprice],[type="hidden"][data-frmprice],select:has([data-frmprice])', calcProductsTotal );
2186 - jQuery( document ).on( 'keyup change', '[data-frmproduct],[type="text"][data-frmprice]', calcProductsTotal );
2187 - calcProductsTotal();
2188 1706 },
2189 1707
2190 1708 getFieldId,
2191 1709
@@ -2195,18 +1713,18 @@
2195 1713 * @param {HTMLElement} captcha
2196 1714 * @param {string} captchaSelector
2197 1715 * @return {void}
2198 1716 */
2199 - renderCaptcha( captcha, captchaSelector ) {
1717 + renderCaptcha: function( captcha, captchaSelector ) {
2200 1718 const rendered = captcha.getAttribute( 'data-rid' ) !== null;
2201 1719 if ( rendered ) {
2202 1720 return;
2203 1721 }
2204 1722
2205 - const size = captcha.getAttribute( 'data-size' );
1723 + const size = captcha.getAttribute( 'data-size' );
2206 1724 const params = {
2207 1725 sitekey: captcha.getAttribute( 'data-sitekey' ),
2208 - size,
1726 + size: size,
2209 1727 theme: captcha.getAttribute( 'data-theme' )
2210 1728 };
2211 1729
2212 1730 if ( size === 'invisible' ) {
@@ -2221,29 +1739,26 @@
2221 1739 frmFrontForm.afterRecaptcha( token, formID );
2222 1740 };
2223 1741 }
2224 1742
2225 - const activeCaptcha = getSelectedCaptcha( captchaSelector );
2226 - const captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? `#${ captcha.id }` : captcha.id;
2227 - const captchaID = activeCaptcha.render( captchaContainer, params );
1743 + const activeCaptcha = getSelectedCaptcha( captchaSelector );
1744 + const captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1745 + const captchaID = activeCaptcha.render( captchaContainer, params );
2228 1746
2229 1747 captcha.setAttribute( 'data-rid', captchaID );
2230 -
2231 - maybeFixCaptchaLabel( captcha );
2232 1748 },
2233 1749
2234 - afterSingleRecaptcha() {
2235 - const recaptcha = document.querySelector( '.frm-show-form .g-recaptcha' );
2236 - const object = recaptcha ? recaptcha.closest( 'form' ) : null;
1750 + afterSingleRecaptcha: function() {
1751 + const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
2237 1752 frmFrontForm.submitFormNow( object );
2238 1753 },
2239 1754
2240 - afterRecaptcha( _, formID ) {
2241 - const object = document.querySelector( `#frm_form_${ formID }_container form` );
1755 + afterRecaptcha: function( _, formID ) {
1756 + const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
2242 1757 frmFrontForm.submitFormNow( object );
2243 1758 },
2244 1759
2245 - submitForm( e ) {
1760 + submitForm: function( e ) {
2246 1761 frmFrontForm.submitFormManual( e, this );
2247 1762 },
2248 1763
2249 1764 /**
@@ -2250,10 +1765,21 @@
2250 1765 * @param {Event} e
2251 1766 * @param {HTMLElement} object The form object that is being submitted.
2252 1767 * @return {void}
2253 1768 */
2254 - submitFormManual( e, object ) {
2255 - if ( document.body.classList.contains( 'wp-admin' ) && ! object.closest( '.frmapi-form' ) ) {
1769 + submitFormManual: function( e, object ) {
1770 + let isPro, errors,
1771 + invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1772 + classList = object.className.trim().split( /\s+/gi );
1773 +
1774 + if ( classList && invisibleRecaptcha.length < 1 ) {
1775 + isPro = classList.indexOf( 'frm_pro_form' ) > -1;
1776 + if ( ! isPro ) {
1777 + return;
1778 + }
1779 + }
1780 +
1781 + if ( jQuery( 'body' ).hasClass( 'wp-admin' ) && jQuery( object ).closest( '.frmapi-form' ).length < 1 ) {
2256 1782 return;
2257 1783 }
2258 1784
2259 1785 e.preventDefault();
@@ -2261,29 +1787,27 @@
2261 1787 if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
2262 1788 return;
2263 1789 }
2264 1790
2265 - const errors = frmFrontForm.validateFormSubmit( object );
1791 + errors = frmFrontForm.validateFormSubmit( object );
2266 1792 if ( Object.keys( errors ).length !== 0 ) {
2267 1793 return;
2268 1794 }
2269 1795
2270 - const invisibleRecaptcha = hasInvisibleRecaptcha( object );
2271 -
2272 - if ( invisibleRecaptcha ) {
1796 + if ( invisibleRecaptcha.length ) {
2273 1797 showLoadingIndicator( jQuery( object ) );
2274 1798 executeInvisibleRecaptcha( invisibleRecaptcha );
2275 1799 } else {
1800 +
2276 1801 showSubmitLoading( jQuery( object ) );
2277 1802
2278 - frmFrontForm.submitFormNow( object );
1803 + frmFrontForm.submitFormNow( object, classList );
2279 1804 }
2280 1805 },
2281 1806
2282 - submitFormNow( object ) {
2283 - let hasFileFields;
2284 - let antispamInput;
2285 - const classList = object.className.trim().split( /\s+/gi );
1807 + submitFormNow: function( object ) {
1808 + let hasFileFields, antispamInput,
1809 + classList = object.className.trim().split( /\s+/gi );
2286 1810
2287 1811 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
2288 1812 // include the antispam token on form submit.
2289 1813 antispamInput = document.createElement( 'input' );
@@ -2289,24 +1813,24 @@
2289 1813 antispamInput = document.createElement( 'input' );
2290 1814 antispamInput.type = 'hidden';
2291 1815 antispamInput.name = 'antispam_token';
2292 1816 antispamInput.value = object.getAttribute( 'data-token' );
2293 - object.append( antispamInput );
1817 + object.appendChild( antispamInput );
2294 1818 }
2295 1819
2296 1820 // Add a unique ID, used for duplicate checks.
2297 1821 const uniqueIDInput = document.createElement( 'input' );
2298 - uniqueIDInput.type = 'hidden';
2299 - uniqueIDInput.name = 'unique_id';
1822 + uniqueIDInput.type = 'hidden';
1823 + uniqueIDInput.name = 'unique_id';
2300 1824 uniqueIDInput.value = getUniqueKey();
2301 - object.append( uniqueIDInput );
1825 + object.appendChild( uniqueIDInput );
2302 1826
2303 - if ( classList.includes( 'frm_ajax_submit' ) ) {
2304 - const fileInputs = object.querySelectorAll( 'input[type="file"]' );
2305 - hasFileFields = Array.from( fileInputs ).filter( input => !! input.value ).length;
1827 + if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1828 + hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1829 + return !! this.value;
1830 + }).length;
2306 1831 if ( hasFileFields < 1 ) {
2307 - const actionInput = object.querySelector( 'input[name="frm_action"]' );
2308 - const action = actionInput ? actionInput.value : '';
1832 + const action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
2309 1833 frmFrontForm.checkFormErrors( object, action );
2310 1834 } else {
2311 1835 object.submit();
2312 1836 }
@@ -2319,11 +1843,10 @@
2319 1843 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
2320 1844 *
2321 1845 * @return {Array} List of errors.
2322 1846 */
2323 - validateFormSubmit( object ) {
2324 - const form = object instanceof jQuery ? object.get( 0 ) : object;
2325 - if ( typeof tinyMCE !== 'undefined' && form?.querySelector( '.wp-editor-wrap' ) ) {
1847 + validateFormSubmit: function( object ) {
1848 + if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
2326 1849 tinyMCE.triggerSave();
2327 1850 }
2328 1851
2329 1852 jsErrors = [];
@@ -2342,19 +1865,16 @@
2342 1865 /**
2343 1866 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
2344 1867 * @return {Array} List of errors.
2345 1868 */
2346 - getAjaxFormErrors( object ) {
2347 - let customErrors;
2348 - let key;
2349 - const form = object instanceof jQuery ? object.get( 0 ) : object;
1869 + getAjaxFormErrors: function( object ) {
1870 + let customErrors, key;
2350 1871
2351 1872 jsErrors = validateForm( object );
2352 1873 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
2353 - const actionInput = form ? form.querySelector( 'input[name="frm_action"]' ) : null;
2354 - const action = actionInput ? actionInput.value : '';
1874 + const action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
2355 1875 customErrors = frmThemeOverride_jsErrors( action, object );
2356 - if ( Object.keys( customErrors ).length ) {
1876 + if ( Object.keys( customErrors ).length ) {
2357 1877 for ( key in customErrors ) {
2358 1878 jsErrors[ key ] = customErrors[ key ];
2359 1879 }
2360 1880 }
@@ -2362,9 +1882,9 @@
2362 1882
2363 1883 triggerCustomEvent( document, 'frm_get_ajax_form_errors', {
2364 1884 formEl: object,
2365 1885 errors: jsErrors
2366 - } );
1886 + });
2367 1887
2368 1888 return jsErrors;
2369 1889 },
2370 1890
@@ -2371,18 +1891,17 @@
2371 1891 /**
2372 1892 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
2373 1893 * @return {void}
2374 1894 */
2375 - addAjaxFormErrors( object ) {
2376 - let key;
2377 - const form = object instanceof jQuery ? object.get( 0 ) : object;
1895 + addAjaxFormErrors: function( object ) {
1896 + let key, $fieldCont;
2378 1897 removeAllErrors();
2379 1898
2380 1899 for ( key in jsErrors ) {
2381 - const fieldCont = form ? form.querySelector( `#frm_field_${ key }_container` ) : null;
1900 + $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
2382 1901
2383 - if ( fieldCont ) {
2384 - addFieldError( fieldCont, key, jsErrors );
1902 + if ( $fieldCont.length ) {
1903 + addFieldError( $fieldCont, key, jsErrors );
2385 1904 } else {
2386 1905 // we are unable to show the error, so remove it
2387 1906 delete jsErrors[ key ];
2388 1907 }
@@ -2396,28 +1915,23 @@
2396 1915 checkRequiredField,
2397 1916 showSubmitLoading,
2398 1917 removeSubmitLoading,
2399 1918
2400 - scrollToID( id ) {
1919 + scrollToID: function( id ) {
2401 1920 const object = jQuery( document.getElementById( id ) );
2402 1921 frmFrontForm.scrollMsg( object, false );
2403 1922 },
2404 1923
2405 - scrollMsg( id, object, animate ) {
2406 - let newPos;
2407 - let screenTop;
2408 - let screenBottom;
2409 - let scrollObj = '';
2410 -
2411 - if ( object === undefined ) {
2412 - scrollObj = jQuery( document.getElementById( `frm_form_${ id }_container` ) );
1924 + scrollMsg: function( id, object, animate ) {
1925 + let newPos, m, b, screenTop, screenBottom,
1926 + scrollObj = '';
1927 + if ( typeof object === 'undefined' ) {
1928 + scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
2413 1929 if ( scrollObj.length < 1 ) {
2414 1930 return;
2415 1931 }
2416 1932 } else if ( typeof id === 'string' ) {
2417 - const formEl = object instanceof jQuery ? object.get( 0 ) : object;
2418 - const fieldEl = formEl ? formEl.querySelector( `#frm_field_${ id }_container` ) : null;
2419 - scrollObj = fieldEl ? jQuery( fieldEl ) : jQuery();
1933 + scrollObj = jQuery( object ).find( '#frm_field_' + id + '_container' );
2420 1934 } else {
2421 1935 scrollObj = id;
2422 1936 }
2423 1937
@@ -2422,17 +1936,17 @@
2422 1936 }
2423 1937
2424 1938 jQuery( scrollObj ).trigger( 'focus' );
2425 1939 newPos = scrollObj.offset().top;
2426 - if ( ! newPos || frm_js.offset === '-1' ) {
1940 + if ( ! newPos || frm_js.offset === '-1' ) { // eslint-disable-line camelcase
2427 1941 return;
2428 1942 }
2429 - newPos = newPos - frm_js.offset;
1943 + newPos = newPos - frm_js.offset; // eslint-disable-line camelcase
2430 1944
2431 - const docMarginTop = getComputedStyle( document.documentElement ).marginTop;
2432 - const bodyMarginTop = getComputedStyle( document.body ).marginTop;
2433 - if ( docMarginTop || bodyMarginTop ) {
2434 - newPos = newPos - parseInt( docMarginTop ) - parseInt( bodyMarginTop );
1945 + m = jQuery( 'html' ).css( 'margin-top' );
1946 + b = jQuery( 'body' ).css( 'margin-top' );
1947 + if ( m || b ) {
1948 + newPos = newPos - parseInt( m ) - parseInt( b );
2435 1949 }
2436 1950
2437 1951 if ( newPos && window.innerHeight ) {
2438 1952 screenTop = document.documentElement.scrollTop || document.body.scrollTop;
@@ -2439,9 +1953,9 @@
2439 1953 screenBottom = screenTop + window.innerHeight;
2440 1954
2441 1955 if ( newPos > screenBottom || newPos < screenTop ) {
2442 1956 // Not in view
2443 - if ( animate === undefined ) {
1957 + if ( typeof animate === 'undefined' ) {
2444 1958 document.documentElement.scrollTop = newPos;
2445 1959 } else {
2446 1960 animateScroll( screenTop, newPos, 500 );
2447 1961 }
@@ -2449,13 +1963,13 @@
2449 1963 }
2450 1964 }
2451 1965 },
2452 1966
2453 - fieldValueChanged( e ) {
1967 + fieldValueChanged: function( e ) {
2454 1968 /*jshint validthis:true */
2455 1969
2456 1970 const fieldId = frmFrontForm.getFieldId( this, false );
2457 - if ( ! fieldId ) {
1971 + if ( ! fieldId || typeof fieldId === 'undefined' ) {
2458 1972 return;
2459 1973 }
2460 1974
2461 1975 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
@@ -2461,9 +1975,9 @@
2461 1975 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
2462 1976 return;
2463 1977 }
2464 1978
2465 - jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ] );
1979 + jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ]);
2466 1980
2467 1981 if ( e.selfTriggered !== true ) {
2468 1982 maybeValidateChange( this );
2469 1983 }
@@ -2468,9 +1982,9 @@
2468 1982 maybeValidateChange( this );
2469 1983 }
2470 1984 },
2471 1985
2472 - escapeHtml( text ) {
1986 + escapeHtml: function( text ) {
2473 1987 console.warn( 'DEPRECATED: function frmFrontForm.escapeHtml in v6.17' );
2474 1988 return text
2475 1989 .replace( /&/g, '&amp;' )
2476 1990 .replace( /</g, '&lt;' )
@@ -2478,9 +1992,31 @@
2478 1992 .replace( /"/g, '&quot;' )
2479 1993 .replace( /'/g, '&#039;' );
2480 1994 },
2481 1995
2482 - triggerCustomEvent,
1996 + /**
1997 + * This function was used in old back end code in v2.0.
1998 + *
1999 + * @param {string} classes
2000 + * @return {void}
2001 + */
2002 + invisible: function( classes ) {
2003 + console.warn( 'DEPRECATED: function frmFrontForm.invisible in v6.16.3' );
2004 + jQuery( classes ).css( 'visibility', 'hidden' );
2005 + },
2006 +
2007 + /**
2008 + * This function was used in old back end code in v2.0.
2009 + *
2010 + * @param {string} classes
2011 + * @return {void}
2012 + */
2013 + visible: function( classes ) {
2014 + console.warn( 'DEPRECATED: function frmFrontForm.visible in v6.16.3' );
2015 + jQuery( classes ).css( 'visibility', 'visible' );
2016 + },
2017 +
2018 + triggerCustomEvent: triggerCustomEvent,
2483 2019 documentOn
2484 2020 };
2485 2021 }
2486 2022
@@ -2487,49 +2023,30 @@
2487 2023 window.frmFrontForm = frmFrontFormJS();
2488 2024
2489 2025 jQuery( document ).ready( function() {
2490 2026 frmFrontForm.init();
2491 -} );
2027 +});
2492 2028
2493 2029 function frmRecaptcha() {
2494 2030 frmCaptcha( '.frm-g-recaptcha' );
2495 2031 }
2496 2032
2497 -function frmHcaptcha() {
2498 - frmCaptcha( '.h-captcha' );
2499 -}
2500 -
2501 2033 function frmTurnstile() {
2502 - frmCaptcha( '.frm-cf-turnstile' );
2034 + frmCaptcha( '.cf-turnstile' );
2503 2035 }
2504 2036
2505 2037 function frmCaptcha( captchaSelector ) {
2506 - if ( '.h-captcha' === captchaSelector ) {
2507 - // hCaptcha is still rendered implicitly, so we only want to handle the label and exit early.
2508 - // Match the hcaptcha labels to the hcaptcha response fields.
2509 - const captchaLabels = document.querySelectorAll( 'label[for="h-captcha-response"]' );
2510 - if ( captchaLabels.length ) {
2511 - captchaLabels.forEach( label => {
2512 - const captchaResponse = label.closest( 'form' )?.querySelector( '[name="h-captcha-response"]' );
2513 - if ( captchaResponse ) {
2514 - label.htmlFor = captchaResponse.id;
2515 - }
2516 - } );
2517 - }
2518 - return;
2519 - }
2520 -
2521 2038 let c;
2522 2039 const captchas = document.querySelectorAll( captchaSelector );
2523 - const cl = captchas.length;
2040 + const cl = captchas.length;
2524 2041 for ( c = 0; c < cl; c++ ) {
2525 - const closestForm = captchas[ c ].closest( 'form' );
2042 + const closestForm = captchas[c].closest( 'form' );
2526 2043 const formIsVisible = closestForm && closestForm.offsetParent !== null;
2527 - const captcha = captchas[ c ];
2044 + const captcha = captchas[c];
2528 2045 if ( ! formIsVisible ) {
2529 2046 // If the form is not visible, try again later in 400ms.
2530 2047 // This fixes issues where the form fades visible on page load.
2531 - // Or when the form is inside of a modal.
2048 + // Or whne the form is inside of a modal.
2532 2049 const interval = setInterval(
2533 2050 function() {
2534 2051 if ( closestForm && closestForm.offsetParent !== null ) {
2535 2052 frmFrontForm.renderCaptcha( captcha, captchaSelector );
@@ -2547,9 +2064,9 @@
2547 2064 function getSelectedCaptcha( captchaSelector ) {
2548 2065 if ( captchaSelector === '.frm-g-recaptcha' ) {
2549 2066 return grecaptcha;
2550 2067 }
2551 - if ( document.querySelector( '.frm-cf-turnstile' ) ) {
2068 + if ( document.querySelector( '.cf-turnstile' ) ) {
2552 2069 return turnstile;
2553 2070 }
2554 2071 return hcaptcha;
2555 2072 }