Setting ui:param conditionally
Asked Answered
A

2

9

I want to set a ui:param depending on a bean value and I thought using c:if was a good idea. So I put in my page the following code:

<ui:composition 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"
    xmlns:wai="http://www.id.ethz.ch/wai/jsf"
    template="/view/listView.xhtml">

        <c:if test="#{subscriptionListController.model.listViewName eq 'mySubscriptions'}">
         <ui:param name="title" value="#{msg.subscriptionTitleMySubscriptions}"/>
        </c:if>
        <c:if test="#{subscriptionListController.model.listViewName eq 'paidSubscriptions'}">
         <ui:param name="title" value="#{msg.subscriptionTitlePaidSubscriptions}"/>
        </c:if>
        <c:if test="#{subscriptionListController.model.listViewName eq 'allSubscriptions'}">
         <ui:param name="title" value="#{msg.subscriptionTitleAllSubscriptions}"/>
        </c:if>
        ....

but the parameter is not set...

If I let print out the value of #{subscriptionListController.model.listViewName eq 'mySubscriptions'} I get true in the corresponding case and false in the other two cases.

At the beginning I had only 2 possibilities and solved it with the ternary operator:

<ui:param name="title" value="#{subscriptionListController.model.listViewName eq 'mySubscriptions' ? msg.subscriptionTitleMySubscriptions : msg.subscriptionTitlePaidSubscriptions}"/>

and it worked. But now I have more possibilities...

What am I doing wrong?

Arachnid answered 4/12, 2013 at 10:10 Comment(4)
You're not clear on when exactly it works/fails. The <c:if>s look fine, but <c:otherwise> is completely misplaced. It belongs in a <c:choose>. Also the code seems to be incomplete, this appears to be a template client, but I'm nowhere seeing an <ui:define>. You do know that anything outside <ui:define> and <ui:composition> is ignored by Facelets?Sundin
The <ui:composition> is there (see the first row of the code) so it shouldn't be ignored. I removed the <c:otherwise> and added a c:if instead. but it still doesn't work... As said, with the ternary operator no-problems, but with the c:if nope... (I edited my code, as the value for the title was wrong)Arachnid
@ BalusC Ok, the <ui:define> is defined below. I shifted the c:ifcode there and now it works! But can you now explain me why with the tern. op. works although it is outside the <ui:define>? Besides I have others ui:param defined outside it (just below the c:if) and they work...Arachnid
@Sundin Can you please put your comment into an answer, so that I can accept it, please?Arachnid
S
12

As indicated by <ui:composition template>, this page represents a template client.

Any <ui:param> outside <ui:define> applies to the master template (the file which you declared in template attribute) and is ignored inside the template client itself. If you intend to prepare variables for inside the template client, you should put <ui:param> inside <ui:define>.

But there's another thing: the original purpose of <ui:param> is to pass variables to the file referenced by <ui:composition template>, <ui:decorate template> or <ui:include src>, not to prepare/set variables inside the current facelet context. For the sole functional requirement of preparing/setting variables in the current EL context, you'd better be using JSTL <c:set> for the job. You can use <ui:param> for this, but this isn't its original intent and didn't work that way in older MyFaces versions.

Thus, so:

<ui:define>
    <c:if test="#{subscriptionListController.model.listViewName eq 'mySubscriptions'}">
        <c:set var="title" value="#{msg.subscriptionTitleMySubscriptions}"/>
    </c:if>
    <c:if test="#{subscriptionListController.model.listViewName eq 'paidSubscriptions'}">
        <c:set var="title" value="#{msg.subscriptionTitlePaidSubscriptions}"/>
    </c:if>
    <c:if test="#{subscriptionListController.model.listViewName eq 'allSubscriptions'}">
        <c:set var="title" value="#{msg.subscriptionTitleAllSubscriptions}"/>
    </c:if>
    ...
</ui:define>

Unrelated to the concrete problem, you can optimize this as follows without the need for an unmaintainable <c:if> group which would only grow with every subscription type:

<ui:define>
    <c:set var="subscriptionTitleKey" value="subscriptionTitle.#{subscriptionListController.model.listViewName}">
    <c:set var="title" value="#{msg[subscriptionTitleKey]}"/>
    ...
</ui:define>

with those keys

subscriptionTitle.mySubscriptions = Title for my subscriptions
subscriptionTitle.paidSubscriptions = Title for paid subscriptions
subscriptionTitle.allSubscriptions = Title for all subscriptions
Sundin answered 4/12, 2013 at 13:39 Comment(3)
"... is in turn technically wrong and works in Mojarra only, but not in MyFaces ..." That's not true. Instead, it is the opposite. It is a looooonnngggg story, but I have written the reasons in MYFACES-3169. Recently in MYFACES-3810 it was added in 2.2.x branch a flag to enable the old behavior. ui:param only works if is a direct child of ui:include, ui:decorate or ui:composition but the params are available in the context of the target template. Use them inside c:if is a bad idea.Intermission
@lu: by the way I also agree that we should really keep the <c:set>. Its ability to store the evaluated value in a fixed scope (request/view/session/application) is very appreciated. The <ui:param> doesn't support it (expression is re-evaluated on every access) and also the tag name "param" is at its own not really self-documenting as to the functional requirement of "setting a variable in the EL context".Sundin
absolutely, c:set is indeed very useful. What we did in MyFaces was fix c:set to use page scope when no scope is set like in JSP and create a "template context" to store ui:param variables. Both c:set and ui:param still relies on EL VariableMapper facility and are considered build view time tags. Without this fix, some optimizations like EL caching cannot be done properly. Really ui:param is a tag used to pass a parameter as an EL Expression to the template.Intermission
G
2

You are using JSTL with Facelets. JSTL are executed during view build time, and not in render phase. Additionally there some issues with processing them in JSF2 libraries - like in older Mojarra versions, where they didn't work on view scoped beans with partial state saving - see https://stackoverflow.com/a/3343681). This is why your EL expression has worked.

The solution is to avoid JSTL - use ui:repeat instead c:forEach and EL expression and conditional rendering instead of c:if.

Gonsalez answered 4/12, 2013 at 10:17 Comment(15)
Thank you for your explanation, but how can I use conditional rendering with ui:param?Arachnid
@Arachnid primefaces components, as well as ui:fragment have 'rendered' attribute, which takes EL expression.Gonsalez
Ok, I see the ui:param has the rendered attribute too (I didn't thought...)Arachnid
Mmm, now I have <ui:param name="title" value="#{msg.subscriptionTitleMySubscriptions}" rendered="#{subscriptionListController.model.listViewName eq 'mySubscriptions'}"/> (three times for the different possibilities), but it doesn't work. Now the last of the three is always choosen...Arachnid
I also tried to specify true, false, false instead of the checking EL, but although the last is false, it is the one which is "rendered"Arachnid
@Arachnid ui:param doesn't have rendered attribute. General advice: if you need to have more complicated expression in EL, consider creating a method in bean that would return what you need.Gonsalez
I tried with <ui:fragment rendered="#{subscriptionListController.model.listViewName eq 'mySubscriptions'}"> <ui:param name="title" value="msg.subscriptionTitleMySubscriptions" rendered="#{subscriptionListController.model.listViewName eq 'mySubscriptions'}"/> </ui:fragment> but it doesn't work too... Sorry but now I don't have any other ideas/jsf-knowledge to solve the problem using your tip to use EL and cond. rendering to replace c:if...Arachnid
I will remove the downvote if you explain "many bugs" in detail. Usually, those are namely just developer's own mistakes/misunderstandings. In the meanwhile, perhaps you want to read #3343484 first.Sundin
@Sundin Sorry BalusC, but I don't understand what do you mean with the first two sentences.Arachnid
Comment is targeted on answer of web devie not on your question.Sundin
@Sundin actually, I've seen that post, well, maybe it's not a bug but issue, anyway I've seen also somewhere in your answer that you've opted against using JSTL.Gonsalez
@Sundin Ah, ok :) Any tip on how to solve the problem? What I need is really just a way to choose between three possibilities... :(Arachnid
Just start giving feedback on my comment on your question.Sundin
@Sundin One think is not clear to me... In your answer(the linked one) you said ...The view build time is that moment when the XHTML/JSP file is to be parsed and converted to a JSF component tree ... and then Use JSTL tags to control flow of JSF component tree building.... But what I did in my (original) question is not that? The c:if doesn't "control/say" which of the three possibilities should be in the JSF component tree?Arachnid
I didn't mean to give feedback on my comment on this answer, but on your own question. Scroll back to top to your question. Look at the bottom of your question. There's a comment on that. Give feedback to that. All comments on a certain post are targeted on the poster of that post, not to someone else.Sundin

© 2022 - 2024 — McMap. All rights reserved.