Подтвердить что ты не робот

Как заполнить текстовое поле с помощью PrimeFaces AJAX после ошибок проверки?

У меня есть форма в представлении, которое выполняет частичную обработку ajax для автозаполнения и локализации gmap. Моя поддержка 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;
    }

Задания автозаполнения и карты ajax работают нормально, прежде чем обрабатывать всю форму при отправке. Если проверка не удалась, ajax все еще работает нормально, за исключением поля fullAddress, который не может обновиться в представлении, даже если его значение правильно установлено на основе bean после запроса 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? Любые идеи?

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 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"/>

Ответ 3

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