Spring Forms, Dynamic Lists and Ajax
Spring Forms and Dynamic Binding Lists
Introduction
After my recent posts on Spring forms and annotations – dynamic lists is logically another step to be discussed.
In the previous examples we use a form backing object – the form backing objects have tended to be very basic and primitive
Using dynamic lists within these objects makes them slightly more complicated. Suppose the following scenario:
Class enrolment example
- User chooses to create a new class
- User types in class name
- User also has to specify one (or many) names of students in the class
Using that scenario you can imagine a form in which there is a class name field and then below that a simple student name field. When the user clicks add next to the student name – javascript should simply show another input field for a student name allowing a user to add one or more students.
Eventually when the form gets submitted to the server a list of students will be populated.
However if we don’t using Spring’s dynamic lists – specifically Spring’s AutoPopulatingList – we cannot perform such an action as we have out of bounds errors.
Configuring your build environment
Your workspace needs setting up as the previous examples. Using Eclipse create a new project. For your src directory enter the value /WEB-INF/src. For your output directory use the WEB-INF/classes directory.
Add a new lib directory in your WEB-INF folder. Under your WEB-INF directoy you will need the following JAR files (all of which can be obtained from the Spring download). Add the jars to your buildpath.
commons-beanutils.jar commons-collections.jar jstl.jar spring-core.jar spring-webmvc.jar spring.jar standard.jar tiles-api-2.0.6.jar tiles-core-2.0.6.jar tiles-jsp-2.0.6.jar
Next add the Spring servlet.xml to your application. Create a file called dynamiclists-servlet.xml in your WEB-INF folder. Add the following to the file:
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.annotations.controller" /> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> <!-- Enables plain Controllers --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/tiles-defs/templates.xml</value> </list> </property> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/> </bean> </beans>
Next add your web.xml in the same location as your Spring servlet.xml file. Add the following contents:
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Spring Dynamic Lists Example</display-name> <servlet> <servlet-name>dynamiclists</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dynamiclists</servlet-name> <url-pattern>*.page</url-pattern> </servlet-mapping> </web-app>
Next add a new folder in your WEB-INF directory called tiles-defs and add a file called templates.xml within that. Add the following to the templates.xml file
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://tiles.apache.org/dtds/tiles-config_2_0.dtd"> <tiles-definitions> <definition name="form.view" template="/pages/form.jsp"> </definition> <definition name="formStudentInsert.view" template="/pages/formStudentInsert.jsp"> </definition> <definition name="formResult.view" template="/pages/formResult.jsp"> </definition> </tiles-definitions>
That should be the workspace setup – next to create our form JSP pages
Form JSP pages and a touch of jQuery
Firstly create a new folder under your application called js. Next download jQuery and place the jQuery download within the folder.
Under your project create a new folder names pages and create a file named form.jsp within that.This will be our class creation form.
Add the following to your form.jsp file
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<script type="text/javascript" src="/dynamiclists/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var studentPosition = 0;
$("#addStudentButton").click(function() {
studentPosition++;
$.get("<%=request.getContextPath()%>/appendStudentView.page", { fieldId: studentPosition},
function(data){
$("#submitRow").before(data);
});
});
});
</script>
<form:form method="post" name="classForm" id="classForm" commandName="classCommand">
<table>
<tr>
<td>Class Name:</td>
<td colspan="2"><form:input path="className" size="40" /></td>
</tr>
<tr>
<td colspan="3"><strong>Students</strong></td>
</tr>
<tr>
<td>Student name</td>
<td>
<spring:bind path="classCommand.students[0].studentName">
<form:input path="${status.expression}" size="40" />
</spring:bind>
</td>
<td><input type="button" id="addStudentButton" value="Add" /></td>
</tr>
<tr id="submitRow">
<td> </td>
<td><input type="submit" value="Save Class" /></td>
</tr>
</table>
</form:form>This probably requires a bit of an explanation. The first two lines of the file include the required Spring tag libraries.
After that we include the link to our downloaded jQuery Javascript source. I’ll make an assumption that people are familiar with jQuery.
Next we use jQuery to add a click event listener to our form ‘Add’ button. When the user clicks add button we fire off an Ajax Get request to our server which sends an incremented number as part of its data string. This Ajax request hits our server which simply appends a new row to the form for another student name. We use the number for the index number in the collection of students.
The response is simply appended as a new row in our form before the form submit button. After that Javascript the rest is a basic Spring form.
Next create a new file called formStudentInsert.jsp in the same location as your form.jsp and add the following contents.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<tr>
<td> </td>
<td>
<form:input path="classCommand.students[${studentNumber}].studentName" size="40" />
</td>
<td> </td>
</tr>As mentioned before this is JSP that gets appended to our form when we send the Ajax request.
The form should look similar to the image below.
Next create a JSP page that is displayed when the user submits the form. Create a new JSP called formResult.jsp and add the following contents.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h2>Class ${savedClass.className}</h2>
<p>Students</p>
<ul>
<c:forEach var="student" items="${savedClass.students}">
<li>${student.studentName}</li>
</c:forEach>
</ul>The Form Backing Object
We next need to create the form backing objects that will be used for this example. Create a new Java class called Student and add the following contents:
/** * @author Eggsy - eggsy _at_ eggsylife.co.uk * */ public class Student{ private String studentName = null; public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } }
This class is a simple Student object. Student objects will belong to a class object. Now we can go ahead and create the Class object.
Create a new class called ClassCommand and add the following:
import org.springframework.util.AutoPopulatingList; /** * @author Eggsy - eggsy _at_ eggsylife.co.uk * */ public class ClassCommand { private String className = null; private AutoPopulatingList students = null; public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public AutoPopulatingList getStudents() { return students; } public void setStudents(AutoPopulatingList students) { this.students = students; } }
The class command object is the backing object we shall use for our form. It simply has a class name. However it does also have a list of students. For this we could have used a basic java.util.ArrayList but this causes issues when attempting to bind the form. As the list wouldn’t (initially) have any contents we would experience IndexOutOfBounds exception messages. This is where we can use the incredibly helpful Spring AutopopulatingList.
Spring Annotated Form Controller
Finally we need to create the Spring Form controller that handles our various requests. Note in the servlet XML file we specified that we would scan the package com.annotations.controller package for controller annotations. Go ahead and create this package. (You can of course change this). Now create a class called ClassFormController containing the following:
import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.util.AutoPopulatingList; 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.RequestParam; import com.annotations.command.ClassCommand; import com.annotations.command.Student; /** * @author Eggsy - eggsy _at_ eggsylife.co.uk */ @Controller public class ClassFormController { @ModelAttribute("classCommand") public ClassCommand getClassCommand() { ClassCommand classCommand = new ClassCommand(); classCommand.setStudents(new AutoPopulatingList(Student.class)); return classCommand; } /** * <p>Called when the form is initially displayed</p> * @param model * @return */ @RequestMapping(method = RequestMethod.GET, value="/processClassForm.page") protected String showNameForm(ModelMap model) { return "form.view"; } /** * <p>Called when the user clicks the add button</p> * @param fieldId * @param model * @return */ @RequestMapping(method = RequestMethod.GET, value="/appendStudentView.page") protected String appendStudentField(@RequestParam Integer fieldId, ModelMap model) { model.addAttribute("studentNumber", fieldId); return "formStudentInsert.view"; } /** * <p>Called when the user finally submits the class form</p> * @param classCommand * @param model * @return */ @RequestMapping(method = RequestMethod.POST, value="/processClassForm.page") protected String onSubmit( @ModelAttribute("classCommand") ClassCommand classCommand, ModelMap model ) { model.addAttribute("savedClass", classCommand); return "formResult.view"; } }
I’ll assume you’re comfortable with Spring annotations so I’ll just highlight the important parts. Firstly notice the way we declare our ClassCommand object. We new up the object then set the Students list as a new AutoPopulatingList of type Student.class. This is again so that we can ensure we experience no IndexOutOfBounds errors. The next method is called when we display our form simply returning the form.view. After that we have the method that gets called when the user clicks the Add button. This returns the small HTMl snippet that gets appended to our form. Finally we have the onSubmit method that gets called when we submit our form and returns the formResult view.
Ok so thats pretty much it. If you followed the examples word for word you after setting up your web application you should be able to visit http://localhost:8080/dynamiclists/processClassForm.page and test your web app.
You can also download the example project here within the project there is a docs folder that also includes a sample Tomcat context file for application deployment.
References: http://blog.richardadamdean.com/?page_id=119
Any comments or questions are always welcome!
