Как заполнить текстовое поле с помощью PrimeFaces AJAX после возникновения ошибок проверки? - PullRequest
45 голосов
/ 10 июля 2011

У меня есть форма в представлении, которая выполняет частичную обработку ajax для автозаполнения и локализации gmap. Мой компонент поддержки создает объект-объект "Адрес" и ссылается на этот объект на входы формы:

@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;
    }

Запросы на автозаполнение и ajax карты работают нормально, прежде чем я обработаю всю форму при отправке. Если проверка завершается неудачно, ajax по-прежнему работает нормально, за исключением поля fullAddress, которое не может обновляться в представлении, даже если его значение правильно задано в компоненте поддержки после запроса ajax.

<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? Есть идеи?

Ответы [ 3 ]

82 голосов
/ 27 июля 2011

Причину проблемы можно понять, рассмотрев следующие факты:

  • Когда проверка JSF завершается успешно для определенного входного компонента на этапе проверки, переданное значение устанавливается наnull и проверенное значение устанавливается как локальное значение компонента ввода.

  • Если проверка JSF не удалась для определенного компонента ввода во время фазы проверки, то отправленное значение сохраняется вкомпонент ввода.

  • Если по крайней мере один компонент ввода недействителен после фазы проверки, JSF не будет обновлять значения модели ни для одного из компонентов ввода.JSF будет непосредственно переходить к фазе ответа.

  • Когда JSF рендерит входные компоненты, он сначала проверит, является ли переданное значение не null, а затем отобразит его, в противном случае - локальноезначение не null, а затем отобразите его, в противном случае будет отображаться значение модели.

  • Пока вы взаимодействуете с одним и тем же представлением JSF, вы имеете дело сто же состояние компонента.

Итак, когда проверка не удалась для конкретной формы, отправьте, и вам может потребоваться обновить значения полей ввода с помощью другого действия AJAX или даже другого AJAX.формы (например, заполнение поля в зависимости от выпадающего списка или результата какой-либо модальной диалоговой формы и т. д.), тогда вам в основном необходимо сбросить целевые входные компоненты, чтобы JSF отображал значение модели, которое было отредактировано во время действия вызова.В противном случае JSF по-прежнему будет отображать свое локальное значение, как это было во время ошибки проверки, и сохранять их в недействительном состоянии.

Один из способов вашего конкретного случая - вручную собрать все идентификаторывходные компоненты, которые должны обновляться / перерисовываться с помощью 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();
}

Это можно сделать внутри метода прослушивателя handleAddressChange() или внутри многократно используемой реализации ActionListener, которую вы присоединяете как <f:actionListener> к компоненту ввода, вызывающему handleAddressChange()Метод слушателя.


Возвращаясь к конкретной проблеме, я бы предположил, что это упущение в спецификации JSF2.Нам, разработчикам JSF, было бы гораздо больше смысла, когда спецификация JSF предписывает следующее:

  • Когда JSF необходимо обновить / повторно обработать входной компонент с помощью запроса ajax и этот входной компонентне включается в процесс / выполнение запроса ajax, тогда JSF должен сбросить значение компонента ввода.

Это было сообщено как выпуск JSF 1060 , полный и многократно используемыйрешение реализовано в библиотеке OmniFaces как ResetInputAjaxActionListener (исходный код здесь и демонстрационная демонстрация здесь ).

Обновление 1: Начиная с версии 3.4, PrimeFaces основывалась на этой идее и также представила полное и многократно используемое решение в стиле <p:resetInput>.

Обновление 2: Начиная с версии 4.0, <p:ajax> получил новый логический атрибут resetValues, который также должен решать эту проблему без необходимостидополнительный тег

Обновление 3: JSF 2.2 представил <f:ajax resetValues>, следуя той же идее, что и <p:ajax resetValues>.Теперь решение является частью стандартного API JSF.

3 голосов
/ 21 августа 2014

Внутри вашего тега <p:ajax/>, пожалуйста, добавьте атрибут resetValues="true", чтобы сообщить представлению о необходимости повторной выборки данных, таким образом, вы сможете исправить вашу проблему.

3 голосов
/ 15 марта 2013

Как объяснил 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"/>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...