Jsf 根据提供的属性初始化复合组件

Jsf 根据提供的属性初始化复合组件,jsf,jsf-2,composite-component,Jsf,Jsf 2,Composite Component,我正在用MojarraJSF编写我的定制表复合组件。我还尝试将该组合绑定到支持组件。其目的是能够指定表在复合属性中的元素数量,稍后绑定的支持组件将在呈现视图之前自动生成元素本身。我有以下示例代码: 主页: <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=

我正在用MojarraJSF编写我的定制表复合组件。我还尝试将该组合绑定到支持组件。其目的是能够指定表在复合属性中的元素数量,稍后绑定的支持组件将在呈现视图之前自动生成元素本身。我有以下示例代码:

主页:

<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>
<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>
@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;
    }

}

myTable.xhtml:

<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>
<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>
@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;
    }

}

#{value}
MyTable.java:

<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>
<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>
@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;
    }

}
@FacesComponent(“components.myTable”)
公共类MyTable扩展了UINamingContainer{
私有列表值=新的ArrayList();
公共无效行动(){
System.out.println(“被调用”);
}
@凌驾
public void encodeBegin(FacesContext上下文)引发IOException{
//根据元素编号初始化列表
整数num=(整数)getAttributes().get(“itemNumber”);
for(int i=0;i
问题是,表格正确呈现(在本例中有两项),但按下行上的按钮时,
action
method不会被调用

如果我遵循for composite components,我可以让它以这种方式工作,但是每次调用
getValues()
时都必须初始化
列表,从而将逻辑引入getter方法:-(

知道吗?这似乎是一个与重写方法有关的问题。我也尝试过初始化它,但属性在那里还不可用



使用Mojarra 2.1.27+Tomcat 6-7和Mojarra 2.2.5+Tomcat 7进行测试,关于原因,
UIComponent
实例固有的请求范围。回发有效地创建了一个全新的实例,其属性如
重新初始化为默认值。在您的实现中,它仅在
encodeXxx()期间填充
,在
decode()
很久之后调用,其中操作事件需要排队,因此太晚了

您最好在组件初始化期间填充它。如果您想为
UIComponent
实例创建一个类似
@PostConstruct
的钩子,那么
postAddToView
事件是一个很好的候选事件。这将在组件实例添加到组件树后直接调用

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

...

私有列表值;
公共void init(){
值=新的ArrayList();
整数num=(整数)getAttributes().get(“值”);
for(int i=0;i
(如果
encodeBegin()
方法不再有用,请将其删除)


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

更简单的解决方案是将
值作为组件状态的一部分进行存储和检索。存储可以在
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");
    }
}
@FacesComponent(“components.myTable”)
公共类TestTable扩展了UINamingContainer{
公共无效行动(){
System.out.println(“被调用”);
}
@凌驾
public void encodeBegin(FacesContext上下文)引发IOException{
//根据元素编号初始化列表
列表值=新的ArrayList();
整数num=(整数)getAttributes().get(“itemNumber”);
for(int i=0;i
为了避免重复
getValues()
中的逻辑,在更复杂的情况下可能需要额外的解析,应该有一种在属性可用后立即处理和缓存属性的方法,尽管目前我不确定何时以及如何处理


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

再次感谢@BalusC。我不知道
UIComponent
是无状态的,但它确实有意义。作为保持状态的解决方案,我使用了一个
列表
引用,它由
@ViewScoped
托管bean和共享wi创建组件本身;-)这种解决方法似乎有缺点。我通常使用
preRenderView
方法初始化托管bean。我不使用
@PostConstruct
,因为我处理的是必须在确定加载内容之前设置的视图参数
postAddToView
似乎是在这之前被调用的,所以我的托管bean还没有显示这些数据。有没有其他事件可以替代它?
preRenderView
应该可以,只要在
FacesContext\isPostback()
true
时跳过它。bean是视图范围的,对吗?使用
preRenderView
事件会导致调用组件备份的两个部分:初始化一个后,调用另一个的getter,始终返回
null
。因此,我最终在getter方法中进行了延迟初始化。仍然需要对其进行更深入的测试,如果我在测试上下文中重播该问题,将尝试提供一个基本案例。@BalusC-使用
getstateheloper()
值作为组件状态的一部分似乎更简单。在我的回答中,在修改
encodeBegin
getValues
以从缓存中存储和检索后,组件似乎工作正常。