In using/learning Struts2, I ran into a situation where I needed a lot of checkboxes in a form. The checkboxes from the xhtml theme do not have a break after them.
I was using Spring Security, and needed a form with checkboxes for the roles. Looking at the xhtml form tags, most of them generate table rows for the form fields.
The solution I came up with was to extend the checkbox tag to use table elements for each checkbox in the list, rather than one for the whole lot of them. This puts them each on their own row in the table, and is much more readable. This is particularly the case if you have more than a one-word description beside the checkbox.
To see how to do this, read on...
To start, here is the jsp snipped that I am using. It remains almost the same, because properly creating my own theme and extending the relevant template makes it very easy to extend the struts UI elements. I'm not going to post the POJO that this comes from, but it should be obvious that it has a getUser() method that returns a user object, and a getRoles() method that returns List for the application.
<div id="body_div"> <p>Editing <s:property value='user.name'/> (<s:property value='user.userName'/>) <s:form action="save" method="POST"> <s:password label="Password" name="password1" size="10" maxLength='15' /> <s:password label="Verify" name="password2" size="10" maxLength='15' /> <s:hidden name="id" value="id" /> <s:hidden name="form_submit" value="true"/> <s:checkboxlist name="roles" list="roles" listKey="id" label="Roles" listValue="roleDescription"/> <s:submit value="Save"/> </s:form> </div>
Here is the generated HTML for that jsp:
<form id="save" name="save" action="/myaction/save" method="POST"> <table class="wwFormTable"> <tr> <td class="tdLabel"><label for="save_password1" class="label">Password:</label></td> <td><input type="password" name="password1" size="10" maxlength="15" id="save_password1"/></td> </tr> <tr> <td class="tdLabel"><label for="save_password2" class="label">Verify:</label></td> <td> <input type="password" name="password2" size="10" maxlength="15" id="save_password2"/></td> </tr> <input type="hidden" name="id" value="id" id="save_id"/> <input type="hidden" name="form_submit" value="true" id="save_form_submit"/> <tr> <td class="tdLabel"><label for="save_roles" class="label">Roles:</label></td> <td> <input type="checkbox" name="roles" value="1" id="roles-1"/> <label for="roles-1" class="checkboxLabel">Application Administrator with all permissions</label> <input type="checkbox" name="roles" value="1" id="roles-2"/> <label for="roles-2" class="checkboxLabel">Search/View Records</label> <input type="checkbox" name="roles" value="1" id="roles-3"/> <label for="roles-3" class="checkboxLabel">Add Comments to Records</label> <input type="checkbox" name="roles" value="1" id="roles-4"/> <label for="roles-4" class="checkboxLabel">Terminate Records</label> </td> </tr> <tr> <td colspan="2"><div align="right"><input type="submit" id="save_0" value="Save"/> </div></td> </tr> </table></form>
Now, notice that the HTML contains all the checkboxes in one td, with no line breaks when using the xhtml theme. Notice also that the rest of the elements for generating this form use the table rows a little bit differently.
I wanted each checkbox input to be in it's own row, to make the page more readable. The solution to this is fairly simple, once you figure it out. Here is what you do:
Here is the checkboxlist.ftl file from my Struts2 source:
<#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
<#include "/${parameters.templateDir}/simple/checkboxlist.ftl" />
<#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" /><#nt/>Notice that it is simply including the controlheader, the template from the simple theme, and the control footer. The control header and footer add a td tag at the top and bottom of the tag, so I removed those.
Here is what I wound up with:
<#assign itemCount = 0/>
<#if parameters.list??>
<@s.iterator value="parameters.list">
<tr><td>
<#assign itemCount = itemCount + 1/>
<#if parameters.listKey??>
<#assign itemKey = stack.findValue(parameters.listKey)/>
<#else>
<#assign itemKey = stack.findValue('top')/>
</#if>
<#if parameters.listValue??>
<#assign itemValue = stack.findString(parameters.listValue)?default("")/>
<#else>
<#assign itemValue = stack.findString('top')/>
</#if>
<#assign itemKeyStr=itemKey.toString() />
<input type="checkbox" name="${parameters.name?html}" value="${itemKeyStr?html}" id="${parameters.name?html}-${itemCount}"<#rt/>
<#if tag.contains(parameters.nameValue, itemKey)>
checked="checked"<#rt/>
</#if>
<#if parameters.disabled?default(false)>
disabled="disabled"<#rt/>
</#if>
<#if parameters.title??>
title="${parameters.title?html}"<#rt/>
</#if>
<#include "/${parameters.templateDir}/simple/scripting-events.ftl" />
<#include "/${parameters.templateDir}/simple/common-attributes.ftl" />
/>
<label for="${parameters.name?html}-${itemCount}" class="checkboxLabel">${itemValue?html}</label>
</tr></td>
</@s.iterator>
<#else>
</#if>
<#nt/>Note that all I really had to change was adding a table row and cell at the beginning of the iterator of the list to open the new row:
<#if parameters.list??>
<@s.iterator value="parameters.list">
<tr><td>I also had to add the close tags at the end of the iterator to close the row for each item:
</tr></td> </@s.iterator>
Now, one small change to the jsp, and it's done. I needed to tell the new jsp to use my 'checkbox-fix' theme instead of the default. Here is the new jsp tag:
<s:checkboxlist name="roles" theme="checkbox-fix" list="roles" listKey="id" label="Roles" listValue="roleDescription"/>
Here is the resulting HTML for that page:
<tr><td> <input type="checkbox" name="roles" value="1" id="roles-1"/> <label for="roles-1" class="checkboxLabel">Application Administrator with all permissions</label> </tr></td> <tr><td> <input type="checkbox" name="roles" value="1" id="roles-2"/> <label for="roles-2" class="checkboxLabel">Search/View GetPic Records</label> </tr></td> <tr><td> <input type="checkbox" name="roles" value="1" id="roles-3"/> <label for="roles-3" class="checkboxLabel">Add Comments to GetPic Records</label> </tr></td> <tr><td> <input type="checkbox" name="roles" value="1" id="roles-4"/> <label for="roles-4" class="checkboxLabel">Terminate GetPic Records</label> </tr></td>
Works great!