by Matthias Laux
October 2002
HTML form button elements -- such as radio buttons
and checkboxes -- are used in most of the forms currently on the web. In many
cases, these forms are created by JavaServer Pages (JSP)
on the server side. Often, it's necessary for buttons to be selected in advance-- for instance, the first time the form is displayed, or when the form
is redisplayed after
the user makes an unacceptable entry somewhere. In these cases,
the dynamic information required to determine which buttons to select -- that is, the
state
of these buttons -- must be transported into the JSP pages, so that the
checked
attribute can be dynamically included in the <input>
tag.
This article describes how to maintain button state using JSP custom tags. This lightweight solution
can easily be incorporated
into both new and existing applications.
Why Use Custom Tags?
One typical approach to maintaining state
is to store state information as properties in a JavaBeans component,
and then check the value attribute
for each button element to see whether it requires the checked
attribute or not. This has the obvious disadvantage that it requires Java
scriptlet coding in the JSP page, and thus is more difficult for web designers to maintain. It is
also error-prone. In addition, this decision logic
needs to be reimplemented time and again, wherever such form elements are used. This can get pretty unwieldy.
For example, take a look at this JSP page segment:
<%
if (bean.getCheckedButton().equals("button1")) {
%>
<input type="RADIO" name="radioSet"
value="button1" checked>
<%
} else {
%>
<input type="RADIO" name="radioSet"
value="button1">
<%
}
if (bean.getCheckedButton().equals("button2")) {
%>
<input type="RADIO" name="radioSet"
value="button2" checked>
<%
} else {
%>
<input type="RADIO" name="radioSet"
value="button2">
<%
}
%>
It is assumed here that a JavaBean component
bean provides the value attribute
of the checked radio button as a string, using the getCheckedButton()
method. (Similarly, you could store this data in the
HTTP session.) For each button,
you need to check (using scriptlets) whether its value
attribute is equal to this particular attribute, and then branch
accordingly to the correct <input>
tag. Imagine a construction like this for a list of ten or more buttons!
Now, what about the following approach?
<input type="RADIO" name="radioSet"
value="button1" <%=bean.checkStatus("button1")%>>
<input type="RADIO" name="radioSet"
value="button2" <%=bean.checkStatus("button2")%>>
This is a more concise approach,
based on the bean providing a method -- checkStatus() --
which returns either "checked" or an empty string, depending on the argument.
But the huge disadvantage here is that such a method not only needs
to be implemented for each group of buttons, it also needs to be set up
with the correct data (that is, the value
attribute of the checked button) prior to the invocation of the JSP page. So basically
the workload is moved to both the bean implementation and
the controller servlet forwarding the request to the JSP page.
The following approach would handle
things differently:
<mlt:RadioButtonSet name="radioSet" checked="<%=bean.getCheckedButton()%>">
<mlt:RadioButton value="button1"/> The first button
<mlt:RadioButton value="button2"/> The second button
</mlt:RadioButtonSet>
The buttons are now grouped by an enclosing
tag <RadioButtonSet>. This
simplifies several tasks: you need only specify the name
attribute once, instead of in every input tag.
To inform the enclosing
tag about which button is checked, you use a method like getCheckedButton(),
which is fairly simple to implement in the bean (since it just has to return
a string that was stored in the bean previously). This information is stored
"under the hood" in a helper class, which is accessible to the <RadioButton>
child tags. Each child tag can then determine whether it needs to include the
checked
attribute or not.
In a web application developed according
to the Model-View-Controller (MVC) paradigm, the integration with a controller
servlet is very simple. I typically use two code sections in this servlet
to manage a particular JSP page with an entry form:
-
The first time the user arrives at this web
page (by clicking a link on another page), form elements may need to be
preloaded with data. In the case of button elements, this means that
particular radio buttons or checkbox elements are selected according to
state; and the state might be stored in other objects, or retrieved from
a database. Then a bean component can be instantiated and used to transport
the desired state to the JSP page:
bean.setCheckedButton("button1"); // Mark "button1"
as checked
For simplicity's sake, I use a string constant to set up the
state to be used by the <RadioButtonSet> tag in the JSP page. Note
that for more complex HTML forms (with the JSP page holding multiple sets of radio
buttons and/or checkbox sets), the bean methods to set and retrieve individual
states must also be more complex, to account for the different groups
of elements. Note also that the bean component instantiated here
would typically be stored in the session, so that the JSP page has access to it.
The request is then forwarded to the JSP page,
which accesses the bean as usual via the <jsp:useBean>
tag. The state for the radio buttons or checkbox elements is then retrieved
using Java scriptlets (as shown in the example above), and the button elements
show the desired state.
-
After the user enters data and selects/checks
button elements in the web form, the form is submitted and received by
the second code section in the controller servlet. This code
section needs to validate the entries and react accordingly: if the entries
are acceptable, the request is forwarded to the next page in the flow logic
of the application; otherwise, the request is forwarded back to the JSP page
with an error message asking the user to correct any erroneous entry.
To stick with our simple example, the state of the radio button must first be retrieved and updated in the bean:
bean.setCheckedButton(request.getParameter("radioSet"));
Here, we assume that the set of radio buttons is identified by the
name "radioSet" in the JSP page. This code snippet makes sure that the user's
selections are preserved, should the request need to be forwarded back to
the JSP page due to some error in the entries. Then the entries and selections can be verified, and the controller
servlet forwards the request accordingly as described.
Simple: Only two calls to the setCheckedButton()
method in the controller are required to maintain the state of a set of radio buttons.
For checkbox elements, the situation is
very similar; the only difference is that multiple buttons
can be selected. The most straightforward approach is to use a
bean method like setCheckedButtons(String[]),
which takes a string array with the value
attributes to be checked. The corresponding JSP tags are CheckBoxSet
and CheckBox, where the checked
attribute in CheckBoxSet also
expects a string array as the value. HTTPServletRequest.getParameterValues(String)
also returns a string array for checkbox elements, so the update of the
bean in the controller would be very similar for checkboxes:
String[] checkedButtons = request.getParameterValues("radioSet");
bean.setCheckedButtons(checkedButtons);
In the JSP page, code for checkboxes would look like this:
<table>
<mlt:CheckBoxSet name="order"
checked="<%=bean.getCheckedButtons()%>">
<tr><td>
<mlt:CheckBox value="salami"/>
Salami pizza
</td></tr>
<tr><td>
<mlt:CheckBox value="anchovies"/>
Pizza with anchovies
</td></tr>
<tr><td>
<mlt:CheckBox value="ham"/>
Our great ham pizza
</td></tr>
<tr><td>
<mlt:CheckBox value="margherita"/>
A plain margherita
</td></tr>
<tr><td>
<mlt:CheckBox value="cheese"/>
Loads of cheese on this one
</td></tr>
</mlt:CheckBoxSet>
</table>
Note that the approach described here in no way depends on the use of
the MVC paradigm or a controller servlet. The code snippets above
could be used in the same way in an application that, for example, just uses JSP pages. In this case,
Java scriptlets within the JSP pages
can take care of the required steps to control flow logic, and standard
JSP bean tags can be used to keep the state.
Technical Implementation
Let's start with a look at the ButtonHolder
class:
public class ButtonHolder {
private static final String CHECKED = "checked";
private static final String UNCHECKED = "";
private String checkedButton = null;
private String[] checkedButtons = null;
private boolean radio
= false;
public void setCheckedButton(String value) {
checkedButton = value;
radio
= true;
}
public void setCheckedButtons(String[] values) {
checkedButtons = values;
radio
= false;
}
public String getCheckStatus(String value) {
if (radio) {
if (value.equals(checkedButton))
{
return CHECKED;
} else {
return UNCHECKED;
}
} else {
if (checkedButtons == null) {
return UNCHECKED;
} else {
for (int i = 0; i <
checkedButtons.length; i++) {
if (value.equals(checkedButtons[i]))
{
return
CHECKED;
}
}
return UNCHECKED;
}
}
}
}
This very simple helper class is instantiated
(under the hood) by the tag implementation for RadioButtonSet
and CheckBoxSet, so the user
never has to deal with this class directly. The setCheckedButton()
and setCheckedButtons() methods
are also used by these tag implementations, if the checked
attribute is used; this attribute is optional, but without it,
the tags described here don't add real value. The getCheckStatus()
method is used by the RadioButton
and CheckBox tags to insert
the checked attribute in the
HTML <input> tag where
required.
The RadioButtonSet
tag implementation is also very simple:
public class RadioButtonSetTag extends BodyTagSupport {
private String name = null;
private String checked = null;
private ButtonHolder holder = null;
...
public String getName() {
return name;
}
public ButtonHolder getHolder() {
return holder;
}
public int doStartTag() throws JspException {
holder = new ButtonHolder();
if (checked != null) {
holder.setCheckedButton(checked);
}
return EVAL_BODY_AGAIN;
}
...
}
Some obvious methods (like the setter methods
for the attributes) are left out here for clarity. In the doStartTag()
method, ButtonHolder is
instantiated and -- if the checked
attribute was included -- this information is stored in this instance. The
RadioButton
child tag accesses this information through the getName()
and
getHolder() methods. The
implementation for the CheckBoxSet
method is almost identical, except for the checked
member variable, which is of type String[]
in this case.
The RadioButton
tag implementation actually writes the required HTML <input>
form tag into the page:
public class RadioButtonTag extends TagSupport {
private String value = null;
public void setValue(String value) {
this.value = value;
}
public int doStartTag() throws JspException {
RadioButtonSetTag tag = (RadioButtonSetTag)findAncestorWithClass(this,
RadioButtonSetTag.class);
if (tag == null) {
throw new JspTagException("Must
be enclosed within RadioButtonSet");
}
try {
pageContext.getOut().print("<input
type=\"RADIO\" name=\""
+ tag.getName()
+ "\" value=\""
+ value + "\" "
+ tag.getHolder().getCheckStatus(value)
+ ">");
} catch (Exception e) {
throw new JspTagException("ERROR:
" + e.getMessage());
}
return SKIP_BODY;
}
}
The first step in the doStartTag()
method is to get a reference to the enclosing RadioButtonSetTag
implementation. Then, we simply assemble the text for the <input>
form tag using the name and
the value attribute. The checked
attribute is inserted through the getCheckStatus()
method where required. Recall that this information has been inserted into
the ButtonHolder instance
in the enclosing RadioButtonSet
tag. The corresponding implementation for the CheckBox
tag is identical, except for the class type of the enclosing tag, which
is CheckButtonSetTag in this
case.
The tag lib descriptor also contains no
surprises. This is the section for the RadioButtonSet
and RadioButton tags (the
two other tags look similar; just replace RadioButton
with CheckBox):
<tag>
<name>RadioButton</name>
<tagclass>RadioButtonTag</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>RadioButtonSet</name>
<tagclass>RadioButtonSetTag</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>checked</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
Summary
The JSP custom tag extensions described in
this article provide a simple yet powerful way to maintain state for radio
button and checkbox HTML form elements. The syntax is very close to the
original HTML form elements, thus their use is straightforward. Also,
the integration with a controller servlet is very convenient, requiring
only a few lines of code to provide the state information to the JSP page,
and also to update this information using the HTTP request for the next
invocation of the JSP page. The approach described here completely avoids
clumsy Java scriptlet constructions in JSP pages (for marking button elements
as checked). In addition, this approach is problem-independent, and can thus be easily
reused.
References
Struts: An open-source framework for building web applications.
This is a very comprehensive approach to authoring web applications. Struts works best if an application is built
on it from the start; it is not readily retrofitted into existing applications.
Facilitate Form Processing
with the Form Processing API by Ilirjan Ostrovica, in JavaWorld, April 2001: An alternative approach based on JavaBeans
technology, rather than JSP custom tags.
About the Author
Dr. Matthias Laux is a systems engineer
working in the Global SAP-Sun Competence Center in Walldorf, Germany. His
main interests are Java and J2EE technology and programming, XML technology;
databases, and SAP benchmarking. Although he also has a background in aerospace
engineering and HPC/parallel programming, today his languages of choice
are Java and Perl. You can reach him at matthias.laux@sun.com.
|