th:field と th:object によるフォームバインディング機能(inputタグ・radio編)
th:field
と th:object
が提供する機能説明の続きです。
今回は inputタグの type="radio" をみていきましょう。
*th:field
と th:object
の提供機能はタグや属性値によって異なります。
サポートされているタグの一覧はこちら
casual-tech-note.hatenablog.com
inputタグ・type="radio" における提供機能
input タグの type="radio" における th:field
と th:object
の提供機能を示していきたいと思います。実は inputタグ・type="radio" のカテゴリー型における提供機能と、とても似ています。
→ 参考: inputタグ・type="checkbox" における提供機能
id
属性 にth:field
に指定した変数名を 連番付き で代入
→ 詳細は 「連番付きの id 属性」を参考name
属性 にth:field
に指定した変数名を代入checked
属性 の設定
value
に指定された値とフィールドに指定した値が同じであるならchecked
属性 が付与される。
→ 詳細は 「checked 属性の設定」を参考value
属性 の設定
別途value
属性で指定することが必須となる。
→ 詳細は 「カテゴリー型における value 属性値の設定」を参考
サンプルコード
毎回のことながら、簡単なサンプル画面とコードをみていきましょう!
名前と性別を尋ねます。
性別によって色と敬称を変えてみました!というページになります。
Input Page と Output Page の実際のコードは次の通りです。
<!-- Input Page --> <h1>Input Page</h1> <form action="/binding/input/radio" method="post" th:object="${radioForm}"> <p>name</p> <input type="text" th:field="*{name}"/> <p>gender</p> <input type="radio" th:field="*{gender}" value="male"/> <label th:for="${#ids.prev('gender')}" th:text="male"></label> <input type="radio" th:field="*{gender}" value="female"/> <label th:for="${#ids.prev('gender')}" th:text="female"></label> <br/> <input type="submit" value="submit"/> </form>
<!-- Output Page --> <h1>Output Page</h1> <p>Welcome for visiting this page, <span th:text="${radioForm.gender == 'male' ? 'Mr.' : 'Ms.'} + ${radioForm.name}" th:style="${radioForm.gender == 'male' ? 'color: blue;' : 'color: red;'} + 'font-size: 20px'"></span> </p>
結果 Input Page のレンダリング結果は次のようになります。
デフォルトで checked
属性が male のラジオボタンに付与されていますね。
これは、フォームオブジェクトの gender フィールドに "male" を初期値として与えることで付与されます。
public class RadioForm { private String name; // これで male のラジオボタンはチェックされた状態でレンダリング private String gender = "male"; public void setName(String name) { this.name = name; } public String getName() { return name; } public void setGender(String gender) { this.gender = gender; } public String getGender() { return gender; } }
カテゴリー型チェックボックスとの違い
今回紹介している inputタグ・type="radio" における提供機能は本当にカテゴリー型のチェックボックスと似ています。なのでここでは異なる点を取り上げてみます。
ぜひカテゴリー型のチェックボックスと比較してみてください!
対応するフィールドの型
すでに少しバックエンドのコードも見ましたが、性別を受け取るためのフィールド gender は String 型を採用しています。カテゴリ型チェックボックスであれば、ここは List などの Collection を採用していました。
// ラジオボタンでのフィールド例 private String gender = "male";
// チェックボックスでのフィールド例 private List<String> favoriteFruits = Collections.singletonList("apple");
なぜ型が異なるかという理由は単純で、ラジオボタンは必ず一つの値しか格納しないためです。
今回の例では、性別は男と女しかなく、必ず片方が選択されることを想定しています。
デフォルト値の設定
「対応するフィールドの型」での話とも関連しますが、ラジオボタンでは必ず一つの値が格納されていることを想定しています。複数の値を格納することもないですが、同時に空の状態も想定していません。
そのため 必ずデフォルト値を格納しておくことが望ましいです。
初期値を特に設定していなければ gender の値には null が格納されます。 つまり gender の値が "male" でも "female" でもないため、どちらのラジオボタンもチェックされていない状態でレンダリングされることになります。バリデーションの実装をしていない状態ではこのまま選択せずに送信することも可能です。そのままではヌルポなど、危険な状態になりえるかもしれません。
また、カテゴリー型チェックボックスでは提供されていた type
="hidden" の input タグもラジオボタンでは提供されていません。
参考: type="hidden" の input タグ
この input タグは特定のフィールドの値が送信されないことを想定して付与されるというものでした。このタグが付与されないということは、必ずフィールドの値が送信されることを想定しているからだと思われます。
まとめ
チェックボックスとラジオボタンは、実用面でも似ていますが、使い分けはしっかり存在します。
複数項目を選択するとか、しないとか。。
実装も似ているんだけど、微妙に違っていて、それはちょうど使い分けの違いと対応するようになっていると、この記事を書いてて思いました。
th:field と th:object によるフォームバインディング機能(inputタグ・checkbox編)
th:field
と th:object
が提供する機能説明の続きです。
今回は inputタグの type="checkbox" をみていきましょう。
*th:field
と th:object
の提供機能はタグや属性値によって異なります。
サポートされているタグの一覧はこちら
casual-tech-note.hatenablog.com
inputタグ・type="checkbox" における提供機能
input タグの type="checkbox" における th:field
と th:object
の提供機能を次に示します。チェックボックスにおける提供機能は、他と比べても多機能で複雑な部類に入るでしょう。
各機能の詳細をさっと見たいなら、各々リンクをたどるのがおすすめです!
id
属性 にth:field
に指定した変数名を 連番付き で代入
→ 詳細は 「連番付きの id 属性」name
属性 にth:field
に指定した変数名を代入checked
属性 の設定
value
に指定された値とフィールドに指定した値が同じであるならchecked
属性 が付与される。
→ 詳細は 「checked 属性の設定」inputタグ・type="hidden" の付与
→ 詳細は 「inputタグ・type="hidden" の付与」
そしてさらに value
属性 の設定にも関与しますが、th:field
で指定するフィールドの型が Boolean または boolean かどうかによって提供機能は変化します。
フィールドの型が Boolean でも boolean でもない時の
value
属性 の設定
別途value
属性で指定することが必須となる。
→ 詳細は 「カテゴリー型における value 属性値の設定」フィールドの型が Boolean または boolean の時
value
属性 の設定
value
に true を代入
→ 詳細は 「要素型における value 属性値の設定」
サンプルコードと2種類の checkbox
th:field
と th:object
の提供機能はフィールドの型(Boolean または boolean なのか、そうでないのか)によって異なると述べましたが、どのように型を選択するのがいいのでしょうか? 。使い分けもありますが、それぞれの実装イメージを掴むためにもう少し深ぼってみます。
そこで次のようなサンプル画面を作ることを考えましょう。
この中に好きな果物はありますか?
何個選んでもいいですよ(選択肢をもっと増やしたい。。)という画面です。
フィールドの型が Boolean または boolean であるかどうかで大きく変わってくるので、ここではしっかりと次のように区別することにします。
- カテゴリー型: 型が Boolean でも boolean でもないフィールド
- 要素型: 型が Boolean または boolean であるフィールド
なぜこのような名前にしたのか。。
今回の例では次のような対応関係だと考えてください。
- カテゴリー: 好きな果物
- 要素: apple、banana、mango
つまり、入力を受け取り格納するフィールドを各要素にするのか、それともカテゴリーにしてしまうのかということにそれぞれ対応しています。
どちらのフィールド設計でもサンプル画面は実装可能です。
では、実際の実装をそれぞれ見ていきましょう。
カテゴリー型による実装
今回のサンプル画面におけるカテゴリーは「好きな果物」ということでした。
フィールド名を fruits
としてフォームクラスを実装していきます。
public class CheckboxCategoryForm { private List<String> fruits; public void setFruits(List<String> fruits) { this.fruits = fruits; } public List<String> getFruits() { return fruits; } }
チェックボックスは複数選択可能であるため、複数要素を格納できる Collection を fruits
の型として選択するのが良いでしょう(ラジオボタンのように一つしか選択できないなら Collection 型にする必要はないのですが)。ここでは List<String>
を選択、つまり好きな果物の要素(apple、banana、mango)は String 型で受け取るということにしています。
次は入力画面の view の実装です。
<form action="/binding/input/checkbox/category" method="post" th:object="${checkboxCategoryForm}"> <p>What is your favorite fruit?</p> <div> <label th:for="${#ids.next('fruits')}" th:text="apple"></label> <input type="checkbox" th:field="*{fruits}" value="apple"/> </div> <div> <label th:for="${#ids.next('fruits')}" th:text="banana"></label> <input type="checkbox" th:field="*{fruits}" value="banana"/> </div> <div> <label th:for="${#ids.next('fruits')}" th:text="mango"></label> <input type="checkbox" th:field="*{fruits}" value="mango"/> </div> <input type="submit" value="submit"/> </form>
結果、実際のレンダリングは次のようになります。
要素型による実装
今回のサンプル画面では apple、banana、mango の3つの要素を準備する必要があります。 要素型での実装だと、入力を格納するためのフォームクラスは次のようになります。
public class CheckboxElementForm { private Boolean apple; private Boolean banana; private Boolean mango; public void setApple(boolean apple) { this.apple = apple; } public Boolean getApple() { return apple; } public void setBanana(Boolean banana) { this.banana = banana; } public Boolean getBanana() { return banana; } public void setMango(Boolean mango) { this.mango = mango; } public Boolean getMango() { return mango; } }
対して、入力画面はこんな感じでしょうか。
<h1>Input page</h1> <form action="/binding/input/checkbox/element" method="post" th:object="${checkboxElementForm}"> <p>What is your favorite fruit?</p> <div> <label th:for="${#ids.next('apple')}" th:text="apple"></label> <input type="checkbox" th:field="*{apple}"/> </div> <div> <label th:for="${#ids.next('banana')}" th:text="banana"></label> <input type="checkbox" th:field="*{banana}"/> </div> <div> <label th:for="${#ids.next('mango')}" th:text="mango"></label> <input type="checkbox" th:field="*{mango}"/> </div> <input type="submit" value="submit"/> </form>
レンダリング結果です。
二通りの実装を一気にとりあえず書きました(息切れしていないですか?)。
ここからはサンプルコードを通して、inputタグ・type="checkbox" における提供機能を一つ一つ、より深く見ていきます。
連番付きの id 属性
レンダリング結果を順に見ていきます。
まずは id
属性 ですが th:field
に指定した変数名に連番をつけた値 となっています。
<!-- カテゴリー型 apple チェックボックスの実装 --> <div> <label th:for="${#ids.next('apple')}" th:text="apple"></label> <input type="checkbox" th:field="*{apple}"/> </div>
この機能は2種類の checkbox のなかでも カテゴリー型 向けの機能です。
基本入力系では、 name
属性も value
属性も対応するフィールド名が入ってました。そして今回の例ではどのチェックボックスも対応するフィールドは fruits フィールドです。 カテゴリー型のチェックボックスでは、 name
属性がどれも同じで value
属性は異なるものを複数レンダリングしないといけないことがほとんどです。 でも id
属性は重複が許されないので、どのチェックボックスにも同じ fruits を指定するわけにはいきません。これを回避するための連番ということですね。
value 属性の設定
既に述べた通り value
属性値の設定は、カテゴリー型か要素型かによって異なります。
カテゴリー型における value 属性値の設定
カテゴリー型ということはフィールドの型は Boolean でも boolean でもないケースです。
カテゴリー型の場合は th:field
とは別に value
属性値を指定してあげる必要があります。
例としてカテゴリー型によるサンプル画面の実装から apple のチェックボックスの実装を抜粋します。
<!-- カテゴリー型 apple チェックボックスの実装 --> <div> <label th:for="${#ids.next('fruits')}" th:text="apple"></label> <input type="checkbox" th:field="*{fruits}" value="apple"/> </div>
ここで実装しているのは apple のチェックボックスですが th:field
に指定した fruits だけでは apple の情報が一切含まれていません。そのため、value
属性値に apple を別途指定してあげる必要があるのです。
基本入力系では th:field
属性と th:value
属性を併用して実装した場合 th:field
属性 の方が優先されましたがチェックボックスではちゃんと th:value
属性 が優先される仕様になっています。
そしてこの apple のチェックボックスをチェックして submit した時、value
属性の値である apple が送信され、フォームオブジェクトの fruits フィールドに格納されます。
要素型における value 属性値の設定
要素型ということはフィールドの型は Boolean または boolean であるケースです。
要素型の場合は value
属性値はかならず true だと決まっています。 Boolean または boolean であるため、チェックされたときの値は一意に決まってしまうのです。
<!-- 要素型 apple チェックボックスの実装 --> <div> <label th:for="${#ids.next('apple')}" th:text="apple"></label> <input type="checkbox" th:field="*{apple}"/> </div>
例えば apple のチェックボックスをチェックして submit した時、value 属性の値である true が送信され、フォームオブジェクトの apple フィールドに格納されます。
checked 属性の設定
th:field
と th:object
の提供機能として、value
属性 の値をフィールドが保持した状態でレンダリングされると checked
属性が付与されます。
サンプル画面において、apple のチェックボックスに checked
属性を付与され、チェックされた状態でレンダリングすることを考えてみます。カテゴリー型と要素型、それぞれで checked
属性を付与するには、
この状態で入力画面をレンダリングすると checked
属性が付与されます。例としてフォームクラスのフィールドに初期値を与えることで apple のチェックボックスがチェックされた状態で表示されるようにして見ましょう。フォームクラスの apple フィールドに次のように初期値を与えることで、チェックされた状態になります。
// カテゴリー型フォームクラスの apple フィールドの変更 private List<String> fruits = Collections.singletonList("apple");
// 要素型フォームクラスの apple フィールドの変更 private Boolean apple = true;
inputタグ・type="hidden" の付与
今回付与されている type
属性が hidden の input タグを観察して見ましょう。
こちらは要素型の input タグですね。
<!-- 要素型 apple チェックボックスの実装 --> <div> <label th:for="${#ids.next('apple')}" th:text="apple"></label> <input type="checkbox" th:field="*{apple}"/> </div>
name
属性値はフィールド名の先頭に _
が付け足されたものとなっています。これは 該当するフィールドのデフォルト値を設定するためのタグ になります。このタグは、特定のタグがチェックされていない状態で値が送信されなかった時に効果を発揮します。
例として先ほどの checked
属性が付与された apple のチェックボックスをもちいて説明します。まず _apple="on" は submit 時にチェックの有無に関わらず必ず送信されます。一方 apple の値はチェックが入っていれば送信され、そうでなければ送信されません。
POST により apple
と _apple
の両方が送信されるときは、 _apple
は特に意味を持ちませんが、 _apple
属性値のみが送信されたときは、フォームオブジェクトの apple フィールドにデフォルト値を設定してくれます(_apple
の値である "on" は特に意味を持ちません)。
ここでいうデフォルト値とは、チェックボックスにチェックが入っていない時に本来フィールドに入っているはずの値です。
つまり、要素型では false が、カテゴリー型では空の Collection です。
この追加されたタグの効力を見るために、hidden タイプの input タグが付与されない実装に改良して比較して見ましょう。
これを実現するために th:field
属性を使わずに実装して見ます。
<!-- 修正後の要素型の実装 --> <form action="/binding/input/checkbox/element" method="post" th:object="${checkboxElementForm}"> <p>What is your favorite fruit?</p> <div> <label th:for="${#ids.next('apple')}" th:text="apple"></label> <input type="checkbox" id="apple1" name="apple" value="true" th:checked="*{apple}"/> </div> <div> <label th:for="${#ids.next('banana')}" th:text="banana"></label> <input type="checkbox" id="banana1" name="banana" value="true" th:checked="*{banana}"/> </div> <div> <label th:for="${#ids.next('mango')}" th:text="mango"></label> <input type="checkbox" id="mango1" name="mango" value="true" th:checked="*{mango}"/> </div> <input type="submit" value="submit"/> </form>
注)フォームオブジェクトの apple フィールドに true を代入しておいた状態にしてください(checked 属性値の設定で行った処理です)。
レンダリング結果は hidden タイプの input タグが付与されない点以外は同じです。ここで apple のチェックボックスのチェックを外して submit してみましょう。
遷移結果は次のようになるはずです。
チェックを外しているのに、apple が好きな食べ物として認識されてしまっています。値が特に何も送信されなければ、フォームオブジェクトのフィールドは値が更新されることもないため初期値のままです。これを防ぐために hidden 属性の input タグでデフォルトの値を送信しているということになります。
まとめ
チェックボックスにおける th:field
と th:object
による提供機能、いかがだったでしょうか?
提供機能がてんこ盛りだったので、もうお腹いっぱいかもしれません。。
でもその分、使用した時の効果は大きいです(同じ機能を th:field
使用せずに実装することを考えて見てください。途端に属性値まみれになってしまいます。。)。
だからこそ、ぜひ実際に th:field
と th:object
を使用して実装していきたいところでもありますね。
お疲れ様でした〜。
th:field と th:object によるフォームバインディング機能(inputタグ・password編)
th:field
と th:object
が提供する機能説明の続きです。
今回は input タグの type="password" をみていきましょう。
*th:field
と th:object
の提供機能はタグや属性値によって異なります。
サポートされているタグの一覧はこちら
casual-tech-note.hatenablog.com
inputタグ・type="password" における提供機能
パスワード入力を受け付けるinputタグにおける th:field
と th:object
の提供機能は以下の三つです。
id
属性 にth:field
に指定した変数名を代入name
属性 にth:field
に指定した変数名を代入value
属性 に 空文字 を代入
inputタグ・基本入力系での提供機能と似ていますが、微妙に違いますね。
value
属性 にはフィールドの値は代入されずに必ず空文字が入ります。このような仕様になっている背景をサンプルコードを通して見ていきましょう。
シンプルなサンプルコード
inputタグ・password における th:field
と th:object
の提供機能を確認するために次のようなサンプル画面を考えていきましょう。
名前とパスワードを入力として受け付け、受け取った値を出力するだけのシンプル画面です。
th:field
と th: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 が代入されていることが確認できます。
一方 value
属性 は空になっています(
仮に password フィールドに値が入っていたとして空になります)。
完全なフロントエンドのコードはこちらに用意しておきました。
パスワードのバリデーションチェック
今回の Inputタグの type="password" では、なぜ value
属性 に空文字を代入するのでしょうか?。実はこれによって パスワードのバリデーションチェックに引っかかった際に、フィールド値を空にする手間を省いています。
ここでは例として、 パスワードの入力値は8〜16文字でなければならない という制限をかけることを考えて見ましょう。つまり入力のパスワード長がこの範囲外なら、バリデーションエラーにより再度入力画面に戻されるという仕様をイメージしてください。
バリデーションを施している実装を見て見ましょう。
こちらはバックエンドにて実装します(完全版はこちら)。
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:field
と th:object
における提供機能は、value
属性における扱い以外は基本入力系と同じでいわばマイナーチェンジです。
なかなか使っていても見落としがちな機能ですが意外と仕事をしている、そんな黒子役のパスワード入力におけるお話でした。
th:field と th:object によるフォームバインディング機能(inputタグ・基本入力系編)
Spring + Thymeleaf でよく見かける th:field
と th:object
。
フォームで入力を受け取る際に、ほぼ必ずと言っていいほどセットで使用します。
しかし入門者にとっては、とりあえず書いておけばフォームで値を受け取ることができる構文に見えかねない気もします(少なくとも、自分が最初にお目にかかった時はそんなイメージでした。。。)。
とりあえずルールとして th:object
にフォームオブジェクトを、 th:field
に対応するフォームオブジェクトのフィールドを書けばいいのはなんとなくわかります。
でもそれ以降の仕組みが一目ではわからず気持ち悪い。。
そんな風に感じていたので、 th:field
と th:object
が実際にはどんな機能を提供しているのか、主な機能を解説します。
th:field と th:object がサポートしているタグ一覧
th:field
と th:object
はどのタグにでも使用できるわけではありません。
現在デフォルトでサポートされているのは次の4つです。
- input タグ
- select タグ
- option タグ
- textarea タグ
そして タグによって th:field
と th:object
が提供している機能はいくつか異なります。
また input タグに関しては type 属性によっても提供される機能が変わってきます (input タグに関しては type 属性によってはサポートされていないものもあります)。
一方で input タグでサポートしている type 属性における提供機能は大半が同じ だったりします。 今回はこの大半の type 属性群を 基本入力系 と呼ぶこととしましょう。
まずどの type 属性の input タグがこの基本入力系に分類されるのか、以下にまとめてみました(Thymeleaf 3.0現在)。
パターン | type 属性一覧 |
---|---|
基本入力系 | text, hidden, detetime, datetime-local, date, time, month, week, number, range, email, url, search, tel, color |
その他 | password, checkbox, radio, file |
上記の表に含まれない type 属性はサポート外です。
基本入力系のへの提供機能は他のタグとその他に分類されているものと共通している点も多いので、以降こちらを取り上げていきます(その他に分類されるものにも重要なものが多いのでまたの機会に。。)。
基本入力系以外における提供機能は、別記事で紹介します。
- inputタグ・password編
th:field と th:object によるフォームバインディング機能(inputタグ・password編) - カジュアルな技術ノート - inputタグ・checkbox編
th:field と th:object によるフォームバインディング機能(inputタグ・checkbox編) - カジュアルな技術ノート - inputタグ・radio編(準備中)
th:field と th:object によるフォームバインディング機能(inputタグ・radio編) - カジュアルな技術ノート - inputタグ・file編(準備中)
- selectタグ・optionタグ編(準備中)
- textareaタグ編(準備中)
サンプルコード
実際にフォームで入力を受け取るための UI にはいろんなものがありますが、最も基本的なものの一つがテキスト入力です。
今回はテキスト入力を受け取り、その内容を出力する単純なプログラムを通して th:field
と th:object
の提供機能を見ていきましょう。
つまり基本入力系の中でも input タグの type 属性が text のものを扱っていきます。
具体的には次のような入力画面と出力画面を作ることを考えてみます。
例として Input Page にて name に「subaru」、text に「I love india curry」と入力し submit すると Output Page はこのようになります。
これ以上ないほどシンプルな実装です。
以下に実際のコードを書いていきましょう。
<!-- Input Page --> <h1>Input page</h1> <form action="/binding/input/text" method="post" th:object="${textForm}"> <p>name</p> <input type="text" th:field="*{name}"/> <p>text</p> <input type="text" th:field="*{text}"/> <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="${textForm.name}"></span>」</p> <p>Your text is 「<span th:text="${textForm.text}"></span>」</p>
ここで登場している textForm
はコントローラで model に追加した入力値を受け取るためのオブジェクトです。
一応実際のバックエンドとフロントエンドの完全版実装も準備して見ました(見るにしてもあまりに単純かもしれませんが。。。)。
th:field と th:object による提供機能
公式のチュートリアル(https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#inputs)によると、th:field
は 次のような機能を提供しているとのことです(厳密にはこれ以外にもありますが、本質から外れるので割愛)。
id
属性 にth:field
に指定した変数名を代入name
属性 にth:field
に指定した変数名を代入value
属性 にth:field
に指定した変数に格納された値を代入
このことから Input Page の form タグは以下と概ね同じで、書き換えてあげることが可能です。
<!-- Input Page --> <form action="/binding/input/text" method="post" th:object="${textForm}"> <p>name</p> <input type="text" id="name" name="name" th:value="*{name}"/> <p>text</p> <input type="text" id="text" name="text" th:value="*{text}"/> <br/> <input type="submit" value="submit"/> </form>
元々の Input Page のレンダリングを確認しても、一致することが確認できます。
これが th:field
と th:object
による基本的な提供機能となります。
他の多くのタグや input の属性値でも 共通しています。
th:field と name, id, value 属性の併用
th:field
と th:object
によって id
属性、name
属性、value
属性 が生成されることはわかりました。
ここで一つ疑問に思うこととして th:field
と一緒にこれらの属性を併用して記述した場合はどうなるのでしょうか?
どちらかの値が上書きされることは予想できますが、実はここの優先度は属性値によって異なります。
検証用に、以下のような view を用意してみました。
<h1>Input page</h1> <form action="/binding/input/text" method="post" th:object="${textForm}"> <p>name</p> <input type="text" th:field="*{name}" name="manualName" id="manualId" value="manualValue"/> <p>text</p> <input type="text" th:field="*{text}"/> <br/> <input type="submit" value="submit"/> </form>
レンダリングの結果は次のようになります。
この結果からそれぞれの属性についてみてみると、
name
属性
値が name であることから、th:field
属性が優先。id
属性
値が manualId であることから、id
属性が優先。value
属性
値が 空の入力値 になっていることから、th:field
が優先。
実は id
属性だけ優先度が違うのです。
実際フォームバインディングに使用されるのは name
属性 と value
属性 のみです(詳しくは後述します)。
id
属性は通常のセレクタとして使用されることを想定しているため、このような優先度になっているものと推測されます。
まとめ
今回は th:field
と th:object
を使用するケースの中でも基本的なものを紹介してみました。
th:field
と th:object
は構文的に使用しがちということを言いましたが、実際の挙動はタグや属性値によって異なっています。
しっかり挙動を押さえることは、短く可読性よく記述することにも繋がるかと思うので参考になればと思います。