如何在 JSF 2 中以编程方式或动态创建复合组件
Posted
技术标签:
【中文标题】如何在 JSF 2 中以编程方式或动态创建复合组件【英文标题】:How to programmatically or dynamically create a composite component in JSF 2 【发布时间】:2011-07-19 05:12:26 【问题描述】:我需要在 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);
问题是,只有当 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)
compositeRoot
组件中的父集存在一些问题(j_id2
是自动生成的compositeRoot
的 ID)。此外,这段代码没有经过足够彻底的测试,所以我不确定我是否可以依赖它。
我认为能够以编程方式操作复合组件非常重要。否则复合组件是没用的。
【问题讨论】:
经过一些测试我还发现,我不能以编程方式将任何组件(添加为子组件)嵌套在其他复合组件中:(。 好的,所以我终于找到了解决方案。在这里,您可以下载演示程序以编程方式创建复合组件。 confluence.highsource.org/display/Hifaces20/… 我现在尝试了链接,它可以工作。它非常复杂,因为它是关于复合组件的程序化创建和 JSF 页面的动态构建的整个项目。 答案中的链接有效,但链接页面中应提供源代码下载的链接已损坏,因此此答案不完整。实际上,正是出于这个原因,强烈建议不要在 Stack Exchange 上使用仅链接的答案。 【参考方案1】:我无法详细解释具体问题,但我只能观察并确认问题中显示的方法笨拙且与 Mojarra 紧密耦合。有 com.sun.faces.*
需要 Mojarra 的特定依赖项。这种方法没有使用标准的 API 方法,此外,在 MyFaces 等其他 JSF 实现中也不起作用。
这是一种使用标准 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);
假设您想在 URI xmlns:my="http://java.sun.com/jsf/composite/mycomponents"
中包含 <my:testComposite id="someId">
,然后按如下方式使用它:
includeCompositeComponent(parent, "mycomponents", "testComposite.xhtml", "someId");
这也被添加到 JSF 实用程序库 OmniFaces 作为 Components#includeCompositeComponent()
(从 V1.5 开始)。
更新 从 JSF 2.2 开始,ViewDeclarationLanguage
类获得了一个新的 createComponent()
方法,该方法采用标记库 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);
假设您想从 URI xmlns:my="http://xmlns.jcp.org/jsf/composite/mycomponents"
中包含 <my:testComposite id="someId">
,然后按如下方式使用它:
includeCompositeComponent(parent, "http://xmlns.jcp.org/jsf/composite/mycomponents", "testComposite", "someId");
【讨论】:
你好。我也面临这个问题; facelet 动态创建示例是否完全适用于 jsf 2.0?因为我在这一行面临 NPE*.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
我们如何传递 attirubute 参数?
composite.getAttributes().put("name", value)
【参考方案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'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;
【讨论】:
当<cc:implementation>
依赖于 <cc:interface componentType>
时,此操作将失败。以上是关于如何在 JSF 2 中以编程方式或动态创建复合组件的主要内容,如果未能解决你的问题,请参考以下文章
JSF 2 - 如何将 Ajax 侦听器方法添加到复合组件接口?