18

I need to display Map using <h:dataTable>. My backing bean has a Map property as below:

public class Bean {

    private Map<Integer,String> map; // +getter

    @PostConstruct
    public void init() {
        map = new TreeMap<Integer,String>();
        map.put(1,"Sasi");
        map.put(2,"Pushparaju");
        map.put(3,"Venkat Raman");
        map.put(3,"Prabhakaran");
    }

}

Then in JSF page I am trying to bind this Map property to the value attribute of <h:dataTable>.

 <h:dataTable border="1" value="#{bean.map}" var="map">
    <h:column id="column1">
        <f:facet name="header">
            <h:outputText value="UserId"></h:outputText>
        </f:facet>
        <h:outputText value="#{map.getKey}"></h:outputText>
    </h:column>
    <h:column id="column2">
        <f:facet name="header">
            <h:outputText value="Email Id"></h:outputText>
        </f:facet>
        <h:outputText value="#{map.getValue}"></h:outputText>
    </h:column>
</h:dataTable>

It is giving en error that getKey and getValue is not present. I can understand that this is not the correct way to do it. How can I present a Map using <h:dataTable>?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
JPS
  • 2,730
  • 5
  • 32
  • 54

3 Answers3

31

Until upcoming JSF 2.3, UIData components such as <h:dataTable>, <p:dataTable>, etc and <ui:repeat> does not support iterating over a Map. This is only supported in <c:forEach>.

One way is to convert the map entries to an array (alone entrySet() won't work as UIData also doesn't support Set until upcoming JSF 2.3).

<h:dataTable value="#{bean.map.entrySet().toArray()}" var="entry">
    <h:column>#{entry.key}</h:column>
    <h:column>#{entry.value}</h:column>
</h:dataTable>

Another way is to wrap the map's entry set in a collection which the <h:dataTable> can iterate over, such as an ArrayList.

public class Bean {

    private Map<Integer, String> map;
    private List<Entry<Integer, String>> entries; // +getter (no setter necessary)

    @PostConstruct
    public void init() {
        map = new TreeMap<>();
        map.put(1, "Sasi");
        map.put(2, "Pushparaju");
        map.put(3, "Venkat Raman");
        map.put(4, "Prabhakaran");
        entries = new ArrayList<>(map.entrySet());
    }

    // ...
}
<h:dataTable value="#{bean.entries}" var="entry">
    <h:column>#{entry.key}</h:column>
    <h:column>#{entry.value}</h:column>
</h:dataTable>

However, more clean, self documenting and reusable is to use a List<User> instead wherein the User class has the necessary properties id and name.

public class Bean {

    private List<User> users; // +getter (no setter necessary)

    @PostConstruct
    public void init() {
        users = new ArrayList<>();
        users.add(new User(1, "Sasi"));
        users.add(new User(2, "Pushparaju"));
        users.add(new User(3, "Venkat Raman"));
        users.add(new User(4, "Prabhakaran"));
    }

    // ...
}
<h:dataTable value="#{bean.users}" var="user">
    <h:column>#{user.id}</h:column>
    <h:column>#{user.name}</h:column>
</h:dataTable>
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Hi BalusC.., `Entry` is a Non `Serializable` Interface. Doesn't affect when using `ViewScoped` Bean. [p.s I'm using `LinkedHashMap`] ? – Kishor Prakash Nov 27 '13 at 13:22
  • @KishorP: Nope. For future unrelated questions and/or more detailed answers, please press "Ask Question" button. – BalusC Nov 27 '13 at 13:25
4

You can try this alternative too.

<h:dataTable border="1" value="#{myBean.map.keySet().toArray()}" var="myVar">
    <h:column id="column1">
        <f:facet name="header">
            <h:outputText value="UserId"></h:outputText>
        </f:facet>
            <h:outputText value="#{myVar}"></h:outputText>
    </h:column>
    <h:column id="column2">
        <f:facet name="header">
            <h:outputText value="Email Id"></h:outputText>
        </f:facet>
            <h:outputText value="#{myBean.map.get(myVar)}"></h:outputText>
    </h:column>
</h:dataTable>
prageeth
  • 7,159
  • 7
  • 44
  • 72
  • 2
    This is very performance inefficient. The EL expression in the `value` attribute is evaluated during every iteration round. So, map keyset is read and converted to an array during every iteration round. And, the `map.get(key)` does unnecessarily a second iteration over the map in order to get the value. So, you've there a nested iteration in an iteration which is executed during every iteration! You should at least use `map.entrySet()` instead and preferably wrap it in a fixed array list. Exactly as the currently accepted answer does. – BalusC Feb 19 '15 at 08:21
4

Regarding to the last answer from prageeth, you may use entrySet instead of keySet; then you can get rid of myBean.map.get. See this example:

<h:dataTable border="1" value="#{myBean.map.entrySet().toArray()}" var="map">
<h:column id="column1">
    <f:facet name="header">
        <h:outputText value="UserId"></h:outputText>
    </f:facet>
        <h:outputText value="#{map.key}"></h:outputText>
</h:column>
<h:column id="column2">
    <f:facet name="header">
        <h:outputText value="Email Id"></h:outputText>
    </f:facet>
        <h:outputText value="#{map.value}"></h:outputText>
</h:column>
</h:dataTable>

This works on myfaces 2.2.3 as I've just used it myself.

Annotation: I'd better had commented the last post, but my reputation is not high enough, therefore this is an extra answer.

Jörg Henke
  • 123
  • 2
  • 10