JSTL in JSF2 Facelets... makes sense?

94,318

Solution 1

Introduction

JSTL <c:xxx> tags are all taghandlers and they are executed during view build time, while JSF <h:xxx> tags are all UI components and they are executed during view render time.

Note that from JSF's own <f:xxx> and <ui:xxx> tags only those which do not extend from UIComponent are also taghandlers, e.g. <f:validator>, <ui:include>, <ui:define>, etc. The ones which extend from UIComponent are also JSF UI components, e.g. <f:param>, <ui:fragment>, <ui:repeat>, etc. From JSF UI components only the id and binding attributes are also evaluated during view build time. Thus the below answer as to JSTL lifecycle also applies to the id and binding attributes of JSF components.

The view build time is that moment when the XHTML/JSP file is to be parsed and converted to a JSF component tree which is then stored as UIViewRoot of the FacesContext. The view render time is that moment when the JSF component tree is about to generate HTML, starting with UIViewRoot#encodeAll(). So: JSF UI components and JSTL tags doesn't run in sync as you'd expect from the coding. You can visualize it as follows: JSTL runs from top to bottom first, producing the JSF component tree, then it's JSF's turn to run from top to bottom again, producing the HTML output.

<c:forEach> vs <ui:repeat>

For example, this Facelets markup iterating over 3 items using <c:forEach>:

<c:forEach items="#{bean.items}" var="item">
    <h:outputText id="item_#{item.id}" value="#{item.value}" />
</c:forEach>

...creates during view build time three separate <h:outputText> components in the JSF component tree, roughly represented like this:

<h:outputText id="item_1" value="#{bean.items[0].value}" />
<h:outputText id="item_2" value="#{bean.items[1].value}" />
<h:outputText id="item_3" value="#{bean.items[2].value}" />

...which in turn individually generate their HTML output during view render time:

<span id="item_1">value1</span>
<span id="item_2">value2</span>
<span id="item_3">value3</span>

Note that you need to manually ensure the uniqueness of the component IDs and that those are also evaluated during view build time.

While this Facelets markup iterating over 3 items using <ui:repeat>, which is a JSF UI component:

<ui:repeat id="items" value="#{bean.items}" var="item">
    <h:outputText id="item" value="#{item.value}" />
</ui:repeat>

...already ends up as-is in the JSF component tree whereby the very same <h:outputText> component is during view render time being reused to generate HTML output based on current iteration round:

<span id="items:0:item">value1</span>
<span id="items:1:item">value2</span>
<span id="items:2:item">value3</span>

Note that the <ui:repeat> as being a NamingContainer component already ensured the uniqueness of the client ID based on iteration index; it's also not possible to use EL in id attribute of child components this way as it is also evaluated during view build time while #{item} is only available during view render time. Same is true for an h:dataTable and similar components.

<c:if>/<c:choose> vs rendered

As another example, this Facelets markup conditionally adding different tags using <c:if> (you can also use <c:choose><c:when><c:otherwise> for this):

<c:if test="#{field.type eq 'TEXT'}">
    <h:inputText ... />
</c:if>
<c:if test="#{field.type eq 'PASSWORD'}">
    <h:inputSecret ... />
</c:if>
<c:if test="#{field.type eq 'SELECTONE'}">
    <h:selectOneMenu ... />
</c:if>

...will in case of type = TEXT only add the <h:inputText> component to the JSF component tree:

<h:inputText ... />

While this Facelets markup:

<h:inputText ... rendered="#{field.type eq 'TEXT'}" />
<h:inputSecret ... rendered="#{field.type eq 'PASSWORD'}" />
<h:selectOneMenu ... rendered="#{field.type eq 'SELECTONE'}" />

...will end up exactly as above in the JSF component tree regardless of the condition. This may thus end up in a "bloated" component tree when you have many of them and they are actually based on a "static" model (i.e. the field does not ever change during at least the view scope). Also, you may run into EL trouble when you deal with subclasses with additional properties in Mojarra versions before 2.2.7.

<c:set> vs <ui:param>

They are not interchangeable. The <c:set> sets a variable in the EL scope, which is accessible only after the tag location during view build time, but anywhere in the view during view render time. The <ui:param> passes an EL variable to a Facelet template included via <ui:include>, <ui:decorate template>, or <ui:composition template>. Older JSF versions had bugs whereby the <ui:param> variable is also available outside the Facelet template in question, this should never be relied upon.

The <c:set> without a scope attribute will behave like an alias. It does not cache the result of the EL expression in any scope. It can thus perfectly fine be used inside for example iterating JSF components. Thus, e.g. below will work fine:

<ui:repeat value="#{bean.products}" var="product">
    <c:set var="price" value="#{product.price}" />
    <h:outputText value="#{price}" />
</ui:repeat>

It's only not suitable for e.g. calculating the sum in a loop. For that instead use EL 3.0 stream:

<ui:repeat value="#{bean.products}" var="product">
    ...
</ui:repeat>
<p>Total price: #{bean.products.stream().map(product->product.price).sum()}</p>

Only, when you set the scope attribute with one of allowable values request, view, session, or application, then it will be evaluated immediately during view build time and stored in the specified scope.

<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />

This will be evaluated only once and available as #{dev} throughout the entire application.

Use JSTL to control JSF component tree building

Using JSTL may only lead to unexpected results when being used inside JSF iterating components such as <h:dataTable>, <ui:repeat>, etc, or when JSTL tag attributes depend on results of JSF events such as preRenderView or submitted form values in the model which aren't available during view build time. So, use JSTL tags only to control flow of JSF component tree building. Use JSF UI components to control flow of HTML output generation. Do not bind the var of iterating JSF components to JSTL tag attributes. Do not rely on JSF events in JSTL tag attributes.

Anytime you think you need to bind a component to the backing bean via binding, or grab one via findComponent(), and create/manipulate its children using Java code in a backing bean with new SomeComponent() and what not, then you should immediately stop and consider using JSTL instead. As JSTL is also XML based, the code needed to dynamically create JSF components will become so much better readable and maintainable.

Important to know is that Mojarra versions older than 2.1.18 had a bug in partial state saving when referencing a view scoped bean in a JSTL tag attribute. The whole view scoped bean would be newly recreated instead of retrieved from the view tree (simply because the complete view tree isn't available yet at the point JSTL runs). If you're expecting or storing some state in the view scoped bean by a JSTL tag attribute, then it won't return the value you expect, or it will be "lost" in the real view scoped bean which is restored after the view tree is built. In case you can't upgrade to Mojarra 2.1.18 or newer, the work around is to turn off partial state saving in web.xml like below:

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>false</param-value>
</context-param>

See also:

To see some real world examples where JSTL tags are helpful (i.e. when really properly used during building the view), see the following questions/answers:


In a nutshell

As to your concrete functional requirement, if you want to render JSF components conditionally, use the rendered attribute on the JSF HTML component instead, particularly if #{lpc} represents the currently iterated item of a JSF iterating component such as <h:dataTable> or <ui:repeat>.

<h:someComponent rendered="#{lpc.verbose}">
    ...
</h:someComponent>

Or, if you want to build (create/add) JSF components conditionally, then keep using JSTL. It's way much better than verbosely doing new SomeComponent() in java.

<c:if test="#{lpc.verbose}">
    <h:someComponent>
        ...
    </h:someComponent>
</c:if>

See also:

Solution 2

use

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>

Solution 3

For switch-like output, you can use the switch face from PrimeFaces Extensions.

Share:
94,318

Related videos on Youtube

Jan
Author by

Jan

Updated on December 18, 2020

Comments

  • Jan
    Jan over 3 years

    I would like to output a bit of Facelets code conditionally.

    For that purpose, the JSTL tags seem to work fine:

    <c:if test="${lpc.verbose}">
        ...
    </c:if>
    

    However, I'm not sure if this is a best practice? Is there another way to achieve my goal?

  • Jan
    Jan almost 14 years
    Thx, great answer. More in general: Do JSTL tags still make sense or should we consider them as deprecated since JSF 2.0?
  • Bozho
    Bozho almost 14 years
    In most cases, yes. But sometimes it is appropriate to use them
  • Aklin
    Aklin over 11 years
    however rendered attribute of JSF components or anything in JSF does not provide a nicer way to render one out of several possibilities based on a (switch case scenario) condition(as could be implemented using JSTL's c:choose). So when you need to render one out of several possibilities there is nothing good in JSF..
  • BalusC
    BalusC over 11 years
    @Aklin: No? How about this example?
  • Webel IT Australia - upvoter
    Webel IT Australia - upvoter almost 11 years
    Just updating from BalusC's comment at bottom of java.net/jira/browse/JAVASERVERFACES-1492 "balusc 13/Apr/13 .. I can't reproduce this problem on 2.1 anymore since 2.1.18. Since this version, taghandlers started to work fine together with view scoped beans. I'm not sure which 2.1.18 issue exactly fixed it for 2.1 as well (note that this ticket was targeted at 2.2)."
  • Tiny
    Tiny over 9 years
    I cannot interpret the first paragraph properly for a long time (the examples given are very clear though). Hence, I am leaving this comment as the only way. By that paragraph, I am in the impression that <ui:repeat> is a tag handler (because of this line, "Note that JSF's own <f:xxx> and <ui:xxx>...") just like <c:forEach> and hence, it is evaluated at view build time (again just like just like <c:forEach>). If it is that then, there should not be any visible, functional difference between <ui:repeat> and <c:forEach>? I do not get what exactly that paragraph means :)
  • BalusC
    BalusC over 9 years
    @Tiny UIRepeat extends UIComponent and supports rendered attribute. So, it's not a taghandler.
  • Tiny
    Tiny over 9 years
    Sorry, I will not pollute this post any further. I took your previous comment into my attention but doesn't this sentence, "Note that JSF's own <f:xxx> and <ui:xxx> tags which do not extend UIComponent are also tag handlers." attempts to imply that <ui:repeat> is also a tag handler because <ui:xxx> also includes <ui:repeat>? This should then mean that <ui:repeat> is one of the components in <ui:xxx> that extends UIComponent. Hence, it is not a tag handler. (Some of them may not extend UIComponent. Hence, they are tag handlers) Does it?
  • BalusC
    BalusC over 9 years
    @Tiny: only those which do not extend UIComponent, such as <f:ajax> and <ui:include>.
  • noup
    noup about 9 years
    @BalusC would you also recommend this option as an alternative to c:choose when implementing an if-elseif*-else? If you have something like (in pseudo-code) when C1 then R1, when C2 then R2, otherwise R3 it would become `R1 rendered="C1", R2 rendered="not C1 and C2", R3 rendered="not C1 and not C2 and C3". Maybe we could also use nested ui:fragments but both solutions seem rather developer-unfriendly.
  • BalusC
    BalusC about 9 years
    @noup: <c:choose> is in that construct already not possible for the reaons explained in above answer. However, it's possible in a fixed grid construct.
  • Farhan Shirgill Ansari
    Farhan Shirgill Ansari about 8 years
    @BalusC: I understood it perfectly except one thing, when you're using JSTL tag inside a JSF UI component. An extract from the above answer - <ui:repeat id="items" value="#{bean.items}" var="item">. You said, #{item} is only available during view render time. For this <ui:repeat value="#{bean.products}" var="product"> <c:set var="totalPrice" value="#{totalPrice + product.price}" /> </ui:repeat>, how was product.price able to reflect the correct price in iteration. Moreever <p>Total price: #{totalPrice}</p> doesn't gives the consolidated price as mention in your snippet above.
  • BalusC
    BalusC about 8 years
    @Shirgill: you're right, the snippet was wrong. I corrected the answer.
  • Farhan Shirgill Ansari
    Farhan Shirgill Ansari about 8 years
    @BalusC: I won't pollute it any further. But the snippet you wrote in that, there is a ui:repeat component which is executed at view render time, whereas the c:set nested inside the ui:repeat is executed at view build time. And since #{product} won't be available at view build time, how was c:set able to set the value <c:set var="price" value="#{product.price}" />. Request you to please elucidate. I shall be highly grateful to you.
  • BalusC
    BalusC about 8 years
    @Shirgill: <c:set> without scope creates an alias of the EL expression instead of setting the evaluated value in target scope. Try scope="request" instead, which will immediately evaluate the value (during view build time indeed) and set it as request attribute (which won't be "overwritten" during iteration). Under the covers, it creates and sets a ValueExpression object.
  • Kukeltje
    Kukeltje about 7 years
    Never mind, problem is related to stackoverflow.com/questions/10780858/… (I think) but using 'boolean'
  • Rober2D2
    Rober2D2 over 6 years
    Using h:panelGroup is a dirty solution, because it generates a <span> tag, while c:if adds nothing to the html code. h:panelGroup is also problematic inside panelGrids, as it groups the elements.
  • K.Nicholas
    K.Nicholas over 5 years
    The example for <c:forEach> vs. <ui:repeat> seems broken because by reasoning the bean.items has not been created yet during the view restore (build) phase for the <c:forEach> case. When I tried testing it I also got an error: javax.faces.view.facelets.FaceletException: javax/servlet/jsp/jstl/core/LoopTagStatus. The explanation claims it will produce HTML output but from what I can tell it creates an exception.
  • BalusC
    BalusC over 5 years
    @K.Nicholas: It's under the covers a ClassNotFoundException. Runtime dependencies of your project are broken. Most likely you're using a non-JavaEE server such as Tomcat and you forgot to install JSTL, or you have accidentally included both JSTL 1.0 and JSTL 1.1+. Because in JSTL 1.0 the package is javax.servlet.jstl.core.* and since JSTL 1.1 this has become javax.servlet.jsp.jstl.core.*. Clues for installing JSTL can be found here: stackoverflow.com/a/4928309
  • K.Nicholas
    K.Nicholas over 5 years
    @BalusC - okay, yes, that fixed it. Tx.