Search code examples
jsfjsf-2faceletsconditional-rendering

Why is <ui:fragment rendered> not evaluated on <h:outputStylesheet> and <h:outputScript>


I have a JSF 2 master template as follows:

<!DOCTYPE html>
<html lang="#{localeManager.language}" 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">

    <f:view locale="#{localeManager.locale}">
        <h:head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            <title><ui:insert name="title">Default Title.</ui:insert></title>

            <!-- Development Stylesheets -->
            <ui:fragment rendered="#{facesContext.application.projectStage eq 'Development'}">
                <h:outputStylesheet name="css/bootstrap.css" library="bootstrap" />
                <h:outputStylesheet name="css/font-awesome.css" library="fontawesome" />
            </ui:fragment>

            <h:outputStylesheet name="css/main.css" library="core" />
            <ui:insert name="header-stylesheet" />

            <!-- Production Stylesheets -->
            <ui:fragment rendered="#{facesContext.application.projectStage eq 'Production'}">
                <h:outputStylesheet name="css/bootstrap.min.css" library="bootstrap" />
                <h:outputStylesheet name="css/font-awesome.min.css" library="fontawesome" />
            </ui:fragment>

            <ui:insert name="header-script" />
        </h:head>

        <h:body>
            <div id="wrapper">
                <div id="header">
                    <ui:insert name="header">Default content</ui:insert>
                </div>

                <div id="body">
                    <ui:insert name="body">Default body</ui:insert>
                </div>

                <div id="footer">
                    <ui:insert name="footer">Default footer</ui:insert>
                </div>
            </div>

            <!-- Development Scripts -->
            <ui:fragment rendered="#{facesContext.application.projectStage eq 'Development'}">
                <h:outputScript name="jquery-2.1.4.js" library="jquery" />
                <h:outputScript name="js/bootstrap.js" library="bootstrap" />
            </ui:fragment>

            <!-- Production Scripts -->
            <ui:fragment rendered="#{facesContext.application.projectStage eq 'Production'}">
                <h:outputScript name="jquery-2.1.4.min.js" library="jquery" />
                <h:outputScript name="js/bootstrap.min.js" library="bootstrap" />
            </ui:fragment>
            <ui:insert name="body-script" />
        </h:body>
    </f:view>
</html>

When deploying it in Wildfly 9.0.1 Final, I see that all my <ui:fragment> attribute gets rendered. Why is that? I am developing using JSF 2.2 framework.

My Faces Project stage is Development.

Note: Of all the answers in SO concerning this issue, none is the solution to this problem (so I did my homework).


Solution

  • The <h:outputStylesheet> and <h:outputScript> are special components. They will during view build time already add the declared stylesheet or script resource to the view via UIViewRoot#addComponentResource() (see Mojarra source code here).

    This is regardless of the rendered condition of the component or its parents. In effects, they are basically relocated to the very end of the <h:head> or the <h:body>, depending on the value of target attribute, causing them to not sit inside <ui:fragment> anymore.

    Then, during rendering, only their own rendered attribute is considered (and actually also of <h:head>, but that's pointless).

    You have 2 options:

    1. Use a view build time tag to conditionally add them to the view. The project stage condition is application scoped anyway.

       <!-- Development Stylesheets -->
       <c:if test="#{facesContext.application.projectStage eq 'Development'}">
           <h:outputStylesheet ... />
           <h:outputStylesheet ... />
       </c:if>
      
       <!-- Production Stylesheets -->
       <c:if test="#{facesContext.application.projectStage eq 'Production'}">
           <h:outputStylesheet ... />
           <h:outputStylesheet ... />
       </c:if>
      
    2. Check the condition in component resource's own rendered attribute. Use if necessary <c:set> to create a short EL variable.

       <c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />
      
       <!-- Development Stylesheets -->
       <h:outputStylesheet ... rendered="#{dev}" />
       <h:outputStylesheet ... rendered="#{dev}" />
      
       <!-- Production Stylesheets -->
       <h:outputStylesheet ... rendered="#{not dev}" />
       <h:outputStylesheet ... rendered="#{not dev}" />
      

    See also: