java 如何在 JSF 2 中以编程方式或动态创建复合组件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5370184/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to programmatically or dynamically create a composite component in JSF 2
提问by Ondrej Bozek
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):
我需要在 JSF 2 中以编程方式创建复合组件。 经过几天的搜索和实验,我找到了这个方法(受到 java.net 上的 Lexi 的高度启发):
/**
* 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.PROJECT_STAGE
设置为时才对我有用PRODUCTION
(我花了一整天的时间才弄明白)。如果javax.faces.PROJECT_STAGE
设置为DEVELOPMENT
在标记点 ( <==[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.
compositeRoot
组件中设置的父级存在一些问题(j_id2
是自动生成的 ID compositeRoot
)。此外,这段代码没有经过足够彻底的测试,所以我不确定我是否可以依赖它。
I think it's very important to be able to manipulate Composite Components programmatically. Otherwise Composite Components are half useless.
我认为能够以编程方式操作复合组件非常重要。否则复合组件一半没用。
回答by BalusC
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.
我无法详细解释具体问题,但我只能观察并确认问题中所示的方法笨拙且与 Mojarra 紧密耦合。有com.sun.faces.*
需要 Mojarra 的特定依赖项。这种方法没有使用标准的 API 方法,此外,在其他 JSF 实现(如 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.
这是一种使用标准 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);
}
}
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:
假设您想<my:testComposite id="someId">
从 URI包含xmlns:my="http://java.sun.com/jsf/composite/mycomponents"
,然后按如下方式使用它:
includeCompositeComponent(parent, "mycomponents", "testComposite.xhtml", "someId");
This has also been added to JSF utility library OmniFacesas Components#includeCompositeComponent()
(since V1.5).
这也已添加到 JSF 实用程序库OmniFaces中Components#includeCompositeComponent()
(自 V1.5 起)。
Updatesince 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:
自 JSF 2.2 起更新,ViewDeclarationLanguage
该类获得了一个createComponent()
采用 taglib URI 和标签名称的新方法,该方法也可用于此目的。因此,如果您使用的是 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);
}
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:
假设您想<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");
回答by Andre Pohl
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:
由于这两种解决方案对我都不起作用,我深入研究了 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'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;
}