I need to programmatically create composite components in JSF 2. After few days of searching and experiments I figured out this method (highly inspired by Lexi at java.net):
As both solutions did not work for me, i dig into the JSF implementation to find out, how the statically inserted composites are added and handled in the component tree. This is the working code i finally ended up with:
public UIComponent addWidget( UIComponent parent, String widget ) {
UIComponent cc = null;
UIComponent facetComponent = null;
FacesContext ctx = FacesContext.getCurrentInstance();
Resource resource = ctx.getApplication().getResourceHandler().createResource( widget + ".xhtml", "widgets" );
FaceletFactory faceletFactory = (FaceletFactory) RequestStateManager.get( ctx, RequestStateManager.FACELET_FACTORY );
// create the facelet component
cc = ctx.getApplication().createComponent( ctx, resource );
// create the component to be populated by the facelet
facetComponent = ctx.getApplication().createComponent( UIPanel.COMPONENT_TYPE );
facetComponent.setRendererType( "javax.faces.Group" );
// set the facelet's parent
cc.getFacets().put( UIComponent.COMPOSITE_FACET_NAME, facetComponent );
// populate the facetComponent
try {
Facelet facelet = faceletFactory.getFacelet( resource.getURL() );
facelet.apply( ctx, facetComponent );
} catch ( IOException e ) {
e.printStackTrace();
}
// finally add the facetComponent to the given parent
parent.getChildren().add( cc );
return cc;
}
I can't explain the concrete problem in detail, but I can only observe and confirm that the approach as shown in the question is clumsy and tight coupled to Mojarra. There are com.sun.faces.*
specific dependencies requiring Mojarra. This approach is not utilizing the standard API methods and, additionally, would not work in other JSF implementations like MyFaces.
Here's a much simpler approach utilizing standard API-provided methods. The key point is that you should use FaceletContext#includeFacelet() to include the composite component resource in the given parent.
public static void includeCompositeComponent(UIComponent parent, String libraryName, String resourceName, String id) {
// Prepare.
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
// This basically creates <ui:component> based on <composite:interface>.
Resource resource = application.getResourceHandler().createResource(resourceName, libraryName);
UIComponent composite = application.createComponent(context, resource);
composite.setId(id); // Mandatory for the case composite is part of UIForm! Otherwise JSF can't find inputs.
// This basically creates <composite:implementation>.
UIComponent implementation = application.createComponent(UIPanel.COMPONENT_TYPE);
implementation.setRendererType("javax.faces.Group");
composite.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, implementation);
// Now include the composite component file in the given parent.
parent.getChildren().add(composite);
parent.pushComponentToEL(context, composite); // This makes #{cc} available.
try {
faceletContext.includeFacelet(implementation, resource.getURL());
} catch (IOException e) {
throw new FacesException(e);
} finally {
parent.popComponentFromEL(context);
}
}
Imagine that you want to include <my:testComposite id="someId">
from URI xmlns:my="http://java.sun.com/jsf/composite/mycomponents"
, then use it as follows:
includeCompositeComponent(parent, "mycomponents", "testComposite.xhtml", "someId");
This has also been added to JSF utility library OmniFaces as Components#includeCompositeComponent()
(since V1.5).
Update since JSF 2.2, the ViewDeclarationLanguage
class got a new createComponent() method taking the taglib URI and tag name which could also be used for this purpose. So, if you're using JSF 2.2, the approach should be done as follows:
public static void includeCompositeComponent(UIComponent parent, String taglibURI, String tagName, String id) {
FacesContext context = FacesContext.getCurrentInstance();
UIComponent composite = context.getApplication().getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
.createComponent(context, taglibURI, tagName, null);
composite.setId(id);
parent.getChildren().add(composite);
}
Imagine that you want to include <my:testComposite id="someId">
from URI xmlns:my="http://xmlns.jcp.org/jsf/composite/mycomponents"
, then use it as follows:
includeCompositeComponent(parent, "http://xmlns.jcp.org/jsf/composite/mycomponents", "testComposite", "someId");