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

时间:2014-02-03 11:05:32

标签: jsf jsf-2 composite-component

我正在使用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,我可以通过这种方式使其工作,但每次调用List时都必须初始化getValues(),将逻辑引入getter方法:-(。

有什么想法吗?这似乎是一个与覆盖encodeBegin方法相关的麻烦。我也试过在markInitialState上初始化它,但那里的属性还没有......


使用Mojarra 2.1.27 + Tomcat 6-7&amp;测试Mojarra 2.2.5 + Tomcat 7

2 个答案:

答案 0 :(得分:17)

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

您最好在组件初始化期间填充它。如果你想要一个UIComponent - 类似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()方法,如果它没有做任何有用的事情)

另一种方法是在{{1}}方法中进行延迟初始化。

答案 1 :(得分:1)

一个更简单的解决方案是将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()中的逻辑,在更复杂的情况下可能需要额外的解析,应该有一种在属性可用后立即处理和缓存属性的方法,尽管我不确定何时以及此时如何。

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