Как программно или динамически создавать составной компонент в JSF 2
Мне нужно программно создавать составные компоненты в JSF 2. После нескольких дней поиска и экспериментов я выясню этот метод (сильно вдохновленный Lexi на 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);
}
}
Проблема в том, что этот код работает для меня только тогда, когда javax.faces.PROJECT_STAGE установлен в ПРОИЗВОДСТВО (Мне понадобилось целый день, чтобы понять это). Если для javax.faces.PROJECT_STAGE установлено значение РАЗВИТИЕ Исключение выбрано в отмеченной точке (< == [здесь]):
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)
Возникает некоторая проблема с родительским набором в компоненте составной части (j_id2 является автоматическим сгенерированным идентификатором composRoot). Также этот код не проверен достаточно точно, поэтому я не уверен, могу ли я на него положиться.
Я думаю, что очень важно иметь возможность манипулировать Composite Components programmaticaly. В противном случае составные компоненты бесполезны.
Большое спасибо.
Ответы
Ответ 1
Я не могу подробно объяснить конкретную проблему, но я могу только наблюдать и подтверждать, что подход, как показано в вопросе, неуклюж и тесно связан с Мохаррой. Существуют com.sun.faces.*
конкретные зависимости, требующие Mojarra. Этот подход не использует стандартные методы API и, кроме того, не будет работать в других реализациях JSF, таких как MyFaces.
Здесь используется гораздо более простой подход, использующий стандартные API-методы. Главное, что вы должны использовать FaceletContext#includeFacelet()
для включения ресурса составных компонентов в данном родителе.
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);
}
}
Представьте, что вы хотите включить <my:testComposite id="someId">
из URI xmlns:my="http://java.sun.com/jsf/composite/mycomponents"
, затем используйте его следующим образом:
includeCompositeComponent(parent, "mycomponents", "testComposite.xhtml", "someId");
Это также добавлено в библиотеку служебных программ JSF OmniFaces как Components#includeCompositeComponent()
(начиная с версии 5.1).
Обновить, так как JSF 2.2, класс ViewDeclarationLanguage
получил новый метод createComponent()
, в котором были указаны URI и тег taglib, которые могли бы также можно использовать для этой цели. Итак, если вы используете JSF 2.2, подход должен быть выполнен следующим образом:
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);
}
Представьте, что вы хотите включить <my:testComposite id="someId">
из URI xmlns:my="http://xmlns.jcp.org/jsf/composite/mycomponents"
, а затем использовать его следующим образом:
includeCompositeComponent(parent, "http://xmlns.jcp.org/jsf/composite/mycomponents", "testComposite", "someId");
Ответ 2
Поскольку оба решения не сработали для меня, я вникаю в реализацию JSF, чтобы узнать, как добавлены статически вставленные композиты и обрабатываются в дереве компонентов. Это рабочий код, который, наконец, закончился:
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 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;
}