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.
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" |
<input type="radio" name="card"
value="Diners"> . . . |
5. Selection list - single type |
<html:select property="country"> and |
<select name="country"> |
6. Selection list - multiple type |
<html:select property="food" multiple="true"> and |
<select name="food" multiple> |
7. List of checkboxes |
<html:multibox property="site"> |
<input type="checkbox" name="site"
value="jb"> |
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.
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"); . . .
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.
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>
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.
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.
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 option
s
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.
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>
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>
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.
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 Customer
s.
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 Customer
s 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 Customer
s, 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.
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
throughActionForm
to access the data in the
form.Radiobuttons | <logic:iterate id="customer" name="customers"> |
Selection list - single value | <html:select property="control" size="2"> |
Selection list - multiple values | <html:select property="control" size="2" multiple="true"> |
List of checkboxes | <logic:iterate id="customer" name="customers"> |
List of text fields | <logic:iterate id="customer"
name="listTextForm" property="customer"> |
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!