web-dev-qa-db-ja.com

検証エラーが発生した後、PrimeFaces AJAXを使用してテキストフィールドに入力するにはどうすればよいですか?

オートコンプリートと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;
    }

送信時にフォーム全体を処理する前に、オートコンプリートおよびマップAJAX要求は正常に機能します。検証に失敗した場合、ajaxリクエストの後にバッキングBeanに値が正しく設定されていても、ビューで更新できないフィールド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ライフサイクルをデバッグする方法を知っていますか?何か案は?

45
Erick Martinez

問題の原因は、次の事実を考慮することで理解できます。

  • 検証フェーズで特定の入力コンポーネントのJSF検証が成功すると、送信された値はnullに設定され、検証された値は入力コンポーネントのローカル値として設定されます。

  • 検証フェーズ中に特定の入力コンポーネントのJSF検証が失敗すると、送信された値は入力コンポーネントに保持されます。

  • 検証フェーズ後に少なくとも1つの入力コンポーネントが無効な場合、JSFはどの入力コンポーネントのモデル値も更新しません。 JSFは、応答フェーズのレンダリングに直接進みます。

  • JSFは入力コンポーネントをレンダリングするときに、送信された値がnullではないかどうかを最初にテストしてから表示します。そうでない場合は、ローカル値がnullでない場合に表示します。モデル値。

  • 同じJSFビューと対話している限り、同じコンポーネント状態を扱っています。

したがって、特定のフォーム送信の検証に失敗し、異なるajaxアクションまたは別のajaxフォームで入力フィールドの値を更新する必要が生じた場合(たとえば、ドロップダウン選択またはいくつかの結果に応じてフィールドに入力するモーダルダイアログフォームなど)、JSFに呼び出しアクション中に編集されたモデル値を表示させるために、基本的にターゲット入力コンポーネントをリセットする必要があります。それ以外の場合、JSFは検証の失敗時のローカル値を表示し、無効な状態に保ちます。

あなたの特定のケースの方法の1つは、 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()リスナーメソッド内で、またはhandleAddressChange()リスナーメソッドを呼び出している入力コンポーネントに <f:actionListener> としてアタッチする再利用可能な ActionListener 実装内で実行できます。


具体的な問題に戻ると、これは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> は、この種類も解決する新しいブール属性resetValuesを取得しました。追加のタグを必要とせずに問題の。

更新3:JSF 2.2では、<f:ajax resetValues>と同じ考え方に従って、 <p:ajax resetValues> が導入されました。このソリューションは現在、標準JSF APIの一部です。

82
BalusC

タグ内<p:ajax/>、属性を追加してくださいresetValues="true"は、データを再度取得するようビューに指示します。これにより、問題を修正できるはずです。

3
BobGao