Apache Click 2.2.0: Dynamic Form Validation

Click 2.2.0の新機能紹介の日本語訳第三弾です。原文は以下のエントリです。

Click 2.2.0の新機能紹介の3回目は条件付きのフォームバリデーションです。この機能は開発者がフォームのバリデーションを制御できるようにすることで動的なフォームのバリデーションの振る舞いをシンプルにします。通常のフォームバリデーションはユーザがフォームを送信したタイミングで適用されます。しかし、動的名フォームではしばしば動的にフィールドやコンポーネントを追加するためにJavaScriptを使って不完全な状態でフォームが送信されます。
理想的には、発生したバリデーションエラーはユーザに表示されるので、不完全なサブミットに対してバリデーションを適用するべきではありません。ユーザはまだフォームの入力中なので、フォームを送信するつもりはないはずなので、バリデーションエラーが表示されるのは予想外の挙動でしょう。
以下のシンプルなサンプルを見てみましょう。以下のページには必須入力の名前フィールドと、ユーザが結婚しているかどうかを選択するチェックボックスがあります。チェックボックスがチェックされていたら、配偶者の名前を入力するためのフィールドが表示されます。配偶者の名前を入力するためのフィールドも必須入力です。

public class ValidationDemo extends BorderPage {
 
  Form form = new Form("form");
 
  // The submit button is used to submit the completed form. Note, do not
  // name the Submit button "submit". That will cause a JavaScript error when
  // the button is clicked.
  Submit ok = new Submit("ok");
 
  @Override
  public void onInit() {
    super.onInit();
    form.add(new TextField("fullName", true));
    addControl(form);
    Checkbox married = new Checkbox("married", "Married?");
 
    // We use JavaScrcipt to submit the "incomplete" form when the user
    // clicks the checkbox.
    married.setAttribute("onclick", "form.submit();");
    form.add(married);
 
    form.add(ok);
 
    // Explicitly bind the checkbox
    ClickUtils.bind(married);
    if (married.isChecked()) {
      form.add(new TextField("spouseName", true));
    }
  }
}

married.setAttribute("onclick", "form.submit();"); の行に注目してください。チェックボックスがチェックされたらJavaScriptを使ってフォームを送信しています。画面のスクリーンショットは以下のようになります。


チェックボックスがチェックされるとフォームが送信され、配偶者のフィールドが追加されます。次にフォームのバリデーションが実行され、名前と配偶者のフィールドにエラーメッセージが表示されます。このときのスクリーンショットは以下のようになります。

"ok"ボタンでのサブミット以外のサブミット時(要するにJavaScriptでのサブミット時)にエラーメッセージをクリアする1つの解決策は、以下のロジックをページのonPostイベントに追加することです。

...
 
@Override
public void onPost() {
  // Clear form errors if the "ok" submit button was not clicked
  if (!ok.isClicked()) {
    form.clearErrors();
  }
}

このときのスクリーンショットは以下のようになります。


これはよく見えます。配偶者のフィールドが追加され、エラーメッセージも表示されていません。
しかし、この解決策は理想的なものではありません。なぜならエラーメッセージが表示されていないだけで、実際にはバリデーションが実行されているからです。手動でエラーメッセージをクリアしているだけです。もっと良い方法はないでしょうか?
より良い解決策として、明示的なバインディングの作用によってフォームとフィールドのバリデーションを行わないようにすることができます。

if(!submit.isClicked()) {
  form.setValidate(false);
}

最初のサンプルを条件付きバリデーションを使用するように修正してみましょう。

public class ValidationDemo extends BorderPage {
 
  Form form = new Form("form");
 
  // The submit button is used to submit the completed form. Note, do not
  // name the Submit button "submit". That will cause a JavaScript error when
  // the button is clicked.
  Submit ok = new Submit("ok");
 
  @Override
  public void onInit() {
    super.onInit();
    form.add(new TextField("fullName", true));
    addControl(form);
    Checkbox married = new Checkbox("married", "Married?");
 
    // We use JavaScript to submit the "incomplete" form when the user
    married.setAttribute("onclick", "form.submit();");
    form.add(married);
 
    form.add(ok);
 
    // Bind the submit button. If it wasn't clicked it means the Form was
    // submitted using JavaScript and we don't want to validate yet
    ClickUtils.bind(submit);
 
    // If the Form was submitted but the submit was not clicked, don't validate
    if(form.isFormSubmission() && !submit.isClicked()) {
      form.setValidate(false);
    }
 
    if (married.isChecked()) {
      form.add(new TextField("spouseName", true));
    }
  }
}

チェックボックスをチェックした後のスクリーンショットは以下のようになります。


見ての通り、フォームのバリデーションは行われず、エラーメッセージも表示されていません。
条件付きバリデーションと明示的なバインディングはClickのページとフォームに動的名振る舞いを加えるための良い組み合わせです。