Spring Forms with Dynamic Lists and Javascript | PermGen error
Home > Spring Framework > Spring Forms, Dynamic Lists and Ajax

Spring Forms, Dynamic Lists and Ajax

November 30th, 2009 admin Leave a comment Go to comments

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>&nbsp;</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>&nbsp;</td>
	<td>
		<form:input path="classCommand.students[${studentNumber}].studentName" size="40" />
	</td>
	<td>&nbsp;</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.

Picture of class form

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!

  1. admin
    April 29th, 2010 at 19:35 | #1

    Hi there,

    Many thanks for your comments I am glad it helped you :)

  2. Brian
    April 29th, 2010 at 15:24 | #2

    This is a tremendous help. It solves a problem that I have encountered with a new project. I just want to say thank you for such a thorough post — it will be a big time saver for me.

    Cheers!

  3. admin
    April 22nd, 2010 at 12:34 | #3

    Hi there,

    I guess in the controller submit method you could add the BindingResult parameter and implement your own validator.

    You could also use the new JSR-303 Bean Annotation validation on each individual student and that should vailidate so the onSubmit method would become:

    @RequestMapping(method = RequestMethod.POST, value="/processClassForm.page")
    protected String onSubmit( @ModelAttribute("classCommand") @Valid ClassCommand classCommand,  ModelMap model )
    {
    	model.addAttribute("savedClass", classCommand);
    	return "formResult.view";
    }
  4. Gerald
    April 21st, 2010 at 17:31 | #4

    Is it posible validate the generated inputs?
    Best Regards and Thanks for the information.

  5. admin
    April 14th, 2010 at 22:47 | #5

    Hi there,

    In that case, rather than using the @ModelAttribute function you could just populate the required object (by placing into the ModelMap) in the GET annotated controller method in that case the object would be populated when and only when the form is displayed.

    If you need to pre-populate the Class Command in this example such as calling into a database as you mention you can do so by all means. The AutoPopulating list of the class command can be updated in the same way as a java.util.List

  6. jovox
    April 14th, 2010 at 22:04 | #6

    I wrote a controller the same way you did (using annotations) as I wasn’t able to translate it to non annotated code.

    I see one issue with your code and that is that getClassCommand will be called every time @ModelAttribute(“classCommand”) is injected.

    What about if you need your ClassCommand instance to be “kept alive” between the calls?
    That would be necessary if you did something else than just “ClassCommand classCommand = new ClassCommand();” in you getClassCommand() method. Loading a ClassCommand from the database for instance.

  7. jovox
    April 13th, 2010 at 12:38 | #7

    “admin”
    The problem with just inherit SimpleFormController and override the two methods you mentioned is that SimpleFormController is not a MultiActionController which would be required to add appendStudentField for instance.

  8. admin
    April 13th, 2010 at 09:03 | #8

    Hi there,

    I have tested the download and everything seems fine.

    After downloading and extracting the Zip file. In Eclipse Click File then Click Import and choose ‘Existing projects into workspace…’ then on the next screen choose the downloaded folder.

    After importing everything should build OK?

  9. April 12th, 2010 at 23:56 | #9

    I’m sorry disturbing you.
    I have downloaded the source.
    http://eggsylife.co.uk/SpringDynamicListsExample.zip
    And I try to open the .zip package.. I think this package is broken. I have downloaded this file more than three times! Can you fix the package, and give the new download Link?

  10. admin
    April 10th, 2010 at 19:17 | #10

    Hi there

    I’m glad it helped you.

    In terms of doing it with a non annotated controller – I haven’t done it a while – but I think you should just be able to extend the Spring ‘SimpleFormController’ object then override the onSubmit and formBackingObject methods. The formBackingObject method would be where you return the ClassCommand object as dictated by the tutorial.

    I hope this helps,

    Please let me know if you need any further advice

  11. jovox
    April 9th, 2010 at 22:37 | #11

    This code is almost exactly was I was looking for except I’m not a big fan of annotating my controllers. I.e I prefer the xml configuration. I’m not very familiar with Springs annotations and find it difficult to “translate” your annotations to xml. Could you give me some hints?

  12. admin
    December 15th, 2009 at 04:12 | #12

    Hi Dan,

    Assuming I have understood you correctly, it might be worth looking at extending the SpringMultiActionController for this. Then use different actions rather than different controller objects.

    You should then be able to re-use form backing object? Section 13.3.3 of the Spring manual discusses MultiActionControllers: http://static.springsource.org/spring/docs/2.0.x/reference/mvc.html

  13. Dan Scrima
    December 14th, 2009 at 13:54 | #13

    Any way this could be done with an app that is already using the non-annotation-based spring mvc? I’m trying to create a second controller extending AbstractController and return the html, and I have that done with the jQuery etc… The problem is trying to bind to the command with form:input coughs because the command initiated by the original controller isn’t available to the 2nd controller. Thoughts? Thanks a lot, this is great so far!

  1. No trackbacks yet.