Search code examples
jsfprimefacesxhtmlfacetcomposite-component

JSF Composite Components: Facet Content Not Rendering Through Nested Components


I'm facing an issue with rendering content passed through facets in nested composite components. Despite the facets being recognized as not empty, the content inside these facets does not appear on the final page. Below are the relevant parts of my implementation:

  1. draft_scenario_details.xhtml
<ui:composition template="/virets/templates/base_template.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://xmlns.jcp.org/jsf/core"
                xmlns:p="http://primefaces.org/ui"
                xmlns:virets="http://java.sun.com/jsf/composite/composite">
    <ui:define name="content">
        <virets:scenarioDetails>
            <f:facet name="additionalButtons">
                <p:outputLabel value="MY DRAFT"/>
            </f:facet>
        </virets:scenarioDetails>
    </ui:define>
</ui:composition>
  1. scenarioDetails.xhtml
<ui:composition
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:cc="http://java.sun.com/jsf/composite"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:p="http://primefaces.org/ui"
        xmlns:virets="http://java.sun.com/jsf/composite/composite">
    <cc:interface>
        <cc:facet name="additionalButtons"/>
    </cc:interface>
    <cc:implementation>
        <virets:scenarioDetailsData>
            <f:facet name="additionalButtonsFacet">
                <cc:renderFacet name="additionalButtons"/>
            </f:facet>
        </virets:scenarioDetailsData>
    </cc:implementation>
</ui:composition>
  1. scenarioDetailsData.xhtml
<ui:composition
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:cc="http://java.sun.com/jsf/composite"
        xmlns:p="http://primefaces.org/ui">
    <cc:interface>
        <cc:facet name="additionalButtonsFacet"/>
    </cc:interface>
    <cc:implementation>
        <cc:renderFacet name="additionalButtonsFacet"/>
    </cc:implementation>
</ui:composition>

Issue:

The label "MY DRAFT" passed via the "additionalButtons" facet in viretsScenarioDetails should render in scenarioDetailsData, but it doesn't appear on the page.

What I have tried:

  • Ensuring facet names are consistent across components.
  • Directly rendering the facet in intermediate components to verify its presence.

Question:

How can I ensure that the content passed through facets in nested JSF composite components renders correctly in the final output?

Any help or insights would be greatly appreciated!


Solution

  • Double rendering a facet is not what you want. The first "rendering" should just be a passing on of the facet.

    scenarioDetails.xhtml:

    <ui:composition
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:cc="http://java.sun.com/jsf/composite"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:p="http://primefaces.org/ui"
        xmlns:demo="urn:be:e-contract:ejsf:demo">
        <cc:interface componentType="scenarioDetails">
            <cc:facet name="additionalButtons"/>
        </cc:interface>
        <cc:implementation>
            <div id="#{cc.clientId}">
                <demo:scenarioDetailsData binding="#{cc.scenarioDetailsData}"/>
            </div>
        </cc:implementation>
    </ui:composition>
    

    ScenarioDetails.java:

    package be.e_contract.demo;
    
    import javax.faces.component.FacesComponent;
    import javax.faces.component.NamingContainer;
    import javax.faces.component.UIComponent;
    import javax.faces.component.UIComponentBase;
    import javax.faces.component.UINamingContainer;
    import javax.faces.event.AbortProcessingException;
    import javax.faces.event.ComponentSystemEvent;
    import javax.faces.event.ComponentSystemEventListener;
    import javax.faces.event.ListenerFor;
    import javax.faces.event.PostAddToViewEvent;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    @FacesComponent(ScenarioDetails.COMPONENT_TYPE)
    @ListenerFor(systemEventClass = PostAddToViewEvent.class)
    public class ScenarioDetails extends UIComponentBase implements NamingContainer, ComponentSystemEventListener {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(ScenarioDetails.class);
    
        public static final String COMPONENT_TYPE = "scenarioDetails";
    
        private UIComponent scenarioDetailsData;
    
        public void setScenarioDetailsData(UIComponent scenarioDetailsData) {
            this.scenarioDetailsData = scenarioDetailsData;
        }
    
        public UIComponent getScenarioDetailsData() {
            return this.scenarioDetailsData;
        }
    
        public ScenarioDetails() {
            setRendererType(null);
        }
    
        @Override
        public String getFamily() {
            return UINamingContainer.COMPONENT_FAMILY;
        }
    
        @Override
        public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
            UIComponent facet = getFacet("additionalButtons");
            this.scenarioDetailsData.getFacets().put("additionalButtonsFacet", facet);
        }
    }