Monday, October 23, 2017

Sending form data as a custom object to a Spring MVC Controller


Form Data and Spring MVC Controllers

In a Spring MVC application, sending form data to the controller is quite well known and trivial. Typically all data gets sent as a bunch of key=value pairs. Each key is a request param which can be captured in the Controller using the @RequestParam("key") String key construct in the method signature.

If the number of fields are less, then you can use the request param construct to capture each form field, but if there are several form fields, then using this approach can create a unwieldy method signature and create room for error.

There are several ways to collect form data but in this post, I am going to address one specific use case.

Motivation for this post

Recently, someone at work asked me how to send data so that it was received by the Spring controller as a custom object/Pojo. I suggested using javascript to massage and format the data as desired and then send it.

Now using jquery forms on the browser side you can send data in any format after converting it to a JSON string.

For some business reasons, javascript was not an option. The HTML form had to be submitted as is using traditional http post method.

The purpose of this short blog post is to demonstrate how to send data from the browser without the help of javascript or any jquery plugins. This is typically useful when you have checkboxes and radio buttons in the UI and there is a relation between them.

Example

I have created an example UI which shows the kind of form that was to be submitted and then the controller code which processes the http post of this form.



As you can see, this form shows a list of car makes and their colors. The idea is that the user will check a checkbox for a car make and then select a color using the radio buttons.

Note that this form is rendered on the server side by reading the car makes and colors and then sent as a HTML page to the browser.

The HTML generated for this form is like this

 <form name="customObjectForm" id="customObjectForm" method="post" action="/submit">  
   <table class="table table-striped table-condensed table-bordered">  
     <thead>  
       <tr>  
         <th>Vehicle</th>  
         <th>Select</th>  
         <th>Color</th>  
       </tr>  
     </thead>  
     <tbody>  
       <tr>  
         <td>Toyota Camry</td>  
         <td><input type="checkbox" name="carModel" value="Camry" /></td>  
         <td>  
           <input type="radio" name="colors[Camry]" value="RED" /> RED &nbsp;  
           <input type="radio" name="colors[Camry]" value="YELLOW" /> YELLOW&nbsp;  
           <input type="radio" name="colors[Camry]" value="GREEN" /> GREEN&nbsp;  
         </td>  
       </tr>  
       <tr>  
         <td>Honda Civic</td>  
         <td><input type="checkbox" name="carModel" value="Honda" /></td>  
         <td>  
           <input type="radio" name="colors[Honda]" value="BLUE" /> BLUE&nbsp;  
           <input type="radio" name="colors[Honda]" value="BLACK" /> BLACK&nbsp;  
           <input type="radio" name="colors[Honda]" value="GREEN" /> GREEN&nbsp;  
         </td>  
       </tr>  
     </tbody>  
     <tfoot>  
       <tr><td colspan="3"><input type="Submit" name="submit" class="btn btn-primary" /></td></tr>  
     </tfoot>  
   </table>  
 </form>  

Domain model

If you examine the HTML, you will notice that we can infer a Key Value pair type relationship between the car make and the color options. To relate the car make to the colors, the name of the radio buttons is named like an associative array with the car make as the value of the colors variable. 

The car make checkboxes can be represented as a List in the domain model. If a user checks a checkbox then that checkbox value is submitted by the form, otherwise it is not.

Next, the row showing color options must be related to the car make in the domain model. To make that happen, the radio button selection can be submitted as a Map in the domain model.

So the car model can be a List<String> carMakes and the car colors can be a Map<String, String> colors where the key is the car make and the value is the actual selected color.

There can be a better domain model created and the HTML can be structured accordingly, but this is a quick example to show how Spring can convert browser submitted values in ready to use custom objects that you define.

Here is the CarMakeColorModel domain object / POJO.

package com.example.formobjects.model;

import java.util.List;
import java.util.Map;

public class CarMakeColorModel {

 // for the checkboxes for selecting the car makes
 private List<String> carMakes;

 // for the radio buttons to select the car color
 private Map<String, String> colors;

 public CarMakeColorModel() {
  // default constructor
  super();
 }

 public CarMakeColorModel(List<String> carMakes, Map<String, String> colors) {
  super();
  this.carMakes = carMakes;
  this.colors = colors;
 }

 public List<String> getCarMakes() {
  return carMakes;
 }

 public void setCarMakes(List<String> carMakes) {
  this.carMakes = carMakes;
 }

 public Map<String, String> getColors() {
  return colors;
 }

 public void setColors(Map<String, String> colors) {
  this.colors = colors;
 }

}


On the Controller side, use the @ModelAttribute annotation to make Spring collect all the form submitted values and populate the CarMakeColorModel pojo.

And here is the Spring Controller snippet which processes this HTML form.


package com.example.formobjects.controllers;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.example.formobjects.model.CarMakeColorModel;

/**
 * Controller to handle the form submission
 * 
 * @author <a href="mailto:sunitkatkar@gmail.com">Sunit Katkar</a>
 * @version 1.0
 * @since Oct 2017
 */
@Controller
public class FormCustomObjectController {

 @RequestMapping(value = "/", method = RequestMethod.GET)
 public String index(ModelMap modelMap) {
  return "index";
 }

 @RequestMapping(value = "/submit", method = RequestMethod.POST)
 public String submit(HttpServletRequest request, ModelMap modelMap,
   @ModelAttribute CarMakeColorModel carMakeColorModel) {

  List<String> carMakes = carMakeColorModel.getCarMakes();
  Map<String, String> carColors = carMakeColorModel.getColors();
  for (String carModel : carMakes) {
   String color = carColors.get(carModel);
   System.out.println("Car make:" + carModel + " Car color:" + color);
  }
  return "index";
 }

}


What I have shown is a known way of processing forms completely on the server side without aid of javascript. Though many of you know this technique, I am sure there are some out there, like my colleague, who are not aware of this; hence this post.

That's all there is to it. Happy coding :)