如何在 JSF 2 中以编程方式或动态创建复合组件 [英] How to programmatically or dynamically create a composite component in JSF 2

查看:20
本文介绍了如何在 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆