2

I have got a problem with my JSF-rendering. A given condition in Expression Language will not be executed in the right way. E.g:

Example 1

<f:param name="cat" value="#{product.category.uri}" rendered="#{product.category.parent.uri == null}" />
<f:param name="cat" value="#{product.category.parent.uri}" rendered="#{product.category.parent.uri != null}" />

Example 2

<c:if test="#{product.category.parent.uri == null}">
    <f:param name="cat" value="#{product.category.uri}" />
</c:if>

<c:if test="#{product.category.parent.uri != null}">
    <f:param name="cat" value="#{product.category.parent.uri}" />
</c:if>

Problem

In both examples, both my parameters will be added to my surrounding h:outputLink. I am not sure what other code to add, so if you guys need anything else in order to help me, I'll be happy to provide it.

Thanks in advance.

Example 3 (on request)

<?xml version='1.0' encoding='UTF-8' ?>

<ui:composition template="./WEB-INF/templates/base.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:c="http://java.sun.com/jsp/jstl/core">
    <ui:define name="content">
        <c:choose>
            <c:when test="#{webshop.productlist.size() > 0}">
                <div id="spacer">
                    <ui:repeat value="#{webshop.productlist}" var="product">
                        <div id="block">
                            <p>
                                <h:outputLink value="product.xhtml">
                                    #{product.name}
                                    <c:choose>
                                        <c:when test="#{product.category.parent.uri == null}">
                                            <f:param name="cat" value="#{product.category.uri}" rendered="" />
                                        </c:when>
                                        <c:otherwise>
                                            <f:param name="cat" value="#{product.category.parent.uri}" />
                                        </c:otherwise>
                                    </c:choose>
                                    <f:param name="product" value="#{product.uri}" />
                                </h:outputLink>
                            </p>
                        </div>
                    </ui:repeat>
                </div>
            </c:when>

            <c:otherwise>
                (...)
            </c:otherwise>
        </c:choose>
    </ui:define>
</ui:composition>

I have cleaned up this example a bit, but the essence is there. I have replaced the first examples by a when/otherwise construction, whether my product.category.parent.uri is null or not, it will give me the first result in this case.

Menno
  • 12,175
  • 14
  • 56
  • 88
  • Nope, and the given .uri is null in fact. – Menno Sep 07 '12 at 18:02
  • In fact you should debug why the product is not in the scope. – Roman C Sep 07 '12 at 18:02
  • why does your EL have the "#" sign? shouldn't it be "$" ? – happymeal Sep 07 '12 at 18:13
  • You could be on to something. The $ statements will be rendered instantly while the # statements will be rendered when needed. Given my product instance is being used within an ui:repeat, the $-statement might fix the problem. I'll report as soon as I have tested it. – Menno Sep 07 '12 at 18:19
  • Example 1 with an EL-statement annotated with $ will result in the same issue, namely two parameters with the name "cat". – Menno Sep 07 '12 at 18:27
  • With the new EL Specification, the use of #{...} and ${...} are both supported and Facelets makes no distinction between the two. The short of it is that you can freely interchange the two with whatever is most familiar to what you're working on. Both syntaxes will be handled the same way and share the same lifecycles and features. – Adrian Mitev Sep 07 '12 at 18:46
  • f:param doesn't support rendered attribute so the first example is not going to work. Could you share some more code? In what parent component your outputLink is nested to? – Adrian Mitev Sep 07 '12 at 18:50
  • As requested, the code has been added. – Menno Sep 07 '12 at 19:02

2 Answers2

10

Your core problem is that you're completely confusing view build time tags and view render time tags.

The view build time is that moment when a XHTML file is to be converted to a JSF component tree as available by FacesContext#getViewRoot(). The view render time is that moment when the JSF component tree is about to produce HTML code, as initiated by UIViewRoot#encodeAll().

All JSTL <c:xxx> tags and all JSF <ui:xxx> tags which do not have a rendered attribute run during view build time. All JSF <ui:xxx> tags which do have a rendered attribute and all JSF <h:xxx> tags run during view render time. So, they don't run in sync as you'd expect from the coding.

Coming back to your concrete problem, this is two-fold:

  1. The <f:param> does as being a tag handler not support the rendered attribute at all.

  2. The #{product} is in your code definied by <ui:repeat var>, which is a view render time tag, but yet you're trying to let JSTL <c:xxx> view build time tags depend on that. This will of course not work. The #{product} is null during the view build time, simply because the <ui:repeat> hasn't run at that moment.

Your concrete problem can only be solved by using the view build time tag <c:forEach> instead of the view render time tag <ui:repeat> to iterate over products.

<c:forEach items="#{webshop.productlist}" var="product">

See also


Unrelated to the concrete problem, the follwing clumsy block

<c:choose>
    <c:when test="#{product.category.parent.uri == null}">
        <f:param name="cat" value="#{product.category.uri}" rendered="" />
    </c:when>
    <c:otherwise>
        <f:param name="cat" value="#{product.category.parent.uri}" />
    </c:otherwise>
</c:choose>

can be replaced by the following simpler approach with help of the conditional operator in EL:

<f:param name="cat" value="#{empty product.category.parent.uri ? product.category.uri : product.category.parent.uri}" />

This way you must be able to keep using the <ui:repeat>.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Ofcourse, I have just recently done some research on the JSF lifecycle, yet I have managed to make such a 'stupid' mistake. BalusC, thanks once again for your great help! – Menno Sep 07 '12 at 19:48
  • @BalusC: So if some faclets (ui tags) and h tags belong to the view render time, to what belong the composite and rich (richfaces) tags? – feder Aug 19 '13 at 22:39
  • @BalusC It must be a taghandler, the way it behaves. So custom tags always run view build time? Well, how do you create then custom tags that run in view render time (so, that is after view build time)? – feder Aug 19 '13 at 22:55
  • @feder: please don't confuse components with taghandlers. – BalusC Aug 19 '13 at 23:36
  • @BalusC I don't. So here is one of the two answers. Custom tags belong to taghandlers as assumed: http://balusc.blogspot.ch/2011/09/communication-in-jsf-20.html#ViewScopedFailsInTagHandlers – feder Aug 20 '13 at 05:46
  • @feder: as said here and many other places: taghandlers run during view build time (so also custom taghandlers). UI components run during view render time (so also custom components). – BalusC Aug 20 '13 at 09:26
0

According to the documentation, there is no rendered attribute available for the f:param tag so it is being ignored.

See for JSF <2.x - http://docs.oracle.com/javaee/5/javaserverfaces/1.2/docs/tlddocs/f/param.html

See for JSF >2.0 - http://javaserverfaces.java.net/nonav/docs/2.0/pdldocs/facelets/f/param.html

Now that may suggest that example 2 should still work, so I might question whether or not product.category.parent.uri is really null as opposed to empty (i.e. blank string). Have you tried to test for empty (checks for null and empty string)?

<c:if test="#{empty product.category.parent.uri}">  
<f:param name="cat" value="#{product.category.uri}" />  
</c:if>  

<c:if test="#{!empty product.category.parent.uri}">  
<f:param name="cat" value="#{product.category.parent.uri}" />
</c:if>

The other alternative, while not ideal, is to conditionally render two separate output links and determine which to render based on your value. Such as:

<c:choose> 
  <c:when test="#{empty product.category.parent.uri}">
     <h:outputLink value="product.xhtml">
       <f:param name="cat" value="#{product.category.uri}"/>
       <f:param name="product" value="#{product.uri}" />
     </h:outputLink>
  </c:when>
  <c:otherwise>
     <h:outputLink value="product.xhtml">
        <f:param name="cat" value="#{product.category.parent.uri}" />
        <f:param name="product" value="#{product.uri}" />
     </h:outputLink>
  </c:otherwise>
</c:choose>
Bionic_Geek
  • 536
  • 4
  • 24
  • I should add that according to the 2.0 docs it looks like there is 'disabled' attribute, so if you're using JSF 2.0 or above you might be able to accomplish your original goal with that. I don't have a sandbox available to me to test that out at the moment, but thought it was worth mentioning... – Bionic_Geek Sep 07 '12 at 19:35
  • I'll give both suggestions a try! – Menno Sep 07 '12 at 19:38
  • Save yourself the effort. None of both will solve your particular problem. Bionic_Geek is itself not understanding the concrete problem at all and just guessing the answer without even having tested/experimented the use case itself. – BalusC Sep 07 '12 at 19:39
  • Why? I agree with your point on render time issue, but merging the two answers we've provided together, with a proper loop over the data the parameter choice issue is the same. Even with a c:ForEach tag you're still not going to have a rendered attribute on a f:param. And it's worth mentioning that the code snippet I put there is not including the rest of the facelets tags which makes it outside the scope of the main issue you were talking about. But I do appreciate the unconstructive flame in your comments. – Bionic_Geek Sep 07 '12 at 19:42