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):
/**
* Method will attach composite component to provided component
* @param viewPanel parent component of newly created composite component
*/
public void setComponentJ(UIComponent viewPanel) {
FacesContext context = FacesContext.getCurrentInstance();
viewPanel.getChildren().clear();
// load composite component from file
Resource componentResource = context.getApplication().getResourceHandler().createResource("whatever.xhtml", "components/form");
UIComponent composite = context.getApplication().createComponent(context, componentResource);
// push component to el
composite.pushComponentToEL(context, composite);
boolean compcompPushed = false;
CompositeComponentStackManager ccStackManager = CompositeComponentStackManager.getManager(context);
compcompPushed = ccStackManager.push(composite, CompositeComponentStackManager.StackType.TreeCreation);
// Populate the component with value expressions
Application application = context.getApplication();
composite.setValueExpression("value", application.getExpressionFactory().createValueExpression(
context.getELContext(), "#{stringValue.value}",
String.class));
// Populate the component with facets and child components (Optional)
UIOutput foo = (UIOutput) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
foo.setValue("Foo");
composite.getFacets().put("foo", foo);
UIOutput bar = (UIOutput) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
bar.setValue("Bar");
composite.getChildren().add(bar);
// create composite components Root
UIComponent compositeRoot = context.getApplication().createComponent(UIPanel.COMPONENT_TYPE);
composite.getAttributes().put(Resource.COMPONENT_RESOURCE_KEY, componentResource);
compositeRoot.setRendererType("javax.faces.Group");
composite.setId("compositeID");
try {
FaceletFactory factory = (FaceletFactory) RequestStateManager.get(context, RequestStateManager.FACELET_FACTORY);
Facelet f = factory.getFacelet(componentResource.getURL());
f.apply(context, compositeRoot); //<==[here]
} catch (Exception e) {
log.debug("Error creating composite component!!", e);
}
composite.getFacets().put(
UIComponent.COMPOSITE_FACET_NAME, compositeRoot);
// attach composite component to parent componet
viewPanel.getChildren().add(composite);
// pop component from el
composite.popComponentFromEL(context);
if (compcompPushed) {
ccStackManager.pop(CompositeComponentStackManager.StackType.TreeCreation);
}
}
Problem is that this code works for me only when javax.faces.PROJECT_STAGE
is set to PRODUCTION
(It took me all day to figure this out). If javax.faces.PROJECT_STAGE
is set to DEVELOPMENT
Exception is thrown on marked point (<==[here]
):
javax.faces.view.facelets.TagException: /resources/components/form/pokus.xhtml @8,19 <cc:interface> Component Not Found for identifier: j_id2.getParent().
at com.sun.faces.facelets.tag.composite.InterfaceHandler.validateComponent(InterfaceHandler.java:135)
at com.sun.faces.facelets.tag.composite.InterfaceHandler.apply(InterfaceHandler.java:125)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:98)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:82)
at com.sun.faces.facelets.impl.DefaultFacelet.apply(DefaultFacelet.java:152)
at cz.boza.formcreator.formcore.Try.setComponentJ(Try.java:83)
at cz.boza.formcreator.formcore.FormCreator.<init>(FormCreator.java:40)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
It's some problem with parent set in compositeRoot
component (j_id2
is automaticaly generated ID of compositeRoot
). Also this code is not tested thoroughly enough, so I'm not sure if I can rely on it.
I think it's very important to be able to manipulate Composite Components programmatically. Otherwise Composite Components are half useless.
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");