Search code examples
jsfprimefacesconditional-rendering

Conditionally render depend on p:selectOneMenu value


I'm trying to build a custom component from existing Primefaces components and I need some help here. I have a selectOneMenu and I want it to render or not (and disable or enable) other components according to which option is selected in the menu. The hard thing here is that I can't do it using a Managed Bean (I have a few reasons for that), I need a pure xhtml code.

I tried some <c:choose> and <ui:parameter> stuff to create booleans but for some reason that I can't see it's not working. Could you guys take a look at my code and see if you have any ideas? It may be something simple that I can't figure or something I don't know yet.

<h:body>
<h:form id="abc">
    <ui:repeat var="pd" value="#{produtoMB.produtos}">
        <h:panelGroup id="linha">
        <c:choose>
            <c:when test="#{pd.marca == X1}">
                <c:set var="render" value="#{true}" />
            </c:when>
            <c:otherwise>
                <c:set var="render" value="#{false}" />
            </c:otherwise>
        </c:choose>

        <p:selectOneMenu value="#{pd.marca}">
            <f:selectItem itemLabel="Select One" itemValue="" noSelectionOption="true"/>
            <f:selectItem itemLabel="Xbox One" itemValue="X1" />
            <f:selectItem itemLabel="PlayStation 4" itemValue="PS4" /> 
            <f:selectItem itemLabel="Wii U" itemValue="WU" />
            <p:ajax update="linha" />
        </p:selectOneMenu>
        <p:inputText value="#{pd.aparelho}" disabled="#{render}"/>
        <h:outputText value="Microsoft" rendered="#{render}"/>
        <p:commandButton value="X" />
        </h:panelGroup>
        <br />
    </ui:repeat>
    <br />
    <p:commandButton actionListener="#{produtoMB.botaoMais}" value="+" update="abc"/>
    <p:commandButton actionListener="#{produtoMB.botaoMenos}" value="-" update="abc"/>
</h:form>


Solution

  • There are a couple of issues here.

    First,

    <ui:repeat ...>
        <c:choose>
            ...
        </c:choose>
    </ui:repeat>
    

    JSTL tags run during view build time. It's executed only once before all JSF component runs. So, on contrary to what you'd intuitively expect from the XML code flow, it isn't executed when the <ui:repeat> component runs and iterates. See also JSTL in JSF2 Facelets... makes sense?

    Second,

    <c:when test="#{pd.marca == X1}">
    ...
    <f:selectItem ... itemValue="X1" />
    

    The itemValue="X1" represents a String. The EL expression #{X1} basically looks for a variable named X1 in respectively the facelet, request, view, session, and application scopes until the first non-null value is found. In order to represent a String value in EL, you need to quote it with singlequotes like so #{'X1'}. So, you should have used #{pd.marca == 'X1'} instead. Nonetheless, this still won't work for the reason mentioned in the first point. See also Specify conditional rendering of element inside <ui:repeat>? The <c:if> does not seem to work.

    You can however use <c:set> to create an alias in the EL scope, as long as you don't set its scope attribute. See also Defining and reusing an EL variable in JSF page.

    After removing JSTL <c:choose> and fixing the EL expression #{X1} to represent a real String literal #{'X1'}, here's how it should look like:

    <ui:repeat var="pd" value="#{produtoMB.produtos}">
        <p:selectOneMenu value="#{pd.marca}">
            <f:selectItem itemLabel="Select One" itemValue="#{null}" />
            <f:selectItem itemLabel="Xbox One" itemValue="X1" />
            <f:selectItem itemLabel="PlayStation 4" itemValue="PS4" /> 
            <f:selectItem itemLabel="Wii U" itemValue="WU" />
            <p:ajax update="linha" />
        </p:selectOneMenu>
        <h:panelGroup id="linha">
            <c:set var="marcaIsX1" value="#{pd.marca eq 'X1'}" />
            <p:inputText value="#{pd.aparelho}" disabled="#{marcaIsX1}" />
            <h:outputText value="Microsoft" rendered="#{marcaIsX1}" />
        </h:panelGroup>
        <p:commandButton value="X" />
    </ui:repeat>
    

    Note that I also removed the unused noSelectionOption="true" and moved the <h:panelGroup id="linha"> to wrap only the components which really need to be updated.