Sunday, September 1, 2013

Building UI using Spring MVC - Part I

In my previous blog, I wrote about implementing Restful services using Spring MVC. In this blog, we will see how to build a UI using Spring MVC.

Let us try to implement a UI to create a User. Again the intent is to show to use Spring MVC and hence we can reuse most of the artifacts from previous blog, except for the controller.


Create User URI:


Before landing on the jsp for creating a User, we will need some preprocessing to happen, which will include creation of User model and also model for drop downs if any (let us say a drop down to show Countries).

Now, there are two approaches to build the URI for create form as explained below.
1. Same URI for preprocesing as well as actual create.
In this approach, the same URI let us say '/example/users/user/new' will be used for preprocessing and user creation. The only difference being, for preprocessing we will use GET and for user creation (upon form submission) we will use POST.

@RequestMapping(value="/example/users/user/new", method=RequestMethod.GET) 
public ModelAndView preprocessCreateUser(){
   ........
}

This method will do the preprocessing and return the view name. The JSP form will look like:

<form:form commandname="user">
    ........
</form:form>
Note that there is no 'action' for this form. The same action attribute from the GET request is carried over to this form as well.  Upon submitting this form it will do a POST to the same controller and the method which supports POST will be invoked.
@RequestMapping(value="/example/users/user/new", method=RequestMethod.POST) 
public ModelAndView addUser(){
   ........
}

2. Use different URI for preprocess as well as user creation.

In this approach, the URI for preprocess will look like '/example/users/user/preprocess'.

The URI for user creation will look like '/example/users/user/new'.
These two URIs will map to the two methods preprocessCreateUser() and addUser() respectively.

In the jsp, we need to explicitly mention the action attribute for submission.
<form:form commandName="user" action="/example/users/user/new">
    ........
</form:form> 

The advantage with the second approach is easy to maintain. If your application is a huge one with lots of jsp pages, then looking at the jsp page you can figure out to which controller it is submitting to. This is something you cannot do with the first approach.

The index.jsp which serves as welcome page will have the link to create User.

<a href="/example/users/user/new">Create User</a>


Controller code:


Pre-processing and handling POST request:
@RequestMapping(value="/example/users/user/new", method=RequestMethod.GET) 
public ModelAndView preprocessCreateUser(){
  ModelAndView mav = new ModelAndView("addUser");
  User user = new User(); 
  mav.addObject("user",user);
  
  return mav;
}

@RequestMapping(value="/example/users/user/new", method=RequestMethod.POST) 
public String addUser(@ModelAttribute @Valid User user, BindingResult result ) 
                     throws UserCreateException{
       if(result.hasErrors()){
           return addUserErrorView;
       }
       userService.createUser(user);

       return addUserSuccessView;
}

Validation:


Validation could be performed by using either a separate validator implementation or by annotating the domain object by using JSR 303 annotation support. Here, we will use a separate validator.
public class UserValidator implements Validator {
       public boolean supports(Class _class) {
           return User.class.equals(_class);
       }
 
        public void validate(Object obj, Errors e) {
            ValidationUtils.rejectIfEmpty(e, "firstName", "firstname.empty", "first Name is empty");
     }
}

Note that the validator implements Spring Validator interface. The supports() method is to invoked by the Spring container to tie this validator to the corresponding model object. The key "firstName.empty" would be looked upon in the resource bundle file messages.properties.
To register this validator we use the @InitBinder annotation.

@InitBinder("user")
protected void initBinder(WebDataBinder binder) {
        binder.setValidator(new UserValidator());
}

To invoke this validator we have to use the @Valid annotation on the model object in the method argument (see addUser() method above). Note that BindingResults argument will contain the error results and this method argument should always be present next to the model object.
The controller methods can accept various arguments like HttpRequest, HttpResponse, Model object in any order. The return type could be either a String which will indicate a view name or a ModelAndView object.
In the preprocessCreateUser() method which returns a ModelAndView object, the constructor argument "addUser" represents the view name. The User model object is added to the ModelAndView and this model object could be referenced in the view by the name "user". Note that this attribute name is optional, and if you do not specify any name Spring will add the lower case name as attribute name. For eg, the object Account could be referenced by the name "account", the list userList could be referenced by "userList" and so on.


View:

Let us write the JSP addUser.jsp
<form:form commandName="user">
    <!-- HTML constructs omitted-->
    <form:errors path="firstName"/>
    <form:input path="firstName"/>
 
    <form:errors path="lastName"/> 
    <form:input path="lastName"/>
    <button type="submit">Add User</button>
</form:form>

Use of PUT and DELETE:

The browsers do not support PUT and DELETE methods currently. To overcome this, Spring provides a response filter which should be configured in the web.xml as below:
<filter>
        <filter-name>httpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
        <filter-name>httpMethodFilter</filter-name>
        <servlet-name>mvc-dispatcher</servlet-name>
</filter-mapping>

In the JSP, we need to mention the form action method as PUT/DELETE. While rendering the view, the response filter would replace the PUT/DELETE with POST and will also place a hidden parameter as below. 
View source of the page would look like:
<form id="user" action="/example/users/user/1111" method="post">
<input type="hidden" name="_method" value="PUT"/>
This parameter is used by Spring, during submission to route the request to PUT/DELETE methods respectively.


Handling multiple submits:


If there are multiple submit buttons, lets say 'Save As Draft', 'Publish' etc, then we can use @RequestParam annotation with specific parameter names to distinguish each request.

@RequestMapping(value="/example/users/user/{userId}",params="actionParam=publish", method={RequestMethod.POST}) 
public String publish(){

}

View Names:

The view names could be hardcoded into the controller like we have done above. However, this would tightly couple the view and the controller. In order to avoid this, we can inject the view names into the controller. The view names could be stored in a properties file (lets say view.properties) and they could be referenced in the controller as below.
@Value("${addUserView}")
private String addUserView;

The @Value annotation injects the value into the addUserView variable. The "addUserView" key is also defined in view.properties as key/value pair "addUserView= addUser", where addUser is the jsp name.
This view.properties file should be placed in root of the classpath and the following entry in the dispatcher-servlet.xml file will help Spring to recognize and load the properties file:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location">
             <value>classpath:/view.properties</value>
        </property>
</bean>

1 comment: