Advanced Forms Handling in Struts 1.1IntroductionThis 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. Every case, of course, is not covered here. 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 formHere is a list of the most commonly used input controls:
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:
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 controlsRule # 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 <form-bean name="detailForm" type="hansen.playground.DetailForm"/> 2. a "dynamic" form bean, also defined in the <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
1. 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 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" controlsWe'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 optionsLet'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 database. 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
<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
You might now think that you simply use the
The tag inventors have had other challenges as well, so
therefore the syntax for the
The Radio Button
Now I'll present a solution, and afterwards discuss the
explanations. First the struts-config file, where we define a
dynamic form bean (but you could just as well use the old
<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
Struts has a utility bean class called
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
You may set or get the current value of the radio buttons in the
The Selection List - Single TypeSince 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,
<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 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
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
The Selection List - multiple type
You may copy the solutions from the single choice selection list if you add
<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 <form-bean name="multiSelectForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="control" type="java.lang.String[]"/> </form-bean>
List of checkboxesFinally, 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 called the
<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 choicesConsider the situation where you have either a list of checkboxes or a multi-selection list and have defined the Struts action in session scope. Take a look at this 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 public void reset(
ActionMapping mapping,
HttpServletRequest request) {
this.control = new String[0];
}
If you are using a
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 fieldsLet'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 a variable. This is a case for Struts' "Indexed Properties".
We'll once more use the
<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 The action in the struts-config must have <action path="/listtext"
type="hansen.playground.ListTextAction"
scope="session"
name="listTextForm">
<forward name="OK" path="/listtext.jsp"/>
</action>
Now the 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 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 If you were to look in the generated HTML you would 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 This setup will also work for lists of other controls as well. Read more about Indexed Properties in the Struts documentation.
Recap
ConclusionStruts 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! Keld is currently working as a web architect for one of the largest IT companies in Denmark. He battled with the mainframes during the 70's when they were the size of a gymnasium and had the power of your PalmPilot. He also struggled with CASE-tools in the 90s and now explores the cutting edge technology of the Web. While not busy at his computer he likes to vacation on the Greek islands.
Resources
|