Skip to main content
 首页 » 编程设计

ajax之发生验证错误后如何使用 PrimeFaces AJAX 填充文本字段

2024年06月20日22webabcd

我在 View 中有一个表单,它执行自动完成和 gmap 本地化的 ajax 部分处理。我的支持 bean 实例化一个实体对象“Address”,并且表单的输入被引用到该对象:

@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("[^\\p{ASCII}]", ""); 
 
        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; 
    } 

在提交时处理整个表单之前,自动完成和 map ajax 请求工作正常。如果验证失败,除了 fullAddress 字段无法在 View 中更新之外,ajax 仍然可以正常工作,即使在 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 生命周期?有什么想法吗?

请您参考如下方法:

可以通过考虑以下事实来理解问题的原因:

  • 当 JSF 在验证阶段对特定输入组件验证成功时,提交的值将设置为 null并将验证后的值设置为输入组件的本地值。

  • 当 JSF 在验证阶段对特定输入组件进行验证失败时,提交的值将保留在输入组件中。

  • 当验证阶段后至少有一个输入组件无效时,JSF 将不会更新任何输入组件的模型值。 JSF 将直接进入渲染响应阶段。

  • 当 JSF 渲染输入组件时,它会首先测试提交的值是否不是 null然后显示它,否则如果本地值不是 null然后显示它,否则显示模型值。

  • 只要您与相同的 JSF View 交互,您就会处理相同的组件状态。

因此,当特定表单提交的验证失败并且您恰好需要通过不同的 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> 获得了一个新的 bool 属性 resetValues这也应该可以解决此类问题,而不需要额外的标签。

更新 3:引入 JSF 2.2 <f:ajax resetValues> ,遵循与<p:ajax resetValues>相同的想法。该解决方案现在是标准 JSF API 的一部分。