I have a project that uses JSF and Hibernate. Today, I was trying to use h:dataTable to render a Set. What surprised me was that h:dataTable would only support UIData, which excluded Set. Heavily influenced by Hibernate, my project uses Set quite extensively. It would therefore be very annoying to change every Set into a UIData-supported collection, such as a List.
Solutions found on Google here and here suggest workarounds either using the map hack or custom components. Well, the map hack is a “hack,” so I’m not really a fan of that. Creating custom components is just trying to reinvent the wheel, so I’m also against it.
The solution that I have is simpler. I created a static function, that would convert a Set into an array, and registered it as an EL function. When passing the Set to dataTable, just wrap the Set with the function.
Specifically, I declare my own taglib, in /WEB-INF/functions.taglib.xml:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"><facelet-taglib><namespace>/functions</namespace>
<function><function-name>toArray</function-name>
<function-class>com.ms.design.web.util.ExpressionLanguage
</function-class> <function-signature>java.lang.Object[] toArray(java.util.Collection)
</function-signature> </function></facelet-taglib>Register it in /WEB-INF/web.xml:
<context-param><param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/functions.taglib.xml</param-value>
</context-param>The content of com.ms.design.web.util.ExpressionLanguage is:
package com.ms.design.web.util;
import java.util.Collection;
public class ExpressionLanguage {
@SuppressWarnings("rawtypes")
public static Object[] toArray(Collection collection) {
return collection.toArray();
}}That’s it. I can now use the function like:
<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ms="/functions" xmlns:ui="http://java.sun.com/jsf/facelets">…
<h:dataTable value="#{ms:toArray(projectController.projects)}" var="project" frame="above" rules="all" styleClass="data" headerClass="header" rowClasses="odd,even" summary="A table of projects.">…
So far, it works great. The only potential problem is the performance overhead caused by copying of the data. Fortunately, unlike c:forEach, h:dataTable only attempts to retrieve the value once, prior to rendering. If performance problem does occur, however, just switch to a List, or any other UIData-supported collections.
Update on February 23, 2011:
OpenFaces provides o:dataTable, which iterates any Collection. It would be a good solution, too.