45 votes

Comment puis-je remplir un champ de texte à l'aide de PrimeFaces AJAX après des erreurs de validation?

J'ai un formulaire dans une vue qui effectue ajax traitement partiel pour l'auto-complétion et plan de localisation. Mon backing bean instancie un objet entité "Adresse" et est à cet objet que la forme des entrées sont référencés:

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

L'auto-complétion et la carte de requêtes ajax fonctionnent bien avant de me traiter l'intégralité du formulaire sur "soumettre". Si la validation échoue, ajax fonctionne toujours ok sauf pour le champ fullAddress qui est incapable de mise à jour de la vue, même lorsque sa valeur est correctement fixé sur le backing bean après la requête 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"/>

Si j'actualise la page, les messages d'erreur de validation disparaissent et l'ajax se termine champ fullAddress comme prévu.

Un autre comportement étrange se produit également lors de la validation: j'ai désactivé bean validation pour un champ de formulaire, comme on le voit sur le code. Ce travail va bien jusqu'à ce que d'autres erreurs de validation sont trouvés, alors, si j'ai soumettre à nouveau le formulaire, JSF fait bean validation pour ce domaine!

Je suppose que je manque quelque chose dans le cadre de la validation de l'état, mais je n'arrive pas à comprendre quel est le problème avec elle. Personne ne sait comment déboguer JSF le cycle de vie? Des idées?

82voto

BalusC Points 498232

La cause du problème peut être compris en considérant les faits suivants:

  • Lors de la JSF validation réussit pour un composant d'entrée pendant les validations de phase, puis le soumis valeur est null et validés valeur est définie comme la valeur locale de la composante d'entrée.

  • Lors de la JSF validation échoue pour un composant d'entrée pendant les validations de phase, puis la valeur envoyée est conservé dans le composant d'entrée.

  • Lorsqu'au moins un composant d'entrée n'est pas valide après les validations de phase, puis JSF ne pas mettre à jour le modèle de valeurs pour l'entrée des composants. JSF va directement vous rendre phase de réponse.

  • Lors de la JSF rend l'entrée des composants, puis il va d'abord tester si la valeur soumise n'est pas null , puis l'afficher, sinon si la valeur locale n'est pas null , puis l'afficher, sinon il affichera la valeur de modèle.

  • Tant que vous êtes en interaction avec la même vue JSF, vous avez affaire à la même composante de l'état.

Ainsi, lors de la validation a échoué pour une forme particulière de soumettre et vous avez besoin de mettre à jour les valeurs des champs de saisie par un autre ajax d'action ou même une autre ajax formulaire (par exemple, le remplissage d'un champ en fonction d'une liste déroulante de sélection ou le résultat d'une boîte de dialogue modale de forme, etc), alors vous avez essentiellement besoin de réinitialiser la cible d'entrée des composants afin d'obtenir JSF pour afficher la valeur de modèle qui a été édité lors d'invoquer l'action. Sinon JSF continue d'afficher sa valeur locale comme il l'a été au cours de l'échec de validation et de les conserver dans un invalidé état.

L'une des façons dans votre cas en particulier, est de collecter manuellement tous les Id de l'entrée des composants qui doivent être mis à jour et un nouveau rendu en PartialViewContext#getRenderIds() manuellement, puis réinitialiser l'état et les valeurs soumises par 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();
}

Vous pouvez faire cela à l'intérieur de l' handleAddressChange() méthode d'écouteur, ou à l'intérieur d'une réutilisables, ActionListener mise en œuvre qui vous joindre à titre d' <f:actionListener> pour le composant d'entrée qui est de l'appel de la handleAddressChange() méthode d'écouteur.


En revenant au problème concret, j'imagine que c'est un oubli dans la JSF2 spécification. Il ferait beaucoup plus de sens pour nous, JSF développeurs, lorsque la spécification JSF mandats suivants:

  • Lors de la JSF doit mettre à jour/re-rendre un composant d'entrée par une requête ajax, et que la composante d'entrée n'est pas inclus dans le processus d'/exécution de la requête ajax, puis JSF doit réinitialiser le composant d'entrée de la valeur.

Cela a été rapporté que JSF question 1060 et complète et réutilisables, et la solution a été mise en œuvre dans le OmniFaces de la bibliothèque en tant que ResetInputAjaxActionListener (code source ici et la vitrine de démonstration ici). Plus tard, depuis la version 3.4, PrimeFaces est basé sur cette idée a également introduit un complet et réutilisables solution dans la saveur de l' <p:resetInput>. Plus tard, depuis la version 4.0, l' <p:ajax> obtenu un nouvel attribut booléen resetValues qui devrait également résoudre ce genre de problème sans avoir besoin d'un supplément de la balise.

6voto

Latent Boy Points 147

Les Primefaces déjà pris en charge pour éviter ce problème:
http://www.primefaces.org/showcase/ui/misc/resetInput.xhtml

3voto

Ana Points 53

Comme BalusC l'a expliqué, vous pouvez également ajouter un écouteur réutilisable qui nettoie toutes les valeurs d'entrée, par exemple:

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

Et utilisez-le chaque fois que vous avez besoin de nettoyer vos valeurs locales:

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

3voto

BobGao Points 181

Dans votre balise <p:ajax/> , ajoutez un attribut resetValues="true" pour indiquer à la vue de récupérer les données, ce qui devrait pouvoir résoudre votre problème.

0voto

opfau Points 25

Tout mentionné ici ne fonctionne pas pour moi, pas complètement. J'ai un sorcier. Sur la 1ère page, les valeurs sont réinitialisées. Sur la 2ème page de l'assistant, les valeurs ne sont pas réinitialisées (ces valeurs sont rendues multiples avec p: dataList). Une solution pour cela?

Tout ajouté à la 1ère page maintenant et le problème est le même.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X