Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using EL variable of <c:set> inside Facelets <ui:repeat> tags

I have a Home. Each Home has a list of Rooms. Each Room has zero or more Persons.

I would like to count total persons of each home. But I can't add a new variable to record person count in any backing beans or entity. So I'd like to count it in the view only via <c:set>.

My first attempt look like:

<c:set var="personCount" value="${0}" />
<ui:repeat var="home" value="#{mybackingBean.homes}">
    <ui:repeat var="room" value="#{home.rooms}">
        ${personCount += room.persons.size()}
    </ui:repeat>
    <h:panelGrid columns="2">
        <h:outputLabel value="#{home.id}" />
        <h:outputLabel value="#{personCount}" />
    </h:panelGrid>
</ui:repeat>

How can I achieve it?

like image 862
Cataclysm Avatar asked Sep 04 '15 04:09

Cataclysm


1 Answers

There are several mistakes.


First,

${personCount += room.persons.size()}

In EL, the += is the string concatenation operator. So, this will give you basically the result of String.valueOf(personCount) + String.valueOf(size). If the count is initialized to 0 and there are 3 persons, then this will print 03.

You basically want to re-assign the result of personCount + size back to variable personCount. You need to re-set the variable using <c:set> as below.

<c:set var="personCount" value="${personCount + room.persons.size()}" />

Second,

<c:set var="personCount" value="${0}" />
<[for each home]>
    <[for each room]>
        <c:set var="personCount" value="${personCount + room.persons.size()}" />

The personCount is initialized with 0 outside both iterations. So, for each home you'll this way count the persons of all previous homes as well. You want to declare it inside the first iteration.

<[for each home]>
    <c:set var="personCount" value="${0}" />
        <[for each room]>
            <c:set var="personCount" value="${personCount + room.persons.size()}" />

This is not EL related. This is just a logic error which would cause the same problem in "plain vanilla" Java as well.


Third,

JSTL tags like <c:set> run during view build time (during conversion from XHTML template to JSF component tree). JSF components like <ui:repeat> run during view render time (during conversion from JSF component tree to HTML output). So, the <c:set> doesn't run during <ui:repeat> iteration. Thus, it cannot see the iteration variable var. You basically need to iterate during view build time as well. You can use JSTL <c:forEach> for this.

So, all in all, your intented solution look like this:

<c:forEach items="#{bean.homes}" var="home">
    <c:set var="personCount" value="${0}" />
    <c:forEach items="#{home.rooms}" var="room">
        <c:set var="personCount" value="${personCount + room.persons.size()}" />
    </c:forEach>

    <h:panelGrid columns="2">
        <h:outputLabel value="#{home.id}" />
        <h:outputLabel value="#{personCount}" />
    </h:panelGrid>
</c:forEach>

For an in depth explanation, see also JSTL in JSF2 Facelets... makes sense?


However, as the += operator apparently doesn't cause an EL exception in your case, this means that you're already using EL 3.0. This in turn means that you can use the new EL 3.0 lambda and stream features.

<ui:repeat value="#{bean.homes}" var="home">
    <h:panelGrid columns="2">
        <h:outputLabel value="#{home.id}" />
        <h:outputLabel value="#{home.rooms.stream().map(room -> room.persons.size()).sum()}" />
    </h:panelGrid>
</ui:repeat>

No need for a counting loop anymore. No, this does not require Java 8, it works on Java 7 as good.

like image 96
BalusC Avatar answered Oct 14 '22 16:10

BalusC