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