Dynamic ui:include with ui:fragment: component with rendered false still make inside component tree

jsfjsf-2myfaces

I am force to use a very old version of MyFaces 2.0.5 I think, and dynamic include with EL cause so much problem, so I switch to static include with ui:fragment

<ui:fragment rendered="#{filtersPopup.filterFileName == 'checkBoxFilters'}">
    checkBoxFilters
    <ui:include src="/analytics/checkBoxFilters.xhtml" rendered="#{filtersPopup.filter != null}"/>
</ui:fragment>
<ui:fragment rendered="#{filtersPopup.filterFileName == 'DateRelativeFilter'}">
    DateRelativeFilter
    <ui:include src="/analytics/DateRelativeFilter.xhtml" rendered="#{filtersPopup.filter != null}"/>
</ui:fragment>
<ui:fragment rendered="#{filtersPopup.filterFileName == 'listboxFilter'}">
    listboxFilter
    <ui:include src="/analytics/listboxFilter.xhtml" rendered="#{filtersPopup.filter != null}"/>
</ui:fragment>

With this, it still complain about duplicate id because I use same id on couples of these source file. So it seems that even rendered should be false, it still make it to the component tree. However duplicate id is fine. I can fix that easy, so now when I submit form, I got this exception

 /analytics/checkBoxFilters.xhtml at line 24 and column 118 value="#{filtersPopup.filter.groups}": Property 'groups' not found on type ListBoxFilter

so as you can see from the exception, it ListBoxFilter is the object I want, so only include ListBoxFilter.xhtml, but is being accessed checkBoxFilters.xhtml hence the error. Any idea how to fix this issue please?

Best Answer

The <ui:include> doesn't extend from UIComponent. It's a tag handler. It does thus not support the rendered attribute at all. It's also nowhere listed in the tag documentation.

<ui:fragment> is indeed an UIComponent. So basically you end up with all those 3 include files being included during view build time and thus all 3 physically end up in JSF component tree. It's only the HTML output which is conditionally rendered via <ui:fragment rendered>. You still end up with duplicate component IDs from those includes because they all end up in JSF component tree.

You should be using a taghandler like <c:if> instead of <ui:fragment> to conditionally build the JSF component tree.

So, theoretically, this should do for each of them:

<c:if test="#{filtersPopup.filterFileName == 'checkBoxFilters'}">
    checkBoxFilters
    <c:if test="#{filtersPopup.filter != null}">
        <ui:include src="/analytics/checkBoxFilters.xhtml" />
    </c:if>
</c:if>

... etc

This has however some caveats when #{filtersPopup} is a view scoped bean and/or when you're using <f:viewParam>:

  1. This requires minimum Mojarra 2.1.18, otherwise JSF view scoped beans will fail to restore.

  2. Taghandlers run during view build time. So things which runs thereafter like <f:viewParam> won't work at all. You'd need to fall back to manually grabbing HTTP request parameters via ExternalContext.

See also:

Related Topic