我面临以下问题:在一个页面中,我列出了应用程序的所有用户,每个用户都有一个"编辑"按钮,这是一个带有?id=<userid>
的"GET"链接
编辑页面在元数据中有一个<f:viewParam name="id" value="#{editUserBean.id}"/>
如果我犯了一些输入错误并提交(我使用CDI-WeldBean验证(,页面会再次显示,但我在URL中丢失了?id=...
,因此丢失了我正在编辑的用户的用户id。
我已经看到了JSF验证错误中描述的类似问题,丢失了值,但使用inputhidden(或者更糟的是,使用tomahawk,看起来过于夸张(的解决方案需要大量uggly代码。
我试着添加了一个与CDI的"对话",它很有效,但对我来说又太过分了。
JSF中是否有一个简单的解决方案可以在验证错误的情况下保留视图参数?
[我的环境:Tomcat7+MyFaces 2.1.0+Hibernate Validator 4.2.0+CDI(Weld(1.1.2]
有趣的案例。对于每个人来说,以下最小代码再现了这一点:
Facelet:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<f:metadata>
<f:viewParam id="id" name="id" value="#{viewParamBean.id}"/>
</f:metadata>
<h:body>
<h:messages />
#{viewParamBean.id} <br/>
<h:form>
<h:inputText value="#{viewParamBean.text}" >
<f:validateLength minimum="2"/>
</h:inputText>
<h:commandButton value="test" action="#{viewParamBean.actionMethod}"/>
</h:form>
</h:body>
</html>
Bean:
@ManagedBean
@RequestScoped
public class ViewParamBean {
private long id;
private String text;
public void actionMethod() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
如果您使用viewparam.xhtml?id=12
呼叫Facelet,它将在屏幕上显示12
。如果您随后输入有效的内容,例如aaaaa
,id将从URL中消失,但仍会显示在屏幕上(由于ui组件的有状态性(。
然而。。。正如OP所提到的,一旦出现任何验证器错误(例如输入a
(,id就会永久丢失。之后输入有效的输入不会使其返回。这看起来几乎是一个bug,但我尝试了Mojarra 2.1和Myfaces 2.1,两者都有相同的行为。
更新:
经过一些检查,问题似乎出现在"UIViewParameter"(Mojarra(的这种方法中:
public void encodeAll(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
// if there is a value expression, update view parameter w/ latest value after render
// QUESTION is it okay that a null string value may be suppressing the view parameter value?
// ANSWER: I'm not sure.
setSubmittedValue(getStringValue(context));
}
然后更具体地说,这种方法:
public String getStringValue(FacesContext context) {
String result = null;
if (hasValueExpression()) {
result = getStringValueFromModel(context);
} else {
result = (null != rawValue) ? rawValue : (String) getValue();
}
return result;
}
因为hasValueExpression()
为true,所以它将尝试从模型(backingbean(中获取值。但是,由于这个bean是请求范围的,它将不会有任何值用于这个请求,因为验证刚刚失败,因此从未设置任何值。实际上,UIViewParameter
的有状态值被作为默认值返回的任何支持bean覆盖(通常为null,但这当然取决于您的bean(。
一种解决方法是使您的bean成为@ViewScoped
,这通常是一个更好的范围(我假设您使用该参数从服务中获取用户,可能没有必要在每次回发时反复这样做(。
另一种选择是创建自己的UIViewParameter
版本,如果验证失败(基本上所有其他UIInput
组件都会这样做(,该版本不会尝试从模型中获取值。
实际上并没有丢失视图参数。f: viewParam是有状态的,所以即使它不在URL中,它仍然存在。只需在绑定到视图param的setter中放置一个断点或system.out。
(如果你在viewParam无状态状态上搜索,你会发现更多信息(
我的应用程序中也有相同的功能。我切换到@ViewAccessScoped,它允许更优雅的实现。
<f:metadata>
<f:viewParam id="id" name="id" value="#{baen.id}"/>
</f:metadata>
或者,当您第一次从url获取参数时,将其保存在会话映射中并从该映射继续使用,然后保存/或更新表单干净映射。
这很棘手,但您可以尝试使用History API:恢复视图参数
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<f:metadata >
<f:viewParam name="param1" value="#{backingBean.viewParam1}" />
<f:viewParam name="param2" value="#{backingBean.viewParam2}" />
<f:viewAction action="#{view.viewMap.put('queryString', request.queryString)}" />
</f:metadata>
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<ui:fragment rendered="#{facesContext.postback}" >
<script type="text/javascript">
var url = '?#{view.viewMap.get('queryString')}';
history.replaceState({}, document.title, url);
</script>
</ui:fragment>
<h:form>
<h:inputText id="name" value="#{backingBean.name}" />
<h:message for="name" style="color: red" />
<br />
<h:commandButton value="go" action="#{backingBean.go}" />
</h:form>
<h:messages globalOnly="true" />
</h:body>
</html>