@@ -59,37 +59,23 @@ const addFieldValidationHandlers = (field, validateFieldFn) => {
5959 } ) ;
6060} ;
6161
62- const handleFormSubmit = ( form , validateFieldFn ) => async ( event ) => {
63- const isValid = await validateForm ( form , validateFieldFn ) ;
64-
65- if ( ! isValid ) {
66- event . preventDefault ( ) ;
67- return ;
68- }
69-
70- // Don't call form.submit() in test environment
71- if ( typeof jest === 'undefined' ) {
72- form . submit ( ) ;
73- }
74- } ;
75-
7662const validateForm = async ( form , validateFieldFn ) => {
77- const fields = form . querySelectorAll ( 'input, textarea, select' ) ;
63+ const fields = form . querySelectorAll (
64+ 'input:not([type="submit"]):not([type="button"]):not([type="hidden"]), textarea, select' ,
65+ ) ;
7866
7967 fields . forEach ( ( field ) => {
8068 clearValidationErrorFn ( field ) ;
8169 } ) ;
8270
8371 const validationResults = await Promise . all (
84- Array . from ( fields )
85- . filter ( ( field ) => ! [ 'submit' , 'button' , 'hidden' ] . includes ( field . type ) )
86- . map ( async ( field ) => {
87- const result = await validateFieldFn ( field ) ;
88- if ( ! result . isValid ) {
89- showValidationErrorFn ( field , result . message ) ;
90- }
91- return result . isValid ;
92- } ) ,
72+ Array . from ( fields ) . map ( async ( field ) => {
73+ const result = await validateFieldFn ( field ) ;
74+ if ( ! result . isValid ) {
75+ showValidationErrorFn ( field , result . message ) ;
76+ }
77+ return result . isValid ;
78+ } ) ,
9379 ) ;
9480
9581 return validationResults . every ( Boolean ) ;
@@ -110,12 +96,150 @@ const initFormValidation = (form, validateFieldFn) => {
11096 }
11197} ;
11298
113- const initFormWithValidation = ( form , validateFieldFn ) => {
114- form . addEventListener ( 'submit' , handleFormSubmit ( form , validateFieldFn ) ) ;
99+ const collectFormData = ( form ) => new FormData ( form ) ;
115100
116- // Initialize validation for all fields in the form
101+ const scrollToFirstInvalidField = ( form ) => {
117102 const fields = form . querySelectorAll ( 'input, textarea, select' ) ;
103+ for ( const field of fields ) {
104+ const errorText = field
105+ . closest ( '.sf-field' )
106+ ?. querySelector ( '.validation-error' )
107+ ?. textContent ?. trim ( ) ;
108+
109+ if ( errorText ) {
110+ field . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
111+ field . focus ( ) ;
112+ break ;
113+ }
114+ }
115+ } ;
116+
117+ const onValidateForm = ( isValid , form , validateFieldFn ) => {
118+ if ( ! isValid ) {
119+ scrollToFirstInvalidField ( form ) ;
120+ return null ;
121+ }
122+ const formData = collectFormData ( form ) ;
123+ return sendFormData ( form , formData )
124+ . then ( ( response ) => onSendFormDataResponse ( response , form ) )
125+ . catch ( ( ) => {
126+ const errorMessage = form . querySelector ( '.error-message' ) ;
127+ if ( errorMessage ) {
128+ errorMessage . textContent =
129+ 'Failed to submit form. Please try again later.' ;
130+ }
131+ return null ;
132+ } ) ;
133+ } ;
134+
135+ const onSendFormDataResponse = ( response , form ) => {
136+ return handleServerResponse ( response , form ) . then ( ( ok ) => {
137+ if ( ! ok ) {
138+ showValidationErrorFn (
139+ form ,
140+ 'Submission failed. Please check the form and try again.' ,
141+ ) ;
142+ }
143+ return ok ;
144+ } ) ;
145+ } ;
146+
147+ const onHandleServerResponse = ( data , form ) => {
148+ if ( data && ( data . success || data . ok ) ) {
149+ form . reset ( ) ;
150+ const thankYou = document . querySelector ( '[data-apos-form-thank-you]' ) ;
151+ if ( thankYou ) {
152+ thankYou . style . display = 'block' ;
153+ }
154+ form . style . display = 'none' ;
155+ return true ;
156+ }
157+ // Show server validation errors if they exist
158+ if ( data ?. errors ) {
159+ showValidationErrorFn ( form , data . errors . join ( ', ' ) ) ;
160+ } else {
161+ showValidationErrorFn ( form , 'Submission failed. Please try again.' ) ;
162+ }
163+ return false ;
164+ } ;
165+
166+ const handleServerResponse = ( response , form ) => {
167+ if ( ! response . ok ) {
168+ return response
169+ . json ( )
170+ . then ( ( data ) => {
171+ showValidationErrorFn (
172+ form ,
173+ data . message || 'Submission failed. Please try again.' ,
174+ ) ;
175+ return false ;
176+ } )
177+ . catch ( ( ) => {
178+ showValidationErrorFn ( form , 'Submission failed. Please try again.' ) ;
179+ return false ;
180+ } ) ;
181+ }
182+ return response . json ( ) . then ( ( data ) => onHandleServerResponse ( data , form ) ) ;
183+ } ;
184+
185+ const sendFormData = ( form , formData ) => {
186+ // Convert FormData to a plain object for the server
187+ const data = { } ;
188+ for ( const [ key , value ] of formData . entries ( ) ) {
189+ if ( key in data ) {
190+ data [ key ] = [ ] . concat ( data [ key ] , value ) ;
191+ } else {
192+ data [ key ] = value ;
193+ }
194+ }
195+
196+ return fetch ( form . action , {
197+ method : 'POST' ,
198+ body : JSON . stringify ( { data } ) ,
199+ headers : {
200+ 'Content-Type' : 'application/json' ,
201+ 'Accept' : 'application/json' ,
202+ 'CSRF-Token' :
203+ document
204+ . querySelector ( 'meta[name="csrf-token"]' )
205+ ?. getAttribute ( 'content' ) || '' ,
206+ } ,
207+ credentials : 'same-origin' ,
208+ } ) ;
209+ } ;
210+
211+ const handleFormSubmit = ( event , form , validateFieldFn ) => {
212+ event . preventDefault ( ) ;
213+ // Disable submit button(s) to prevent multiple submissions
214+ const submitButtons = form . querySelectorAll (
215+ 'button[type="submit"], input[type="submit"]' ,
216+ ) ;
217+ submitButtons . forEach ( ( btn ) => ( btn . disabled = true ) ) ;
218+
219+ validateForm ( form , validateFieldFn )
220+ . then ( ( isValid ) => onValidateForm ( isValid , form , validateFieldFn ) )
221+ . finally ( ( ) => {
222+ // Re-enable submit button(s) after processing
223+ submitButtons . forEach ( ( btn ) => ( btn . disabled = false ) ) ;
224+ } )
225+ . catch ( ( ) => false ) ;
226+ } ;
227+
228+ const initFormWithValidation = ( form , validateFieldFn ) => {
229+ // Initialize validation for all fields in the form
230+ const fields = form . querySelectorAll (
231+ 'input:not([type="submit"]):not([type="button"]):not([type="hidden"]), textarea, select' ,
232+ ) ;
118233 fields . forEach ( ( field ) => addFieldValidationHandlers ( field , validateFieldFn ) ) ;
234+
235+ // Add validation before form submission
236+ form . addEventListener (
237+ 'submit' ,
238+ function ( event ) {
239+ handleFormSubmit ( event , form , validateFieldFn ) ;
240+ } ,
241+ true ,
242+ ) ;
119243} ;
120244
121245module . exports = { initFormValidation } ;
0 commit comments