July 29, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Succeeding With Struts: Dynamically Sized Forms

  • March 4, 2004
  • By James M. Turner
  • Send Email »
  • More Articles »

In the previous installment of Succeeding with Struts, I alluded to the ability of DynaForms to dynamically size forms at run time.  In other words, the ability to have a form that could be 5 rows long, or 10 rows, or 15 rows as needed.  Perhaps a bit unwisely, I let the actual implementation of such a strategy as an exercise to the reader.  In the following months, I've received dozens of inquires from readers looking for the dirty details, so this month I'll demonstrate not one, but two seperate ways to implement dynamically sizable forms.

The first method is the one I mentioned in passing in the previous column, leaving the size parameter off the form-property attribute of a DynaForm..  To see how this works, let's look at a very simple application that lets the user add comments about various Star Wars actors.  The key fact in this application of interest to us is that the number of actors is dynamically set during the form setup, rather than in the struts-config.xml file.



First, let's look at the struts-config.xml file itself:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="dynamicArrayForm" type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="people" type="demo.Person[]"/>
</form-bean>
</form-beans>
<action-mappings>
<action path="/setupForm" type="demo.SetupFormAction" name="dynamicArrayForm" scope="session"
validate="false">
<forward name="success" path="/displayForm.jsp"/>
</action>
<action path="/processActorComments" type="demo.ProcessFormAction" name="dynamicArrayForm" scope="session" validate="false">
<forward name="success" path="/displayForm.jsp"/>
</action>
</action-mappings>
</struts-config>


As you can see, this is a fairly simple configuration file with one form and two actions defined.
The first Action, /setupForm, is used to set up the form before the initial display,
and the other action, /processActorComments is what processes the comments entered by the user.

There are two important things to notice in this file that are critical to making things work:

  1. The people form property is defined as type demo.Person[] (meaning an array of demo.Person), but no size parameter is given.  This will result in a placeholder for an array being created, but no actual array being instantiated.
  2. The two actions define the form as session scoped.  This is critical because when the user submits the form after filling in values, the values are populated back into the form before the action is run.  This means that there is no opportunity to manually create the array with the proper number of slots, as you will see is done in the SetupFormAction class before the form is displayed.  In other words, when the form is submitted, there must already be the appropriate slots to accept the form values, and the only way to ensure this is to have the form already instantiated in session scope.
There's basically no value in looking at the Person bean, it's just a vanilla bean with lastName, firstName, dateOfBirth, gender and comment fields.  The source is included in the WAR file.

Now let's take a look at the SetupFormAction class, which is called before the form is first displayed.

package demo;


/**
* Copyright 2004, James M. Turner.
* All Rights Reserved
*
* A Struts action that sets up a DynaForm which is globally scoped
*/

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;

import org.apache.struts.action.*;
import org.apache.struts.validator.DynaValidatorForm;

public class SetupFormAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

DynaValidatorForm df = (DynaValidatorForm) form;
Person[] p = new Person[3];
p[0] = new Person();
p[0].setDateOfBirth("07/13/1942");
p[0].setLastName("Ford");
p[0].setFirstName("Harrison");
p[0].setGender("M");
p[1] = new Person();
p[1].setDateOfBirth("10/21/1956");
p[1].setLastName("Fisher");
p[1].setFirstName("Carrie");
p[1].setGender("F");
p[2] = new Person();
p[2].setDateOfBirth("09/25/1951");
p[2].setLastName("Hamill");
p[2].setFirstName("Mark");
p[2].setGender("M");

df.set("people", p);

return mapping.findForward("success");
}
}

Again, there's not a lot to look at. The first thing the execute method does, as any DynaForm-based action does, is to cast the generic ActionForm class to a DynaValidatorForm. This allows us to use the get and set methods on the form. Next, the method creates a three-element array of type Person. In this method the size is hardwired, in a real application this could be a size based on doing a select from a database. The important thing to consider is that the array is being created in the code here, not by the Struts engine itself. So any arbitrary number of rows could be created by the code in response to application requirements.


Once the array is in place, the method creates three instances of the Person class and populates the values.  Again, in a real application this would more likely be done inside a loop reading rows from a database and populating the form rows.  Lastly, the action returns success, causing Struts to transfer control to the displayForm.jsp page.

<!--
Copyright 2004, James M Turner.
All Rights Reserved

-->

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<head>
<title>Star Wars Actor Fact Page</title>
</head>
<H1><center>Start Wars Actor Fact Page</title>
<html:form action="/processActorComments" >
<table border="1" width="80%">
<tr><th>Last Name</th><th>First Name</th><th>Date of Birth</th><th>Comment</th></tr>
<c:forEach var="people" items="${dynamicArrayForm.map.people}">
<tr><td><c:out value="${people.lastName}"/></td>
<td><c:out value="${people.firstName}"/></td>
<td><c:out value="${people.dateOfBirth}"/></td>
<td><html:text name="people" indexed="true" property="comment"/></td>
</tr>
</c:forEach>
</table>
<P/>
<html:submit value="Update Comments"/>
</html:form>


Again, there's not a huge amount to look at here, it's exactly the same as the code we looked at in the last article when we were looking at fixed-length rows. The page iterates over the rows (remembering that we have to use the map property in JSTL to gain access to DynaForm properties), and displays the last name, first name, and date of birth of the actor, and providing a text field to enter a comment.


When we fire up our browser and request http://localhost:8080/struts/setupForm.do (assuming you put the struts.war file in Tomcat on your local machine), the following page will appear:

Start Wars Actor Fact Page
Last Name First Name Date of Birth Comment
Ford Harrison 07/13/1942
Fisher Carrie 10/21/1956
Hamill Mark 09/25/1951

Once the form is submitted, another simple Struts action processes the results:

package demo;

/**
* Copyright 2004, James M. Turner.
* All Rights Reserved
*
* A Struts action that sends the new comments to the console
*/

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;

import org.apache.struts.action.*;
import org.apache.struts.validator.DynaValidatorForm;

public class ProcessFormAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

DynaValidatorForm df = (DynaValidatorForm) form;
Person[] p = (Person[]) df.get("people");

for (int i = 0; i < p.length; i++) {
System.out.println(p[i].getFirstName() + " " + p[i].getLastName() + ":" + p[i].getComment());
}

return mapping.findForward("success");
}
}

In a real application, this is where data would be written back to the database.  In this case, it just dumps the data to the console so we can see it was correctly received.  Assuming that we filled out appropriate values for each of the actors, we'd see the following on the console:

Harrison Ford:Indiana Jones
Carrie Fisher:Postcards from the Edge
Mark Hamill:Wing Commander
As I mentioned at the beginning of the article, there's another way to handle things however, that doesn't require the use of a session-scoped form.  That way is to use HashMaps to store the rows.  Let's look at the same code, reimplemented using HashMaps.

To start, we'll add a new form to our struts-config.xml:

        <form-bean name="dynamicHashmapForm" type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="people" type="java.util.HashMap"/>
<form-property name="comments" type="java.util.HashMap"/>
</form-bean>
Instead of arrays of beans, now we use a HashMap to store each person's data.  In addition, we need a new HashMap to store the comments, for a reason I'll explain a bit later.  We also need a new action to populate the data:
package demo;

/**
* Copyright 2004, James M. Turner.
* All Rights Reserved
*
* A Struts action that sets up a DynaForm which is globally scoped
*/

import java.io.IOException;
import java.util.HashMap;
import javax.servlet.ServletException;
import javax.servlet.http.*;

import org.apache.struts.action.*;
import org.apache.struts.validator.DynaValidatorForm;

public class SetupHashFormAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

DynaValidatorForm df = (DynaValidatorForm) form;
HashMap hm = (HashMap) df.get("people");
Person p = new Person();
p = new Person();
p.setDateOfBirth("07/13/1942");
p.setLastName("Ford");
p.setFirstName("Harrison");
p.setGender("M");
hm.put("1", p);
p = new Person();
p.setDateOfBirth("10/21/1956");
p.setLastName("Fisher");
p.setFirstName("Carrie");
p.setGender("F");
hm.put("2", p);
p = new Person();
p.setDateOfBirth("09/25/1951");
p.setLastName("Hamill");
p.setFirstName("Mark");
p.setGender("M");
hm.put("3", p);
return mapping.findForward("success");
}
}
Essentially, this is the same code as before, except we're storing the Person objects into a HashMap instead of an array.  We also don't need to create the HashMap, because it's done automatically as part of the form instantiation.

The tricky part comes in the JSP itself:
<!--
Copyright 2004, James M Turner.
All Rights Reserved

-->

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-html-el.tld" prefix="html-el" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib prefix="fmt" uri="/WEB-INF/fmt.tld" %>

<head>
<title>Star Wars Actor Fact Page</title>
</head>
<H1><center>Start Wars Actor Fact Page</title>
<html:form action="/processHashActorComments" >
<table border="1" width="80%">
<tr><th>Last Name</th><th>First Name</th><th>Date of Birth</th><th>Comment</th></tr>
<c:forEach var="people" items="${dynamicHashmapForm.map.people}">
<tr><td><c:out value="${people.value.lastName}"/></td>
<td><c:out value="${people.value.firstName}"/></td>
<td><c:out value="${people.value.dateOfBirth}"/></td>
<td><html-el:text property="comments(${people.value.lastName},${people.value.firstName})" /></td>
</tr>
</c:forEach>
</table>
<P/>
<html:submit value="Update Comments"/>
</html:form>




Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel