Dynamically generate h:column based on list of hashmaps

In my application I want to display a <h:dataTable> with managed bean properties. Currently this table is created from a List<Folder>. Now I want to change the Folder to something more dynamic. That's because I don't want to change the Folder class if I decide to add another field later. I would just have to add another entry in the Map<String, Object> instead of introducing a new field in Folder.

So, is it possible to bind a List<Map<String, Object>> to the <h:dataTable>?


Solution 1:

Is it possible to bind a List of HashMaps to the jsf component h:dataTable?

That's only possible if you generate the necessary <h:column> tags with a view build time tag such as JSTL <c:forEach>.

Here's a concrete kickoff example, assuming that your environment supports EL 2.2:

<h:dataTable value="#{bean.listOfMaps}" var="map">
    <c:forEach items="#{bean.listOfMaps[0].keySet().toArray()}" var="key">
        <h:column>
            #{map[key]}
        </h:column> 
    </c:forEach>
</h:dataTable>

(if your environment doesn't support EL 2.2, you'd need to provide another getter which returns the map key set as a String[] or List<String>; also keep in mind that a HashMap is by nature unordered, you might want to use LinkedHashMap instead to maintain insertion order)

When you're using Mojarra version older than 2.1.18, the disadvantage is that the #{bean} has to be request scoped (not view scoped). Or at least, the <c:forEach items> should refer a request scoped bean. A view scoped bean would otherwise be recreated on every single HTTP request as the <c:forEach> runs during view build time, when the view scope isn't availabe yet. If you absolutely need a view scoped bean for the <h:dataTable>, then you can always create a separate request scoped bean exclusively for <c:forEach items>. The solution would be to upgrade to Mojarra 2.1.18 or newer. For some background information, see also JSTL in JSF2 Facelets... makes sense?

JSF component libraries such as PrimeFaces may offer a <x:columns> tag which makes this more easy, such as <p:dataTable> with <p:columns>.

<p:dataTable value="#{bean.listOfMaps}" var="map">
    <p:columns value="#{bean.listOfMaps[0].keySet().toArray()}" var="key">
        #{map[key]}
    </p:columns>
</p:dataTable>