发生验证错误后,如何使用 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 实现中执行此操作,您将其作为 &lt;f:actionListener&gt; 附加到调用 handleAddressChange() 侦听器方法的输入组件。


回到具体问题,我想这是 JSF2 规范中的疏忽。当 JSF 规范要求以下内容时,对我们 JSF 开发人员来说会更有意义:

当 JSF 需要通过 ajax 请求更新/重新呈现输入组件,并且该输入组件未包含在 ajax 请求的处理/执行中,则 JSF 应重置输入组件的值。

这已报告为JSF issue 1060,并且已在OmniFaces 库中以ResetInputAjaxActionListener 实现了一个完整且可重复使用的解决方案(源代码here 和展示演示here)。

更新 1: 从 3.4 版开始,PrimeFaces 基于这个想法还引入了一个完整且可重复使用的解决方案,类似于 &lt;p:resetInput&gt;

更新 2: 从 4.0 版开始,&lt;p:ajax&gt; 有了一个新的布尔属性 resetValues,它也应该可以解决此类问题,而无需额外的标记。

更新 3: JSF 2.2 引入了 &lt;f:ajax resetValues&gt;,遵循与 &lt;p:ajax resetValues&gt; 相同的想法。该解决方案现在是标准 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"/>

【讨论】:

这种方法对于 &lt;ui:repeat&gt;&lt;h:dataTable&gt; 等转发器组件中的输入失败。你真的需要一个完整的树参观。另请参阅我的答案中指向ResetInputAjaxActionListener 源代码的链接。【参考方案3】:

在你的标签&lt;p:ajax/&gt;中,请添加一个属性resetValues="true"告诉视图重新获取数据,这样应该可以解决你的问题。

【讨论】:

请注意,在 OP 提出问题时,此功能不可用。正如您在我的回答中所指出的,PrimeFaces 根据我的回答承认了这个问题,并在之后对其库进行了改进。

以上是关于发生验证错误后,如何使用 PrimeFaces AJAX 填充文本字段?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PrimeFaces oncomplete 中触发 args.validationFailed

如何验证 primefaces 日期

Primefaces,如何禁用表单验证?

当我在 Primefaces 上打开带有必填字段的模态对话框时,如何阻止验证触发

本机win10远程桌面连接发生身份验证错误,代码:0x80004005如何解决?

PrimeFaces禁用取消按钮上的验证[重复]