Struts Multiboxes
The goal of this exercise was to have a list of the 10 Weeks of the Camp Reservation system, and for each week have the CampWeek for which a particular child is scheduled along with a check box that would indicate if the reservation is "confirmed". The check box serves two functions, when the list is initially displayed it shows the user whether that CampWeek is confirmed, but the user can use the checkbox to change the confirmation status (to set it or unset it).
The way this is done in Struts is to use the multibox. But the multibox has always been a bit confusing to me, so since I got this working yesterday, I figured it was time to post this for when I need it again.
My Struts pattern is to have a PrepareFooAction that generates the form that the user can use to take FooAction. I also use Struts' Dynamic Forms and use the same form for both the PrepareFooAction and FooAction. Finally, my JSP page is written using JSTL and is part of a tiled structure.
Here is what I needed to do to get the multibox to work correctly. The guiding principle is stated in the multibox documentation
Multibox: Renders an HTML <input> element of type checkbox, whose "checked" status is initialized based on whether the specified value matches one of the elements of the underlying property's array of current values. This element is useful when you have large numbers of checkboxes, and prefer to combine the values into a single array-valued property instead of multiple boolean properties. This tag is only valid when nested inside a form tag body.
But I have to read that over and over until it sinks in, thus my example code below, looking at the items by file
struts-config.xml:
Add one line:
<form-property name="selectedConfirms" type="java.lang.String[]" size="10"/>
This will hold a list of numbers indicating which of the 10 items were selected. In this particular case, if I record the simple index of the selected rows, my application can determine which Week was chosen and from there I can do what I need. Other times, you will need to store other identifying information here.
The other relevant parts of the struts-config.xml is how the form is associated with both the :
<action name="FooForm" path="/prepareFoo" scope="request" type="camp.struts.PrepareFooAction">
<forward name="success" path=".Foo"/>
</action>
<action name="FooForm" path="/Foo" scope="request" type="camp.struts.FooAction">
<forward name="success" path=".childReport"/>
</action>
foo.jsp:
<c:forEach items="${weeks}" var="week" varStatus="status">
<tr>
.....
<td>
<html:multibox property="selectedConfirms" value="${status.index}" ></html:multibox>
Confirmed?
</td>
</tr>
</c:forEach>
This will generate HTML like....
<tr>
...
<td>
<input type="checkbox" name="selectedConfirms" value="0" checked="checked">
Confirmed?
</td>
</tr>
<tr>
...
<td>
<input type="checkbox" name="selectedConfirms" value="1">
Confirmed?
</td>
</tr>
<tr>
...
<td>
<input type="checkbox" name="selectedConfirms" value="2">
Confirmed?
</td>
</tr>
....
I find it useful to look at this from two perspectives. First, the easy part, when the Submit button is pressed, the selectedConfirms[] array is passed to FooAction. For each checkbox that is pressed, the value for that check box (e.g. "0", "1", "2", ...., "9") will be added to the array, so we will have something like ["1", "3", "5", "7", "9"] (if the user selected the odd weeks). The FooAction code then does the right thing with those values (obviously highly application dependent).
The second part is the part I always find difficult - how to initialize the checkboxes correctly. The key here is that the "property" (e.g. selectedConfirms) must contain a set of the checkboxes that you want to have checked - and in particular, it must contain an array of values - and each checkbox that has a value that is contained in the array will be checked.
PrepareFooAction.java:
So, in the PrepareFooAction, there is code that determines which CampWeeks are confirmed, creates an array of strings holding those numbers and sets the selectedConfirms variable with:
dynaForm.set("selectedConfirms", confirmedOptions);
Now, the system will mark all the checkboxes as "CHECKED" when their VALUE matches one of the items stored in selectedConfirms.
This all seems pretty obvious when I write it down - in retrospect, I think the key thing is realizing the importance of the what you store in the "value" field. It has to potentially match the array in "property" (for initialization) and it has to be useful enough so that when submitted the code can do the right thing.