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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6642242/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 11:01:52  来源:igfitidea点击:

How can I populate a text field using PrimeFaces AJAX after validation errors occur?

ajaxvalidationjsfprimefaces

提问by Erick Martinez

I have a form in a view which performs ajax partial processing for autocompletion and gmap localization. My backing bean instantiates an entity object "Address" and is to this object that the form's inputs are referenced:

我在视图中有一个表单,它为自动完成和 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("[^\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;
    }

Autocompletion and map ajax requests work fine before I process the whole form on submit. If validation fails, ajax still works ok except for the field fullAddress which is unable to update in the view, even when it's value is correctly set on the backing bean after the ajax request.

在我处理整个表单提交之前,自动完成和映射 ajax 请求工作正常。如果验证失败,除了在视图中无法更新的字段 fullAddress 之外,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"/>

If I refresh the page, validation error messages disappear and the ajax completes fullAddress field as expected.

如果我刷新页面,验证错误消息就会消失,ajax 会按预期完成 fullAddress 字段。

Another weird behavior occurs also during validation: I have disabled bean validation for a form field, as seen on the code. This work alright until other validation errors are found, then, if I resubmit the form, JSF makes bean validation for this field!

在验证过程中还会发生另一个奇怪的行为:如代码所示,我已禁用表单字段的 bean 验证。这个工作正常,直到发现其他验证错误,然后,如果我重新提交表单,JSF 会对此字段进行 bean 验证!

I guess I am missing something in during the validation state but I can't figure out what's wrong with it. Does anyone knows how to debug JSF life cycle? Any ideas?

我想我在验证状态中遗漏了一些东西,但我无法弄清楚它有什么问题。有谁知道如何调试 JSF 生命周期?有任何想法吗?

回答by BalusC

The cause of the problem can be understood by considering the following facts:

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

  • When JSF validation succeeds for a particular input component during the validations phase, then the submitted value is set to nulland the validated value is set as local value of the input component.

  • When JSF validation fails for a particular input component during the validations phase, then the submitted value is kept in the input component.

  • When at least one input component is invalid after the validations phase, then JSF will not update the model values for any of the input components. JSF will directly proceed to render response phase.

  • When JSF renders input components, then it will first test if the submitted value is not nulland then display it, else if the local value is not nulland then display it, else it will display the model value.

  • As long as you're interacting with the same JSF view, you're dealing with the same component state.

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

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

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

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

  • 只要您与同一个 JSF 视图交互,您就在处理相同的组件状态。

So, when the validation has failed for a particular form submit and you happen to need to update the values of input fields by a different ajax action or even a different ajax form (e.g. populating a field depending on a dropdown selection or the result of some modal dialog form, etc), then you basically need to reset the target input components in order to get JSF to display the model value which was edited during invoke action. Otherwise JSF will still display its local value as it was during the validation failure and keep them in an invalidated state.

因此,当特定表单提交的验证失败并且您碰巧需要通过不同的 ajax 操作甚至不同的 ajax 表单更新输入字段的值时(例如,根据下拉选择或某些结果填充字段)模态对话框等),那么您基本上需要重置目标输入组件,以便让 JSF 显示在调用操作期间编辑的模型值。否则 JSF 仍将显示其在验证失败期间的本地值,并使它们处于无效状态。

One of the ways in your particular caseis to manually collect all IDs of input components which are to be updated/re-rendered by PartialViewContext#getRenderIds()and then manually reset its state and submitted values by EditableValueHolder#resetValue().

您的特定情况下,其中一种方法是手动收集要更新/重新呈现的输入组件的所有 ID,PartialViewContext#getRenderIds()然后手动重置其状态和提交的值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();
}

You could do this inside the handleAddressChange()listener method, or inside an reuseable ActionListenerimplementation which you attach as <f:actionListener>to the input component which is calling the handleAddressChange()listener method.

您可以在handleAddressChange()listener 方法中执行此操作,也可以在将ActionListener其附加<f:actionListener>到调用handleAddressChange()listener 方法的输入组件的可重用实现中执行此操作。



Coming back to the concrete problem, I'd imagine that this is an oversight in the JSF2 specification. It would make much more sense to us, JSF developers, when the JSF specification mandates the following:

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

  • When JSF needs to update/re-render an input component by an ajax request, and that input component is not included in the process/execute of the ajax request, then JSF should reset the input component's value.
  • 当 JSF 需要通过 ajax 请求更新/重新呈现输入组件,并且该输入组件未包含在 ajax 请求的进程/执行中时,JSF 应重置输入组件的值。

This has been reported as JSF issue 1060and a complete and reuseable solution has been implemented in the OmniFaceslibrary as ResetInputAjaxActionListener(source code hereand showcase demo here).

这已被报告为JSF 问题 1060,并且已在OmniFaces库中实现了一个完整且可重用的解决方案ResetInputAjaxActionListener(源代码在这里,演示演示在这里)。

Update 1:Since version 3.4, PrimeFaces has based on this idea also introduced a complete and reusable solution in flavor of <p:resetInput>.

更新一:从 3.4 版本开始,PrimeFaces 基于这个想法也引入了一个完整的、可重用的解决方案<p:resetInput>

Update 2:Since version 4.0, the <p:ajax>got a new boolean attribute resetValueswhich should also solve this kind of problem without the need for an additional tag.

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

Update 3:JSF 2.2 introduced <f:ajax resetValues>, following the same idea as <p:ajax resetValues>. The solution is now part of standard JSF API.

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

回答by Ana

As BalusC explained, you can also add a reusable listener that cleans all input values, for instance:

正如 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();
            }
        }
    }
  }
}

And use it whenever you need to clean your local values:

并在需要清理本地值时使用它:

<f:actionListener type="com.cacib.bean.CleanLocalValuesListener"/>

回答by BobGao

Inside your tag <p:ajax/>, please add an attribute resetValues="true"to tell the view to fetch data again, in this way should be able to fix your problem.

在您的标签内<p:ajax/>,请添加一个属性resetValues="true"来告诉视图再次获取数据,这样应该可以解决您的问题。