カジュアルな技術ノート

小難しい技術のお話をできるだけわかりやすく...

th:field と th:object によるフォームバインディング機能(inputタグ・password編)

th:fieldth:object が提供する機能説明の続きです。
今回は input タグの type="password" をみていきましょう。

th:fieldth:object の提供機能はタグや属性値によって異なります。
サポートされているタグの一覧はこちら

casual-tech-note.hatenablog.com

inputタグ・type="password" における提供機能

パスワード入力を受け付けるinputタグにおける th:fieldth:object の提供機能は以下の三つです。

  • id 属性th:field に指定した変数名を代入
  • name 属性th:field に指定した変数名を代入
  • value 属性空文字 を代入

inputタグ・基本入力系での提供機能と似ていますが、微妙に違いますね。

value 属性 にはフィールドの値は代入されずに必ず空文字が入ります。このような仕様になっている背景をサンプルコードを通して見ていきましょう。

シンプルなサンプルコード

inputタグ・password における th:fieldth:object の提供機能を確認するために次のようなサンプル画面を考えていきましょう。 f:id:shin-kinoshita:20180923193836p:plain

名前とパスワードを入力として受け付け、受け取った値を出力するだけのシンプル画面です。
th:fieldth:object を使用した実際のコードは次の通りです。

<!-- Input Page -->
<h1>Input page</h1>
<form action="/binding/input/password" method="post" th:object="${passwordForm}">
  <p>name</p>
  <input type="text" th:field="*{name}"/>
  <p>password</p>
  <input type="password" th:field="*{password}"/>
  <br/>
  <input type="submit" value="submit"/>
</form>
<!-- Output Page -->
<h1>Output Page</h1>
<p>Thank you for your submitting</p>
<p>Your name is 「<span th:text="${passwordForm.name}"></span></p>
<p>Your password is 「<span th:text="${passwordForm.password}"></span></p>

名前とパスワードの入力値を受け取るためのフォームオブジェクトとして passwordForm を用意しています。パスワード入力のレンダリング結果を見ると、id 属性name 属性th:field で指定したフィールド名、password が代入されていることが確認できます。
f:id:shin-kinoshita:20181007005620p:plain

一方 value 属性 は空になっています( 仮に password フィールドに値が入っていたとして空になります)。

完全なフロントエンドのコードはこちらに用意しておきました。

パスワードのバリデーションチェック

今回の Inputタグの type="password" では、なぜ value 属性 に空文字を代入するのでしょうか?。実はこれによって パスワードのバリデーションチェックに引っかかった際に、フィールド値を空にする手間を省いています。

ここでは例として、 パスワードの入力値は8〜16文字でなければならない という制限をかけることを考えて見ましょう。つまり入力のパスワード長がこの範囲外なら、バリデーションエラーにより再度入力画面に戻されるという仕様をイメージしてください。
f:id:shin-kinoshita:20180923195007p:plain

バリデーションを施している実装を見て見ましょう。
こちらはバックエンドにて実装します(完全版はこちら)。

public class PasswordForm {
    private String name;

    // ここでバリデーションを定義
    @Size(min = 8, max = 16)
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

フォームクラスの password フィールドに @Size を使用することで、文字数制限を実現しています。

そしてバリデーションエラーの有無によって遷移先を変更するする必要があります。これはコントローラーの output メソッドで設定します。

@Controller
@RequestMapping("/binding/input/password")
public class PasswordController {
    @GetMapping
    public String input(Model model) {
        model.addAttribute("passwordForm", new PasswordForm());
        return "binding/input/password/input";
    }

    @PostMapping
    public String output(@Validated PasswordForm passwordForm, BindingResult br) {
        if (br.hasErrors()) {
            // Input Page へ遷移
            return "binding/input/password/input";
        }
        // Output Page へ遷移
        return "binding/input/password/output";
    }
}

バリデーションエラーが存在するときは Input Page へ、存在しないときは Output Page へ遷移するように分岐しています。

またエラーメッセージを表示するために、Input Page の view の実装も修正します。

<h1>Input page</h1>
<form action="/binding/input/password" method="post" th:object="${passwordForm}">
  <p>name</p>
  <input type="text" th:field="*{name}"/>
  <p>password</p>
  <!-- バリデーションエラーはここで表示だよー -->
  <ul th:if="${#fields.hasErrors('password')}">
    <li th:each="e : ${#fields.detailedErrors('password')}" th:text="${e.message}" style="color: red;"></li>
  </ul>
  <!------------------------->
  <input type="password" th:field="*{password}"/>
  <br/>
  <input type="submit" value="submit"/>
</form>

さて、今回のようにバリデーションエラーによってユーザに入力値の修正を求める場合、すでに入力してもらった全項目の値を再入力する手間を防ぐためにそのまま保持する必要があります(今回の例では name の入力値ですね)。具体的には passwordForm を output メソッドの引数で受け取った時点で model に格納されているので、これによって保持しています。

しかしパスワードは中でもデリケートな項目であるため、パスワードの入力値のバリデーション結果に関係なく再入力を促すのが普通です。つまり password フィールドだけ値を初期化したい。こういう事情で、value 属性だけには空文字を設定してくれているというわけです。

まとめ

input タグ・type="password" における th:fieldth:object における提供機能は、value 属性における扱い以外は基本入力系と同じでいわばマイナーチェンジです。
なかなか使っていても見落としがちな機能ですが意外と仕事をしている、そんな黒子役のパスワード入力におけるお話でした。