Sun Java Solaris Communities My SDN Account
 
Article

Maintaining State for HTML Form Buttons

 
 

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:

  1. 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:
  2. 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.

  3. 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.
  4. 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.

Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.