Use Custom Functions to Render Set with h:dataTable

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:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE facelet-taglib PUBLIC
  3. 	"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  4. 	"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
  5. <facelet-taglib>
  6.     <namespace>/functions</namespace>
  7. 	<function>
  8. 		<function-name>toArray</function-name>
  9. 		<function-class>
  10. 			com.ms.design.web.util.ExpressionLanguage
  11. 		</function-class>
  12. 		<function-signature>
  13. 			java.lang.Object[] toArray(java.util.Collection)
  14. 		</function-signature>
  15. 	</function>
  16. </facelet-taglib>

Register it in /WEB-INF/web.xml:

  1. <context-param>
  2. 	<param-name>facelets.LIBRARIES</param-name>
  3. 	<param-value>/WEB-INF/functions.taglib.xml</param-value>
  4. </context-param>

The content of com.ms.design.web.util.ExpressionLanguage is:

  1. package com.ms.design.web.util;
  2.  
  3. import java.util.Collection;
  4.  
  5. public class ExpressionLanguage {
  6.  
  7. 	@SuppressWarnings("rawtypes")
  8. 	public static Object[] toArray(Collection collection) {
  9. 		return collection.toArray();
  10. 	}
  11.  
  12. }

That’s it. I can now use the function like:

  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml"
  3. 	xmlns:h="http://java.sun.com/jsf/html"
  4. 	xmlns:f="http://java.sun.com/jsf/core"
  5. 	xmlns:ms="/functions"
  6. 	xmlns:ui="http://java.sun.com/jsf/facelets">
  7.  
  8.  
  9. 		<h:dataTable value="#{ms:toArray(projectController.projects)}"
  10. 			var="project" frame="above" rules="all" styleClass="data"
  11. 			headerClass="header" rowClasses="odd,even"
  12. 			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.