如何在 JSF 2 中以编程方式或动态创建复合组件 [英] How to programmatically or dynamically create a composite component in JSF 2
问题描述
我需要在 JSF 2 中以编程方式创建复合组件.经过几天的搜索和实验,我找到了这个方法(受到 java.net 的 Lexi 的高度启发):
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);
}
}
问题是此代码仅在 javax.faces.PROJECT_STAGE
设置为 PRODUCTION
时才对我有用(我花了一整天才弄明白).如果 javax.faces.PROJECT_STAGE
设置为 DEVELOPMENT
在标记点上抛出异常 (<==[here]
):
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)
compositeRoot
组件(j_id2
是自动生成的compositeRoot
的ID)中的父设置有问题.此外,这段代码没有经过足够彻底的测试,所以我不确定我是否可以依赖它.
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.
推荐答案
我无法详细解释具体问题,但我只能观察并确认问题中所示的方法笨拙且与 Mojarra 紧耦合.有 com.sun.faces.*
需要 Mojarra 的特定依赖项.这种方法没有使用标准的 API 方法,此外,它也不适用于 MyFaces 等其他 JSF 实现.
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.
这是一种使用标准 API 提供的方法的更简单的方法.关键是你应该使用 FaceletContext#includeFacelet()
在给定的父级中包含复合组件资源.
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);
}
}
假设您想从 URI xmlns:my="http://java.sun.com/jsf/中包含
,然后按如下方式使用:<my:testComposite id="someId">
Composite/mycomponents"
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");
这也已作为 Components#includeCompositeComponent()
添加到 JSF 实用程序库 OmniFaces(自 V1.5 起).
This has also been added to JSF utility library OmniFaces as Components#includeCompositeComponent()
(since V1.5).
更新自 JSF 2.2 起,ViewDeclarationLanguage
类获得了一个新的 createComponent()
方法采用 taglib URI 和标签名称,也可用于此目的.因此,如果您使用的是 JSF 2.2,则应按如下方式完成该方法:
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);
}
假设您想从 URI xmlns:my="http://xmlns.jcp.org/jsf/中包含
,然后按如下方式使用:<my:testComposite id="someId">
Composite/mycomponents"
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");
这篇关于如何在 JSF 2 中以编程方式或动态创建复合组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!