发生验证错误后,如何使用 PrimeFaces AJAX 填充文本字段?
Posted
技术标签:
【中文标题】发生验证错误后,如何使用 PrimeFaces AJAX 填充文本字段?【英文标题】:How can I populate a text field using PrimeFaces AJAX after validation errors occur? 【发布时间】:2011-10-02 07:17:22 【问题描述】:我在视图中有一个表单,它为自动完成和 gmap 本地化执行 ajax 部分处理。我的支持 bean 实例化了一个实体对象“地址”,并且该对象引用了表单的输入:
@ManagedBean(name="mybean")
@SessionScoped
public class Mybean implements Serializable
private Address address;
private String fullAddress;
private String center = "0,0";
....
public mybean()
address = new Address();
...
public void handleAddressChange()
String c = "";
c = (address.getAddressLine1() != null) c += address.getAddressLine1();
c = (address.getAddressLine2() != null) c += ", " + address.getAddressLine2();
c = (address.getCity() != null) c += ", " + address.getCity();
c = (address.getState() != null) c += ", " + address.getState();
fullAddress = c;
addMessage(new FacesMessage(FacesMessage.SEVERITY_INFO, "Full Address", fullAddress));
try
geocodeAddress(fullAddress);
catch (MalformedURLException ex)
Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex);
catch (UnsupportedEncodingException ex)
Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex);
catch (IOException ex)
Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex);
catch (ParserConfigurationException ex)
Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex);
catch (SAXException ex)
Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex);
catch (XPathExpressionException ex)
Logger.getLogger(Mybean.class.getName()).log(Level.SEVERE, null, ex);
private void geocodeAddress(String address)
throws MalformedURLException, UnsupportedEncodingException,
IOException, ParserConfigurationException, SAXException,
XPathExpressionException
// prepare a URL to the geocoder
address = Normalizer.normalize(address, Normalizer.Form.NFD);
address = address.replaceAll("[^\\pASCII]", "");
URL url = new URL(GEOCODER_REQUEST_PREFIX_FOR_XML + "?address="
+ URLEncoder.encode(address, "UTF-8") + "&sensor=false");
// prepare an HTTP connection to the geocoder
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
Document geocoderResultDocument = null;
try
// open the connection and get results as InputSource.
conn.connect();
InputSource geocoderResultInputSource = new InputSource(conn.getInputStream());
// read result and parse into XML Document
geocoderResultDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(geocoderResultInputSource);
finally
conn.disconnect();
// prepare XPath
XPath xpath = XPathFactory.newInstance().newXPath();
// extract the result
NodeList resultNodeList = null;
// c) extract the coordinates of the first result
resultNodeList = (NodeList) xpath.evaluate(
"/GeocodeResponse/result[1]/geometry/location/*",
geocoderResultDocument, XPathConstants.NODESET);
String lat = "";
String lng = "";
for (int i = 0; i < resultNodeList.getLength(); ++i)
Node node = resultNodeList.item(i);
if ("lat".equals(node.getNodeName()))
lat = node.getTextContent();
if ("lng".equals(node.getNodeName()))
lng = node.getTextContent();
center = lat + "," + lng;
在我处理提交时的整个表单之前,自动完成和映射 ajax 请求工作正常。如果验证失败,ajax 仍然可以正常工作,除了在视图中无法更新的字段 fullAddress,即使它的值在 ajax 请求后在支持 bean 上正确设置。
<h:outputLabel for="address1" value="#label.addressLine1"/>
<p:inputText required="true" id="address1"
value="#mybean.address.addressLine1">
<p:ajax update="latLng,fullAddress"
listener="#mybean.handleAddressChange"
process="@this"/>
</p:inputText>
<p:message for="address1"/>
<h:outputLabel for="address2" value="#label.addressLine2"/>
<p:inputText id="address2"
value="#mybean.address.addressLine2"
label="#label.addressLine2">
<f:validateBean disabled="#true" />
<p:ajax update="latLng,fullAddress"
listener="#mybean.handleAddressChange"
process="address1,@this"/>
</p:inputText>
<p:message for="address2"/>
<h:outputLabel for="city" value="#label.city"/>
<p:inputText required="true"
id="city" value="#mybean.address.city"
label="#label.city">
<p:ajax update="latLng,fullAddress"
listener="#mybean.handleAddressChange"
process="address1,address2,@this"/>
</p:inputText>
<p:message for="city"/>
<h:outputLabel for="state" value="#label.state"/>
<p:autoComplete id="state" value="#mybean.address.state"
completeMethod="#mybean.completeState"
selectListener="#mybean.handleStateSelect"
onSelectUpdate="latLng,fullAddress,growl"
required="true">
<p:ajax process="address1,address2,city,@this"/>
</p:autoComplete>
<p:message for="state"/>
<h:outputLabel for="fullAddress" value="#label.fullAddress"/>
<p:inputText id="fullAddress" value="#mybean.fullAddress"
style="width: 300px;"
label="#label.fullAddress"/>
<p:commandButton value="#label.locate" process="@this,fullAddress"
update="growl,latLng"
actionListener="#mybean.findOnMap"
id="findOnMap"/>
<p:gmap id="latLng" center="#mybean.center" zoom="18"
type="ROADMAP"
style="width:600px;height:400px;margin-bottom:10px;"
model="#mybean.mapModel"
onPointClick="handlePointClick(event);"
pointSelectListener="#mybean.onPointSelect"
onPointSelectUpdate="growl"
draggable="true"
markerDragListener="#mybean.onMarkerDrag"
onMarkerDragUpdate="growl" widgetVar="map"/>
<p:commandButton id="register" value="#label.register"
action="#mybean.register" ajax="false"/>
如果我刷新页面,验证错误消息会消失,并且 ajax 会按预期完成 fullAddress 字段。
另一个奇怪的行为也发生在验证过程中:我已禁用表单字段的 bean 验证,如代码所示。在发现其他验证错误之前一切正常,然后,如果我重新提交表单,JSF 会对该字段进行 bean 验证!
我想我在验证状态期间遗漏了一些东西,但我无法弄清楚它有什么问题。有谁知道如何调试 JSF 生命周期?有什么想法吗?
【问题讨论】:
【参考方案1】:问题的原因可以通过考虑以下事实来理解:
当特定输入组件在验证阶段的 JSF 验证成功时,提交的值将设置为 null
,并且已验证的值设置为输入组件的本地值。
当特定输入组件在验证阶段的 JSF 验证失败时,提交的值将保留在输入组件中。
如果在验证阶段后至少有一个输入组件无效,则 JSF 不会更新任何输入组件的模型值。 JSF 将直接进入渲染响应阶段。
JSF在渲染输入组件时,会先测试提交的值是否不是null
再显示,否则本地值不是null
再显示,否则显示模型值。
只要您与同一个 JSF 视图交互,您就在处理同一个组件状态。
因此,当特定表单提交的验证失败并且您碰巧需要通过不同的 ajax 操作甚至不同的 ajax 表单来更新输入字段的值(例如,根据下拉选择或一些模态对话框表单的结果等),那么您基本上需要重置目标输入组件才能让 JSF 显示在调用操作期间编辑的模型值。否则,JSF 仍将显示其在验证失败期间的本地值,并将它们保持在无效状态。
您的特殊情况中的一种方法是手动收集将由PartialViewContext#getRenderIds()
更新/重新呈现的输入组件的所有 ID,然后通过手动重置其状态和提交的值EditableValueHolder#resetValue()
.
FacesContext facesContext = FacesContext.getCurrentInstance();
PartialViewContext partialViewContext = facesContext.getPartialViewContext();
Collection<String> renderIds = partialViewContext.getRenderIds();
for (String renderId : renderIds)
UIComponent component = viewRoot.findComponent(renderId);
EditableValueHolder input = (EditableValueHolder) component;
input.resetValue();
您可以在 handleAddressChange()
侦听器方法中执行此操作,或者在可重用的 ActionListener
实现中执行此操作,您将其作为 <f:actionListener>
附加到调用 handleAddressChange()
侦听器方法的输入组件。
回到具体问题,我想这是 JSF2 规范中的疏忽。当 JSF 规范要求以下内容时,对我们 JSF 开发人员来说会更有意义:
当 JSF 需要通过 ajax 请求更新/重新呈现输入组件,并且该输入组件未包含在 ajax 请求的处理/执行中,则 JSF 应重置输入组件的值。这已报告为JSF issue 1060,并且已在OmniFaces 库中以ResetInputAjaxActionListener
实现了一个完整且可重复使用的解决方案(源代码here 和展示演示here)。
更新 1: 从 3.4 版开始,PrimeFaces 基于这个想法还引入了一个完整且可重复使用的解决方案,类似于 <p:resetInput>
。
更新 2: 从 4.0 版开始,<p:ajax>
有了一个新的布尔属性 resetValues
,它也应该可以解决此类问题,而无需额外的标记。
更新 3: JSF 2.2 引入了 <f:ajax resetValues>
,遵循与 <p:ajax resetValues>
相同的想法。该解决方案现在是标准 JSF API 的一部分。
【讨论】:
我会徘徊,如果它是一个严重的错误,当简单而琐碎的任务需要大量的知识和代码行时。这个技术有问题,不知道还是程序员的工具,还是管理员做任务的工具,没人管。 像往常一样,在调查了这个问题一天多之后,你救了我。在此之前我一直使用primefaces,这就是为什么我从未遇到过这个问题。谢谢@balusC @BalusC:对于第一个要点,当您通过 input.setSubmittedValue(request.getParameter(input.getClientId())) 收集用户输入值时。验证成功后,此提交的值设置为 null。然后将验证值设置为本地值。这个本地值和bean值有什么区别。我的印象是提交的值在验证成功后设置为 bean 实例变量值,即在 UPDATE MODEL VALUES PHASE 中。 @Shirgill:本地值是转换/验证的提交值(因此不一定是String
,而提交的值始终是String
)。
@BalusC:谢谢,您的回答帮助我了解了 renderResponse 阶段的工作原理。我正在使用 JSF 2.3。就我而言,我在重置 dataTable 中的输入字段 以外的任何字段的值时都没有遇到任何问题。而且,在这种情况下,即使我使用您的“更新 3”解决方案,它也无济于事。重置值的方法是否不适用于dataTables?【参考方案2】:
正如 BalusC 所解释的,您还可以添加一个可重用的侦听器来清除所有输入值,例如:
public class CleanLocalValuesListener implements ActionListener
@Override
public void processAction(ActionEvent actionEvent) throws AbortProcessingException
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = context.getViewRoot();
List<UIComponent> children = viewRoot.getChildren();
resetInputValues(children);
private void resetInputValues(List<UIComponent> children)
for (UIComponent component : children)
if (component.getChildCount() > 0)
resetInputValues(component.getChildren());
else
if (component instanceof EditableValueHolder)
EditableValueHolder input = (EditableValueHolder) component;
input.resetValue();
并在需要清理本地值时使用它:
<f:actionListener type="com.cacib.bean.CleanLocalValuesListener"/>
【讨论】:
这种方法对于<ui:repeat>
和 <h:dataTable>
等转发器组件中的输入失败。你真的需要一个完整的树参观。另请参阅我的答案中指向ResetInputAjaxActionListener
源代码的链接。【参考方案3】:
在你的标签<p:ajax/>
中,请添加一个属性resetValues="true"
告诉视图重新获取数据,这样应该可以解决你的问题。
【讨论】:
请注意,在 OP 提出问题时,此功能不可用。正如您在我的回答中所指出的,PrimeFaces 根据我的回答承认了这个问题,并在之后对其库进行了改进。以上是关于发生验证错误后,如何使用 PrimeFaces AJAX 填充文本字段?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 PrimeFaces oncomplete 中触发 args.validationFailed
当我在 Primefaces 上打开带有必填字段的模态对话框时,如何阻止验证触发