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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 10:47:46  来源:igfitidea点击:

How to programmatically or dynamically create a composite component in JSF 2

javadynamicjsf-2faceletscomposite-component

提问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_STAGEis set to PRODUCTION(It took me all day to figure this out). If javax.faces.PROJECT_STAGEis set to DEVELOPMENTException 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 compositeRootcomponent (j_id2is 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 实用程序库OmniFacesComponents#includeCompositeComponent()(自 V1.5 起)。



Updatesince JSF 2.2, the ViewDeclarationLanguageclass 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;
}