PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.19
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.19
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 +319 -414 6.286.19 View file →
@@ -15,9 +15,9 @@
15 15 * @since 5.5.3
16 16 *
17 17 * @param {HTMLElement} el The HTML element.
18 18 * @param {string} eventName Event name.
19 - * @param {*} data The passed data.
19 + * @param {mixed} data The passed data.
20 20 */
21 21 function triggerCustomEvent( el, eventName, data ) {
22 22 if ( typeof window.CustomEvent !== 'function' ) {
23 23 return;
@@ -39,21 +39,24 @@
39 39 function getFieldId( field, fullID ) {
40 40 let nameParts, fieldId,
41 41 isRepeating = false,
42 42 fieldName = '';
43 -
44 43 if ( field instanceof jQuery ) {
45 - field = field.get( 0 );
44 + fieldName = field.attr( 'name' );
45 + } else {
46 + fieldName = field.name;
46 47 }
47 48
48 - fieldName = field.name;
49 -
50 49 if ( typeof fieldName === 'undefined' ) {
51 50 fieldName = '';
52 51 }
53 52
54 53 if ( fieldName === '' ) {
55 - fieldName = field.getAttribute( 'data-name' );
54 + if ( field instanceof jQuery ) {
55 + fieldName = field.data( 'name' );
56 + } else {
57 + fieldName = field.getAttribute( 'data-name' );
58 + }
56 59
57 60 if ( typeof fieldName === 'undefined' ) {
58 61 fieldName = '';
59 62 }
@@ -70,24 +73,25 @@
70 73 return 0;
71 74 }
72 75 nameParts = nameParts.filter( function( n ) {
73 76 return n !== '';
74 - } );
77 + });
75 78
76 - fieldId = nameParts[ 0 ];
79 + fieldId = nameParts[0];
77 80
78 81 if ( nameParts.length === 1 ) {
79 82 return fieldId;
80 83 }
81 84
82 - if ( nameParts[ 1 ] === '[form' || nameParts[ 1 ] === '[row_ids' ) {
85 + if ( nameParts[1] === '[form' || nameParts[1] === '[row_ids' ) {
83 86 return 0;
84 87 }
85 88
86 89 // Check if 'this' is in a repeating section
87 90 if ( document.querySelector( 'input[name="item_meta[' + fieldId + '][form]"]' ) ) {
91 +
88 92 // this is a repeatable section with name: item_meta[repeating-section-id][row-id][field-id]
89 - fieldId = nameParts[ 2 ].replace( '[', '' );
93 + fieldId = nameParts[2].replace( '[', '' );
90 94 isRepeating = true;
91 95 }
92 96
93 97 // Check if 'this' is an other text field and get field ID for it
@@ -93,21 +97,21 @@
93 97 // Check if 'this' is an other text field and get field ID for it
94 98 if ( 'other' === fieldId ) {
95 99 if ( isRepeating ) {
96 100 // name for other fields: item_meta[370][0][other][414]
97 - fieldId = nameParts[ 3 ].replace( '[', '' );
101 + fieldId = nameParts[3].replace( '[', '' );
98 102 } else {
99 103 // Other field name: item_meta[other][370]
100 - fieldId = nameParts[ 1 ].replace( '[', '' );
104 + fieldId = nameParts[1].replace( '[', '' );
101 105 }
102 106 }
103 107
104 108 if ( fullID === true ) {
105 109 // For use in the container div id
106 - if ( fieldId === nameParts[ 0 ] ) {
107 - fieldId = fieldId + '-' + nameParts[ 1 ].replace( '[', '' );
110 + if ( fieldId === nameParts[0]) {
111 + fieldId = fieldId + '-' + nameParts[1].replace( '[', '' );
108 112 } else {
109 - fieldId = fieldId + '-' + nameParts[ 0 ] + '-' + nameParts[ 1 ].replace( '[', '' );
113 + fieldId = fieldId + '-' + nameParts[0] + '-' + nameParts[1].replace( '[', '' );
110 114 }
111 115 }
112 116
113 117 return fieldId;
@@ -120,15 +124,9 @@
120 124 *
121 125 * @param {Object} $form
122 126 */
123 127 function disableSubmitButton( $form ) {
124 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
125 - if ( ! form ) {
126 - return;
127 - }
128 - form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).forEach(
129 - button => button.disabled = true
130 - );
128 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"], button.frm_save_draft' ).attr( 'disabled', 'disabled' );
131 129 }
132 130
133 131 /**
134 132 * Enable the submit button for a given jQuery form object
@@ -134,16 +132,12 @@
134 132 * Enable the submit button for a given jQuery form object
135 133 *
136 134 * @since 2.03.02
137 135 *
138 - * @param {HTMLElement} form
139 - *
140 - * @return {void}
136 + * @param {Object} $form
141 137 */
142 - function enableSubmitButton( form ) {
143 - form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"]' ).forEach(
144 - button => button.disabled = false
145 - );
138 + function enableSubmitButton( $form ) {
139 + $form.find( 'input[type="submit"], input[type="button"], button[type="submit"]' ).prop( 'disabled', false );
146 140 }
147 141
148 142 /**
149 143 * Disable the save draft link for a given jQuery form object
@@ -152,33 +146,26 @@
152 146 *
153 147 * @param {Object} $form
154 148 */
155 149 function disableSaveDraft( $form ) {
156 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
157 - if ( ! form ) {
158 - return;
159 - }
160 - form.querySelectorAll( 'a.frm_save_draft' ).forEach(
161 - link => link.style.pointerEvents = 'none'
162 - );
150 + $form.find( 'a.frm_save_draft' ).css( 'pointer-events', 'none' );
163 151 }
164 152
165 153 /**
166 - * Enable the save draft link for a given form object.
154 + * Enable the save draft link for a given jQuery form object
167 155 *
168 156 * @since 4.04.03
169 157 *
170 - * @param {jQuery|HTMLElement} $form
158 + * @param {jQuery} $form
171 159 */
172 160 function enableSaveDraft( $form ) {
173 - const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
174 - if ( ! form ) {
161 + if ( ! $form.length ) {
175 162 return;
176 163 }
177 - form.querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
178 - saveDraftButton.disabled = false;
164 + $form[0].querySelectorAll( '.frm_save_draft' ).forEach( saveDraftButton => {
165 + saveDraftButton.disabled = false;
179 166 saveDraftButton.style.pointerEvents = '';
180 - } );
167 + });
181 168 }
182 169
183 170 /**
184 171 * Validate form with JS.
@@ -256,9 +243,9 @@
256 243 return;
257 244 }
258 245
259 246 fieldID = getFieldId( field, true );
260 - if ( 'undefined' === typeof errors[ fieldID ] ) {
247 + if ( 'undefined' === typeof errors[ fieldID ]) {
261 248 errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' );
262 249 }
263 250
264 251 if ( 'function' === typeof field.reportValidity ) {
@@ -282,9 +269,9 @@
282 269 * @param {HTMLElement} field
283 270 */
284 271 function maybeValidateChange( field ) {
285 272 if ( field.type === 'url' ) {
286 - maybeAddHttpsToUrl( field );
273 + maybeAddHttpToUrl( field );
287 274 }
288 275 const form = field.closest( 'form' );
289 276 if ( form && hasClass( form, 'frm_js_validate' ) ) {
290 277 validateField( field );
@@ -293,13 +280,13 @@
293 280
294 281 /**
295 282 * @param {HTMLElement} field
296 283 */
297 - function maybeAddHttpsToUrl( field ) {
284 + function maybeAddHttpToUrl( field ) {
298 285 const url = field.value;
299 286 const matches = url.match( /^(https?|ftps?|mailto|news|feed|telnet):/ );
300 287 if ( field.value !== '' && matches === null ) {
301 - field.value = 'https://' + url;
288 + field.value = 'http://' + url;
302 289 }
303 290 }
304 291
305 292 /**
@@ -311,9 +298,9 @@
311 298 */
312 299 function validateField( field ) {
313 300 let errors, key;
314 301
315 - errors = [];
302 + errors = [];
316 303 const fieldContainer = field.closest( '.frm_form_field' );
317 304
318 305 if ( ! fieldContainer ) {
319 306 // Hidden fields do not have a field container and do not require JS validation.
@@ -327,12 +314,13 @@
327 314 if ( errors.length < 1 ) {
328 315 validateFieldValue( field, errors, false );
329 316 }
330 317
331 - removeFieldError( fieldContainer );
318 + const $fieldCont = jQuery( fieldContainer );
319 + removeFieldError( $fieldCont );
332 320 if ( Object.keys( errors ).length > 0 ) {
333 321 for ( key in errors ) {
334 - addFieldError( fieldContainer, key, errors );
322 + addFieldError( $fieldCont, key, errors );
335 323 }
336 324 }
337 325 }
338 326
@@ -359,12 +347,8 @@
359 347 } else if ( field.pattern !== null ) {
360 348 checkPatternField( field, errors );
361 349 }
362 350
363 - if ( 'tel' === field.type && shouldCheckConfirmField( field, onSubmit ) ) {
364 - confirmField( field, errors );
365 - }
366 -
367 351 /**
368 352 * @since 6.15 Added `onSubmit` to the data.
369 353 */
370 354 triggerCustomEvent( document, 'frm_validate_field_value', {
@@ -370,9 +354,9 @@
370 354 triggerCustomEvent( document, 'frm_validate_field_value', {
371 355 field: field,
372 356 errors: errors,
373 357 onSubmit: onSubmit
374 - } );
358 + });
375 359 }
376 360
377 361 /**
378 362 * @param {HTMLElement} field
@@ -379,9 +363,9 @@
379 363 * @param {Array} errors
380 364 * @return {Array} Errors
381 365 */
382 366 function checkRequiredField( field, errors ) {
383 - let tempVal, i, placeholder,
367 + let checkGroup, tempVal, i, placeholder,
384 368 val = '',
385 369 fieldID = '',
386 370 fileID = field.getAttribute( 'data-frmfile' );
387 371
@@ -389,19 +373,12 @@
389 373 return errors;
390 374 }
391 375
392 376 if ( field.type === 'checkbox' || field.type === 'radio' ) {
393 - document.querySelectorAll( 'input[name="' + field.name + '"]' ).forEach( function( input ) {
394 - const requiredField = input.closest( '.frm_required_field' );
395 - if ( ! requiredField ) {
396 - return;
397 - }
398 -
399 - const checkedInputs = requiredField.querySelectorAll( 'input:checked' );
400 - checkedInputs.forEach( function( checkedInput ) {
401 - val = checkedInput.value;
402 - } );
403 - } );
377 + checkGroup = jQuery( 'input[name="' + field.name + '"]' ).closest( '.frm_required_field' ).find( 'input:checked' );
378 + jQuery( checkGroup ).each( function() {
379 + val = this.value;
380 + });
404 381 } else if ( field.type === 'file' || fileID ) {
405 382 if ( typeof fileID === 'undefined' ) {
406 383 fileID = getFieldId( field, true );
407 384 fileID = fileID.replace( 'file', '' );
@@ -416,10 +393,9 @@
416 393 // skip hidden other fields
417 394 return errors;
418 395 }
419 396
420 - val = jQuery( field ).val(); // eslint-disable-line no-jquery/no-val
421 -
397 + val = jQuery( field ).val();
422 398 if ( val === null ) {
423 399 val = '';
424 400 } else if ( typeof val !== 'string' ) {
425 401 tempVal = val;
@@ -424,10 +400,10 @@
424 400 } else if ( typeof val !== 'string' ) {
425 401 tempVal = val;
426 402 val = '';
427 403 for ( i = 0; i < tempVal.length; i++ ) {
428 - if ( tempVal[ i ] !== '' ) {
429 - val = tempVal[ i ];
404 + if ( tempVal[i] !== '' ) {
405 + val = tempVal[i];
430 406 }
431 407 }
432 408 }
433 409
@@ -451,11 +427,9 @@
451 427 // set id for time field
452 428 fieldID = fieldID.replace( '-H', '' ).replace( '-m', '' );
453 429 } else if ( isSignatureField( field ) ) {
454 430 if ( val === '' ) {
455 - const fieldContainer = field.closest( '.frm_form_field' );
456 - const outputField = fieldContainer ? fieldContainer.querySelector( '[name="' + field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) + '"]' ) : null;
457 - val = outputField ? outputField.value : '';
431 + val = jQuery( field ).closest( '.frm_form_field' ).find( '[name="' + field.getAttribute( 'name' ).replace( '[typed]', '[output]' ) + '"]' ).val();
458 432 }
459 433 fieldID = fieldID.replace( '-typed', '' );
460 434 }
461 435
@@ -506,16 +480,16 @@
506 480 * @param {string|number} fileID
507 481 * @return {string} File input value.
508 482 */
509 483 function getFileVals( fileID ) {
510 - let val = '';
511 - const fileFields = document.querySelectorAll( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
484 + let val = '',
485 + fileFields = jQuery( 'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]' );
512 486
513 - fileFields.forEach( function( field ) {
487 + fileFields.each( function() {
514 488 if ( val === '' ) {
515 - val = field.value;
489 + val = this.value;
516 490 }
517 - } );
491 + });
518 492 return val;
519 493 }
520 494
521 495 /**
@@ -685,9 +659,9 @@
685 659 }
686 660
687 661 // Function to change the color of a select element
688 662 changeSelectColor = function( select ) {
689 - if ( select.options[ select.selectedIndex ] && hasClass( select.options[ select.selectedIndex ], 'frm-select-placeholder' ) ) {
663 + if ( select.options[select.selectedIndex] && hasClass( select.options[select.selectedIndex], 'frm-select-placeholder' ) ) {
690 664 select.style.setProperty( 'color', textColorDisabled, 'important' );
691 665 } else {
692 666 select.style.color = '';
693 667 }
@@ -700,68 +674,57 @@
700 674
701 675 // Add an event listener for future changes
702 676 select.addEventListener( 'change', function() {
703 677 changeSelectColor( select );
704 - } );
705 - } );
678 + });
679 + });
706 680 }
707 681
708 682 /**
709 683 * @param {HTMLElement|jQuery} object
710 - *
711 - * @return {HTMLElement|false} Captcha element if there is an invisible recaptcha.
684 + * @return {boolean} True if there is an invisible recaptcha.
712 685 */
713 686 function hasInvisibleRecaptcha( object ) {
687 + let recaptcha, recaptchaID, alreadyChecked;
688 +
714 689 if ( isGoingToPrevPage( object ) ) {
715 690 return false;
716 691 }
717 692
718 - const form = object instanceof jQuery ? object.get( 0 ) : object;
719 - if ( ! form ) {
720 - return false;
721 - }
722 -
723 - const recaptcha = form.querySelector( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
724 - if ( recaptcha ) {
725 - const recaptchaID = recaptcha.dataset.rid;
726 - const alreadyChecked = grecaptcha.getResponse( recaptchaID );
693 + recaptcha = jQuery( object ).find( '.frm-g-recaptcha[data-size="invisible"], .g-recaptcha[data-size="invisible"]' );
694 + if ( recaptcha.length ) {
695 + recaptchaID = recaptcha.data( 'rid' );
696 + alreadyChecked = grecaptcha.getResponse( recaptchaID );
727 697 if ( alreadyChecked.length === 0 ) {
728 698 return recaptcha;
729 699 }
730 700 }
731 -
732 701 return false;
733 702 }
734 703
735 704 /**
736 - * @param {HTMLElement} invisibleRecaptcha
737 - *
738 - * @return {void}
705 + * @param {jQuery} invisibleRecaptcha
739 706 */
740 707 function executeInvisibleRecaptcha( invisibleRecaptcha ) {
741 - const recaptchaID = invisibleRecaptcha.dataset.rid;
708 + const recaptchaID = invisibleRecaptcha.data( 'rid' );
742 709 grecaptcha.reset( recaptchaID );
743 710 grecaptcha.execute( recaptchaID );
744 711 }
745 712
746 713 function validateRecaptcha( form, errors ) {
747 - const formEl = form instanceof jQuery ? form.get( 0 ) : form;
748 - if ( ! formEl ) {
749 - return errors;
750 - }
714 + let response;
751 715
752 - const recaptcha = formEl.querySelector( '.frm-g-recaptcha' );
753 - if ( ! recaptcha ) {
716 + const $recaptcha = jQuery( form ).find( '.frm-g-recaptcha' );
717 + if ( ! $recaptcha.length ) {
754 718 return errors;
755 719 }
756 720
757 - const recaptchaID = recaptcha.dataset.rid;
758 - let response;
721 + const recaptchaID = $recaptcha.data( 'rid' );
759 722
760 723 try {
761 724 response = grecaptcha.getResponse( recaptchaID );
762 725 } catch ( e ) {
763 - if ( formEl.querySelector( 'input[name="recaptcha_checked"]' ) ) {
726 + if ( jQuery( form ).find( 'input[name="recaptcha_checked"]' ).length ) {
764 727 return errors;
765 728 }
766 729 response = '';
767 730 }
@@ -766,13 +729,11 @@
766 729 response = '';
767 730 }
768 731
769 732 if ( response.length === 0 ) {
770 - const fieldContainer = recaptcha.closest( '.frm_form_field' );
771 - if ( fieldContainer && fieldContainer.id ) {
772 - const fieldID = fieldContainer.id.replace( 'frm_field_', '' ).replace( '_container', '' );
773 - errors[ fieldID ] = '';
774 - }
733 + const fieldContainer = $recaptcha.closest( '.frm_form_field' );
734 + const fieldID = fieldContainer.attr( 'id' ).replace( 'frm_field_', '' ).replace( '_container', '' );
735 + errors[ fieldID ] = '';
775 736 }
776 737
777 738 return errors;
778 739 }
@@ -805,15 +766,15 @@
805 766 if ( null === errorHtml ) {
806 767 return msg;
807 768 }
808 769
809 - errorHtml = errorHtml.replace( /\+/g, '%20' );
810 - msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
811 - const fieldId = getFieldId( field, false );
812 - const split = fieldId.split( '-' );
770 + errorHtml = errorHtml.replace( /\+/g, '%20' );
771 + msg = decodeURIComponent( errorHtml ).replace( '[error]', msg );
772 + const fieldId = getFieldId( field, false );
773 + const split = fieldId.split( '-' );
813 774 const fieldIdParts = field.id.split( '_' );
814 775 fieldIdParts.shift(); // Drop the "field" value from the front.
815 - split[ 0 ] = fieldIdParts.join( '_' );
776 + split[0] = fieldIdParts.join( '_' );
816 777 const errorKey = split.join( '-' );
817 778 return msg.replace( '[key]', errorKey );
818 779 }
819 780
@@ -845,18 +806,18 @@
845 806 * @param {string} action
846 807 * @return {void}
847 808 */
848 809 function getFormErrors( object, action ) {
849 - let data, success, error, shouldTriggerEvent;
810 + let fieldset, data, success, error, shouldTriggerEvent;
850 811
851 - const fieldsets = object.querySelectorAll( '.frm_form_field' );
852 - fieldsets.forEach( field => field.classList.add( 'frm_doing_ajax' ) );
812 + fieldset = jQuery( object ).find( '.frm_form_field' );
813 + fieldset.addClass( 'frm_doing_ajax' );
853 814
854 - data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line no-jquery/no-serialize, camelcase
815 + data = jQuery( object ).serialize() + '&action=frm_entries_' + action + '&nonce=' + frm_js.nonce; // eslint-disable-line camelcase
855 816 shouldTriggerEvent = object.classList.contains( 'frm_trigger_event_on_submit' );
856 817
857 818 const doRedirect = response => {
858 - jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ] );
819 + jQuery( document ).trigger( 'frmBeforeFormRedirect', [ object, response ]);
859 820
860 821 if ( ! response.openInNewTab ) {
861 822 // We return here because we're redirecting there is no need to update content.
862 823 window.location = response.redirect;
@@ -910,9 +871,9 @@
910 871 if ( 'string' === typeof response.content && response.content !== '' ) {
911 872 // the form or success message was returned
912 873
913 874 if ( shouldTriggerEvent ) {
914 - triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content } );
875 + triggerCustomEvent( object, 'frmSubmitEvent', { content: response.content });
915 876 return;
916 877 }
917 878
918 879 removeSubmitLoading( jQuery( object ) );
@@ -919,17 +880,18 @@
919 880 if ( frm_js.offset != -1 ) { // eslint-disable-line camelcase
920 881 frmFrontForm.scrollMsg( jQuery( object ), false );
921 882 }
922 883
923 - const formIdInput = object.querySelector( 'input[name="form_id"]' );
924 - formID = formIdInput ? formIdInput.value : '';
884 + formID = jQuery( object ).find( 'input[name="form_id"]' ).val();
925 885 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
886 + replaceContent = jQuery( object ).closest( '.frm_forms' );
927 887 removeAddedScripts( replaceContent, formID );
928 888 delay = maybeSlideOut( replaceContent, response.content );
929 889
930 890 setTimeout(
931 891 function() {
892 + let container, input, previousInput;
893 +
932 894 afterFormSubmittedBeforeReplace( object, response );
933 895
934 896 replaceContent.replaceWith( response.content );
935 897
@@ -935,17 +897,25 @@
935 897
936 898 addUrlParam( response );
937 899
938 900 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 : '';
941 - const tempDiv = document.createElement( 'div' );
942 - tempDiv.innerHTML = response.content;
943 - const formReturnedInput = tempDiv.querySelector( 'input[name="form_id"]' );
944 - formReturned = formReturnedInput ? formReturnedInput.value : '';
901 + pageOrder = jQuery( 'input[name="frm_page_order_' + formID + '"]' ).val();
902 + formReturned = jQuery( response.content ).find( 'input[name="form_id"]' ).val();
945 903 frmThemeOverride_frmAfterSubmit( formReturned, pageOrder, response.content, object );
946 904 }
947 905
906 + if ( typeof response.recaptcha !== 'undefined' ) {
907 + container = jQuery( '#frm_form_' + formID + '_container' ).find( '.frm_fields_container' );
908 + input = '<input type="hidden" name="recaptcha_checked" value="' + response.recaptcha + '">';
909 + previousInput = container.find( 'input[name="recaptcha_checked"]' );
910 +
911 + if ( previousInput.length ) {
912 + previousInput.replaceWith( input );
913 + } else {
914 + container.append( input );
915 + }
916 + }
917 +
948 918 afterFormSubmitted( object, response );
949 919 },
950 920 delay
951 921 );
@@ -959,14 +929,13 @@
959 929
960 930 $fieldCont = null;
961 931
962 932 for ( key in response.errors ) {
963 - const fieldContEl = object.querySelector( '#frm_field_' + key + '_container' );
964 - $fieldCont = fieldContEl ? jQuery( fieldContEl ) : jQuery();
933 + $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
965 934
966 935 if ( $fieldCont.length ) {
967 - 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
936 + if ( ! $fieldCont.is( ':visible' ) ) {
937 + inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' );
969 938 if ( inCollapsedSection.length ) {
970 939 frmTrigger = inCollapsedSection.prev();
971 940 if ( ! frmTrigger.hasClass( 'frm_trigger' ) ) {
972 941 // If the frmTrigger object is the section description, check to see if the previous element is the trigger
@@ -975,9 +944,9 @@
975 944 frmTrigger.trigger( 'click' );
976 945 }
977 946 }
978 947
979 - if ( $fieldCont.is( ':visible' ) ) { // eslint-disable-line no-jquery/no-is
948 + if ( $fieldCont.is( ':visible' ) ) {
980 949 addFieldError( $fieldCont, key, response.errors );
981 950 contSubmit = false;
982 951 }
983 952 }
@@ -982,10 +951,11 @@
982 951 }
983 952 }
984 953 }
985 954
986 - object.querySelectorAll( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).forEach( function( captchaEl ) {
987 - const recaptchaID = captchaEl.dataset.rid;
955 + jQuery( object ).find( '.frm-g-recaptcha, .g-recaptcha, .h-captcha' ).each( function() {
956 + const $recaptcha = jQuery( this ),
957 + recaptchaID = $recaptcha.data( 'rid' );
988 958
989 959 if ( typeof grecaptcha !== 'undefined' && grecaptcha ) {
990 960 if ( recaptchaID ) {
991 961 grecaptcha.reset( recaptchaID );
@@ -992,23 +962,22 @@
992 962 } else {
993 963 grecaptcha.reset();
994 964 }
995 965 }
996 -
997 966 if ( typeof hcaptcha !== 'undefined' && hcaptcha ) {
998 967 hcaptcha.reset();
999 968 }
1000 - } );
969 + });
1001 970
1002 971 if ( window.turnstile ) {
1003 - object.querySelectorAll( '.frm-cf-turnstile' ).forEach(
972 + object.querySelectorAll( '.cf-turnstile' ).forEach(
1004 973 turnstileField => turnstileField.dataset.rid && turnstile.reset( turnstileField.dataset.rid )
1005 974 );
1006 975 }
1007 976
1008 - jQuery( document ).trigger( 'frmFormErrors', [ object, response ] );
977 + jQuery( document ).trigger( 'frmFormErrors', [ object, response ]);
1009 978
1010 - fieldsets.forEach( field => field.classList.remove( 'frm_doing_ajax' ) );
979 + fieldset.removeClass( 'frm_doing_ajax' );
1011 980 scrollToFirstField( object );
1012 981
1013 982 if ( contSubmit ) {
1014 983 object.submit();
@@ -1025,11 +994,9 @@
1025 994 }
1026 995 };
1027 996
1028 997 error = function() {
1029 - object.querySelectorAll( 'input[type="submit"], input[type="button"]' ).forEach(
1030 - button => button.disabled = false
1031 - );
998 + jQuery( object ).find( 'input[type="submit"], input[type="button"]' ).prop( 'disabled', false );
1032 999 object.submit();
1033 1000 };
1034 1001
1035 1002 postToAjaxUrl( object, data, success, error );
@@ -1041,9 +1008,9 @@
1041 1008 ajaxUrl = frm_js.ajax_url; // eslint-disable-line camelcase
1042 1009 action = form.getAttribute( 'action' );
1043 1010
1044 1011 if ( 'string' === typeof action && -1 !== action.indexOf( '?action=frm_forms_preview' ) ) {
1045 - ajaxUrl = action.split( '?action=frm_forms_preview' )[ 0 ];
1012 + ajaxUrl = action.split( '?action=frm_forms_preview' )[0];
1046 1013 }
1047 1014
1048 1015 ajaxParams = {
1049 1016 type: 'POST',
@@ -1055,19 +1022,17 @@
1055 1022 if ( 'function' === typeof error ) {
1056 1023 ajaxParams.error = error;
1057 1024 }
1058 1025
1059 - jQuery.ajax( ajaxParams ); // eslint-disable-line no-jquery/no-ajax
1026 + jQuery.ajax( ajaxParams );
1060 1027 }
1061 1028
1062 1029 function afterFormSubmitted( object, response ) {
1063 - const tempDiv = document.createElement( 'div' );
1064 - tempDiv.innerHTML = response.content;
1065 - const formCompleted = tempDiv.querySelector( '.frm_message' );
1066 - if ( formCompleted ) {
1067 - jQuery( document ).trigger( 'frmFormComplete', [ object, response ] );
1030 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
1031 + if ( formCompleted.length ) {
1032 + jQuery( document ).trigger( 'frmFormComplete', [ object, response ]);
1068 1033 } else {
1069 - jQuery( document ).trigger( 'frmPageChanged', [ object, response ] );
1034 + jQuery( document ).trigger( 'frmPageChanged', [ object, response ]);
1070 1035 }
1071 1036 }
1072 1037
1073 1038 /**
@@ -1079,21 +1044,19 @@
1079 1044 * @param {Object} response The response from submitting the form with AJAX.
1080 1045 * @return {void}
1081 1046 */
1082 1047 function afterFormSubmittedBeforeReplace( object, response ) {
1083 - const tempDiv = document.createElement( 'div' );
1084 - tempDiv.innerHTML = response.content;
1085 - const formCompleted = tempDiv.querySelector( '.frm_message' );
1086 - if ( formCompleted ) {
1087 - triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response } );
1048 + const formCompleted = jQuery( response.content ).find( '.frm_message' );
1049 + if ( formCompleted.length ) {
1050 + triggerCustomEvent( document, 'frmFormCompleteBeforeReplace', { object, response });
1088 1051 }
1089 1052 }
1090 1053
1091 1054 function removeAddedScripts( formContainer, formID ) {
1092 - const endReplace = document.querySelectorAll( '.frm_end_ajax_' + formID );
1055 + const endReplace = jQuery( '.frm_end_ajax_' + formID );
1093 1056 if ( endReplace.length ) {
1094 1057 formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
1095 - endReplace.forEach( el => el.remove() );
1058 + endReplace.remove();
1096 1059 }
1097 1060 }
1098 1061
1099 1062 function maybeSlideOut( oldContent, newContent ) {
@@ -1098,11 +1061,11 @@
1098 1061
1099 1062 function maybeSlideOut( oldContent, newContent ) {
1100 1063 let c,
1101 1064 newClass = 'frm_slideout';
1102 - if ( newContent.includes( ' frm_slide' ) ) {
1065 + if ( newContent.indexOf( ' frm_slide' ) !== -1 ) {
1103 1066 c = oldContent.children();
1104 - if ( newContent.includes( ' frm_going_back' ) ) {
1067 + if ( newContent.indexOf( ' frm_going_back' ) !== -1 ) {
1105 1068 newClass += ' frm_going_back';
1106 1069 }
1107 1070 c.removeClass( 'frm_going_back' );
1108 1071 c.addClass( newClass );
@@ -1114,9 +1077,9 @@
1114 1077 function addUrlParam( response ) {
1115 1078 let url;
1116 1079 if ( history.pushState && typeof response.page !== 'undefined' ) {
1117 1080 url = addQueryVar( 'frm_page', response.page );
1118 - window.history.pushState( { html: response.html }, '', '?' + url );
1081 + window.history.pushState({ 'html': response.html }, '', '?' + url );
1119 1082 }
1120 1083 }
1121 1084
1122 1085 function addQueryVar( key, value ) {
@@ -1128,13 +1091,13 @@
1128 1091 kvp = document.location.search.substr( 1 ).split( '&' );
1129 1092
1130 1093 i = kvp.length;
1131 1094 while ( i-- ) {
1132 - x = kvp[ i ].split( '=' );
1095 + x = kvp[i].split( '=' );
1133 1096
1134 - if ( x[ 0 ] == key ) {
1135 - x[ 1 ] = value;
1136 - kvp[ i ] = x.join( '=' );
1097 + if ( x[0] == key ) {
1098 + x[1] = value;
1099 + kvp[i] = x.join( '=' );
1137 1100 break;
1138 1101 }
1139 1102 }
1140 1103
@@ -1145,59 +1108,44 @@
1145 1108 return kvp.join( '&' );
1146 1109 }
1147 1110
1148 1111 function addFieldError( $fieldCont, key, jsErrors ) {
1149 - let id, describedBy, roleString;
1150 - const container = $fieldCont instanceof jQuery ? $fieldCont.get( 0 ) : $fieldCont;
1151 - if ( ! container || container.offsetParent === null ) {
1152 - return;
1153 - }
1112 + let input, id, describedBy, roleString;
1113 + if ( $fieldCont.length && $fieldCont.is( ':visible' ) ) {
1114 + $fieldCont.addClass( 'frm_blank_field' );
1115 + input = $fieldCont.find( 'input, select, textarea' );
1116 + id = getErrorElementId( key, input.get( 0 ) );
1154 1117
1155 - container.classList.add( 'frm_blank_field' );
1156 - const input = container.querySelector( 'input, select, textarea' );
1157 - id = getErrorElementId( key, input );
1118 + describedBy = input.attr( 'aria-describedby' );
1158 1119
1159 - describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
1160 -
1161 - if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
1162 - frmThemeOverride_frmPlaceError( key, jsErrors );
1163 - } else {
1164 - let errorHtml;
1165 - if ( -1 !== jsErrors[ key ].indexOf( '<div' ) ) {
1166 - errorHtml = jsErrors[ key ];
1120 + if ( typeof frmThemeOverride_frmPlaceError === 'function' ) { // eslint-disable-line camelcase
1121 + frmThemeOverride_frmPlaceError( key, jsErrors );
1167 1122 } 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>';
1170 - }
1171 - container.insertAdjacentHTML( 'beforeend', errorHtml );
1123 + if ( -1 !== jsErrors[key].indexOf( '<div' ) ) {
1124 + $fieldCont.append(
1125 + jsErrors[key]
1126 + );
1127 + } else {
1128 + roleString = frm_js.include_alert_role ? 'role="alert"' : ''; // eslint-disable-line camelcase
1129 + $fieldCont.append( '<div class="frm_error" ' + roleString + ' id="' + id + '">' + jsErrors[key] + '</div>' );
1130 + }
1172 1131
1173 - if ( input ) {
1174 - if ( ! describedBy ) {
1132 + if ( typeof describedBy === 'undefined' ) {
1175 1133 describedBy = id;
1176 - } else if ( ! describedBy.includes( id ) && ! describedBy.includes( 'frm_error_field_' ) ) {
1177 - const errorFirst = input.dataset.errorFirst;
1178 - if ( errorFirst === '0' ) {
1134 + } else if ( describedBy.indexOf( id ) === -1 && describedBy.indexOf( 'frm_error_field_' ) === -1 ) {
1135 + if ( input.data( 'error-first' ) === 0 ) {
1179 1136 describedBy = describedBy + ' ' + id;
1180 1137 } else {
1181 1138 describedBy = id + ' ' + describedBy;
1182 1139 }
1183 1140 }
1184 - input.setAttribute( 'aria-describedby', describedBy );
1141 +
1142 + input.attr( 'aria-describedby', describedBy );
1185 1143 }
1186 - }
1144 + input.attr( 'aria-invalid', true );
1187 1145
1188 - if ( input ) {
1189 - if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
1190 - const group = input.closest( '[role="radiogroup"], [role="group"]' );
1191 - if ( group ) {
1192 - group.setAttribute( 'aria-invalid', 'true' );
1193 - }
1194 - } else {
1195 - input.setAttribute( 'aria-invalid', 'true' );
1196 - }
1146 + jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
1197 1147 }
1198 -
1199 - jQuery( document ).trigger( 'frmAddFieldError', [ jQuery( container ), key, jsErrors ] );
1200 1148 }
1201 1149
1202 1150 /**
1203 1151 * Get the ID to use for an error element added when submitting with AJAX.
@@ -1217,56 +1165,36 @@
1217 1165 /**
1218 1166 * Removes errors before validating with JS.
1219 1167 * This prevents issues with stale errors that has since been fixed.
1220 1168 *
1221 - * @param {HTMLElement|jQuery} fieldCont Field container element.
1169 + * @param {Object} $fieldCont jQuery object.
1222 1170 * @return {void}
1223 1171 */
1224 - function removeFieldError( fieldCont ) {
1225 - const container = fieldCont instanceof jQuery ? fieldCont.get( 0 ) : fieldCont;
1226 - if ( ! container ) {
1227 - return;
1228 - }
1172 + function removeFieldError( $fieldCont ) {
1173 + const errorMessage = $fieldCont.find( '.frm_error' );
1174 + const errorId = errorMessage.attr( 'id' );
1175 + const input = $fieldCont.find( 'input, select, textarea' );
1176 + let describedBy = input.attr( 'aria-describedby' );
1229 1177
1230 - const errorMessage = container.querySelector( '.frm_error' );
1231 - const errorId = errorMessage ? errorMessage.id : '';
1232 - const input = container.querySelector( 'input, select, textarea' );
1233 - let describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
1234 -
1235 - container.classList.remove( 'frm_blank_field', 'has-error' );
1236 -
1237 - if ( input ) {
1238 - if ( 'true' === input.getAttribute( 'aria-invalid' ) ) {
1239 - input.setAttribute( 'aria-invalid', 'false' );
1240 - } else if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
1241 - const group = input.closest( '[role="radiogroup"], [role="group"]' );
1242 - if ( group ) {
1243 - group.setAttribute( 'aria-invalid', 'false' );
1244 - }
1245 - }
1178 + const fieldContainer = $fieldCont.get( 0 );
1179 + if ( fieldContainer && fieldContainer.classList ) {
1180 + fieldContainer.classList.remove( 'frm_blank_field', 'has-error' );
1246 1181 }
1247 1182
1248 - if ( errorMessage ) {
1249 - errorMessage.remove();
1250 - }
1183 + errorMessage.remove();
1184 + input.attr( 'aria-invalid', false );
1185 + input.removeAttr( 'aria-describedby' );
1251 1186
1252 - if ( input ) {
1253 - input.removeAttribute( 'aria-describedby' );
1254 - if ( describedBy ) {
1255 - describedBy = describedBy.replace( errorId, '' ).trim();
1256 - if ( describedBy ) {
1257 - input.setAttribute( 'aria-describedby', describedBy );
1258 - }
1259 - }
1187 + if ( typeof describedBy !== 'undefined' ) {
1188 + describedBy = describedBy.replace( errorId, '' );
1189 + input.attr( 'aria-describedby', describedBy );
1260 1190 }
1261 1191 }
1262 1192
1263 1193 function removeAllErrors() {
1264 - document.querySelectorAll( '.form-field' ).forEach( field => {
1265 - field.classList.remove( 'frm_blank_field', 'has-error' );
1266 - } );
1267 - document.querySelectorAll( '.form-field .frm_error' ).forEach( error => error.remove() );
1268 - document.querySelectorAll( '.frm_error_style' ).forEach( error => error.remove() );
1194 + jQuery( '.form-field' ).removeClass( 'frm_blank_field has-error' );
1195 + jQuery( '.form-field .frm_error' ).replaceWith( '' );
1196 + jQuery( '.frm_error_style' ).remove();
1269 1197 }
1270 1198
1271 1199 /**
1272 1200 * @param {HTMLElement|Object} object Form object.
@@ -1289,9 +1217,9 @@
1289 1217 disableSaveDraft( $object );
1290 1218 }
1291 1219
1292 1220 function showLoadingIndicator( $object ) {
1293 - if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) { // eslint-disable-line no-jquery/no-class
1221 + if ( ! $object.hasClass( 'frm_loading_form' ) && ! $object.hasClass( 'frm_loading_prev' ) ) {
1294 1222 addLoadingClass( $object );
1295 1223 $object.trigger( 'frmStartFormLoading' );
1296 1224 }
1297 1225 }
@@ -1298,9 +1226,9 @@
1298 1226
1299 1227 function addLoadingClass( $object ) {
1300 1228 const loadingClass = isGoingToPrevPage( $object ) ? 'frm_loading_prev' : 'frm_loading_form';
1301 1229
1302 - $object.addClass( loadingClass ); // eslint-disable-line no-jquery/no-class
1230 + $object.addClass( loadingClass );
1303 1231 }
1304 1232
1305 1233 function isGoingToPrevPage( $object ) {
1306 1234 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
@@ -1306,36 +1234,37 @@
1306 1234 return ( typeof frmProForm !== 'undefined' && frmProForm.goingToPreviousPage( $object ) );
1307 1235 }
1308 1236
1309 1237 function removeSubmitLoading( _, enable, processesRunning ) {
1238 + let loadingForm;
1239 +
1310 1240 if ( processesRunning > 0 ) {
1311 1241 return;
1312 1242 }
1313 1243
1314 - document.querySelectorAll( '.frm_loading_form' ).forEach( function( form ) {
1315 - form.classList.remove( 'frm_loading_form', 'frm_loading_prev' );
1316 - jQuery( form ).trigger( 'frmEndFormLoading' );
1244 + loadingForm = jQuery( '.frm_loading_form' );
1245 + loadingForm.removeClass( 'frm_loading_form' );
1246 + loadingForm.removeClass( 'frm_loading_prev' );
1317 1247
1318 - if ( enable === 'enable' ) {
1319 - enableSubmitButton( form );
1320 - enableSaveDraft( form );
1321 - }
1322 - } );
1248 + loadingForm.trigger( 'frmEndFormLoading' );
1249 +
1250 + if ( enable === 'enable' ) {
1251 + enableSubmitButton( loadingForm );
1252 + enableSaveDraft( loadingForm );
1253 + }
1323 1254 }
1324 1255
1325 1256 function showFileLoading( object ) {
1326 - const loading = document.getElementById( 'frm_loading' );
1327 - if ( loading === null ) {
1328 - return;
1257 + let fileval,
1258 + loading = document.getElementById( 'frm_loading' );
1259 + if ( loading !== null ) {
1260 + fileval = jQuery( object ).find( 'input[type=file]' ).val();
1261 + if ( typeof fileval !== 'undefined' && fileval !== '' ) {
1262 + setTimeout( function() {
1263 + jQuery( loading ).fadeIn( 'slow' );
1264 + }, 2000 );
1265 + }
1329 1266 }
1330 -
1331 - const fileInput = object.querySelector( 'input[type=file]' );
1332 - const fileval = fileInput ? fileInput.value : '';
1333 - if ( fileval !== '' ) {
1334 - setTimeout( function() {
1335 - jQuery( loading ).fadeIn( 'slow' ); // eslint-disable-line no-jquery/no-fade
1336 - }, 2000 );
1337 - }
1338 1267 }
1339 1268
1340 1269 /**********************************************
1341 1270 * General Helpers
@@ -1342,9 +1271,9 @@
1342 1271 *********************************************/
1343 1272
1344 1273 function confirmClick() {
1345 1274 /*jshint validthis:true */
1346 - const message = this.dataset.frmconfirm;
1275 + const message = jQuery( this ).data( 'frmconfirm' );
1347 1276 return confirm( message );
1348 1277 }
1349 1278
1350 1279 /**
@@ -1352,15 +1281,35 @@
1352 1281 * If this is a match, the User is autofilling the input on a Webkit browser.
1353 1282 * We want to delete the Honeypot field, otherwise it will get triggered as spam on autocomplete.
1354 1283 */
1355 1284 function onHoneypotFieldChange() {
1356 - /*jshint validthis:true */
1357 - const css = window.getComputedStyle( this ).boxShadow;
1358 - if ( css && css.match( /inset/ ) ) {
1359 - this.remove();
1285 + const css = jQuery( this ).css( 'box-shadow' );
1286 + if ( css.match( /inset/ ) ) {
1287 + this.parentNode.removeChild( this );
1360 1288 }
1361 1289 }
1362 1290
1291 + function maybeMakeHoneypotFieldsUntabbable() {
1292 + document.addEventListener( 'keydown', handleKeyUp );
1293 +
1294 + function handleKeyUp( event ) {
1295 + if ( 'Tab' === event.key ) {
1296 + makeHoneypotFieldsUntabbable();
1297 + document.removeEventListener( 'keydown', handleKeyUp );
1298 + }
1299 + }
1300 +
1301 + function makeHoneypotFieldsUntabbable() {
1302 + document.querySelectorAll( '.frm_verify' ).forEach(
1303 + function( input ) {
1304 + if ( input.id && 0 === input.id.indexOf( 'frm_email_' ) ) {
1305 + input.setAttribute( 'tabindex', -1 );
1306 + }
1307 + }
1308 + );
1309 + }
1310 + }
1311 +
1363 1312 /**
1364 1313 * Focus on the first sub field when clicking to the primary label of combo field.
1365 1314 *
1366 1315 * @since 4.10.02
@@ -1380,10 +1329,10 @@
1380 1329 }
1381 1330
1382 1331 label.addEventListener( 'click', function() {
1383 1332 inputsContainer.querySelector( '.frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea' ).focus();
1384 - } );
1385 - } );
1333 + });
1334 + });
1386 1335 }
1387 1336
1388 1337 /**
1389 1338 * Sets focus on a the first subfield of a combo field that has an error.
@@ -1419,9 +1368,9 @@
1419 1368 if ( ! errors.length ) {
1420 1369 return;
1421 1370 }
1422 1371
1423 - element = errors[ 0 ];
1372 + element = errors[0];
1424 1373 do {
1425 1374 element = element.previousSibling;
1426 1375 if ( -1 !== [ 'input', 'select', 'textarea' ].indexOf( element.nodeName.toLowerCase() ) ) {
1427 1376 focusInput( element );
@@ -1443,14 +1392,8 @@
1443 1392 } else if ( element.classList.contains( 'tmce-active' ) ) {
1444 1393 timeoutCallback = function() {
1445 1394 tinyMCE.activeEditor.focus();
1446 1395 };
1447 - } else if ( element.classList.contains( 'frm_opt_container' ) ) {
1448 - const firstInput = element.querySelector( 'input' );
1449 - if ( firstInput ) {
1450 - focusInput( firstInput );
1451 - break;
1452 - }
1453 1396 }
1454 1397
1455 1398 if ( 'function' === typeof timeoutCallback ) {
1456 1399 setTimeout( timeoutCallback, 0 );
@@ -1471,9 +1414,9 @@
1471 1414 function focusInput( input ) {
1472 1415 if ( input.offsetParent !== null ) {
1473 1416 input.focus();
1474 1417 } else {
1475 - triggerCustomEvent( document, 'frmMaybeDelayFocus', { input } );
1418 + triggerCustomEvent( document, 'frmMaybeDelayFocus', { input });
1476 1419 }
1477 1420 }
1478 1421
1479 1422 /**
@@ -1495,9 +1438,9 @@
1495 1438 let target;
1496 1439
1497 1440 // loop parent nodes from the target to the delegation node.
1498 1441 for ( target = e.target; target && target != this; target = target.parentNode ) {
1499 - if ( target.matches && target.matches( selector ) ) {
1442 + if ( target && target.matches && target.matches( selector ) ) {
1500 1443 handler.call( target, e );
1501 1444 break;
1502 1445 }
1503 1446 }
@@ -1506,9 +1449,9 @@
1506 1449
1507 1450 function initFloatingLabels() {
1508 1451 let checkFloatLabel, checkDropdownLabel, runOnLoad, selector, floatClass;
1509 1452
1510 - selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1453 + selector = '.frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea';
1511 1454 floatClass = 'frm_label_float_top';
1512 1455
1513 1456 checkFloatLabel = function( input ) {
1514 1457 let container, shouldFloatTop, firstOpt;
@@ -1544,9 +1487,9 @@
1544 1487 if ( firstOpt.textContent ) {
1545 1488 firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1546 1489 firstOpt.textContent = '';
1547 1490 }
1548 - } );
1491 + });
1549 1492 };
1550 1493
1551 1494 [ 'focus', 'blur', 'change' ].forEach( function( eventName ) {
1552 1495 documentOn(
@@ -1556,10 +1499,14 @@
1556 1499 checkFloatLabel( event.target );
1557 1500 },
1558 1501 true
1559 1502 );
1560 - } );
1503 + });
1561 1504
1505 + jQuery( document ).on( 'change', selector, function( event ) {
1506 + checkFloatLabel( event.target );
1507 + });
1508 +
1562 1509 runOnLoad = function( firstLoad ) {
1563 1510 if ( firstLoad && document.activeElement && -1 !== [ 'INPUT', 'SELECT', 'TEXTAREA' ].indexOf( document.activeElement.tagName ) ) {
1564 1511 checkFloatLabel( document.activeElement );
1565 1512 } else if ( firstLoad ) {
@@ -1579,13 +1526,13 @@
1579 1526 runOnLoad( true );
1580 1527
1581 1528 jQuery( document ).on( 'frmPageChanged', function( event ) {
1582 1529 runOnLoad();
1583 - } );
1530 + });
1584 1531
1585 1532 document.addEventListener( 'frm_after_start_over', function( event ) {
1586 1533 runOnLoad();
1587 - } );
1534 + });
1588 1535 }
1589 1536
1590 1537 function shouldUpdateValidityMessage( target ) {
1591 1538 if ( 'INPUT' !== target.nodeName ) {
@@ -1622,9 +1569,9 @@
1622 1569 if ( 'valid' !== key && field.validity[ key ] === true ) {
1623 1570 isInvalid = true;
1624 1571 break;
1625 1572 }
1626 - }
1573 + };
1627 1574
1628 1575 if ( ! isInvalid ) {
1629 1576 field.setCustomValidity( '' );
1630 1577 }
@@ -1647,9 +1594,9 @@
1647 1594
1648 1595 function setCustomValidityMessage() {
1649 1596 let forms, length, index;
1650 1597
1651 - forms = document.getElementsByClassName( 'frm-show-form' );
1598 + forms = document.getElementsByClassName( 'frm-show-form' );
1652 1599 length = forms.length;
1653 1600
1654 1601 for ( index = 0; index < length; ++index ) {
1655 1602 forms[ index ].addEventListener(
@@ -1670,14 +1617,14 @@
1670 1617 window.addEventListener( 'pageshow', function( event ) {
1671 1618 if ( event.persisted ) {
1672 1619 document.querySelectorAll( '.frm_loading_form' ).forEach(
1673 1620 function( form ) {
1674 - enableSubmitButton( form );
1621 + enableSubmitButton( jQuery( form ) );
1675 1622 }
1676 1623 );
1677 1624 removeSubmitLoading();
1678 1625 }
1679 - } );
1626 + });
1680 1627 }
1681 1628
1682 1629 /**
1683 1630 * Destroys the formidable generated global hcaptcha object since it wouldn't otherwise render.
@@ -1701,69 +1648,23 @@
1701 1648 const timestamp = Date.now().toString( 16 );
1702 1649 return uniqueKey + '-' + timestamp;
1703 1650 }
1704 1651
1705 - /**
1706 - * Animates the scroll position of the document.
1707 - *
1708 - * @since 6.20
1709 - *
1710 - * @param {number} start
1711 - * @param {number} end
1712 - * @param {number} duration
1713 - * @return {void}
1714 - */
1715 - function animateScroll( start, end, duration ) {
1716 - if ( ! window.hasOwnProperty( 'performance' ) || ! window.hasOwnProperty( 'requestAnimationFrame' ) ) {
1717 - document.documentElement.scrollTop = end;
1718 - return;
1719 - }
1720 -
1721 - /* eslint-disable compat/compat */
1722 - const startTime = performance.now();
1723 - const step = currentTime => {
1724 - const progress = Math.min( ( currentTime - startTime ) / duration, 1 );
1725 - document.documentElement.scrollTop = start + ( ( end - start ) * progress );
1726 - if ( progress < 1 ) {
1727 - requestAnimationFrame( step );
1728 - }
1729 - };
1730 - requestAnimationFrame( step );
1731 - /* eslint-enable compat/compat */
1732 - }
1733 -
1734 - /**
1735 - * Make sure that the captcha label for a reCAPTCHA or Turnstile field matches the response input ID.
1736 - * This is determined dynamically, so we check for the ID after the input is rendered.
1737 - * hCaptcha is handled separately, in the frmCaptcha function as it is not rendered explicitly.
1738 - *
1739 - * @since 6.25.1
1740 - *
1741 - * @param {HTMLElement} captcha
1742 - * @return {void}
1743 - */
1744 - function maybeFixCaptchaLabel( captcha ) {
1745 - const form = captcha.closest( 'form' );
1746 - if ( ! form ) {
1747 - return;
1748 - }
1749 -
1750 - const label = form.querySelector( 'label[for="g-recaptcha-response"], label[for="cf-turnstile-response"]' );
1751 - const captchaResponse = form.querySelector( '[name="g-recaptcha-response"], [name="cf-turnstile-response"]' );
1752 -
1753 - if ( label && captchaResponse ) {
1754 - label.htmlFor = captchaResponse.id;
1755 - }
1756 - }
1757 -
1758 1652 return {
1759 1653 init: function() {
1760 1654 jQuery( document ).off( 'submit.formidable', '.frm-show-form' );
1761 1655 jQuery( document ).on( 'submit.formidable', '.frm-show-form', frmFrontForm.submitForm );
1762 1656
1657 + jQuery( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).each( function() {
1658 + if ( jQuery( this ).val() === '' ) {
1659 + jQuery( this ).trigger( 'blur' );
1660 + }
1661 + });
1662 +
1763 1663 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 );
1764 1664
1765 - jQuery( document ).on( 'change', '.frm_verify[id^=field_]', onHoneypotFieldChange );
1665 + jQuery( document ).on( 'change', '[id^=frm_email_]', onHoneypotFieldChange );
1666 + maybeMakeHoneypotFieldsUntabbable();
1766 1667
1767 1668 jQuery( document ).on( 'click', 'a[data-frmconfirm]', confirmClick );
1768 1669
1769 1670 checkForErrorsAndMaybeSetFocus();
@@ -1804,9 +1705,9 @@
1804 1705 if ( rendered ) {
1805 1706 return;
1806 1707 }
1807 1708
1808 - const size = captcha.getAttribute( 'data-size' );
1709 + const size = captcha.getAttribute( 'data-size' );
1809 1710 const params = {
1810 1711 sitekey: captcha.getAttribute( 'data-sitekey' ),
1811 1712 size: size,
1812 1713 theme: captcha.getAttribute( 'data-theme' )
@@ -1824,25 +1725,22 @@
1824 1725 frmFrontForm.afterRecaptcha( token, formID );
1825 1726 };
1826 1727 }
1827 1728
1828 - const activeCaptcha = getSelectedCaptcha( captchaSelector );
1729 + const activeCaptcha = getSelectedCaptcha( captchaSelector );
1829 1730 const captchaContainer = typeof turnstile !== 'undefined' && turnstile === activeCaptcha ? '#' + captcha.id : captcha.id;
1830 - const captchaID = activeCaptcha.render( captchaContainer, params );
1731 + const captchaID = activeCaptcha.render( captchaContainer, params );
1831 1732
1832 1733 captcha.setAttribute( 'data-rid', captchaID );
1833 -
1834 - maybeFixCaptchaLabel( captcha );
1835 1734 },
1836 1735
1837 1736 afterSingleRecaptcha: function() {
1838 - const recaptcha = document.querySelector( '.frm-show-form .g-recaptcha' );
1839 - const object = recaptcha ? recaptcha.closest( 'form' ) : null;
1737 + const object = jQuery( '.frm-show-form .g-recaptcha' ).closest( 'form' )[0];
1840 1738 frmFrontForm.submitFormNow( object );
1841 1739 },
1842 1740
1843 1741 afterRecaptcha: function( _, formID ) {
1844 - const object = document.querySelector( '#frm_form_' + formID + '_container form' );
1742 + const object = jQuery( '#frm_form_' + formID + '_container form' )[0];
1845 1743 frmFrontForm.submitFormNow( object );
1846 1744 },
1847 1745
1848 1746 submitForm: function( e ) {
@@ -1854,9 +1752,20 @@
1854 1752 * @param {HTMLElement} object The form object that is being submitted.
1855 1753 * @return {void}
1856 1754 */
1857 1755 submitFormManual: function( e, object ) {
1858 - if ( document.body.classList.contains( 'wp-admin' ) && ! object.closest( '.frmapi-form' ) ) {
1756 + let isPro, errors,
1757 + invisibleRecaptcha = hasInvisibleRecaptcha( object ),
1758 + classList = object.className.trim().split( /\s+/gi );
1759 +
1760 + if ( classList && invisibleRecaptcha.length < 1 ) {
1761 + isPro = classList.indexOf( 'frm_pro_form' ) > -1;
1762 + if ( ! isPro ) {
1763 + return;
1764 + }
1765 + }
1766 +
1767 + if ( jQuery( 'body' ).hasClass( 'wp-admin' ) && jQuery( object ).closest( '.frmapi-form' ).length < 1 ) {
1859 1768 return;
1860 1769 }
1861 1770
1862 1771 e.preventDefault();
@@ -1864,22 +1773,21 @@
1864 1773 if ( typeof frmProForm !== 'undefined' && typeof frmProForm.submitAllowed === 'function' && ! frmProForm.submitAllowed( object ) ) {
1865 1774 return;
1866 1775 }
1867 1776
1868 - const errors = frmFrontForm.validateFormSubmit( object );
1777 + errors = frmFrontForm.validateFormSubmit( object );
1869 1778 if ( Object.keys( errors ).length !== 0 ) {
1870 1779 return;
1871 1780 }
1872 1781
1873 - const invisibleRecaptcha = hasInvisibleRecaptcha( object );
1874 -
1875 - if ( invisibleRecaptcha ) {
1782 + if ( invisibleRecaptcha.length ) {
1876 1783 showLoadingIndicator( jQuery( object ) );
1877 1784 executeInvisibleRecaptcha( invisibleRecaptcha );
1878 1785 } else {
1786 +
1879 1787 showSubmitLoading( jQuery( object ) );
1880 1788
1881 - frmFrontForm.submitFormNow( object );
1789 + frmFrontForm.submitFormNow( object, classList );
1882 1790 }
1883 1791 },
1884 1792
1885 1793 submitFormNow: function( object ) {
@@ -1891,24 +1799,24 @@
1891 1799 antispamInput = document.createElement( 'input' );
1892 1800 antispamInput.type = 'hidden';
1893 1801 antispamInput.name = 'antispam_token';
1894 1802 antispamInput.value = object.getAttribute( 'data-token' );
1895 - object.append( antispamInput );
1803 + object.appendChild( antispamInput );
1896 1804 }
1897 1805
1898 1806 // Add a unique ID, used for duplicate checks.
1899 1807 const uniqueIDInput = document.createElement( 'input' );
1900 - uniqueIDInput.type = 'hidden';
1901 - uniqueIDInput.name = 'unique_id';
1808 + uniqueIDInput.type = 'hidden';
1809 + uniqueIDInput.name = 'unique_id';
1902 1810 uniqueIDInput.value = getUniqueKey();
1903 - object.append( uniqueIDInput );
1811 + object.appendChild( uniqueIDInput );
1904 1812
1905 - if ( classList.includes( 'frm_ajax_submit' ) ) {
1906 - const fileInputs = object.querySelectorAll( 'input[type="file"]' );
1907 - hasFileFields = Array.from( fileInputs ).filter( input => !! input.value ).length;
1813 + if ( classList.indexOf( 'frm_ajax_submit' ) > -1 ) {
1814 + hasFileFields = jQuery( object ).find( 'input[type="file"]' ).filter( function() {
1815 + return !! this.value;
1816 + }).length;
1908 1817 if ( hasFileFields < 1 ) {
1909 - const actionInput = object.querySelector( 'input[name="frm_action"]' );
1910 - const action = actionInput ? actionInput.value : '';
1818 + const action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1911 1819 frmFrontForm.checkFormErrors( object, action );
1912 1820 } else {
1913 1821 object.submit();
1914 1822 }
@@ -1922,10 +1830,9 @@
1922 1830 *
1923 1831 * @return {Array} List of errors.
1924 1832 */
1925 1833 validateFormSubmit: function( object ) {
1926 - const form = object instanceof jQuery ? object.get( 0 ) : object;
1927 - if ( typeof tinyMCE !== 'undefined' && form && form.querySelector( '.wp-editor-wrap' ) ) {
1834 + if ( typeof tinyMCE !== 'undefined' && jQuery( object ).find( '.wp-editor-wrap' ).length ) {
1928 1835 tinyMCE.triggerSave();
1929 1836 }
1930 1837
1931 1838 jsErrors = [];
@@ -1946,16 +1853,14 @@
1946 1853 * @return {Array} List of errors.
1947 1854 */
1948 1855 getAjaxFormErrors: function( object ) {
1949 1856 let customErrors, key;
1950 - const form = object instanceof jQuery ? object.get( 0 ) : object;
1951 1857
1952 1858 jsErrors = validateForm( object );
1953 1859 if ( typeof frmThemeOverride_jsErrors === 'function' ) { // eslint-disable-line camelcase
1954 - const actionInput = form ? form.querySelector( 'input[name="frm_action"]' ) : null;
1955 - const action = actionInput ? actionInput.value : '';
1860 + const action = jQuery( object ).find( 'input[name="frm_action"]' ).val();
1956 1861 customErrors = frmThemeOverride_jsErrors( action, object );
1957 - if ( Object.keys( customErrors ).length ) {
1862 + if ( Object.keys( customErrors ).length ) {
1958 1863 for ( key in customErrors ) {
1959 1864 jsErrors[ key ] = customErrors[ key ];
1960 1865 }
1961 1866 }
@@ -1963,9 +1868,9 @@
1963 1868
1964 1869 triggerCustomEvent( document, 'frm_get_ajax_form_errors', {
1965 1870 formEl: object,
1966 1871 errors: jsErrors
1967 - } );
1872 + });
1968 1873
1969 1874 return jsErrors;
1970 1875 },
1971 1876
@@ -1973,17 +1878,16 @@
1973 1878 * @param {HTMLElement|Object} object Form object. This might be a jQuery object.
1974 1879 * @return {void}
1975 1880 */
1976 1881 addAjaxFormErrors: function( object ) {
1977 - let key;
1978 - const form = object instanceof jQuery ? object.get( 0 ) : object;
1882 + let key, $fieldCont;
1979 1883 removeAllErrors();
1980 1884
1981 1885 for ( key in jsErrors ) {
1982 - const fieldCont = form ? form.querySelector( '#frm_field_' + key + '_container' ) : null;
1886 + $fieldCont = jQuery( object ).find( '#frm_field_' + key + '_container' );
1983 1887
1984 - if ( fieldCont ) {
1985 - addFieldError( fieldCont, key, jsErrors );
1888 + if ( $fieldCont.length ) {
1889 + addFieldError( $fieldCont, key, jsErrors );
1986 1890 } else {
1987 1891 // we are unable to show the error, so remove it
1988 1892 delete jsErrors[ key ];
1989 1893 }
@@ -2011,11 +1915,9 @@
2011 1915 if ( scrollObj.length < 1 ) {
2012 1916 return;
2013 1917 }
2014 1918 } else if ( typeof id === 'string' ) {
2015 - const formEl = object instanceof jQuery ? object.get( 0 ) : object;
2016 - const fieldEl = formEl ? formEl.querySelector( '#frm_field_' + id + '_container' ) : null;
2017 - scrollObj = fieldEl ? jQuery( fieldEl ) : jQuery();
1919 + scrollObj = jQuery( object ).find( '#frm_field_' + id + '_container' );
2018 1920 } else {
2019 1921 scrollObj = id;
2020 1922 }
2021 1923
@@ -2025,10 +1927,10 @@
2025 1927 return;
2026 1928 }
2027 1929 newPos = newPos - frm_js.offset; // eslint-disable-line camelcase
2028 1930
2029 - m = getComputedStyle( document.documentElement ).marginTop;
2030 - b = getComputedStyle( document.body ).marginTop;
1931 + m = jQuery( 'html' ).css( 'margin-top' );
1932 + b = jQuery( 'body' ).css( 'margin-top' );
2031 1933 if ( m || b ) {
2032 1934 newPos = newPos - parseInt( m ) - parseInt( b );
2033 1935 }
2034 1936
@@ -2038,11 +1940,11 @@
2038 1940
2039 1941 if ( newPos > screenBottom || newPos < screenTop ) {
2040 1942 // Not in view
2041 1943 if ( typeof animate === 'undefined' ) {
2042 - document.documentElement.scrollTop = newPos;
1944 + jQuery( window ).scrollTop( newPos );
2043 1945 } else {
2044 - animateScroll( screenTop, newPos, 500 );
1946 + jQuery( 'html,body' ).animate({ scrollTop: newPos }, 500 );
2045 1947 }
2046 1948 return false;
2047 1949 }
2048 1950 }
@@ -2051,9 +1953,9 @@
2051 1953 fieldValueChanged: function( e ) {
2052 1954 /*jshint validthis:true */
2053 1955
2054 1956 const fieldId = frmFrontForm.getFieldId( this, false );
2055 - if ( ! fieldId ) {
1957 + if ( ! fieldId || typeof fieldId === 'undefined' ) {
2056 1958 return;
2057 1959 }
2058 1960
2059 1961 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
@@ -2059,9 +1961,9 @@
2059 1961 if ( e.frmTriggered && e.frmTriggered == fieldId ) {
2060 1962 return;
2061 1963 }
2062 1964
2063 - jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ] );
1965 + jQuery( document ).trigger( 'frmFieldChanged', [ this, fieldId, e ]);
2064 1966
2065 1967 if ( e.selfTriggered !== true ) {
2066 1968 maybeValidateChange( this );
2067 1969 }
@@ -2076,8 +1978,30 @@
2076 1978 .replace( /"/g, '&quot;' )
2077 1979 .replace( /'/g, '&#039;' );
2078 1980 },
2079 1981
1982 + /**
1983 + * This function was used in old back end code in v2.0.
1984 + *
1985 + * @param {string} classes
1986 + * @return {void}
1987 + */
1988 + invisible: function( classes ) {
1989 + console.warn( 'DEPRECATED: function frmFrontForm.invisible in v6.16.3' );
1990 + jQuery( classes ).css( 'visibility', 'hidden' );
1991 + },
1992 +
1993 + /**
1994 + * This function was used in old back end code in v2.0.
1995 + *
1996 + * @param {string} classes
1997 + * @return {void}
1998 + */
1999 + visible: function( classes ) {
2000 + console.warn( 'DEPRECATED: function frmFrontForm.visible in v6.16.3' );
2001 + jQuery( classes ).css( 'visibility', 'visible' );
2002 + },
2003 +
2080 2004 triggerCustomEvent: triggerCustomEvent,
2081 2005 documentOn
2082 2006 };
2083 2007 }
@@ -2085,45 +2009,26 @@
2085 2009 window.frmFrontForm = frmFrontFormJS();
2086 2010
2087 2011 jQuery( document ).ready( function() {
2088 2012 frmFrontForm.init();
2089 -} );
2013 +});
2090 2014
2091 2015 function frmRecaptcha() {
2092 2016 frmCaptcha( '.frm-g-recaptcha' );
2093 2017 }
2094 2018
2095 -function frmHcaptcha() {
2096 - frmCaptcha( '.h-captcha' );
2097 -}
2098 -
2099 2019 function frmTurnstile() {
2100 - frmCaptcha( '.frm-cf-turnstile' );
2020 + frmCaptcha( '.cf-turnstile' );
2101 2021 }
2102 2022
2103 2023 function frmCaptcha( captchaSelector ) {
2104 - if ( '.h-captcha' === captchaSelector ) {
2105 - // hCaptcha is still rendered implicitly, so we only want to handle the label and exit early.
2106 - // Match the hcaptcha labels to the hcaptcha response fields.
2107 - const captchaLabels = document.querySelectorAll( 'label[for="h-captcha-response"]' );
2108 - if ( captchaLabels.length ) {
2109 - captchaLabels.forEach( label => {
2110 - const captchaResponse = label.closest( 'form' )?.querySelector( '[name="h-captcha-response"]' );
2111 - if ( captchaResponse ) {
2112 - label.htmlFor = captchaResponse.id;
2113 - }
2114 - } );
2115 - }
2116 - return;
2117 - }
2118 -
2119 2024 let c;
2120 2025 const captchas = document.querySelectorAll( captchaSelector );
2121 - const cl = captchas.length;
2026 + const cl = captchas.length;
2122 2027 for ( c = 0; c < cl; c++ ) {
2123 - const closestForm = captchas[ c ].closest( 'form' );
2028 + const closestForm = captchas[c].closest( 'form' );
2124 2029 const formIsVisible = closestForm && closestForm.offsetParent !== null;
2125 - const captcha = captchas[ c ];
2030 + const captcha = captchas[c];
2126 2031 if ( ! formIsVisible ) {
2127 2032 // If the form is not visible, try again later in 400ms.
2128 2033 // This fixes issues where the form fades visible on page load.
2129 2034 // Or whne the form is inside of a modal.
@@ -2145,9 +2050,9 @@
2145 2050 function getSelectedCaptcha( captchaSelector ) {
2146 2051 if ( captchaSelector === '.frm-g-recaptcha' ) {
2147 2052 return grecaptcha;
2148 2053 }
2149 - if ( document.querySelector( '.frm-cf-turnstile' ) ) {
2054 + if ( document.querySelector( '.cf-turnstile' ) ) {
2150 2055 return turnstile;
2151 2056 }
2152 2057 return hcaptcha;
2153 2058 }