PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
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 +662 -217 6.28 → trunk View file →
@@ -1,13 +1,9 @@
1 1 /* exported frmRecaptcha, frmAfterRecaptcha */
2 -/* eslint-disable prefer-const */
3 2
4 3 function frmFrontFormJS() {
5 4 'use strict';
6 5
7 - /*global jQuery:false, frm_js, grecaptcha, hcaptcha, turnstile, frmProForm, tinyMCE */
8 - /*global frmThemeOverride_jsErrors, frmThemeOverride_frmPlaceError, frmThemeOverride_frmAfterSubmit */
9 -
10 6 let jsErrors = [];
11 7
12 8 /**
13 9 * Triggers custom JS event.
@@ -36,11 +32,12 @@
36 32 * @param {boolean} fullID
37 33 * @return {string|number} Field ID.
38 34 */
39 35 function getFieldId( field, fullID ) {
40 - let nameParts, fieldId,
41 - isRepeating = false,
42 - fieldName = '';
36 + let nameParts;
37 + let fieldId;
38 + let isRepeating = false;
39 + let fieldName = '';
43 40
44 41 if ( field instanceof jQuery ) {
45 42 field = field.get( 0 );
46 43 }
@@ -46,9 +43,9 @@
46 43 }
47 44
48 45 fieldName = field.name;
49 46
50 - if ( typeof fieldName === 'undefined' ) {
47 + if ( fieldName === undefined ) {
51 48 fieldName = '';
52 49 }
53 50
54 51 if ( fieldName === '' ) {
@@ -53,9 +50,9 @@
53 50
54 51 if ( fieldName === '' ) {
55 52 fieldName = field.getAttribute( 'data-name' );
56 53
57 - if ( typeof fieldName === 'undefined' ) {
54 + if ( fieldName === undefined ) {
58 55 fieldName = '';
59 56 }
60 57
61 58 if ( fieldName !== '' && fieldName ) {
@@ -83,9 +80,9 @@
83 80 return 0;
84 81 }
85 82
86 83 // Check if 'this' is in a repeating section
87 - if ( document.querySelector( 'input[name="item_meta[' + fieldId + '][form]"]' ) ) {
84 + if ( document.querySelector( `input[name="item_meta[${ fieldId }][form]"]` ) ) {
88 85 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
89 86 fieldId = nameParts[ 2 ].replace( '[', '' );
90 87 isRepeating = true;
91 88 }
@@ -103,11 +100,11 @@
103 100
104 101 if ( fullID === true ) {
105 102 // For use in the container div id
106 103 if ( fieldId === nameParts[ 0 ] ) {
107 - fieldId = fieldId + '-' + nameParts[ 1 ].replace( '[', '' );
104 + fieldId = `${ fieldId }-${ nameParts[ 1 ].replace( '[', '' ) }`;
108 105 } else {
109 - fieldId = fieldId + '-' + nameParts[ 0 ] + '-' + nameParts[ 1 ].replace( '[', '' );
106 + fieldId = `${ fieldId }-${ nameParts[ 0 ] }-${ nameParts[ 1 ].replace( '[', '' ) }`;
110 107 }
111 108 }
112 109
113 110 return fieldId;
@@ -250,15 +247,14 @@
250 247 * @param {Array} errors
251 248 * @return {void}
252 249 */
253 250 function checkValidity( field, errors ) {
254 - let fieldID;
255 251 if ( 'object' !== typeof field.validity || false !== field.validity.valid ) {
256 252 return;
257 253 }
258 254
259 - fieldID = getFieldId( field, true );
260 - if ( 'undefined' === typeof errors[ fieldID ] ) {
255 + const fieldID = getFieldId( field, true );
256 + if ( errors[ fieldID ] === undefined ) {
261 257 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
262 258 }
263 259
264 260 if ( 'function' === typeof field.reportValidity ) {
@@ -285,11 +281,14 @@
285 281 if ( field.type === 'url' ) {
286 282 maybeAddHttpsToUrl( field );
287 283 }
288 284 const form = field.closest( 'form' );
289 - if ( form && hasClass( form, 'frm_js_validate' ) ) {
290 - validateField( field );
285 + if ( ! form ) {
286 + return;
291 287 }
288 +
289 + // Removing stale errors is universal. Adding errors only happens when JS validation is enabled.
290 + validateField( field, hasClass( form, 'frm_js_validate' ) );
292 291 }
293 292
294 293 /**
295 294 * @param {HTMLElement} field
@@ -297,9 +296,9 @@
297 296 function maybeAddHttpsToUrl( field ) {
298 297 const url = field.value;
299 298 const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
300 299 if ( field.value !== '' && matches === null ) {
301 - field.value = 'https://' + url;
300 + field.value = `https://${ url }`;
302 301 }
303 302 }
304 303
305 304 /**
@@ -304,14 +303,20 @@
304 303
305 304 /**
306 305 * Validate a field with JS.
307 306 *
307 + * Removing stale errors is universal. Adding errors only happens when JS validation is enabled.
308 + *
309 + * @since 6.32 Added the `addErrors` parameter.
310 + *
308 311 * @param {HTMLElement} field
312 + * @param {boolean} addErrors Whether to add new errors. Defaults to `true`.
309 313 *
310 314 * @return {void}
311 315 */
312 - function validateField( field ) {
313 - let errors, key;
316 + function validateField( field, addErrors = true ) {
317 + let errors;
318 + let key;
314 319
315 320 errors = [];
316 321 const fieldContainer = field.closest( '.frm_form_field' );
317 322
@@ -327,13 +332,20 @@
327 332 if ( errors.length < 1 ) {
328 333 validateFieldValue( field, errors, false );
329 334 }
330 335
331 - removeFieldError( fieldContainer );
332 - if ( Object.keys( errors ).length > 0 ) {
333 - for ( key in errors ) {
334 - addFieldError( fieldContainer, key, errors );
336 + const hasErrors = Object.keys( errors ).length > 0;
337 +
338 + if ( addErrors ) {
339 + removeFieldError( fieldContainer );
340 + if ( hasErrors ) {
341 + for ( key in errors ) {
342 + addFieldError( fieldContainer, key, errors );
343 + }
335 344 }
345 + } else if ( ! hasErrors ) {
346 + // JS validation is off, so only remove existing errors once the field passes validation.
347 + removeFieldError( fieldContainer );
336 348 }
337 349 }
338 350
339 351 /**
@@ -367,11 +379,11 @@
367 379 /**
368 380 * @since 6.15 Added `onSubmit` to the data.
369 381 */
370 382 triggerCustomEvent( document, 'frm_validate_field_value', {
371 - field: field,
372 - errors: errors,
373 - onSubmit: onSubmit
383 + field,
384 + errors,
385 + onSubmit
374 386 } );
375 387 }
376 388
377 389 /**
@@ -379,12 +391,14 @@
379 391 * @param {Array} errors
380 392 * @return {Array} Errors
381 393 */
382 394 function checkRequiredField( field, errors ) {
383 - let tempVal, i, placeholder,
384 - val = '',
385 - fieldID = '',
386 - fileID = field.getAttribute( 'data-frmfile' );
395 + let tempVal;
396 + let i;
397 + let placeholder;
398 + let val = '';
399 + let fieldID = '';
400 + let fileID = field.getAttribute( 'data-frmfile' );
387 401
388 402 if ( field.type === 'hidden' && fileID === null && ! isAppointmentField( field ) && ! isInlineDatepickerField( field ) ) {
389 403 return errors;
390 404 }
@@ -389,9 +403,9 @@
389 403 return errors;
390 404 }
391 405
392 406 if ( field.type === 'checkbox' || field.type === 'radio' ) {
393 - document.querySelectorAll( 'input[name="' + field.name + '"]' ).forEach( function( input ) {
407 + document.querySelectorAll( `input[name="${ field.name }"]` ).forEach( function( input ) {
394 408 const requiredField = input.closest( '.frm_required_field' );
395 409 if ( ! requiredField ) {
396 410 return;
397 411 }
@@ -401,14 +415,14 @@
401 415 val = checkedInput.value;
402 416 } );
403 417 } );
404 418 } else if ( field.type === 'file' || fileID ) {
405 - if ( typeof fileID === 'undefined' ) {
419 + if ( fileID === undefined ) {
406 420 fileID = getFieldId( field, true );
407 421 fileID = fileID.replace( 'file', '' );
408 422 }
409 423
410 - if ( typeof errors[ fileID ] === 'undefined' ) {
424 + if ( errors[ fileID ] === undefined ) {
411 425 val = getFileVals( fileID );
412 426 }
413 427 fieldID = fileID;
414 428 } else {
@@ -452,9 +466,9 @@
452 466 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
453 467 } else if ( isSignatureField( field ) ) {
454 468 if ( val === '' ) {
455 469 const fieldContainer = field.closest( '.frm_form_field' );
456 - const outputField = fieldContainer ? fieldContainer.querySelector( '[name="' + field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) + '"]' ) : null;
470 + const outputField = fieldContainer ? fieldContainer.querySelector( `[name="${ field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) }"]` ) : null;
457 471 val = outputField ? outputField.value : '';
458 472 }
459 473 fieldID = fieldID.replace( '-typed', '' );
460 474 }
@@ -507,9 +521,9 @@
507 521 * @return {string} File input value.
508 522 */
509 523 function getFileVals( fileID ) {
510 524 let val = '';
511 - const fileFields = document.querySelectorAll( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
525 + const fileFields = document.querySelectorAll( `input[name="file${ fileID }"], input[name="file${ fileID }[]"], input[name^="item_meta[${ fileID }]"]` );
512 526
513 527 fileFields.forEach( function( field ) {
514 528 if ( val === '' ) {
515 529 val = field.value;
@@ -523,10 +537,10 @@
523 537 * @param {Array} errors
524 538 * @return {void}
525 539 */
526 540 function checkUrlField( field, errors ) {
527 - let fieldID,
528 - url = field.value;
541 + let fieldID;
542 + const url = field.value;
529 543
530 544 if ( url !== '' && ! /^http(s)?:\/\/(?:localhost|(?:[\da-z\.-]+\.[\da-z\.-]+))/i.test( url ) ) {
531 545 fieldID = getFieldId( field, true );
532 546 if ( ! ( fieldID in errors ) ) {
@@ -567,10 +581,10 @@
567 581 * @param {Object} errors Errors data.
568 582 * @param {boolean} onSubmit Is `true` if the form is being submitted.
569 583 */
570 584 function checkEmailField( field, errors, onSubmit ) {
571 - const fieldID = getFieldId( field, true ),
572 - 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;
585 + const fieldID = getFieldId( field, true );
586 + 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;
573 587
574 588 // validate the current field we're editing first
575 589 if ( '' !== field.value && pattern.test( field.value ) === false ) {
576 590 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
@@ -601,24 +615,23 @@
601 615 * @param {Array} errors
602 616 * @return {void}
603 617 */
604 618 function confirmField( field, errors ) {
605 - let value, confirmValue, firstField,
606 - fieldID = getFieldId( field, true ),
607 - strippedId = field.id.replace( 'conf_', '' ),
608 - strippedFieldID = fieldID.replace( 'conf_', '' ),
609 - confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
619 + const fieldID = getFieldId( field, true );
620 + const strippedId = field.id.replace( 'conf_', '' );
621 + const strippedFieldID = fieldID.replace( 'conf_', '' );
622 + const confirmField = document.getElementById( strippedId.replace( 'field_', 'field_conf_' ) );
610 623
611 - if ( confirmField === null || typeof errors[ 'conf_' + strippedFieldID ] !== 'undefined' ) {
624 + if ( ! confirmField || errors[ `conf_${ strippedFieldID }` ] !== undefined ) {
612 625 return;
613 626 }
614 627
615 628 if ( fieldID !== strippedFieldID ) {
616 - firstField = document.getElementById( strippedId );
617 - value = firstField.value;
618 - confirmValue = confirmField.value;
629 + const firstField = document.getElementById( strippedId );
630 + const { value } = firstField;
631 + const confirmValue = confirmField.value;
619 632 if ( value !== confirmValue ) {
620 - errors[ 'conf_' + strippedFieldID ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
633 + errors[ `conf_${ strippedFieldID }` ] = getFieldValidationMessage( confirmField, 'data-confmsg' );
621 634 }
622 635 } else {
623 636 validateField( confirmField );
624 637 }
@@ -629,10 +642,10 @@
629 642 * @param {Array} errors
630 643 * @return {void}
631 644 */
632 645 function checkNumberField( field, errors ) {
633 - let fieldID,
634 - number = field.value;
646 + let fieldID;
647 + const number = field.value;
635 648
636 649 if ( number !== '' && isNaN( number / 1 ) !== false ) {
637 650 fieldID = getFieldId( field, true );
638 651 if ( ! ( fieldID in errors ) ) {
@@ -646,11 +659,11 @@
646 659 * @param {Array} errors
647 660 * @return {void}
648 661 */
649 662 function checkPatternField( field, errors ) {
650 - let fieldID,
651 - text = field.value,
652 - format = getFieldValidationMessage( field, 'pattern' );
663 + let fieldID;
664 + const text = field.value;
665 + let format = getFieldValidationMessage( field, 'pattern' );
653 666
654 667 if ( format !== '' && text !== '' ) {
655 668 fieldID = getFieldId( field, true );
656 669 if ( ! ( fieldID in errors ) ) {
@@ -658,9 +671,9 @@
658 671 if ( ! window.frmProForm.validateIntlPhoneInput( field ) ) {
659 672 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
660 673 }
661 674 } else {
662 - format = new RegExp( '^' + format + '$', 'i' );
675 + format = new RegExp( `^${ format }$`, 'i' );
663 676 if ( format.test( text ) === false ) {
664 677 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
665 678 }
666 679 }
@@ -673,13 +686,11 @@
673 686 *
674 687 * @since 6.5.1
675 688 */
676 689 function setSelectPlaceholderColor() {
677 - let selects = document.querySelectorAll( '.form-field select' ),
678 - styleElement = document.querySelector( '.with_frm_style' ),
679 - textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '',
680 - changeSelectColor;
681 -
690 + const selects = document.querySelectorAll( '.form-field select' );
691 + const styleElement = document.querySelector( '.with_frm_style' );
692 + const textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '';
682 693 // Exit if there are no select elements or the textColorDisabled property is missing
683 694 if ( ! selects.length || ! textColorDisabled ) {
684 695 return;
685 696 }
@@ -684,9 +695,9 @@
684 695 return;
685 696 }
686 697
687 698 // Function to change the color of a select element
688 - changeSelectColor = function( select ) {
699 + const changeSelectColor = function( select ) {
689 700 if ( select.options[ select.selectedIndex ] && hasClass( select.options[ select.selectedIndex ], 'frm-select-placeholder' ) ) {
690 701 select.style.setProperty( 'color', textColorDisabled, 'important' );
691 702 } else {
692 703 select.style.color = '';
@@ -767,9 +778,9 @@
767 778 }
768 779
769 780 if ( response.length === 0 ) {
770 781 const fieldContainer = recaptcha.closest( '.frm_form_field' );
771 - if ( fieldContainer && fieldContainer.id ) {
782 + if ( fieldContainer?.id ) {
772 783 const fieldID = fieldContainer.id.replace( 'frm_field_', '' ).replace( '_container', '' );
773 784 errors[ fieldID ] = '';
774 785 }
775 786 }
@@ -845,15 +856,13 @@
845 856 * @param {string} action
846 857 * @return {void}
847 858 */
848 859 function getFormErrors( object, action ) {
849 - let data, success, error, shouldTriggerEvent;
850 -
851 860 const fieldsets = object.querySelectorAll( '.frm_form_field' );
852 861 fieldsets.forEach( field => field.classList.add( 'frm_doing_ajax' ) );
853 862
854 - data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line no-jquery/no-serialize, camelcase
855 - shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
863 + const data = `${ jQuery( object ).serialize() }&action=frm_entries_${ action }&nonce=${ frm_js.nonce }`; // eslint-disable-line no-jquery/no-serialize
864 + const shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
856 865
857 866 const doRedirect = response => {
858 867 jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ] );
859 868
@@ -865,17 +874,14 @@
865 874
866 875 // We don't return here because we're opening in a new tab, the old tab will still update.
867 876 const newTab = window.open( response.redirect, '_blank' );
868 877 if ( ! newTab && response.fallbackMsg && response.content ) {
869 - response.content = response.content.trim().replace( /(<\/div><\/div>)$/, ' ' + response.fallbackMsg + '</div></div>' );
878 + response.content = response.content.trim().replace( /(<\/div><\/div>)$/, ` ${ response.fallbackMsg }</div></div>` );
870 879 }
871 880 };
872 881
873 - success = function( response ) {
874 - let defaultResponse, formID, replaceContent, pageOrder, formReturned, contSubmit, delay,
875 - $fieldCont, key, inCollapsedSection, frmTrigger;
876 -
877 - defaultResponse = {
882 + const success = function( response ) {
883 + const defaultResponse = {
878 884 content: '',
879 885 errors: {},
880 886 pass: false
881 887 };
@@ -891,9 +897,11 @@
891 897 response = defaultResponse;
892 898 }
893 899 }
894 900
895 - if ( typeof response.redirect !== 'undefined' ) {
901 + let willRedirect = false;
902 +
903 + if ( response.redirect !== undefined ) {
896 904 if ( shouldTriggerEvent ) {
897 905 triggerCustomEvent( object, 'frmSubmitEvent' );
898 906 return;
899 907 }
@@ -904,8 +912,10 @@
904 912 }, 1000 * response.delay );
905 913 } else {
906 914 doRedirect( response );
907 915 }
916 +
917 + willRedirect = true;
908 918 }
909 919
910 920 if ( 'string' === typeof response.content && response.content !== '' ) {
911 921 // the form or success message was returned
@@ -915,18 +925,18 @@
915 925 return;
916 926 }
917 927
918 928 removeSubmitLoading( jQuery( object ) );
919 - if ( frm_js.offset != -1 ) { // eslint-disable-line camelcase
929 + if ( frm_js.offset != -1 ) {
920 930 frmFrontForm.scrollMsg( jQuery( object ), false );
921 931 }
922 932
923 933 const formIdInput = object.querySelector( 'input[name="form_id"]' );
924 - formID = formIdInput ? formIdInput.value : '';
934 + const formID = formIdInput ? formIdInput.value : '';
925 935 response.content = response.content.replace( / frm_pro_form /g, ' frm_pro_form frm_no_hide ' );
926 - replaceContent = jQuery( object ).closest( '.frm_forms' ); // eslint-disable-line no-jquery/no-closest
936 + const replaceContent = jQuery( object ).closest( '.frm_forms' ); // eslint-disable-line no-jquery/no-closest
927 937 removeAddedScripts( replaceContent, formID );
928 - delay = maybeSlideOut( replaceContent, response.content );
938 + const delay = maybeSlideOut( replaceContent, response.content );
929 939
930 940 setTimeout(
931 941 function() {
932 942 afterFormSubmittedBeforeReplace( object, response );
@@ -935,14 +945,14 @@
935 945
936 946 addUrlParam( response );
937 947
938 948 if ( typeof frmThemeOverride_frmAfterSubmit === 'function' ) { // eslint-disable-line camelcase
939 - const pageOrderInput = document.querySelector( 'input[name="frm_page_order_' + formID + '"]' );
940 - pageOrder = pageOrderInput ? pageOrderInput.value : '';
949 + const pageOrderInput = document.querySelector( `input[name="frm_page_order_${ formID }"]` );
950 + const pageOrder = pageOrderInput ? pageOrderInput.value : '';
941 951 const tempDiv = document.createElement( 'div' );
942 952 tempDiv.innerHTML = response.content;
943 953 const formReturnedInput = tempDiv.querySelector( 'input[name="form_id"]' );
944 - formReturned = formReturnedInput ? formReturnedInput.value : '';
954 + const formReturned = formReturnedInput ? formReturnedInput.value : '';
945 955 frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
946 956 }
947 957
948 958 afterFormSubmitted( object, response );
@@ -948,28 +958,28 @@
948 958 afterFormSubmitted( object, response );
949 959 },
950 960 delay
951 961 );
952 - } else if ( Object.keys( response.errors ).length ) {
962 + } else if ( response.errors !== undefined && Object.keys( response.errors ).length ) {
953 963 // errors were returned
954 964 removeSubmitLoading( jQuery( object ), 'enable' );
955 965
956 966 //show errors
957 - contSubmit = true;
967 + let contSubmit = true;
958 968 removeAllErrors();
959 969
960 - $fieldCont = null;
970 + let $fieldCont = null;
961 971
962 - for ( key in response.errors ) {
963 - const fieldContEl = object.querySelector( '#frm_field_' + key + '_container' );
972 + for ( const key in response.errors ) {
973 + const fieldContEl = object.querySelector( `#frm_field_${ key }_container` );
964 974 $fieldCont = fieldContEl ? jQuery( fieldContEl ) : jQuery();
965 975
966 976 if ( $fieldCont.length ) {
967 977 if ( ! $fieldCont.is( ':visible' ) ) { // eslint-disable-line no-jquery/no-is
968 - inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' ); // eslint-disable-line no-jquery/no-closest
978 + const inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' ); // eslint-disable-line no-jquery/no-closest, formidable/no-jquery-variable-methods
969 979 if ( inCollapsedSection.length ) {
970 - frmTrigger = inCollapsedSection.prev();
971 - if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) {
980 + let frmTrigger = inCollapsedSection.prev();
981 + if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) { // eslint-disable-line formidable/no-jquery-variable-methods
972 982 // If the frmTrigger object is the section description, check to see if the previous element is the trigger
973 983 frmTrigger = frmTrigger.prev( '.frm_trigger' );
974 984 }
975 985 frmTrigger.trigger( 'click' );
@@ -1015,10 +1025,10 @@
1015 1025 } else {
1016 1026 object.insertAdjacentHTML( 'afterbegin', response.error_message );
1017 1027 checkForErrorsAndMaybeSetFocus();
1018 1028 }
1019 - } else {
1020 - // there may have been a plugin conflict, or the form is not set to submit with ajax
1029 + } else if ( ! willRedirect ) { // Avoid double submission if redirecting to a page.
1030 + // There may have been a plugin conflict, or the form is not set to submit with ajax.
1021 1031
1022 1032 showFileLoading( object );
1023 1033
1024 1034 object.submit();
@@ -1024,9 +1034,9 @@
1024 1034 object.submit();
1025 1035 }
1026 1036 };
1027 1037
1028 - error = function() {
1038 + const error = function() {
1029 1039 object.querySelectorAll( 'input[type="submit"], input[type="button"]' ).forEach(
1030 1040 button => button.disabled = false
1031 1041 );
1032 1042 object.submit();
@@ -1035,22 +1045,20 @@
1035 1045 postToAjaxUrl( object, data, success, error );
1036 1046 }
1037 1047
1038 1048 function postToAjaxUrl( form, data, success, error ) {
1039 - let ajaxUrl, action, ajaxParams;
1049 + let ajaxUrl = frm_js.ajax_url;
1050 + const action = form.getAttribute( 'action' );
1040 1051
1041 - ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
1042 - action = form.getAttribute( 'action' );
1043 -
1044 - if ( 'string' === typeof action && -1 !== action.indexOf( '?action=frm_forms_preview' ) ) {
1052 + if ( 'string' === typeof action && action.includes( '?action=frm_forms_preview' ) ) {
1045 1053 ajaxUrl = action.split( '?action=frm_forms_preview' )[ 0 ];
1046 1054 }
1047 1055
1048 - ajaxParams = {
1056 + const ajaxParams = {
1049 1057 type: 'POST',
1050 1058 url: ajaxUrl,
1051 - data: data,
1052 - success: success
1059 + data,
1060 + success
1053 1061 };
1054 1062
1055 1063 if ( 'function' === typeof error ) {
1056 1064 ajaxParams.error = error;
@@ -1088,18 +1096,18 @@
1088 1096 }
1089 1097 }
1090 1098
1091 1099 function removeAddedScripts( formContainer, formID ) {
1092 - const endReplace = document.querySelectorAll( '.frm_end_ajax_' + formID );
1100 + const endReplace = document.querySelectorAll( `.frm_end_ajax_${ formID }` );
1093 1101 if ( endReplace.length ) {
1094 - formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
1102 + formContainer.nextUntil( `.frm_end_ajax_${ formID }` ).remove();
1095 1103 endReplace.forEach( el => el.remove() );
1096 1104 }
1097 1105 }
1098 1106
1099 1107 function maybeSlideOut( oldContent, newContent ) {
1100 - let c,
1101 - newClass = 'frm_slideout';
1108 + let c;
1109 + let newClass = 'frm_slideout';
1102 1110 if ( newContent.includes( ' frm_slide' ) ) {
1103 1111 c = oldContent.children();
1104 1112 if ( newContent.includes( ' frm_going_back' ) ) {
1105 1113 newClass += ' frm_going_back';
@@ -1112,25 +1120,23 @@
1112 1120 }
1113 1121
1114 1122 function addUrlParam( response ) {
1115 1123 let url;
1116 - if ( history.pushState && typeof response.page !== 'undefined' ) {
1124 + if ( history.pushState && response.page !== undefined ) {
1117 1125 url = addQueryVar( 'frm_page', response.page );
1118 - window.history.pushState( { html: response.html }, '', '?' + url );
1126 + window.history.pushState( { html: response.html }, '', `?${ url }` );
1119 1127 }
1120 1128 }
1121 1129
1122 1130 function addQueryVar( key, value ) {
1123 - let kvp, i, x;
1124 -
1125 1131 key = encodeURI( key );
1126 1132 value = encodeURI( value );
1127 1133
1128 - kvp = document.location.search.substr( 1 ).split( '&' );
1134 + const kvp = document.location.search.substr( 1 ).split( '&' );
1129 1135
1130 - i = kvp.length;
1136 + let i = kvp.length;
1131 1137 while ( i-- ) {
1132 - x = kvp[ i ].split( '=' );
1138 + const x = kvp[ i ].split( '=' );
1133 1139
1134 1140 if ( x[ 0 ] == key ) {
1135 1141 x[ 1 ] = value;
1136 1142 kvp[ i ] = x.join( '=' );
@@ -1145,48 +1151,48 @@
1145 1151 return kvp.join( '&' );
1146 1152 }
1147 1153
1148 1154 function addFieldError( $fieldCont, key, jsErrors ) {
1149 - let id, describedBy, roleString;
1150 1155 const container = $fieldCont instanceof jQuery ? $fieldCont.get( 0 ) : $fieldCont;
1156 +
1151 1157 if ( ! container || container.offsetParent === null ) {
1152 1158 return;
1153 1159 }
1154 1160
1155 1161 container.classList.add( 'frm_blank_field' );
1156 - const input = container.querySelector( 'input, select, textarea' );
1157 - id = getErrorElementId( key, input );
1162 + const inputs = container.querySelectorAll( 'input, select, textarea' );
1163 + const id = getErrorElementId( key, inputs[ 0 ] );
1158 1164
1159 - describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
1165 + let describedBy;
1160 1166
1161 1167 if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
1162 1168 frmThemeOverride_frmPlaceError( key, jsErrors );
1163 1169 } else {
1164 1170 let errorHtml;
1165 - if ( -1 !== jsErrors[ key ].indexOf( '<div' ) ) {
1171 + if ( jsErrors[ key ].includes( '<div' ) ) {
1166 1172 errorHtml = jsErrors[ key ];
1167 1173 } else {
1168 - roleString = frm_js.include_alert_role ? 'role="alert"' : ''; // eslint-disable-line camelcase
1169 - errorHtml = '<div class="frm_error" ' + roleString + ' id="' + id + '">' + jsErrors[ key ] + '</div>';
1174 + const roleString = frm_js.include_alert_role ? 'role="alert"' : '';
1175 + errorHtml = `<div class="frm_error" ${ roleString } id="${ id }">${ jsErrors[ key ] }</div>`;
1170 1176 }
1171 1177 container.insertAdjacentHTML( 'beforeend', errorHtml );
1172 -
1173 - if ( input ) {
1178 + inputs.forEach( input => {
1179 + describedBy = input.getAttribute( 'aria-describedby' );
1174 1180 if ( ! describedBy ) {
1175 1181 describedBy = id;
1176 1182 } else if ( ! describedBy.includes( id ) && ! describedBy.includes( 'frm_error_field_' ) ) {
1177 - const errorFirst = input.dataset.errorFirst;
1183 + const { errorFirst } = input.dataset;
1178 1184 if ( errorFirst === '0' ) {
1179 - describedBy = describedBy + ' ' + id;
1185 + describedBy = `${ describedBy } ${ id }`;
1180 1186 } else {
1181 - describedBy = id + ' ' + describedBy;
1187 + describedBy = `${ id } ${ describedBy }`;
1182 1188 }
1183 1189 }
1184 1190 input.setAttribute( 'aria-describedby', describedBy );
1185 - }
1191 + } );
1186 1192 }
1187 1193
1188 - if ( input ) {
1194 + inputs.forEach( input => {
1189 1195 if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
1190 1196 const group = input.closest( '[role="radiogroup"], [role="group"]' );
1191 1197 if ( group ) {
1192 1198 group.setAttribute( 'aria-invalid', 'true' );
@@ -1193,9 +1199,9 @@
1193 1199 }
1194 1200 } else {
1195 1201 input.setAttribute( 'aria-invalid', 'true' );
1196 1202 }
1197 - }
1203 + } );
1198 1204
1199 1205 jQuery( document ).trigger( 'frmAddFieldError', [ jQuery( container ), key, jsErrors ] );
1200 1206 }
1201 1207
@@ -1208,11 +1214,11 @@
1208 1214 */
1209 1215 function getErrorElementId( key, input ) {
1210 1216 if ( isNaN( key ) || ! input || ! input.id ) {
1211 1217 // If key isn't a number, assume it's already in the right format.
1212 - return 'frm_error_field_' + key;
1218 + return `frm_error_field_${ key }`;
1213 1219 }
1214 - return 'frm_error_' + input.id;
1220 + return `frm_error_${ input.id }`;
1215 1221 }
1216 1222
1217 1223 /**
1218 1224 * Removes errors before validating with JS.
@@ -1227,11 +1233,9 @@
1227 1233 return;
1228 1234 }
1229 1235
1230 1236 const errorMessage = container.querySelector( '.frm_error' );
1231 - const errorId = errorMessage ? errorMessage.id : '';
1232 1237 const input = container.querySelector( 'input, select, textarea' );
1233 - let describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
1234 1238
1235 1239 container.classList.remove( 'frm_blank_field', 'has-error' );
1236 1240
1237 1241 if ( input ) {
@@ -1245,20 +1249,35 @@
1245 1249 }
1246 1250 }
1247 1251
1248 1252 if ( errorMessage ) {
1253 + removeElementFromInputDescribedBy( errorMessage );
1249 1254 errorMessage.remove();
1250 1255 }
1256 + }
1251 1257
1252 - if ( input ) {
1258 + /**
1259 + * Updates the aria-describedby attribute, removing the target element ID.
1260 + *
1261 + * @since 6.32
1262 + *
1263 + * @param {HTMLElement} el The target element that is removed from the aria-describedby data.
1264 + * @return {void}
1265 + */
1266 + function removeElementFromInputDescribedBy( el ) {
1267 + document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
1268 + let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
1269 + ariaDescribedBy = ariaDescribedBy.filter( value => {
1270 + const trimmedValue = value.trim();
1271 + return trimmedValue && trimmedValue !== el.id;
1272 + } );
1273 +
1274 + if ( ariaDescribedBy.length ) {
1275 + input.setAttribute( 'aria-describedby', ariaDescribedBy.join( ' ' ) );
1276 + return;
1277 + }
1253 1278 input.removeAttribute( 'aria-describedby' );
1254 - if ( describedBy ) {
1255 - describedBy = describedBy.replace( errorId, '' ).trim();
1256 - if ( describedBy ) {
1257 - input.setAttribute( 'aria-describedby', describedBy );
1258 - }
1259 - }
1260 - }
1279 + } );
1261 1280 }
1262 1281
1263 1282 function removeAllErrors() {
1264 1283 document.querySelectorAll( '.form-field' ).forEach( field => {
@@ -1263,9 +1282,12 @@
1263 1282 function removeAllErrors() {
1264 1283 document.querySelectorAll( '.form-field' ).forEach( field => {
1265 1284 field.classList.remove( 'frm_blank_field', 'has-error' );
1266 1285 } );
1267 - document.querySelectorAll( '.form-field .frm_error' ).forEach( error => error.remove() );
1286 + document.querySelectorAll( '.form-field .frm_error' ).forEach( el => {
1287 + removeElementFromInputDescribedBy( el );
1288 + el.remove();
1289 + } );
1268 1290 document.querySelectorAll( '.frm_error_style' ).forEach( error => error.remove() );
1269 1291 }
1270 1292
1271 1293 /**
@@ -1289,9 +1311,9 @@
1289 1311 disableSaveDraft( $object );
1290 1312 }
1291 1313
1292 1314 function showLoadingIndicator( $object ) {
1293 - if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) { // eslint-disable-line no-jquery/no-class
1315 + if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) { // eslint-disable-line no-jquery/no-class, formidable/no-jquery-variable-methods
1294 1316 addLoadingClass( $object );
1295 1317 $object.trigger( 'frmStartFormLoading' );
1296 1318 }
1297 1319 }
@@ -1298,13 +1320,13 @@
1298 1320
1299 1321 function addLoadingClass( $object ) {
1300 1322 const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1301 1323
1302 - $object.addClass( loadingClass ); // eslint-disable-line no-jquery/no-class
1324 + $object.addClass( loadingClass ); // eslint-disable-line no-jquery/no-class, formidable/no-jquery-variable-methods
1303 1325 }
1304 1326
1305 1327 function isGoingToPrevPage( $object ) {
1306 - return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
1328 + return typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object );
1307 1329 }
1308 1330
1309 1331 function removeSubmitLoading( _, enable, processesRunning ) {
1310 1332 if ( processesRunning > 0 ) {
@@ -1323,9 +1345,9 @@
1323 1345 }
1324 1346
1325 1347 function showFileLoading( object ) {
1326 1348 const loading = document.getElementById( 'frm_loading' );
1327 - if ( loading === null ) {
1349 + if ( ! loading ) {
1328 1350 return;
1329 1351 }
1330 1352
1331 1353 const fileInput = object.querySelector( 'input[type=file]' );
@@ -1354,9 +1376,9 @@
1354 1376 */
1355 1377 function onHoneypotFieldChange() {
1356 1378 /*jshint validthis:true */
1357 1379 const css = window.getComputedStyle( this ).boxShadow;
1358 - if ( css && css.match( /inset/ ) ) {
1380 + if ( css?.match( /inset/ ) ) {
1359 1381 this.remove();
1360 1382 }
1361 1383 }
1362 1384
@@ -1408,23 +1430,22 @@
1408 1430 return false;
1409 1431 }
1410 1432
1411 1433 function checkForErrorsAndMaybeSetFocus() {
1412 - let errors, element, timeoutCallback;
1413 -
1414 - if ( ! frm_js.focus_first_error ) { // eslint-disable-line camelcase
1434 + if ( ! frm_js.focus_first_error ) {
1415 1435 return;
1416 1436 }
1417 1437
1418 - errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1438 + const errors = document.querySelectorAll( '.frm_form_field .frm_error' );
1419 1439 if ( ! errors.length ) {
1420 1440 return;
1421 1441 }
1422 1442
1423 - element = errors[ 0 ];
1443 + let element = errors[ 0 ];
1444 + let timeoutCallback;
1424 1445 do {
1425 1446 element = element.previousSibling;
1426 - if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1447 + if ( [ 'input', 'select', 'textarea' ].includes( element.nodeName.toLowerCase() ) ) {
1427 1448 focusInput( element );
1428 1449 break;
1429 1450 }
1430 1451
@@ -1431,9 +1452,9 @@
1431 1452 if ( maybeFocusOnComboSubField( element ) ) {
1432 1453 break;
1433 1454 }
1434 1455
1435 - if ( 'undefined' !== typeof element.classList ) {
1456 + if ( element.classList !== undefined ) {
1436 1457 if ( element.classList.contains( 'html-active' ) ) {
1437 1458 timeoutCallback = function() {
1438 1459 const textarea = element.querySelector( 'textarea' );
1439 1460 if ( null !== textarea ) {
@@ -1486,9 +1507,9 @@
1486 1507 * @param {Function} handler Handler.
1487 1508 * @param {boolean | Object} options Options to be added to `addEventListener()` method. Default is `false`.
1488 1509 */
1489 1510 function documentOn( event, selector, handler, options ) {
1490 - if ( 'undefined' === typeof options ) {
1511 + if ( options === undefined ) {
1491 1512 options = false;
1492 1513 }
1493 1514
1494 1515 document.addEventListener( event, function( e ) {
@@ -1504,27 +1525,23 @@
1504 1525 }, options );
1505 1526 }
1506 1527
1507 1528 function initFloatingLabels() {
1508 - let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1529 + const selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1530 + const floatClass = 'frm_label_float_top';
1509 1531
1510 - selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1511 - floatClass = 'frm_label_float_top';
1512 -
1513 - checkFloatLabel = function( input ) {
1514 - let container, shouldFloatTop, firstOpt;
1515 -
1516 - container = input.closest( '.frm_inside_container' );
1532 + const checkFloatLabel = function( input ) {
1533 + const container = input.closest( '.frm_inside_container' );
1517 1534 if ( ! container ) {
1518 1535 return;
1519 1536 }
1520 1537
1521 - shouldFloatTop = input.value || document.activeElement === input;
1538 + const shouldFloatTop = input.value || document.activeElement === input;
1522 1539
1523 1540 container.classList.toggle( floatClass, shouldFloatTop );
1524 1541
1525 1542 if ( 'SELECT' === input.tagName ) {
1526 - firstOpt = input.querySelector( 'option:first-child' );
1543 + const firstOpt = input.querySelector( 'option:first-child' );
1527 1544
1528 1545 if ( shouldFloatTop ) {
1529 1546 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1530 1547 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
@@ -1536,10 +1553,10 @@
1536 1553 }
1537 1554 }
1538 1555 };
1539 1556
1540 - checkDropdownLabel = function() {
1541 - document.querySelectorAll( '.frm-show-form .frm_inside_container:not(.' + floatClass + ') select' ).forEach( function( input ) {
1557 + const checkDropdownLabel = function() {
1558 + document.querySelectorAll( `.frm-show-form .frm_inside_container:not(.${ floatClass }) select` ).forEach( function( input ) {
1542 1559 const firstOpt = input.querySelector( 'option:first-child' );
1543 1560
1544 1561 if ( firstOpt.textContent ) {
1545 1562 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
@@ -1558,10 +1575,10 @@
1558 1575 true
1559 1576 );
1560 1577 } );
1561 1578
1562 - runOnLoad = function( firstLoad ) {
1563 - if ( firstLoad && document.activeElement && -1 !== [ 'INPUT', 'SELECT', 'TEXTAREA' ].indexOf( document.activeElement.tagName ) ) {
1579 + const runOnLoad = function( firstLoad ) {
1580 + if ( firstLoad && document.activeElement && [ 'INPUT', 'SELECT', 'TEXTAREA' ].includes( document.activeElement.tagName ) ) {
1564 1581 checkFloatLabel( document.activeElement );
1565 1582 } else if ( firstLoad ) {
1566 1583 document.querySelectorAll( '.frm_inside_container' ).forEach(
1567 1584 function( container ) {
@@ -1573,8 +1590,9 @@
1573 1590 );
1574 1591 }
1575 1592
1576 1593 checkDropdownLabel();
1594 + calcProductsTotal();
1577 1595 };
1578 1596
1579 1597 runOnLoad( true );
1580 1598
@@ -1607,10 +1625,10 @@
1607 1625 return true;
1608 1626 }
1609 1627
1610 1628 function maybeClearCustomValidityMessage( event, field ) {
1611 - let key,
1612 - isInvalid = false;
1629 + let key;
1630 + let isInvalid = false;
1613 1631
1614 1632 if ( ! shouldUpdateValidityMessage( field ) ) {
1615 1633 return;
1616 1634 }
@@ -1630,33 +1648,29 @@
1630 1648 }
1631 1649 }
1632 1650
1633 1651 function maybeShowNewTabFallbackMessage() {
1634 - let messageEl;
1635 -
1636 1652 if ( ! window.frmShowNewTabFallback ) {
1637 1653 return;
1638 1654 }
1639 1655
1640 - messageEl = document.querySelector( '#frm_form_' + frmShowNewTabFallback.formId + '_container .frm_message' );
1656 + const messageEl = document.querySelector( `#frm_form_${ frmShowNewTabFallback.formId }_container .frm_message` );
1641 1657 if ( ! messageEl ) {
1642 1658 return;
1643 1659 }
1644 1660
1645 - messageEl.insertAdjacentHTML( 'beforeend', ' ' + frmShowNewTabFallback.message );
1661 + messageEl.insertAdjacentHTML( 'beforeend', ` ${ frmShowNewTabFallback.message }` );
1646 1662 }
1647 1663
1648 1664 function setCustomValidityMessage() {
1649 - let forms, length, index;
1665 + const forms = document.getElementsByClassName( 'frm-show-form' );
1666 + const { length } = forms;
1650 1667
1651 - forms = document.getElementsByClassName( 'frm-show-form' );
1652 - length = forms.length;
1653 -
1654 - for ( index = 0; index < length; ++index ) {
1668 + for ( let index = 0; index < length; ++index ) {
1655 1669 forms[ index ].addEventListener(
1656 1670 'invalid',
1657 1671 function( event ) {
1658 - const target = event.target;
1672 + const { target } = event;
1659 1673
1660 1674 if ( shouldUpdateValidityMessage( target ) ) {
1661 1675 target.setCustomValidity( target.dataset.invmsg );
1662 1676 }
@@ -1698,9 +1712,9 @@
1698 1712 const uniqueKey = Array.from( window.crypto.getRandomValues( new Uint8Array( 8 ) ) )
1699 1713 .map( b => b.toString( 16 ).padStart( 2, '0' ) )
1700 1714 .join( '' );
1701 1715 const timestamp = Date.now().toString( 16 );
1702 - return uniqueKey + '-' + timestamp;
1716 + return `${ uniqueKey }-${ timestamp }`;
1703 1717 }
1704 1718
1705 1719 /**
1706 1720 * Animates the scroll position of the document.
@@ -1717,9 +1731,8 @@
1717 1731 document.documentElement.scrollTop = end;
1718 1732 return;
1719 1733 }
1720 1734
1721 - /* eslint-disable compat/compat */
1722 1735 const startTime = performance.now();
1723 1736 const step = currentTime => {
1724 1737 const progress = Math.min( ( currentTime - startTime ) / duration, 1 );
1725 1738 document.documentElement.scrollTop = start + ( ( end - start ) * progress );
@@ -1727,9 +1740,8 @@
1727 1740 requestAnimationFrame( step );
1728 1741 }
1729 1742 };
1730 1743 requestAnimationFrame( step );
1731 - /* eslint-enable compat/compat */
1732 1744 }
1733 1745
1734 1746 /**
1735 1747 * Make sure that the captcha label for a reCAPTCHA or Turnstile field matches the response input ID.
@@ -1754,10 +1766,433 @@
1754 1766 label.htmlFor = captchaResponse.id;
1755 1767 }
1756 1768 }
1757 1769
1770 + /**
1771 + * Check to make sure the quantity field value is within the min and max values.
1772 + *
1773 + * @param {HTMLElement} input
1774 + * @return {number} The quantity value.
1775 + */
1776 + function checkQuantityFieldMinMax( input ) {
1777 + if ( '' === input.value ) {
1778 + // Leave the value if it is empty.
1779 + return 0;
1780 + }
1781 +
1782 + const val = parseFloat( input.value ? input.value.trim() : 0 );
1783 + if ( isNaN( val ) ) {
1784 + return 0;
1785 + }
1786 +
1787 + let max = input.hasAttribute( 'max' ) ? parseFloat( input.getAttribute( 'max' ) ) : 0;
1788 + let min = input.hasAttribute( 'min' ) ? parseFloat( input.getAttribute( 'min' ) ) : 0;
1789 +
1790 + max = isNaN( max ) ? 0 : max;
1791 + min = isNaN( min ) ? 0 : Math.max( 0, min );
1792 +
1793 + if ( val < min ) {
1794 + input.value = min;
1795 + return min;
1796 + }
1797 +
1798 + if ( 0 !== max && val > max ) {
1799 + input.value = max;
1800 + return max;
1801 + }
1802 +
1803 + return val;
1804 + }
1805 +
1806 + function triggerChange( input, fieldKey ) {
1807 + if ( fieldKey === undefined ) {
1808 + fieldKey = 'dependent';
1809 + }
1810 +
1811 + jQuery( input ).trigger( { type: 'change', selfTriggered: true, frmTriggered: fieldKey } );
1812 + }
1813 +
1814 + /**
1815 + * Calculates the total price.
1816 + *
1817 + * @param {Event|undefined} e The event object.
1818 + * @return {void}
1819 + */
1820 + function calcProductsTotal( e ) {
1821 + if ( 'object' === typeof frmProForm ) {
1822 + // Pro is installed, use the Pro JS.
1823 + return;
1824 + }
1825 +
1826 + if ( typeof __FRMCURR === 'undefined' ) {
1827 + return;
1828 + }
1829 +
1830 + const totalFields = document.querySelectorAll( '[data-frmtotal]' );
1831 + if ( ! totalFields.length ) {
1832 + return;
1833 + }
1834 +
1835 + const formTotals = [];
1836 +
1837 + totalFields.forEach( totalField => {
1838 + let total = 0;
1839 + const form = totalField.closest( 'form' );
1840 +
1841 + if ( ! form ) {
1842 + return;
1843 + }
1844 +
1845 + const formId = form.querySelector( 'input[name="form_id"]' ).value;
1846 + const currency = getCurrency( formId );
1847 +
1848 + if ( undefined !== formTotals[ formId ] ) {
1849 + total = formTotals[ formId ];
1850 + } else {
1851 + form.querySelectorAll( 'input[data-frmprice],select:has([data-frmprice])' ).forEach( function( input ) {
1852 + let quantity = 0;
1853 + let price = 0;
1854 + const isSingle = 'hidden' === input.type;
1855 +
1856 + if ( input.tagName === 'SELECT' ) {
1857 + if ( input.selectedIndex !== -1 ) {
1858 + price = input.options[ input.selectedIndex ].getAttribute( 'data-frmprice' );
1859 + }
1860 + } else {
1861 + if ( ! isSingle && ! input.matches( ':checked' ) ) {
1862 + return;
1863 + }
1864 + price = input.getAttribute( 'data-frmprice' );
1865 + }
1866 +
1867 + if ( ! price ) {
1868 + price = 0;
1869 + } else {
1870 + price = preparePrice( price, currency );
1871 + quantity = getQuantity( input );
1872 + price = parseFloat( quantity ) * parseFloat( price );
1873 + }
1874 +
1875 + if ( 'true' === input.getAttribute( 'data-frmdiscount' ) ) {
1876 + price = price * -1;
1877 + }
1878 +
1879 + total += price;
1880 + } );
1881 +
1882 + formTotals[ formId ] = total;
1883 + }
1884 +
1885 + total = isNaN( total ) ? 0 : total;
1886 +
1887 + // Set a decimal separator for currency if no default for it
1888 + currency.decimal_separator = currency.decimal_separator.trim(); // first remove unnecessary space(s)
1889 + if ( ! currency.decimal_separator.length ) {
1890 + currency.decimal_separator = '.';
1891 + }
1892 +
1893 + totalField.value = roundTotal( total, currency );
1894 + total = normalizeTotal( total, currency );
1895 +
1896 + // because of e.g. fields that might be using this field for calculations
1897 + triggerChange( totalField );
1898 +
1899 + total = formatCurrency( total, currency );
1900 + const formatted = totalField.previousElementSibling;
1901 + if ( formatted?.matches( '.frm_total_formatted' ) ) {
1902 + // Use innerHTML so that currency symbols like Euros can render and not their encoded string value.
1903 + formatted.innerHTML = total;
1904 + return;
1905 + }
1906 +
1907 + const formattedEls = totalField.closest( '.frm_form_field' ).querySelectorAll( '.frm_total_formatted' );
1908 + formattedEls.forEach( formattedEl => {
1909 + // Use innerHTML so that currency symbols like Euros can render and not their encoded string value.
1910 + formattedEl.innerHTML = total;
1911 + } );
1912 + } );
1913 + }
1914 +
1915 + /**
1916 + * Round total and maybe add trailing zeros so formatCurrency has a proper format to work with.
1917 + *
1918 + * @param {number} total The total amount to normalize.
1919 + * @param {Object} currency The currency object containing decimal information.
1920 + * @return {string} The normalized total amount.
1921 + */
1922 + function normalizeTotal( total, currency ) {
1923 + const isLargeTotal = total > Number.MAX_SAFE_INTEGER;
1924 +
1925 + total = roundTotal( total, currency );
1926 +
1927 + return maybeAddTrailingZeroToPrice( total, currency, isLargeTotal );
1928 + }
1929 +
1930 + function roundTotal( total, currency ) {
1931 + const isLargeTotal = total > Number.MAX_SAFE_INTEGER;
1932 +
1933 + if ( ! isLargeTotal ) {
1934 + const { decimals } = currency;
1935 + total = decimals > 0 ? round10( total, decimals ) : Math.ceil( total );
1936 + }
1937 +
1938 + return total;
1939 + }
1940 +
1941 + function round10( value, decimals ) {
1942 + return Number( `${ Math.round( `${ value }e${ decimals }` ) }e-${ decimals }` );
1943 + }
1944 +
1945 + /**
1946 + * Format a numeric value according to the specified currency format settings.
1947 + *
1948 + * @param {string} total The numeric value to format.
1949 + * @param {Object} currency The currency formatting configuration.
1950 + * @return {string} The formatted currency string.
1951 + */
1952 + function formatCurrency( total, currency ) {
1953 + total = maybeAddTrailingZeroToPrice( total, currency );
1954 + if ( total.length && ( total[ total.length - 1 ] === '.' || total[ total.length - 1 ] === currency.decimal_separator ) ) {
1955 + total = total.substr( 0, total.length - 1 );
1956 + }
1957 +
1958 + total = maybeRemoveTrailingZerosFromPrice( total, currency );
1959 + total = addThousands( total, currency );
1960 +
1961 + const leftSymbol = currency.symbol_left ? ( currency.symbol_left + currency.symbol_padding ) : '';
1962 + const rightSymbol = currency.symbol_right ? ( currency.symbol_padding + currency.symbol_right ) : '';
1963 +
1964 + return `${ leftSymbol }${ total }${ rightSymbol }`;
1965 + }
1966 +
1967 + /**
1968 + * Gets currency from form id.
1969 + *
1970 + * @param {number} formId Form ID.
1971 + * @return {Object} Currency object.
1972 + */
1973 + function getCurrency( formId ) {
1974 + if ( undefined !== window.__FRMCURR && undefined !== window.__FRMCURR[ formId ] ) {
1975 + return window.__FRMCURR[ formId ];
1976 + }
1977 +
1978 + return {
1979 + symbol_left: '$',
1980 + symbol_right: '',
1981 + symbol_padding: '',
1982 + thousand_separator: ',',
1983 + decimal_separator: '.',
1984 + decimals: 2,
1985 + };
1986 + }
1987 +
1988 + /**
1989 + * Gets quantity.
1990 + *
1991 + * @param {HTMLElement} field The field element.
1992 + * @return {number} The quantity.
1993 + */
1994 + function getQuantity( field ) {
1995 + const fieldID = frmFrontForm.getFieldId( field, false );
1996 + if ( ! fieldID ) {
1997 + return 0;
1998 + }
1999 +
2000 + const quantityField = getQuantityField( field, fieldID );
2001 + if ( ! quantityField ) {
2002 + // If there is no quantity field, assume 1.
2003 + return 1;
2004 + }
2005 +
2006 + return checkQuantityFieldMinMax( quantityField );
2007 + }
2008 +
2009 + /**
2010 + * Gets quantity field.
2011 + *
2012 + * @param {HTMLElement} element The element.
2013 + * @param {number} fieldID The field ID.
2014 + * @return {HTMLElement|null} The quantity field.
2015 + */
2016 + function getQuantityField( element, fieldID ) {
2017 + const quantityFields = element.closest( 'form' ).querySelectorAll( '[data-frmproduct]' );
2018 + if ( ! quantityFields.length ) {
2019 + return null;
2020 + }
2021 +
2022 + fieldID = fieldID.toString();
2023 +
2024 + return Array.from( quantityFields ).find( element => {
2025 + let ids;
2026 +
2027 + ids = JSON.parse( element.getAttribute( 'data-frmproduct' ).trim() );
2028 + if ( '' === ids ) {
2029 + return false;
2030 + }
2031 +
2032 + // Convert to array if necessary because of existing fields that are already using single product fields.
2033 + ids = 'string' === typeof ids ? [ ids ] : ids;
2034 + return ids.includes( fieldID );
2035 + } );
2036 + }
2037 +
2038 + /**
2039 + * Prepare a price for calculation.
2040 + *
2041 + * @param {number|string} price The price to prepare.
2042 + * @param {Object} currency The currency object containing decimal information.
2043 + * @return {string} The prepared price.
2044 + */
2045 + function preparePrice( price, currency ) {
2046 + if ( ! price ) {
2047 + return 0;
2048 + }
2049 + price = `${ price }`; // convert to string just to be sure
2050 +
2051 + const regex = getRegexForPrice( currency );
2052 +
2053 + const matches = price.match( regex );
2054 + if ( null === matches ) {
2055 + return 0;
2056 + }
2057 +
2058 + price = matches.length ? matches[ matches.length - 1 ] : 0;
2059 + price = price.trim();
2060 +
2061 + // Fix issues with parsing Fr.15.00. The regex catches .15.00.
2062 + // This checks for the leading decimal and removes it.
2063 + if ( currency.decimal_separator === '.' && 3 === price.split( '.' ).length && price[ 0 ] === '.' ) {
2064 + price = price.substr( 1 );
2065 + }
2066 +
2067 + if ( price ) {
2068 + price = maybeUseDecimal( price, currency );
2069 + price = price.split( currency.thousand_separator ).join( '' ).replace( currency.decimal_separator, '.' );
2070 + }
2071 +
2072 + return price;
2073 + }
2074 +
2075 + /**
2076 + * @param {Object} currency The currency object.
2077 + * @return {RegExp} The regular expression object.
2078 + */
2079 + function getRegexForPrice( currency ) {
2080 + let regexString = '[0-9,.';
2081 +
2082 + if ( currency.thousand_separator !== '.' && currency.thousand_separator !== ',' ) {
2083 + regexString += currency.thousand_separator;
2084 + }
2085 + if ( currency.decimal_separator !== '.' && currency.decimal_separator !== ',' ) {
2086 + regexString += currency.decimal_separator;
2087 + }
2088 +
2089 + regexString += ']*\\.?\\,?[0-9]+';
2090 +
2091 + return new RegExp( regexString, 'g' );
2092 + }
2093 +
2094 + /**
2095 + * Maybe replace the decimal separator with the currency's decimal separator.
2096 + *
2097 + * @param {string} price The price string.
2098 + * @param {Object} currency The currency object.
2099 + * @return {string} The modified price string.
2100 + */
2101 + function maybeUseDecimal( price, currency ) {
2102 + let usedForDecimal;
2103 + let priceParts;
2104 + if ( '.' === currency.thousand_separator ) {
2105 + priceParts = price.split( '.' );
2106 + usedForDecimal = 2 === priceParts.length && 2 === priceParts[ 1 ].length;
2107 + if ( usedForDecimal ) {
2108 + price = price.replace( '.', currency.decimal_separator );
2109 + }
2110 + }
2111 + return price;
2112 + }
2113 +
2114 + /**
2115 + * Add trailing zeros to a price if necessary and replace the decimal separator.
2116 + *
2117 + * @param {number|string} price The price to format.
2118 + * @param {Object} currency The currency object containing the decimal separator.
2119 + * @param {boolean} [force=false] Whether to force processing even if the price is not a number.
2120 + * @return {string} The formatted price string.
2121 + */
2122 + function maybeAddTrailingZeroToPrice( price, currency, force = false ) {
2123 + if ( 'number' !== typeof price && ! force ) {
2124 + return price;
2125 + }
2126 +
2127 + price = String( price ); // first convert to string
2128 + const pos = price.indexOf( '.' );
2129 +
2130 + if ( pos === -1 ) {
2131 + price = `${ price }.`;
2132 +
2133 + for ( let n = 0; n < currency.decimals; ++n ) {
2134 + price += '0';
2135 + }
2136 + } else {
2137 + const decimalsString = price.substring( pos + 1 );
2138 + if ( decimalsString.length < currency.decimals ) {
2139 + if ( decimalsString.length < 2 ) {
2140 + price += '0';
2141 + }
2142 +
2143 + for ( let n = 2; n < currency.decimals; ++n ) {
2144 + price += '0';
2145 + }
2146 + }
2147 + }
2148 +
2149 + return price.replace( '.', currency.decimal_separator );
2150 + }
2151 +
2152 + /**
2153 + * Format a numeric string by adding thousand separators.
2154 + *
2155 + * @param {string|number} price The numeric value to format.
2156 + * @param {Object} options Formatting options.
2157 + * @param {string} options.decimal_separator Character used as decimal separator.
2158 + * @param {string} options.thousand_separator Character used as thousand separator.
2159 + *
2160 + * @return {string} The price string with thousand separators.
2161 + */
2162 + function addThousands( price, options ) {
2163 + const split = options.decimal_separator === ''
2164 + ? [ price.toString() ]
2165 + : price.split( options.decimal_separator );
2166 +
2167 + if ( options.thousand_separator ) {
2168 + split[ 0 ] = split[ 0 ].replace( /\B(?=(\d{3})+(?!\d))/g, options.thousand_separator );
2169 + }
2170 +
2171 + return split.join( options.decimal_separator );
2172 + }
2173 +
2174 + /**
2175 + * Maybe remove trailing zeros from a price string.
2176 + *
2177 + * @param {string} price The price string.
2178 + * @param {Object} currency The currency data.
2179 + *
2180 + * @return {string} The price string with trailing zeros removed.
2181 + */
2182 + function maybeRemoveTrailingZerosFromPrice( price, currency ) {
2183 + const split = price.split( currency.decimal_separator );
2184 + if ( 2 !== split.length || split[ 1 ].length <= currency.decimals ) {
2185 + return price;
2186 + }
2187 + if ( 0 === currency.decimals ) {
2188 + return split[ 0 ];
2189 + }
2190 + return `${ split[ 0 ] }${ currency.decimal_separator }${ split[ 1 ].substr( 0, currency.decimals ) }`;
2191 + }
2192 +
1758 2193 return {
1759 - init: function() {
2194 + init() {
1760 2195 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1761 2196 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1762 2197
1763 2198 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 );
@@ -1787,8 +2222,13 @@
1787 2222 jQuery( document ).on(
1788 2223 'frmPageChanged',
1789 2224 destroyhCaptcha
1790 2225 );
2226 +
2227 + jQuery( document ).on( 'frmAfterAddRow frmAfterRemoveRow', calcProductsTotal );
2228 + jQuery( document ).on( 'change', '[type="checkbox"][data-frmprice],[type="radio"][data-frmprice],[type="hidden"][data-frmprice],select:has([data-frmprice])', calcProductsTotal );
2229 + jQuery( document ).on( 'keyup change', '[data-frmproduct],[type="text"][data-frmprice]', calcProductsTotal );
2230 + calcProductsTotal();
1791 2231 },
1792 2232
1793 2233 getFieldId,
1794 2234
@@ -1798,9 +2238,9 @@
1798 2238 * @param {HTMLElement} captcha
1799 2239 * @param {string} captchaSelector
1800 2240 * @return {void}
1801 2241 */
1802 - renderCaptcha: function( captcha, captchaSelector ) {
2242 + renderCaptcha( captcha, captchaSelector ) {
1803 2243 const rendered = captcha.getAttribute( 'data-rid' ) !== null;
1804 2244 if ( rendered ) {
1805 2245 return;
1806 2246 }
@@ -1807,9 +2247,9 @@
1807 2247
1808 2248 const size = captcha.getAttribute( 'data-size' );
1809 2249 const params = {
1810 2250 sitekey: captcha.getAttribute( 'data-sitekey' ),
1811 - size: size,
2251 + size,
1812 2252 theme: captcha.getAttribute( 'data-theme' )
1813 2253 };
1814 2254
1815 2255 if ( size === 'invisible' ) {
@@ -1825,9 +2265,9 @@
1825 2265 };
1826 2266 }
1827 2267
1828 2268 const activeCaptcha = getSelectedCaptcha( captchaSelector );
1829 - const captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
2269 + const captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? `#${ captcha.id }` : captcha.id;
1830 2270 const captchaID = activeCaptcha.render( captchaContainer, params );
1831 2271
1832 2272 captcha.setAttribute( 'data-rid', captchaID );
1833 2273
@@ -1833,20 +2273,20 @@
1833 2273
1834 2274 maybeFixCaptchaLabel( captcha );
1835 2275 },
1836 2276
1837 - afterSingleRecaptcha: function() {
2277 + afterSingleRecaptcha() {
1838 2278 const recaptcha = document.querySelector( '.frm-show-form .g-recaptcha' );
1839 2279 const object = recaptcha ? recaptcha.closest( 'form' ) : null;
1840 2280 frmFrontForm.submitFormNow( object );
1841 2281 },
1842 2282
1843 - afterRecaptcha: function( _, formID ) {
1844 - const object = document.querySelector( '#frm_form_' + formID + '_container form' );
2283 + afterRecaptcha( _, formID ) {
2284 + const object = document.querySelector( `#frm_form_${ formID }_container form` );
1845 2285 frmFrontForm.submitFormNow( object );
1846 2286 },
1847 2287
1848 - submitForm: function( e ) {
2288 + submitForm( e ) {
1849 2289 frmFrontForm.submitFormManual( e, this );
1850 2290 },
1851 2291
1852 2292 /**
@@ -1853,9 +2293,9 @@
1853 2293 * @param {Event} e
1854 2294 * @param {HTMLElement} object The form object that is being submitted.
1855 2295 * @return {void}
1856 2296 */
1857 - submitFormManual: function( e, object ) {
2297 + submitFormManual( e, object ) {
1858 2298 if ( document.body.classList.contains( 'wp-admin' ) && ! object.closest( '.frmapi-form' ) ) {
1859 2299 return;
1860 2300 }
1861 2301
@@ -1881,11 +2321,12 @@
1881 2321 frmFrontForm.submitFormNow( object );
1882 2322 }
1883 2323 },
1884 2324
1885 - submitFormNow: function( object ) {
1886 - let hasFileFields, antispamInput,
1887 - classList = object.className.trim().split( /\s+/gi );
2325 + submitFormNow( object ) {
2326 + let hasFileFields;
2327 + let antispamInput;
2328 + const classList = object.className.trim().split( /\s+/gi );
1888 2329
1889 2330 if ( object.hasAttribute( 'data-token' ) && null === object.querySelector( '[name="antispam_token"]' ) ) {
1890 2331 // include the antispam token on form submit.
1891 2332 antispamInput = document.createElement( 'input' );
@@ -1921,11 +2362,11 @@
1921 2362 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1922 2363 *
1923 2364 * @return {Array} List of errors.
1924 2365 */
1925 - validateFormSubmit: function( object ) {
2366 + validateFormSubmit( object ) {
1926 2367 const form = object instanceof jQuery ? object.get( 0 ) : object;
1927 - if ( typeof tinyMCE !== 'undefined' && form && form.querySelector( '.wp-editor-wrap' ) ) {
2368 + if ( typeof tinyMCE !== 'undefined' && form?.querySelector( '.wp-editor-wrap' ) ) {
1928 2369 tinyMCE.triggerSave();
1929 2370 }
1930 2371
1931 2372 jsErrors = [];
@@ -1944,10 +2385,11 @@
1944 2385 /**
1945 2386 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1946 2387 * @return {Array} List of errors.
1947 2388 */
1948 - getAjaxFormErrors: function( object ) {
1949 - let customErrors, key;
2389 + getAjaxFormErrors( object ) {
2390 + let customErrors;
2391 + let key;
1950 2392 const form = object instanceof jQuery ? object.get( 0 ) : object;
1951 2393
1952 2394 jsErrors = validateForm( object );
1953 2395 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
@@ -1972,15 +2414,15 @@
1972 2414 /**
1973 2415 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1974 2416 * @return {void}
1975 2417 */
1976 - addAjaxFormErrors: function( object ) {
2418 + addAjaxFormErrors( object ) {
1977 2419 let key;
1978 2420 const form = object instanceof jQuery ? object.get( 0 ) : object;
1979 2421 removeAllErrors();
1980 2422
1981 2423 for ( key in jsErrors ) {
1982 - const fieldCont = form ? form.querySelector( '#frm_field_' + key + '_container' ) : null;
2424 + const fieldCont = form ? form.querySelector( `#frm_field_${ key }_container` ) : null;
1983 2425
1984 2426 if ( fieldCont ) {
1985 2427 addFieldError( fieldCont, key, jsErrors );
1986 2428 } else {
@@ -1997,24 +2439,27 @@
1997 2439 checkRequiredField,
1998 2440 showSubmitLoading,
1999 2441 removeSubmitLoading,
2000 2442
2001 - scrollToID: function( id ) {
2443 + scrollToID( id ) {
2002 2444 const object = jQuery( document.getElementById( id ) );
2003 2445 frmFrontForm.scrollMsg( object, false );
2004 2446 },
2005 2447
2006 - scrollMsg: function( id, object, animate ) {
2007 - let newPos, m, b, screenTop, screenBottom,
2008 - scrollObj = '';
2009 - if ( typeof object === 'undefined' ) {
2010 - scrollObj = jQuery( document.getElementById( 'frm_form_' + id + '_container' ) );
2448 + scrollMsg( id, object, animate ) {
2449 + let newPos;
2450 + let screenTop;
2451 + let screenBottom;
2452 + let scrollObj = '';
2453 +
2454 + if ( object === undefined ) {
2455 + scrollObj = jQuery( document.getElementById( `frm_form_${ id }_container` ) );
2011 2456 if ( scrollObj.length < 1 ) {
2012 2457 return;
2013 2458 }
2014 2459 } else if ( typeof id === 'string' ) {
2015 2460 const formEl = object instanceof jQuery ? object.get( 0 ) : object;
2016 - const fieldEl = formEl ? formEl.querySelector( '#frm_field_' + id + '_container' ) : null;
2461 + const fieldEl = formEl ? formEl.querySelector( `#frm_field_${ id }_container` ) : null;
2017 2462 scrollObj = fieldEl ? jQuery( fieldEl ) : jQuery();
2018 2463 } else {
2019 2464 scrollObj = id;
2020 2465 }
@@ -2020,17 +2465,17 @@
2020 2465 }
2021 2466
2022 2467 jQuery( scrollObj ).trigger( 'focus' );
2023 2468 newPos = scrollObj.offset().top;
2024 - if ( ! newPos || frm_js.offset === '-1' ) { // eslint-disable-line camelcase
2469 + if ( ! newPos || frm_js.offset === '-1' ) {
2025 2470 return;
2026 2471 }
2027 - newPos = newPos - frm_js.offset; // eslint-disable-line camelcase
2472 + newPos = newPos - frm_js.offset;
2028 2473
2029 - m = getComputedStyle( document.documentElement ).marginTop;
2030 - b = getComputedStyle( document.body ).marginTop;
2031 - if ( m || b ) {
2032 - newPos = newPos - parseInt( m ) - parseInt( b );
2474 + const docMarginTop = getComputedStyle( document.documentElement ).marginTop;
2475 + const bodyMarginTop = getComputedStyle( document.body ).marginTop;
2476 + if ( docMarginTop || bodyMarginTop ) {
2477 + newPos = newPos - parseInt( docMarginTop ) - parseInt( bodyMarginTop );
2033 2478 }
2034 2479
2035 2480 if ( newPos && window.innerHeight ) {
2036 2481 screenTop = document.documentElement.scrollTop || document.body.scrollTop;
@@ -2037,9 +2482,9 @@
2037 2482 screenBottom = screenTop + window.innerHeight;
2038 2483
2039 2484 if ( newPos > screenBottom || newPos < screenTop ) {
2040 2485 // Not in view
2041 - if ( typeof animate === 'undefined' ) {
2486 + if ( animate === undefined ) {
2042 2487 document.documentElement.scrollTop = newPos;
2043 2488 } else {
2044 2489 animateScroll( screenTop, newPos, 500 );
2045 2490 }
@@ -2047,9 +2492,9 @@
2047 2492 }
2048 2493 }
2049 2494 },
2050 2495
2051 - fieldValueChanged: function( e ) {
2496 + fieldValueChanged( e ) {
2052 2497 /*jshint validthis:true */
2053 2498
2054 2499 const fieldId = frmFrontForm.getFieldId( this, false );
2055 2500 if ( ! fieldId ) {
@@ -2066,9 +2511,9 @@
2066 2511 maybeValidateChange( this );
2067 2512 }
2068 2513 },
2069 2514
2070 - escapeHtml: function( text ) {
2515 + escapeHtml( text ) {
2071 2516 console.warn( 'DEPRECATED: function frmFrontForm.escapeHtml in v6.17' );
2072 2517 return text
2073 2518 .replace( /&/g, '&amp;' )
2074 2519 .replace( /</g, '&lt;' )
@@ -2076,9 +2521,9 @@
2076 2521 .replace( /"/g, '&quot;' )
2077 2522 .replace( /'/g, '&#039;' );
2078 2523 },
2079 2524
2080 - triggerCustomEvent: triggerCustomEvent,
2525 + triggerCustomEvent,
2081 2526 documentOn
2082 2527 };
2083 2528 }
2084 2529
@@ -2125,9 +2570,9 @@
2125 2570 const captcha = captchas[ c ];
2126 2571 if ( ! formIsVisible ) {
2127 2572 // If the form is not visible, try again later in 400ms.
2128 2573 // This fixes issues where the form fades visible on page load.
2129 - // Or whne the form is inside of a modal.
2574 + // Or when the form is inside of a modal.
2130 2575 const interval = setInterval(
2131 2576 function() {
2132 2577 if ( closestForm && closestForm.offsetParent !== null ) {
2133 2578 frmFrontForm.renderCaptcha( captcha, captchaSelector );