【问题标题】:Initialize a composite component based on the provided attributes根据提供的属性初始化复合组件
【发布时间】:2014-02-26 20:08:40
【问题描述】:

我正在使用 Mojarra JSF 编写我的自定义表格复合组件。我还试图将该复合材料绑定到支持组件。目的是能够在复合属性中指定表格的元素数量,稍后绑定的支持组件将在视图渲染之前自动生成元素本身。我有这个示例代码:

主页:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:comp="http://java.sun.com/jsf/composite/comp">
<h:head />
<body>
    <h:form>
        <comp:myTable itemNumber="2" />
    </h:form>
</body>
</html>

myTable.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:composite="http://java.sun.com/jsf/composite"
    xmlns:h="http://java.sun.com/jsf/html">

<h:body>
    <composite:interface componentType="components.myTable">
        <composite:attribute name="itemNumber" 
            type="java.lang.Integer" required="true" />
    </composite:interface>

    <composite:implementation>
        <h:dataTable value="#{cc.values}" var="value">
            <h:column headerText="column">
                #{value}
                <h:commandButton value="Action" action="#{cc.action}" />
            </h:column>
        </h:dataTable>
    </composite:implementation>
</h:body>
</html>

MyTable.java:

@FacesComponent("components.myTable")
public class MyTable extends UINamingContainer {

    private List<String> values = new ArrayList<String>();

    public void action() {
        System.out.println("Called");
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        // Initialize the list according to the element number
        Integer num = (Integer) getAttributes().get("itemNumber");
        for (int i = 0; i < num; i++) {
            values.add("item" + i);
        }
        super.encodeBegin(context);
    }

    public List<String> getValues() {
        return values;
    }

}

问题是表格被正确渲染(在这种情况下有两个项目),但action 方法在按下行上的按钮时不会被调用

如果我遵循wiki page 的复合组件,我可以让它以这种方式工作,但是每次调用getValues() 时都必须初始化List,从而在getter 方法中引入逻辑:-(。

对此有任何想法吗?这似乎是与覆盖 encodeBegin 方法有关的问题。我也尝试在markInitialState 上初始化它,但那里还没有属性...


使用 Mojarra 2.1.27 + Tomcat 6-7 和 Mojarra 2.2.5 + Tomcat 7 测试

【问题讨论】:

    标签: jsf jsf-2 composite-component


    【解决方案1】:

    至于原因,UIComponent 实例本质上是请求范围的。回发有效地创建了一个全新的实例,其属性如 values 重新初始化为默认值。在您的实现中,它仅在encodeXxx() 期间填充,它在decode() 之后很长时间被调用,其中动作事件需要排队,因此为时已晚。

    最好在组件初始化的时候填写。如果您想为UIComponent 实例使用类似@PostConstruct 的钩子,那么postAddToView 事件是一个不错的选择。这是在组件实例添加到组件树后直接调用的。

    <cc:implementation>
        <f:event type="postAddToView" listener="#{cc.init}" />
        ...
    </cc:implementation>
    

    private List<String> values;
    
    public void init() {
        values = new ArrayList<String>();
        Integer num = (Integer) getAttributes().get("value");
    
        for (int i = 0; i < num; i++) {
            values.add("item" + i);
        }
    }
    

    (如果 encodeBegin() 方法不再有用,则删除它)

    另一种方法是在 getValues() 方法中进行延迟初始化。

    【讨论】:

    • 再次感谢@BalusC。不知道UIComponent 是无国籍的,但绝对是有道理的。作为保持状态的解决方案,我使用了 List 引用,该引用由 @ViewScoped 托管 bean 创建并与组件本身共享;-)
    • 这种解决方法似乎有一个缺点。我通常使用preRenderView 方法初始化我的托管bean 的东西。我不使用@PostConstruct,因为我处理的视图参数必须先设置才能确定要加载的内容。 postAddToView 似乎在那之前被调用过,所以我的托管 bean 还没有显示该数据。我可以使用其他事件来代替它吗?
    • preRenderView 应该没问题,只要你在FacesContext#isPostback()true 时跳过它。 bean 是视图范围的,对吧?
    • 使用preRenderView 事件会导致组件支持的两个实例被调用:一个被初始化后,另一个的getter 被调用,总是返回null。所以我终于在 getter 方法上进行了延迟初始化。仍然需要更深入地测试它,如果我在我的测试上下文中重放这个问题,我会尝试提供一个基本案例。
    • @BalusC - 使用getStateHelper() 使values 成为组件状态的一部分似乎更简单。正如我的回答一样,在修改 encodeBegingetValues 以从缓存中存储和检索后,该组件似乎工作正常。
    【解决方案2】:

    更简单的解决方案是将values 作为组件状态的一部分进行存储和检索。存储可以在encodeBegin 期间进行,而检索可以直接在 getter 中进行:

    @FacesComponent("components.myTable")
    public class TestTable extends UINamingContainer {
        public void action() {
            System.out.println("Called");
        }
    
        @Override
        public void encodeBegin(FacesContext context) throws IOException {
            // Initialize the list according to the element number
            List<String> values = new ArrayList<>();
            Integer num = (Integer) getAttributes().get("itemNumber");
            for (int i = 0; i < num; i++) {
                values.add("item" + i);
            }
            getStateHelper().put("values",values);
            super.encodeBegin(context);
        }
    
        public List<String> getValues() {
            return (List<String>)getStateHelper().get("values");
        }
    }
    

    为了避免重复getValues() 中的逻辑,在更复杂的情况下可能需要额外的解析,应该有一种方法可以在属性可用后立即处理和缓存,尽管我不确定何时以及如何这一点。

    无论哪种方式 - 这似乎是解决此问题的最简单方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-05-07
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      • 2011-07-17
      • 1970-01-01
      相关资源
      最近更新 更多