views:

2030

answers:

2

I have a JSP that has a Spring form in it. The form's command object is added in the controller before the JSP is rendered. Spring binds the form in the JSP to this command object, and will correctly handle it when submitting a NEW instance.

However, I would like to persist the command object via DWR (which also works correctly), and THEN submit the form to the controller. At the point that the form is submitted to the controller, the command object is no longer a new object, but a persisted object that needs to be updated. This is where I want the form elements to automatically be bound to the command object and be updated via the binding, but they are not being bound.

SIMPLE Example: I will add a new Task to the ModelMap, so that the Spring form will bind to that command object. However, instead of submitting the new Task, I will persist that new Task through DWR, which will return the ID, and then continue editing the Task before submitting the form to the controller.

Controller Class

@Controller
public class ProjectController {

    /**
     * This adds the "task" command object to the session attributes and loads
     * the initial form.
     */
    @RequestMapping(value="/project", method=RequestMethod.GET)
    public String setupForm(@RequestParam(value="id", required=true) String id,
                            HttpServletRequest request, ModelMap modelMap) {

        modelMap.addAttribute("project", projectRepo.get(id));
        modelMap.addAttribute("task", new Task());

        return "/project/task";
    }

    /**
     * This processes the form submit, and should update the Task.
     */
    @RequestMapping(value="/project/task/update", method=RequestMethod.POST)
    public String updateTask(@ModelAttribute(value="task") Task task,
                             @RequestParam(value="taskId") String taskId,
                             HttpServletRequest request, ModelMap modelMap) {

        // BEFORE binding the parameters to the command object (task), 
        // I want to assign the command object as the one already persisted.
        task = taskRepo.get(taskId);

        // NOW, I want the request parameters to be bound to the task command object.
        // HOW ?????????

        // Persist the changes.
        taskRepo.merge(task);

        // BACK to the setupForm method/form view
        return "/project?id=" + task.getProject().getId();
    }
}

Spring Form

<form:form commandName="task" method="post" action="/project/task/update" id="taskForm">
    <form:hidden path="id" id="task.id"/>
    <form:input path="name" id="task.name"/>
    <!-- DWR will save the task (save and continue), then will return the id. -->
    <!-- After saved, the user can still change the name, 
         then submit the form for processing by the controller -->
</form:form>

Can a Spring bound command object be set to a persisted object before any post-submit binding occurs?

A: 

It appears that when using @ModelAttribute to access the command object, that the binding occurs before you have access to the command object. In order to set that command object to what you want before binding the request parameters from the form, simply pass in the attribute's id and grab it from the database, then bind the WebRequest parameters.

In the POST method

@RequestMapping(value="/project/task/update", method=RequestMethod.POST)
public String updateTask(@ModelAttribute(value="task") Task task,
                         @RequestParam(value="taskId") String taskId,
                         HttpServletRequest request, ModelMap modelMap) {

    // BEFORE binding the parameters to the command object (task), 
    // I want to assign the command object as the one already persisted.
    task = taskRepo.get(taskId);

    // NOW, I want the request parameters to be bound to the task command object.
    WebRequestDataBinder binder = new WebRequestDataBinder(task);
    ServletWebRequest webRequest = new ServletWebRequest(request);
    binder.bind(webRequest);

    // Persist the changes.
    taskRepo.merge(task);

    // BACK to the setupForm method/form view
    return "/project?id=" + task.getProject().getId();
}

The Spring 2.5.x documentation of WebRequestDataBinder is where you can find Juergen Hoeller's example of 'manual data binding' for this type of application.

MyBean myBean = new MyBean();
// apply binder to custom target object
WebRequestDataBinder binder = new WebRequestDataBinder(myBean);
// register custom editors, if desired
binder.registerCustomEditor(...);
// trigger actual binding of request parameters
binder.bind(request);
// optionally evaluate binding errors
Errors errors = binder.getErrors();
...
Matt Fisher
You can also go a step further and take advantage of property editors that are wired in to the AnnotationMethodHandlerAdapter's BindingInitializer. To do this, just get the declared BindingInitializer that AnnotatioNMethodHandlerAdapter uses, and call `initBinder(binder,webRequest)` on it: @Autowired private BindingInitializer bindingInitializer; -------- bindingInitializer.initBinder(binder, webRequest);
Matt Fisher
+1  A: 

There is actually a better way to do this using annotations.

Create a ModelAttribute method that returns the command object that you want from the repository.

@ModelAttribute("task")
public Task task(@RequestParam(value = "id", required = true) String id) {
    return taskRepo.get(taskId);
}

Then, simply add the ModelAttribute to your form submission method.

@RequestMapping(value="/project/task/update", method=RequestMethod.POST)
public String updateTask(@ModelAttribute(value="task") Task task,
                         HttpServletRequest request, ModelMap modelMap) {

    taskRepo.merge(task);
    ...
}
Matt Fisher