Jsf ui中的复合组件:重复:如何正确保存组件状态

Jsf ui中的复合组件:重复:如何正确保存组件状态,jsf,Jsf,我有一个自定义组件,它实现了UIInput,需要保存一些状态信息,以便以后在回发请求中重用。单独使用时,它可以正常工作,但在中,回发会查找最新呈现的数据行的保存状态。操作调用的日志输出为 INFORMATION: myData is "third foo" INFORMATION: myData is "third foo" INFORMATION: myData is "third foo" INFORMATION: ok action 在我期待的地方 INFORMATION: myData

我有一个自定义组件,它实现了
UIInput
,需要保存一些状态信息,以便以后在回发请求中重用。单独使用时,它可以正常工作,但在
中,回发会查找最新呈现的数据行的保存状态。操作调用的日志输出为

INFORMATION: myData is "third foo"
INFORMATION: myData is "third foo"
INFORMATION: myData is "third foo"
INFORMATION: ok action
在我期待的地方

INFORMATION: myData is "first foo"
INFORMATION: myData is "second foo"
INFORMATION: myData is "third foo"
INFORMATION: ok action
我知道
myComponent
ui:repeat
中的一个实例。那么,保存组件状态以便正确恢复数据集中每一行的组件状态的最佳方法是什么

我的XHTML表单:

<h:form>
    <ui:repeat var="s" value="#{myController.data}">
        <my:myComponent data="#{s}"/>
    </ui:repeat>

    <h:commandButton action="#{myController.okAction}" value="ok">
        <f:ajax execute="@form" render="@form"/>
    </h:commandButton>
</h:form>

只是试着复制你的问题,是的,现在我知道你到底是什么了。您只想使用JSF组件状态作为计算变量的某种视图范围。我能理解。观察到的行为确实出乎意料

简而言之,莱昂纳多·乌里韦(MyFaces committer)的博客对此进行了解释

这种行为背后的原因是像
h:dataTable
ui:repeat
这样的标记只保存与
EditableValueHolder
接口相关的属性(
value
submittedValue
localValueSet
valid
)。因此,一个常见的使其正常工作的方法是从
UIInput
或使用
EditableValueHolder
接口扩展您的组件,并在“
value
”字段中存储每行要保留的状态

[……]

自JSF2.1以来,
UIData
实现有一个名为
rowStatePreserved
的新属性。目前,此属性没有出现在
h:dataTable
的facelets taglib文档中,但在
UIData
的javadoc中有。因此修复非常简单,只需在
h:dataTable
标记中添加
rowStatePreserved=“true”

最后,您基本上有3个选项:

  • 使用
    UIInput#value
    而不是像
    MYDATA

    按照上述博客的指示,只需将
    getMyData()
    setMyData()
    替换为
    getValue()
    setValue()
    中现有的
    UIInput
    方法即可。您的复合组件已经从中扩展

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        this.setValue(calculateData()); // setValue instead of setMyData
        super.encodeBegin(context);
    }
    
    @Override
    public void processDecodes(FacesContext context) {
        super.processDecodes(context);
        LOG.log(Level.INFO, "myData {0}", getValue() ); // getValue instead of getMyData
    }
    
    在XHTML实现中(顺便说一句)是等价的:

    值得注意的是,在MyFaces2.3.6中,这一切都不起作用。我没有进一步调试它


  • 替换为

    正如上面提到的博客所暗示的,这确实在
    UIData
    javadoc中。只需将
    替换为
    ,并将其
    rowStatePreserved
    属性显式设置为
    true
    。您可以继续在状态中使用
    MYDATA
    属性

    <h:dataTable value="#{myController.data}" var="s" rowStatePreserved="true">
        <h:column><my:myComponent data="#{s}" /></h:column>
    </h:dataTable>
    
    虽然这是一个相对微不足道的改变,但却很尴尬。这并不意味着可移植性。每次实现一个新的(复合)组件属性时,您都必须犹豫并三思而后行,该属性应该保存在JSF状态中。你真的希望JSF能自动处理这个问题


  • 我不确定“正确的方法”是什么,我可以想出一些解决办法:1)使用
    MYDATA+getClientId()
    作为状态键,或2)使用
    getAttributes()
    而不是
    getStateHelper()
    或3)使用
    c:forEach
    而不是
    ui:repeat
    (这导致了完全不同的组件树,正如Kukeltje在这里所指出的;p)但是(3)在使用dataTable中的组件时没有帮助。谢谢@Selaron,但我真的在这个特殊情况下寻找“正确的方法”。
    c:forEach
    getAttributes
    在我们的情况下不是解决办法,使用
    getClientId()
    是一个很好的解决方法。但是,真的,这有点难看,不是吗?是的,这很难看。我很高兴看到这个答案,因为到目前为止我必须解决这个问题。这种方法既奇怪又混乱。我的第一次尝试是使用
    getstateheloper()
    而不是
    this.getAttributes()
    ,因为
    getstateheloper()
    将知道迭代上下文。@BalusC嗯,
    getstateheloper()的行为
    是我的问题。这里我们有两个请求。在第一个请求中,组件被编码,我们使用
    encodeBegin
    myData
    放入
    stateheloper
    中。在第二个请求中,回发,组件被解码。在
    processDecodes
    中,
    stateheloper
    向我们显示意外的数据我的问题是:如何正确地使用
    stateheloper
    ,使它即使在回发请求中也能识别迭代器?我的期望是,它应该开箱即用,但它没有。关于
    o:repeat
    Primefaces,它也做了一次,原因正是这样,iirc…甚至可能也是一个解决方案。我可以试试看…不确定是否可以独立实现,但感觉更像是一个bug,而不是一个特性。无论如何,非常感谢您的努力!顺便说一句,您知道jsf 2.4中关于这个问题的一些正在进行的讨论吗?我认为
    rowStatePreserved
    对于
    ui:repeat
    是必须的。我只是尝试了
    p:repeat
    一次:没有也可以保留行状态。@kukeltje-nope;p:repeat只是mojarrasui:repeat的一个分支,用于为专业用户提供错误修复,这些用户仍然在旧应用程序服务器上的旧Mojarra版本上。
    @FacesComponent
    public class MyComponent extends UIInput implements NamingContainer {
    
        private static final Logger LOG=Logger.getLogger(MyComponent.class.getName());
    
        public String calculateData() {
            return String.format("%s foo", this.getAttributes().get("data") );
        }
    
        public String getMyData() {
            return (String)getStateHelper().get("MYDATA");
        }
    
        public void setMyData( String data ) {
            getStateHelper().put("MYDATA", data);
        }
    
        @Override
        public String getFamily() {
            return UINamingContainer.COMPONENT_FAMILY;
        }
    
        @Override
        public void encodeBegin(FacesContext context) throws IOException {
            this.setMyData( calculateData() );
            super.encodeBegin(context);
        }
    
        @Override
        public void processDecodes(FacesContext context) {
            super.processDecodes(context);
            LOG.log(Level.INFO, "myData {0}", getMyData() );
        }
    }
    
    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        this.setValue(calculateData()); // setValue instead of setMyData
        super.encodeBegin(context);
    }
    
    @Override
    public void processDecodes(FacesContext context) {
        super.processDecodes(context);
        LOG.log(Level.INFO, "myData {0}", getValue() ); // getValue instead of getMyData
    }
    
    <h:outputText value="#{cc.value}" /> <!-- cc.value instead of cc.myData -->
    
    <ui:repeat value="#{''}">
        <ui:repeat value="#{myController.data}" var="s">
            <my:myComponent data="#{s}" />
        </ui:repeat>
    </ui:repeat>
    
    <h:dataTable value="#{myController.data}" var="s" rowStatePreserved="true">
        <h:column><my:myComponent data="#{s}" /></h:column>
    </h:dataTable>
    
    public String getMyData() {
        return (String) getStateHelper().get("MYDATA." + getClientId());
    }
    
    public void setMyData(String data) {
        getStateHelper().put("MYDATA." + getClientId(), data);
    }