Spring MVC - Ajax based form processing using JQuery and JSON with server side validation

Spring MVC provides support for processing the form as well as server side validation. It maps request parameters to form backing bean and validate the bean object if we have used @Valid annotation. When we submit the form, form get displayed with the error messages if validation is failed. Error messages are managed by Spring MVC and spring MVC binds them to the input fields.

But, If we want to submit the form using ajax request, form page will not refresh and spring MVC cannot send validation error messages to browsers. In this case, server side validation does not work as per my expectations. So here I devised my approach to use the spring MVC validation even in ajax based form submission. 

I am using JQuery to serialize the form data and capturing them in controller. In controller, form data is being mapped in User bean and User bean is validated based on field validation annotations. Now if validation is get failed and some errors are appeared there, I collect them in UserJsonResponse object. This is the object being sent to browser in JSON format. I am putting errors, status and bean object in this object to send the errors to browser. Spring MVC provides the facility @ResponseBody that converts the returning object in JSON output.

Now I am processing this output in webpage using the javascript where, all messages are being extracted and associated to corresponding  input fields.

Controller

package controller;

import java.beans.PropertyEditorSupport;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import bo.User;

@Controller
public class RegistrationController {
    @Autowired
    private MessageSource messages;
    
    @ModelAttribute
    public User exposeUser(){
        System.out.println("creating @ModelAttribute object");
        return new User();
    }
    
    @RequestMapping(value = "/submituser",method=RequestMethod.POST )
    public  @ResponseBody UserJsonResponse submitUser(@Valid User user,BindingResult bindingResult){
        System.out.println("Submited User Data : \n"+user);
        UserJsonResponse userJsonResponse=new UserJsonResponse();
        if(bindingResult.hasErrors()){
            Map<String ,String> errors=new HashMap<StringString>();
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                String[] resolveMessageCodes = bindingResult.resolveMessageCodes(fieldError.getCode());
                String string = resolveMessageCodes[0];
                //System.out.println("resolveMessageCodes : "+string);
                String message = messages.getMessage(string+"."+fieldError.getField()new Object[]{fieldError.getRejectedValue()}null);
                //System.out.println("Meassage : "+message);
                errors.put(fieldError.getField(), message)    ;
            }
            userJsonResponse.setErrorsMap(errors);
            userJsonResponse.setUser(user);
            userJsonResponse.setStatus("ERROR");
        }else{
            userJsonResponse.setStatus("SUCCESS");
        }
        return userJsonResponse;
    }
    
    public MessageSource getMessages() {
        return messages;
    }
    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }
    ////////////////////////////////////////////////////////////
    class UserJsonResponse{
        private String status;
        private Map<String,String> errorsMap;
        private User user;
        public String getStatus() {
            return status;
        }
        public void setStatus(String status) {
            this.status = status;
        }
        public Map<String,String> getErrorsMap() {
            return errorsMap;
        }
        public void setErrorsMap(Map<String,String> errorsMap) {
            this.errorsMap = errorsMap;
        }
        public User getUser() {
            return user;
        }
        public void setUser(User user) {
            this.user = user;
        }
    }
    
    
     @InitBinder
     protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
         binder.registerCustomEditor(long.classnew PropertyEditorSupport() {
         @Override
         public void setAsText(String text) {
             if(text.trim().length()==0){
                 text="0";
             }
             long ch = Long.parseLong(text);
             setValue(ch);
         }
         });
        
     }
}


User Bean
package bo;

import javax.validation.Valid;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;

public class User {

    private int id;
    @Size(min = 6, max = 10)
    @NotEmpty
    String name;
    @Email
    String email;

    long phone;
    @Valid
    private Address address;

    public User() {

    }

    public User(String name, String email, long phone) {

        this.name = name;
        this.email = email;
        this.phone = phone;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public long getPhone() {
        return phone;
    }

    public void setPhone(long phone) {
        this.phone = phone;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", email=" + email
                + ", phone=" + phone + "]";
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}


Address
package bo;

import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;

public class Address {

    @NotEmpty
    String addressLine1;
    String addressLine2;
    
    @NotEmpty
    String city;
    long pincode;

    public String getAddressLine1() {
        return addressLine1;
    }

    public void setAddressLine1(String addressLine1) {
        this.addressLine1 = addressLine1;
    }

    public String getAddressLine2() {
        return addressLine2;
    }

    public void setAddressLine2(String addressLine2) {
        this.addressLine2 = addressLine2;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public long getPincode() {
        return pincode;
    }

    public void setPincode(long pincode) {
        this.pincode = pincode;
    }

    @Override
    public String toString() {
        return "Address [addressLine1=" + addressLine1 + ", addressLine2="
                + addressLine2 + ", city=" + city + ", pincode=" + pincode
                + "]";
    }

}

messages.properties
Size.user.name = Name must be of 4 to 10 character NotEmpty.user.name = Name is required NotEmpty.user.address.addressLine1= addressLine1 is required NotEmpty.user.address.city = city is required Email.user.email=Email {0} you entered is invalid NotEmpty.user.email = Email is required typeMismatch.user.phone = This is not a number! typeMismatch.user.address.pincode = This is not a number!

Form.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript">
    jQuery(document).ready(function(){
        jQuery("#userform").submit(function(e){
            jQuery(".formFieldError").remove();
            jQuery.ajax({
                    url: jQuery(this).attr("action"),
                    context: document.body,
                    type: 'post',
                    data:jQuery(this).serialize()
                }).done(function(res) {
                    if(res.status==="ERROR"){
                        for(var key in res.errorsMap){
                            var err="<span class=\"formFieldError\" id=\""+key+"Id\">"+res.errorsMap[key]+"</span>";
                            jQuery("[name^='"+key+"']").after(err);
                        }
                    }else{                      
                        jQuery("#msg").html("Form submitted");
                    }
                }).fail(function(data){
                    jQuery("#msg").html("<span class=\"formFieldError\">Server failed to process request</span>");
                });
            return false;
        });
    });
</script>
<style type="text/css">
.formFieldError{
color:red;
}
</style>
</head>
    <body>
    
        <form id="userform" action="submituser" method="post"  >
            Name  :<input type="text" name="name"/><br/>
            Email  :<input type="text" name="email"/><br/>
            Phone  :<input type="text" name="phone"/><br/>
            AddressLine1  :<input type="text" name="address.addressLine1"/><br/>
            AddressLine2  :<input type="text" name="address.addressLine2"/><br/>
            City  :<input type="text" name="address.city"/><br/>
            Pincode  :<input type="text" name="address.pincode"/><br/>
            <input type="submit"/>
        </form>
        <div id="msg"></div>
    
    </body>
</html>

Dispatcher-Servlet-Config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc  
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="controller"></context:component-scan>
    <mvc:annotation-driven />
    <mvc:resources mapping="/**" location="/" />
    

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name = "prefix" value="/WEB-INF/jsps/" />
        <property name = "suffix" value=".jsp" />
    </bean>
    <bean class="org.springframework.context.support.ResourceBundleMessageSource"
        id="messageSource">
        <property name="basename" value="messages" />
    </bean>
</beans>

Download Source Code 
Spring framework

6 comments:

Anonymous said...

Good example! That is what I want. But I have a question: this form submiting calling reload page or not?

Hemraj said...

Its ajax based form submission. Page will no be reloaded.

Anonymous said...

please post for download link for this example

Anonymous said...

Please post download link

Hemraj said...

Added link to download the source code.

Anonymous said...

I have an error

Request processing failed; nested exception is org.springframework.context.NoSuchMessageException: No message found under code 'NotEmpty.user.address.addressLine1' for locale 'null'.

can you help it? :(

Popular Posts