Advanced forms handling in Struts 1.1

by Keld H. Hansen

Introduction

This article is about the more advanced features Jakarta Struts offers in building HTML forms. If you know how to create forms in plain HTML then the step to building simple forms in Struts with, for example, a couple of input text fields, a checkbox or a radio button is not very complicated. When it comes to the more complex controls like the multi-valued selection list or a variable length list of input fields it gets more challenging. This is especially true when the possible selections are not fixed, but taken from some external source like a database instead. I've too often found myself struggling with the syntax and semantics of the html-tags when the forms get complex, and if you search the web for advice, you'll soon see that you have to collect information from many sources.

In this article I've tried to collect solutions to the most common non-trivial cases. You'll not find a solution to every case, so remember to also read Struts' own documentation, starting with the documentation for the html-tags. You might find what you need there.    

At the end of the article is a table with an overview of the solutions. I'm sure you'll find it useful when coding your Struts applications.

Here's a link to a zip file containing all the examples shown in the article. 

The controls in a form

Here, first of all, is a list of the commonly used input controls:

Type Look Values
1. Text field Anything that can be typed in.
One line
2. Text area field Anything that can be typed in.
Several lines.
3. Checkbox 1 of 2 (yes/no - on/off)
4. Radio button 1 of many fixed values
5. Selection list -
single type
1 of many fixed values.
Gives you the same functionality as the radio buttons, only the presentation differs.  
 
6. Selection list -
multiple type
Several of many fixed values
7. List of checkboxes Several of many fixed values.
Gives you the same functionality as the multi selection list, only the presentation differs.  

In Struts you define a control with a tag from Struts' html-library. The next table shows how these are mapped to the old, well-known HTML tags:   

Type Struts html-tag HTML tag
1. Text field <html:text property="firstname"/> <input type="text" name="firstname">
2. Text area field <html:textarea property="address"/> <textarea name="address">
3. Checkbox <html:checkbox property="married" value="yes"/> <input type="checkbox" name="married" value="yes">
4. Radio button <html:radio property="card"
value="Diners"/> . . .
<input type="radio" name="card" value="Diners"> . . .
5. Selection list -
single type
<html:select property="country"> and
<html:option value="F"/> . . .
<select name="country">
<option value="F"> . . .
6. Selection list -
multiple type
<html:select property="food" multiple="true"> and
<html:option value="milk"/> . . .
<select name="food" multiple>
<option value="milk"> . . .
7. List of checkboxes <html:multibox property="site">
. . .
<input type="checkbox" name="site" value="jb">
<input type="checkbox" name="site" value="oj">
. . .

To begin with, you'll notice that where Struts uses the attribute name "property", HTML uses "name". This is a bit confusing since Struts also has an attribute called "name", which is used to give the name of the bean whose "property" maps the control. The default for the "name" attribute is the name of the form's corresponding bean, so normally you don't need to use the "name" attribute.

Mapping and accessing the controls

Rule # 1:

The name of every control (given by the "property" attribute) must be defined in the form's bean, which may be:

1. a Java ActionForm bean defined in the form-beans section of the struts-config file, for example:

<form-bean name="detailForm"
   type="hansen.playground.DetailForm"/>

2. a "dynamic" form bean, also defined in the form-beans section of struts-config, for example:

<form-bean
   name="simpleForm"
   type="org.apache.struts.action.DynaActionForm">
     <form-property name="firstname" type="java.lang.String"/>
     <form-property name="site" type="java.lang.String[]"/>
</form-bean>    

Note that a control may be mapped to an array if it contains more than one value.

Rule #2:

You may get or set the data in the controls from the execute method in the Action class:

1. ActionForm bean:

public ActionForward execute(ActionMapping mapping,
  ActionForm form,
  HttpServletRequest request,
  HttpServletResponse response) {

  DetailForm df = (DetailForm)form;
  String index  = df.getFirstName();
  String[] site = df.getSite();
  . . .
  df.setFirstName("John");
  . . .  

You may of course also get or set the data in the ActionForm itself - for example in the validate method.

2. Dynamic form bean:

public ActionForward execute(ActionMapping mapping,
  ActionForm form,
  HttpServletRequest request,
  HttpServletResponse response) {
  DynaActionForm f = (DynaActionForm)form;

  String firstName = (String)f.get("firstname");
  String[] site    = (String[])f.get("site");
  . . .
  f.set("firstname", "John");
  . . .	

The "interesting" controls

We'll not spend time on the simple controls "Text field", "Text area field"" and "Checkbox" (marked 1, 2, and 3 in the tables above) since they're all "single-valued", and therefore simple to handle in your classes. Instead we'll look further at the controls that have a set of options or values attached.

Set of fixed options

Let's first take care of the simple situation where the possible options of these controls are fixed. By "fixed" I mean that they won't change over time, for example "Male/Female" or "Spring/Summer/Autumn/Winter". The jsp-code example for such a control could be:

<html:radio property="sex" value="M"/>Male<br>
<html:radio property="sex" value="F"/>Female     

Another example with a multi selection list:

<html:select property="food" multiple="true">
<html:option value="milk">Milk</html:option>
<html:option value="apple">Apple</html:option>
<html:option value="bread">Bread</html:option>
</html:select>

Dynamic options

It's much more interesting--seen from a programmer's point of view--when the selectable options are computed in the application, for example read from a data base. Struts has solutions for all the remaining controls--marked 4, 5, 6, and 7 above--but unfortunately they're rather different. One thing is, however, common for some of them: they use the logic:iterate tag to loop over the set of selectable options. So let's take a quick recap of how to use the iterate tag. A simple example:

<table border=1> 
  <logic:iterate id="customer" name="customers">
    <tr>
      <td><bean:write name="customer" property="firstName"/></td>
      <td><bean:write name="customer" property="lastName"/></td>
      <td><bean:write name="customer" property="address"/></td>
   </tr>
  </logic:iterate>
</table>

The iterate tag's name-attribute refers to a Collection of beans stored in request or session scope. The id-attribute gives the (logical) name of the bean created in each iteration. Other tags--for example the bean:write tag--may then refer to this bean with their name-attribute. The property-attribute finally selects the wanted property from the bean.

You might now think that you simply use the name- and property-attributes with the html-tags, but it's not that simple. The html-tags already use the property-attribute for the name of the property in the ActionForm, so there's a conflict here. 

The tag inventors have had other challenges as well, so therefore the syntax for the html-tags when handling sets of options are a bit heterogeneous.       

The Radio Button

First I'll present the solution, and afterwards the explanations. First the struts-config file, where we define a dynamic form bean (but you could just as well use the old ActionForm bean):

<form-bean name="radioForm"
  type="org.apache.struts.action.DynaActionForm">
  <form-property name="control" type="java.lang.String"/>
</form-bean>
Then the jsp-code:
<logic:iterate id="choice" name="choices">
  <html:radio property="control" idName="choice" value="value"/>
  <bean:write name="choice" property="label"/>
  <br>
</logic:iterate> 

We're using a Collection, choices, of beans with properties value and label. When the idName attribute is used the radio-tag's value-attribute denotes a property in the bean give by idName. It's the id-attribute in the iterate-tag and the idName in the radio-tag that links the tags together.

Struts has a utility bean class called LabelValueBean with exactly these properties: value and label, and it may be used to construct a "minimal" bean to put in a Collection, for example:

import org.apache.struts.util.LabelValueBean;
. . .
Collection choices = new ArrayList();

choices.add(new LabelValueBean("American Express", "AE"));
choices.add(new LabelValueBean("MasterCard", "MC"));
choices.add(new LabelValueBean("Diners", "DN"));

request.setAttribute("choices", choices);

You don't have to use this utility class, any bean will do, as long as it has properties that can be used for the value attribute in the html:radio tag and the property attribute in the bean:write tag.

You may set or get the current value of the radio buttons in the execute method, as described earlier. The name we've used for the radio buttons, control, will also be used in the following examples.

The Selection List - single type

Since the single type selection list returns one value only we can use the same setup in struts-config as for the radio buttons:

<form-bean name="singleSelectForm"
  type="org.apache.struts.action.DynaActionForm">
  <form-property name="control" type="java.lang.String"/>
</form-bean>

Now assume that we have a bean, Customer, with String properties firstName, lastName, address, and id. If you have a Collection of Customer beans then you can show a list of all the customers using jsp-code like this:

<html:select property="control" size="2">
  <logic:iterate id="customer" name="customers" type="hansen.playground.Customer">
    <html:option value="<%=customer.getId()%>">
      <bean:write name="customer" property="firstName"/> 
      <bean:write name="customer" property="lastName"/> 
    </html:option>
  </logic:iterate>
</html:select>

You'll have to specify the type attribute of the iterate tag or the scriptlet will fail.

There is a much nicer solution, however:

<html:select property="control" size="2">
  <html:options collection="customers" property="id" labelProperty="fullName"/> 
</html:select>

This time we use the options tag, where the collection attribute gives the name of the Collection of beans, and property gives the name of the property in the bean whose value will be returned when the form is submitted. labelProperty is the bean property that will be displayed. I've created a pseudo-property, fullName, in the Customer bean with a getter-method that returns the firstName and the lastName. In the browser we could have this presented:

The generated HTML looks like this:

<select name="control" size="2">
<option value="001">John Doe</option>
<option value="002" selected="selected">Peter Smith</option>
</select>

An almost identical solution, but using less confusing attribute names, is this:

<html:select property="control" size="2">
<html:optionsCollection name="customers" value="id" label="fullName"/> 
</html:select>  

The current value of the list is again set or get in the execute method--as described previously.

The Selection List - multiple type

You may copy the solutions from the single choice selection list if you add multiple="true" to the html:select-tag, for example:

<html:select multiple="true" property="control" size="2">
<html:optionsCollection name="customers" value="id" label="fullName"/> 
</html:select>  

This time, however, you'll have to define the control property in the ActionForm as a String array:

<form-bean name="multiSelectForm"
  type="org.apache.struts.action.DynaActionForm">
  <form-property name="control" type="java.lang.String[]"/>
</form-bean>

List of checkboxes

Finally, the case with a set of checkboxes, where the user may select zero, one or many of the presented options. This resembles the multi selection list--only the GUI is different.

You might think that a solution like this will work:

<logic:iterate id="customer" name="customers" type="hansen.playground.Customer">
  <html:checkbox property="control" value="<%=customer.getId()%>"/>
  <bean:write name="customer" property="fullName"/>
  <br>
</logic:iterate>

It will not work 100%. The GUI is OK, and you may also submit the correct data, but if you return to the same page you have lost the data you just typed in.

There is, however, a special tag for this case, the multibox, and it will work:

<logic:iterate id="customer" name="customers">
  <html:multibox property="control">
    <bean:write name="customer" property="id"/>
  </html:multibox> 
  <bean:write name="customer" property="fullName"/>
  <br>
</logic:iterate>

In the browser you would see something like this:

Here's the generated HTML:

<input type="checkbox" name="control" value="001" checked="checked"> 
John Doe
<br>

<input type="checkbox" name="control" value="002"> 
Peter Smith
<br> 

Deselecting all choices

Consider the situation where you have either a list of checkboxes or a multi selection list and have defined the Struts action in session scope, for example:

<action path="/multicheckbox"
  type="hansen.playground.MultiCheckboxAction"
  scope="session"
  name="multiCheckboxForm">
 <forward name="OK" path="/multicheckbox.jsp"/>
</action>

If you deselect all choices and submit the form you'll observe that nothing happens: the previously selected values are still selected. The reason is, since you haven't selected anything, nothing is actually submitted. The ActionForm is therefore not updated. You may correct this behavior if you define a reset method in your ActionForm that initializes the control's array to an empty array:

public void reset(
  ActionMapping mapping,
  HttpServletRequest request) {
    this.control = new String[0]; 
}    

If you are using a DynaActionForm you'd have to code an extension of the DynaActionForm

package hansen.playground;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

public class MyDynaActionForm extends DynaActionForm {

  public void reset(
    ActionMapping mapping,
    HttpServletRequest request) {
    this.set("control", new String[0]);
  }

}

This situation only occurs for an action defined in session scope. In request scope you get a new bean for every request, with the control's property initially set to an empty array. 

List of input fields

Let's finish by looking into how we could construct a list of text input fields. We'd like to present a page that looks like this:

The persons are taken from a database, so the number of "lines" is variable. This is a case for Struts' "Indexed Properties".

We'll once more use the Customer class, so first we define this form in struts-config:

<form-bean
  name="listTextForm"
  type="org.apache.struts.action.DynaActionForm">
     <form-property
       name="customer"
       type="hansen.playground.Customer[]"/>
</form-bean>

This is a very different setup from what we have seen before. This time we define a "control" which is an array of Customers.

The action in the struts-config must have scope="session":

<action path="/listtext"
  type="hansen.playground.ListTextAction"
  scope="session"
  name="listTextForm">
     <forward name="OK" path="/listtext.jsp"/>
</action>

Now the Action class:

public ActionForward execute(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) {

  DynaActionForm f = (DynaActionForm) form;
  Customer[] s = (Customer[])f.get("customer");

  if (s.length == 0) {
    // First time in this class
    Collection c = new ArrayList();
    Customer cust1 = new Customer("001", "John", "Doe", "X-Street 7");
    Customer cust2 = new Customer("002", "Peter", "Smith", "Computer blvd. 123");
    Customer cust3 = new Customer("003", "Keld", "Hansen", "Java Road 13");
    c.add(cust1);
    c.add(cust2);
    c.add(cust3);

    Customer[] cs = (Customer[])c.toArray(new Customer[0]);
    f.set("customer", cs);
  } else {
    // We've been here before
    for (int i = 0; i < s.length; i++) {
    Customer c = (Customer)s[i];
    String t = c.getFullName();
    System.out.println(t);  // For debugging
  }
}

  return (mapping.findForward("OK"));
...

This time we get an array of Customers in the ActionForm.

The jsp-page could look like this:

<html:form action="listtext"> 

  <html:submit/>

  <logic:iterate id="customer" name="listTextForm" property="customer">
    <html:text name="customer" property="firstName" indexed="true"/>
    <html:text name="customer" property="lastName" indexed="true"/>
    <html:text name="customer" property="address" indexed="true"/>
    <br>
  </logic:iterate>

</html:form> 

The new thing here is the indexed attribute. We iterate over the array of Customers, and for each bean we use its attributes in the text fields. Note that we'll have to use the name "customer" in several places to have the Indexed Properties feature working.

If you look in the generated HTML you'll see this:

<input type="text" name="customer[0].firstName" value="John">
<input type="text" name="customer[0].lastName" value="Doe">
<input type="text" name="customer[0].address" value="X-Street 7">
<br>

<input type="text" name="customer[1].firstName" value="Peter">
<input type="text" name="customer[1].lastName" value="Smith">
<input type="text" name="customer[1].address" value="Computer blvd. 123">
<br>

<input type="text" name="customer[2].firstName" value="Keld">
<input type="text" name="customer[2].lastName" value="Hansen">
<input type="text" name="customer[2].address" value="Java Road 13">
<br> 

When such a form is submitted Struts will recognize the syntax of the name-attributes as Indexed Properties, and fill the ActionForm correctly.

This setup will also work for lists of other controls as well.

Read more about Indexed Properties in the Struts documentation.

Recap

  1. Always define properties in the ActionForm matching the property attribute from the html-tag. The exception is Indexed Properties, where you must define an array of the object you iterate through
  2. Use setters and getters from the ActionForm to access the data in the form.
  3. Use these tags in your jsp:
Radiobuttons <logic:iterate id="customer" name="customers">
  <html:radio property="control" idName="customer" value="id"/>
  <bean:write name="customer" property="fullName"/>
  <br>
</logic:iterate>
Selection list - single value <html:select property="control" size="2">
  <html:optionsCollection name="customers" value="id" label="fullName"/>
</html:select> 
Selection list - multiple values <html:select property="control" size="2" multiple="true">
  <html:optionsCollection name="customers" value="id" label="fullName"/>
</html:select> 
List of checkboxes <logic:iterate id="customer" name="customers">
  <html:multibox property="control">
    <bean:write name="customer" property="id"/>
  </html:multibox>
  <bean:write name="customer" property="fullName"/>
  <br>
</logic:iterate>
List of text fields <logic:iterate id="customer" name="listTextForm" property="customer">
  <html:text name="customer" property="firstName" indexed="true"/>
  <html:text name="customer" property="lastName" indexed="true"/>
  <html:text name="customer" property="address" indexed="true"/>
  <br>
 </logic:iterate>

Conclusion

Struts has a rich set of html-tags for building form controls. Unfortunately it's often rather difficult to predict the exact syntax to use in a given situation. The examples presented above should be possible to copy/paste into your own applications. If you dig up another clever solution you're welcome to e-mail me, and I'll try to put your solution in one of my upcoming articles.

Happy coding!

Resources