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

fri*_*fle 5 jsf

我有一个自定义组件,它实现UIInput并需要保存一些状态信息,以便以后在回发请求中重用。独立使用它工作正常,但在<ui:repeat>回发中找到最新呈现的数据行的保存状态。动作调用的日志输出是

INFORMATION: myData is "third foo"
INFORMATION: myData is "third foo"
INFORMATION: myData is "third foo"
INFORMATION: ok action
Run Code Online (Sandbox Code Playgroud)

我期望的地方

INFORMATION: myData is "first foo"
INFORMATION: myData is "second foo"
INFORMATION: myData is "third foo"
INFORMATION: ok action
Run Code Online (Sandbox Code Playgroud)

我知道这myComponentui: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>
Run Code Online (Sandbox Code Playgroud)

我的豆:

@Named
@ViewScoped
public class MyController implements Serializable {

    private static final long serialVersionUID = -2916212210553809L;

    private static final Logger LOG = Logger.getLogger(MyController.class.getName());

    public List<String> getData() {
        return Arrays.asList("first","second","third");
    }

    public void okAction() {
        LOG.info("ok action");
    }
}
Run Code Online (Sandbox Code Playgroud)

复合组件 XHTML 代码:

<ui:component xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://xmlns.jcp.org/jsf/html"
  xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
  xmlns:cc="http://xmlns.jcp.org/jsf/composite">

  <cc:interface componentType="myComponent">
    <cc:attribute name="data"/>
  </cc:interface>

  <cc:implementation>
    <h:panelGrid columns="2">
      <h:outputLabel value="cc.attrs.data"/>
      <h:outputText value="#{cc.attrs.data}"/>
      <h:outputLabel value="cc.myData"/>
      <h:outputText value="#{cc.myData}"/>
    </h:panelGrid>
  </cc:implementation>
</ui:component>
Run Code Online (Sandbox Code Playgroud)

复合组件支持类:

@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() );
    }
}
Run Code Online (Sandbox Code Playgroud)

Bal*_*usC 4

刚刚尝试重现您的问题,是的,现在我终于明白了您的意思。您只想使用 JSF 组件状态作为计算变量的某种视图范围。我能理解。观察到的行为确实是出乎意料的。

简而言之,Leonardo Uribe(MyFaces 提交者)的博客对此进行了解释:数据表的每行 JSF 组件状态

这种行为背后的原因是诸如h:dataTable或 之类的标签仅保存与接口ui:repeat相关的属性( , , , )。因此,使其正常工作的常见方法是从接口扩展组件或使用接口,并将要在“ ”字段内每行保留的状态存储起来。EditableValueHoldervaluesubmittedValuelocalValueSetvalidUIInputEditableValueHoldervalue

[...]

从 JSF 2.1 开始,UIData实现有一个名为 的新属性rowStatePreserved。目前,此属性未出现在 的 Facelets taglib 文档中h:dataTable,但出现在 的 javadoc 中UIData。所以修复非常简单,只需添加rowStatePreserved="true"您的h:dataTable标签:

最后,你基本上有 3 个选择:

  1. 使用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
    }
    
    Run Code Online (Sandbox Code Playgroud)

    同样在 XHTML 实现中(顺便说一句,这里没有必要<h:outputText>

    <h:outputText value="#{cc.value}" /> <!-- cc.value instead of cc.myData -->
    
    Run Code Online (Sandbox Code Playgroud)

    然而,当我在 Mojarra 2.3.14 上尝试时,这并没有真正起作用。事实证明,Mojarra 的实现确实在恢复视图期间<ui:repeat>恢复了状态(耶!),但随后在解码期间完全清除了它(哈?),这变得有点无用。坦率地说,我不确定为什么要这样做。我还在 Mojarra 的源代码中发现,当它嵌套另一个或. 因此,以下将其放入另一个尝试迭代空字符串的小技巧使其起作用:EditableValueHolderUIRepeatUIDataUIRepeatUIRepeat

    <ui:repeat value="#{''}">
        <ui:repeat value="#{myController.data}" var="s">
            <my:myComponent data="#{s}" />
        </ui:repeat>
    </ui:repeat>
    
    Run Code Online (Sandbox Code Playgroud)

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


  2. 替换<ui:repeat><h:dataTable rowStatePreserved="true">

    正如上述博客中所暗示的,这确实记录UIDatajavadoc 中。只需替换<ui:repeat><h:dataTable>并将其rowStatePreserved属性显式设置为true。您可以继续MYDATA在该州使用您的属性。

    <h:dataTable value="#{myController.data}" var="s" rowStatePreserved="true">
        <h:column><my:myComponent data="#{s}" /></h:column>
    </h:dataTable>
    
    Run Code Online (Sandbox Code Playgroud)

    这在 Mojarra 2.3.14 和 MyFaces 2.3.6 中都对我有用。不幸的是,这在UIRepeat. 因此,您将不得不忍受<table><h:dataTable>. 在 JSF 2.3 工作期间,曾经讨论过将功能添加到UIRepeat,但不幸的是在 JSF 2.3 发布之前没有采取任何行动。


  3. 包含getClientId()在状态键中

    正如Selaron在您的问题评论中所建议的,将客户端 ID 作为密钥存储在状态中。

    public String getMyData() {
        return (String) getStateHelper().get("MYDATA." + getClientId());
    }
    
    public void setMyData(String data) {
        getStateHelper().put("MYDATA." + getClientId(), data);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    虽然这是一个相对微不足道的改变,但却很尴尬。这根本不意味着可移植性。每次实现应保存在 JSF 状态的新(复合)组件属性时,您都必须犹豫并三思而后行。您确实希望 JSF 自动处理这个问题。