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
は構文的に使用しがちということを言いましたが、実際の挙動はタグや属性値によって異なっています。
しっかり挙動を押さえることは、短く可読性よく記述することにも繋がるかと思うので参考になればと思います。