Monday April 24, 2006
Mark A. Basler's Weblog
JSF 1.2 Checkbox in a dataTable populating a list of IDs ...
Recently, I have been working on an application that required a list
of database primary keys (IDs) be returned from a search, so the
items that were selected could be populated in a Google Map.
Returning a list of IDs to operate against is a common requirement and
there a number of ways this could be handled. The one thing that
complicated the use of normal paradigms is that persistence entity
beans were being used and I didn't want to introduce presentation
implementation details in the bean. Since we are using EJB 3.0 and
JSF 1.2, I used the Glassfish
Open Source Application Server to run my tests.
I have read a lot of interesting forum postings were developers are
trying to find a lite weight approach to collecting IDs to operate
against when they are developing using JSF dataTables. These
developers are familiar with the standard HTML checkbox String array
approach and are looking for a similar approach using JSF. Some
of
the write ups state the example usecase where items are selected from a
cart for deletion. Hopefully this investigation will help them
and others.
There are a few different methodologies that can be used to resolve
this problem, the following list are approaches that we investigated:
1) Wrap the entity bean with a class that also exposes the values
("SelectItem")
for the checkbox. Having all the values that are to be captured,
represented in a bean is a standard approach when working with
dataTables. I didn't want to use this approach because it
requires the introduction of a new wrapper object for each page that
used entity beans to back the dataTable.
2) Bind the dataTable to a ManagedBean ("HTMLDataTable") so the
children
can be looped through to manually reconcile the items selected.
This could be done but all that was needed was a list of IDs. I
thought this approach was overkill and I wanted to use something more
lite weight.
3) Send the selected values to a the managed bean using a HTML checkbox
through a
managed property. This is more in line with what I was looking
for, a lite weight approach the I can use in other situations without
introducing a new object or binding to a JSF component.
<h:form id="resultsForm">
<h:dataTable id="results" border="1" value="#{SearchBean.hits}" var="item"
rendered="#{SearchBean.showResults}"
style="border-style:double; width:600px; border-color:darkgreen">
...
<input type="checkbox" name="mapSelectedItems"
value="<h:outputText value='#{item.UID}'/>"/>
...
<h:commandButton action="#{MapBean.findAllByIDs}" id="mapSubmit" type="submit"
value="Map Checked Item(s)" rendered="#{SearchBean.showResults}"/>
...
faces-config.xml
<managed-bean>
<managed-bean-name>MapBean</managed-bean-name>
<managed-bean-class>
com.sun.javaee.blueprints.mapviewer.MapBean
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>items</property-name>
<value>#{paramValues.mapSelectedItems}</value>
</managed-property>
</managed-bean>
// search.jsp
public void setItems(String[] items) {
this.items=items;
}
The only problem I found with this approach is that the Managed Bean has to be in the request scope. If you wanted to put the bean in the session, a ServletException would be thrown stating "The scope of the referenced object: #{paramValues.mapSelectedItems} is shorter than the referring object".
4) Since I wanted the MapBean to be in the session scope so the last map could be re-rendered, I ended up using a derivation of approach 3 and retrieved the IDs using the Expression Language Context to resolve a Value Expression. This allowed the MapBean to be in the session scope and the IDs could be retrieved from the request. One thing to note is that since is it quite possible that the request values may not be present when the bean is being access from other objects, some checking is required to keep the IDs available.
<h:form id="resultsForm">
<h:dataTable id="results" border="1" value="#{SearchBean.hits}" var="item"
rendered="#{SearchBean.showResults}"
style="border-style:double; width:600px;border-color:darkgreen">
...
<input type="checkbox" name="mapSelectedItems"
value="<h:outputText value='#{item.UID}'/>"/>
...<h:commandButton action="#{MapBean.findAllByIDs}" id="mapSubmit" type="submit"
value="Map Checked Item(s)" rendered="#{SearchBean.showResults}"/>
...
<managed-bean>
<managed-bean-name>MapBean</managed-bean-name>
<managed-bean-class>
com.sun.javaee.blueprints.mapviewer.MapBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
public String findAllByIDs() {
// get selected items from search
FacesContext context=FacesContext.getCurrentInstance();
ValueExpression vex=context.getApplication().getExpressionFactory().
createValueExpression(context.getELContext(),"#{paramValues.mapSelectedItems}", String[].class);
String[] itemx=(String[])vex.getValue(context.getELContext());
// since looking up values from request, make sure the values exist before replacing old values
if(itemx != null) {
itemIds=itemx;
}
...
Many thanks to Ed Burns for taking
the time to investigate these scenarios with me. His time and effort is
deeply appreciated.
