Serving and processing forms

In the previous chapters, you learned how to retrieve data from an in-memory database using the Controller, but you didn't learn how to store the data in an in-memory database from the View. In Spring MVC, the process of putting an HTML form element's values into model data is called form binding.

In all the previous chapter's examples, you saw that the data transfer took place from the Model to the View via the Controller. The following line is a typical example of how we put data into the Model from the Controller:

model.addAttribute(greeting,"Welcome") 

Similarly, the next line shows how we retrieve that data in the View using a JSTL expression:

<p> ${greeting} </p> 

But what if we want to put data into the Model from the View? How do we retrieve that data in the Controller? For example, consider a scenario where an admin of our store wants to add new product information to our store by filling out and submitting an HTML form. How can we collect the values filled out in the HTML form elements and process them in the Controller? This is where Spring tag library tags help us to bind the HTML tag element's values to a form backing bean in the Model. Later, the Controller can retrieve the form backing bean from the Model using the @ModelAttribute (org.springframework.web.bind.annotation.ModelAttribute) annotation.

Tip

The form backing bean (sometimes called the form bean) is used to store form data. We can even use our domain objects as form beans; this works well when there's a close match between the fields in the form and the properties in our domain object. Another approach is creating separate classes for form beans, which is sometimes called Data Transfer Objects (DTO).

Time for action - serving and processing forms

The Spring tag library provides some special <form> and <input> tags, which are more or less similar to HTML form and input tags, but have some special attributes to bind form elements' data with the form backed bean. Let's create a Spring web form in our application to add new products to our product list:

  1. Open our ProductRepository interface and add one more method declaration to it as follows:
          void addProduct(Product product); 
    
  2. Add an implementation for this method in the InMemoryProductRepository class as follows:
          @Override 
          public void addProduct(Product product) { 
                String SQL = "INSERT INTO PRODUCTS (ID, " 
                      + "NAME," 
                      + "DESCRIPTION," 
                      + "UNIT_PRICE," 
                      + "MANUFACTURER," 
                      + "CATEGORY," 
                      + "CONDITION," 
                      + "UNITS_IN_STOCK," 
                      + "UNITS_IN_ORDER," 
                      + "DISCONTINUED) " 
                      + "VALUES (:id, :name, :desc, :price, 
                      :manufacturer, :category, :condition, :inStock,                    :inOrder, :discontinued)";   
           
                Map<String, Object> params = new HashMap<>(); 
                params.put("id", product.getProductId());   
                params.put("name", product.getName());   
                params.put("desc", product.getDescription());   
                params.put("price", product.getUnitPrice());   
                params.put("manufacturer", product.getManufacturer());   
                params.put("category", product.getCategory());   
                params.put("condition", product.getCondition());   
                params.put("inStock", product.getUnitsInStock());   
                params.put("inOrder", product.getUnitsInOrder());   
                params.put("discontinued", product.isDiscontinued());   
     
                jdbcTemplate.update(SQL, params);      
             } 
    
  3. Open our ProductService interface and add one more method declaration to it as follows:
          void addProduct(Product product); 
    
  4. And add an implementation for this method in the ProductServiceImpl class as follows:
          @Override 
          public void addProduct(Product product) { 
             productRepository.addProduct(product); 
          } 
    
  5. Open our ProductController class and add two more request mapping methods as follows:
          @RequestMapping(value = "/products/add", method = 
          RequestMethod.GET) 
          public String getAddNewProductForm(Model model) { 
             Product newProduct = new Product(); 
             model.addAttribute("newProduct", newProduct); 
             return "addProduct"; 
          } 
        
          @RequestMapping(value = "/products/add", method = 
          RequestMethod.POST) 
          public String 
          processAddNewProductForm(@ModelAttribute("newProduct") 
          Product newProduct) { 
             productService.addProduct(newProduct); 
             return "redirect:/market/products"; 
          }  
    
  6. Finally, add one more JSP View file called addProduct.jsp under the src/main/webapp/WEB-INF/views/ directory and add the following tag reference declaration as the very first line in it:
  7. Now add the following code snippet under the tag declaration line and save addProduct.jsp. Note that I skipped some <form:input> binding tags for some of the fields of the product domain object, but I strongly encourage you to add binding tags for the skipped fields while you are trying out this exercise:
          <html> 
          <head> 
          <meta http-equiv="Content-Type" content="text/html; 
          charset=ISO-8859-1"> 
          <link rel="stylesheet"   
          href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/
          bootstrap.min.css"> 
          <title>Products</title> 
          </head> 
          <body> 
             <section> 
                <div class="jumbotron"> 
                   <div class="container"> 
                      <h1>Products</h1> 
                      <p>Add products</p> 
                   </div> 
                </div> 
             </section> 
             <section class="container"> 
                <form:form  method="POST" modelAttribute="newProduct"               class="form-horizontal"> 
                   <fieldset> 
                      <legend>Add new product</legend> 
     
                      <div class="form-group"> 
                         <label class="control-label col-lg-2 col-lg-2" 
                          for="productId">Product Id</label> 
                         <div class="col-lg-10"> 
                            <form:input id="productId" path="productId"                           type="text" class="form:input-large"/> 
                         </div> 
                      </div> 
     
                      <!-- Similarly bind <form:input> tag for 
                      name,unitPrice,manufacturer,category,unitsInStock                    and unitsInOrder fields--> 
     
                      <div class="form-group"> 
                         <label class="control-label col-lg-2" 
                          for="description">Description</label> 
                         <div class="col-lg-10"> 
                            <form:textarea id="description" 
                             path="description" rows = "2"/> 
                         </div> 
                      </div> 
     
                      <div class="form-group"> 
                         <label class="control-label col-lg-2" 
                          for="discontinued">Discontinued</label> 
                        <div class="col-lg-10"> 
                            <form:checkbox  id="discontinued"       
                             path="discontinued"/> 
                         </div> 
                      </div> 
                 
                      <div class="form-group"> 
                         <label class="control-label col-lg-2" 
                          for="condition">Condition</label> 
                         <div class="col-lg-10"> 
                            <form:radiobutton path="condition" 
                             value="New" />New  
                            <form:radiobutton path="condition" 
                             value="Old" />Old  
                            <form:radiobutton path="condition" 
                             value="Refurbished" />Refurbished 
                         </div> 
                      </div> 
                 
                      <div class="form-group"> 
                         <div class="col-lg-offset-2 col-lg-10"> 
                            <input type="submit" id="btnAdd" class="btn 
                             btn-primary" value ="Add"/> 
                         </div> 
                      </div> 
                   </fieldset> 
                </form:form> 
             </section> 
          </body> 
          </html> 
    
  8. Now run our application and enter the URL: http://localhost:8080/webstore/market/products/add. You will be able to see a web page showing a web form to add product information as shown in the following screenshot:
    Time for action - serving and processing forms

    Add a products web form

  9. Now enter all the information related to the new product that you want to add and click on the Add button. You will see the new product added in the product listing page under the URL http://localhost:8080/webstore/market/products.

What just happened?

In the whole sequence, steps 5 and 6 are very important steps and need to be observed carefully. Everything mentioned prior to step 5 was very familiar to you I guess. Anyhow, I will give you a brief note on what we did in steps 1 to 4.

In step 1, we just created an addProduct method declaration in our ProductRepository interface to add new products. And in step 2, we just implemented the addProduct method in our InMemoryProductRepository class. Steps 3 and 4 are just a Service layer extension for ProductRepository. In step 3, we declared a similar addProduct method in our ProductService and implemented it in step 4 to add products to the repository via the productRepository reference.

Okay, coming back to the important step; all we did in step 5 was add two request mapping methods, namely getAddNewProductForm and processAddNewProductForm:

   @RequestMapping(value = "/products/add", method = RequestMethod.GET) 
   public String getAddNewProductForm(Model model) { 
      Product newProduct = new Product(); 
      model.addAttribute("newProduct", newProduct); 
      return "addProduct"; 
   } 
       
   @RequestMapping(value = "/products/add", method = RequestMethod.POST) 
   public String processAddNewProductForm(@ModelAttribute("newProduct") Product productToBeAdded) { 
      productService.addProduct(productToBeAdded); 
      return "redirect:/market/products"; 
   } 

If you observe those methods carefully, you will notice a peculiar thing: both the methods have the same URL mapping value in their @RequestMapping annotations (value = "/products/add"). So if we enter the URL http://localhost:8080/webstore/market/products/add in the browser, which method will Spring MVC map that request to?

The answer lies in the second attribute of the @RequestMapping annotation (method = RequestMethod.GET and method = RequestMethod.POST). Yes if you look again, even though both methods have the same URL mapping, they differ in the request method.

So what is happening behind the scenes is that, when we enter the URL http://localhost:8080/webstore/market/products/add in the browser, it is considered as a GET request, so Spring MVC will map that request to the getAddNewProductForm method. Within that method, we simply attach a new empty Product domain object with the model, under the attribute name newProduct. So in the addproduct.jsp View, we can access that newProduct Model object:

Product newProduct = new Product(); 
model.addAttribute("newProduct", newProduct); 

Before jumping into the processAddNewProductForm method, let's review the addproduct.jsp View file in some detail, so that you understand the form processing flow without confusion. In addproduct.jsp, we just added a <form:form> tag from Spring's tag library:

<form:form modelAttribute="newProduct" class="form-horizontal"> 

Since this special <form:form> tag is coming from a Spring tag library, we need to add a reference to that tag library in our JSP file; that's why we added the following line at the top of the addProducts.jsp file in step 6:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"  %> 

In the Spring <form:form> tag, one of the important attributes is modelAttribute. In our case, we assigned the value newProduct as the value of  modelAttribute in the <form:form> tag. If you remember correctly, you can see that this value of the modelAttribute, and the attribute name we used to store the newProduct object in the Model from our getAddNewProductForm method, are the same. So the newProduct object that we attached to the model from the Controller method (getAddNewProductForm) is now bound to the form. This object is called the form backing bean in Spring MVC.

Okay, now you should look at every <form:input> tag inside the <form:form> tag. You can observe a common attribute in every tag. That attribute is path:

<form:input id="productId" path="productId" type="text" class="form:input-large"/> 

The path attribute just indicates the field name that is relative to the form backing bean. So the value that is entered in this input box at runtime will be bound to the corresponding field of the form bean.

Okay, now it's time to come back and review our processAddNewProductForm method. When will this method be invoked? This method will be invoked once we press the submit button on our form. Yes, since every form submission is considered a POST request, this time the browser will send a POST request to the same URL http://localhost:8080/webstore/products/add.

So this time the processAddNewProductForm method will get invoked since it is a POST request. Inside the processAddNewProductForm method, we are simply calling the addProduct service method to add the new product to the repository:

   productService.addProduct(productToBeAdded); 

But the interesting question here is, how come the productToBeAdded object is populated with the data that we entered in the form? The answer lies in the @ModelAttribute (org.springframework.web.bind.annotation.ModelAttribute) annotation. Notice the method signature of the processAddNewProductForm method:

public String processAddNewProductForm(@ModelAttribute("newProduct") Product productToBeAdded) 

Here if you look at the value attribute of the @ModelAttribute annotation, you can observe a pattern. Yes, the @ModelAttribute annotation's value and the value of the modelAttribute from the <form:form> tag are the same. So Spring MVC knows that it should assign the form bounded newProduct object to the processAddNewProductForm method's productToBeAddedparameter.

The @ModelAttribute annotation is not only used to retrieve an object from the Model, but if we want we can even use the @ModelAttribute annotation to add objects to the Model. For instance, we can even rewrite our getAddNewProductForm method to something like the following, using the @ModelAttribute annotation:

@RequestMapping(value = "/products/add", method = RequestMethod.GET) 
   public String getAddNewProductForm(@ModelAttribute("newProduct") Product newProduct) { 
      return "addProduct"; 
} 

You can see that we haven't created a new empty Product domain object and attached it to the model. All we did was add a parameter of the type Product and annotated it with the @ModelAttribute annotation, so Spring MVC will know that it should create an object of Product and attach it to the model under the name newProduct.

One more thing that needs to be observed in the processAddNewProductForm method is the logical View name it is returning: redirect:/market/products. So what we are trying to tell Spring MVC by returning the string redirect:/market/products? To get the answer, observe the logical View name string carefully; if we split this string with the ":" (colon) symbol, we will get two parts. The first part is the prefix redirect and the second part is something that looks like a request path: /market/products. So, instead of returning a View name, we are simply instructing Spring to issue a redirect request to the request path /market/products, which is the request path for the list method of our ProductController. So, after submitting the form, we list the products using the list method of ProductController.

Tip

As a matter of fact, when we return any request path with the redirect: prefix from a request mapping method, Spring will use a special View object called RedirectView (org.springframework.web.servlet.view.RedirectView) to issue the redirect command behind the scenes. We will see more about RedirectView in the next chapter.

Instead of landing on a web page after the successful submission of a web form, we are spawning a new request to the request path /market/products with the help of RedirectView. This pattern is called redirect-after-post; it is a commonly used pattern with web-based forms. We are using this pattern to avoid double submission of the same form.

Tip

Sometimes after submitting the form, if we press the browser's refresh button or back button, there are chances to resubmit the same form. This behavior is called double submission.

Have a go hero - customer registration form

It is great that we created a web form to add new products to our web application under the URL http://localhost:8080/webstore/market/products/add. Why don't you create a customer registration form in our application to register a new customer in our application? Try to create a customer registration form under the URL http://localhost:8080/webstore/customers/add.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.62.239