カジュアルな技術ノート

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

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

Spring + Thymeleaf でよく見かける th:fieldth:object
フォームで入力を受け取る際に、ほぼ必ずと言っていいほどセットで使用します。

しかし入門者にとっては、とりあえず書いておけばフォームで値を受け取ることができる構文に見えかねない気もします(少なくとも、自分が最初にお目にかかった時はそんなイメージでした。。。)。

とりあえずルールとして th:object にフォームオブジェクトを、 th:field に対応するフォームオブジェクトのフィールドを書けばいいのはなんとなくわかります。
でもそれ以降の仕組みが一目ではわからず気持ち悪い。。
そんな風に感じていたので、 th:fieldth:object が実際にはどんな機能を提供しているのか、主な機能を解説します。

th:field と th:object がサポートしているタグ一覧

th:fieldth:object はどのタグにでも使用できるわけではありません。
現在デフォルトでサポートされているのは次の4つです。

  • input タグ
  • select タグ
  • option タグ
  • textarea タグ

そして タグによって th:fieldth:object が提供している機能はいくつか異なります。 また input タグに関しては type 属性によっても提供される機能が変わってきます (input タグに関しては type 属性によってはサポートされていないものもあります)。

一方で input タグでサポートしている type 属性における提供機能は大半が同じ だったりします。 今回はこの大半の type 属性群を 基本入力系 と呼ぶこととしましょう。

まずどの type 属性の input タグがこの基本入力系に分類されるのか、以下にまとめてみました(Thymeleaf 3.0現在)。

input タグの type 属性による th:field と th:object の提供機能分類
パターン type 属性一覧
基本入力系 text, hidden, detetime, datetime-local, date, time, month, week, number, range, email, url, search, tel, color
その他 password, checkbox, radio, file

上記の表に含まれない type 属性はサポート外です。
基本入力系のへの提供機能は他のタグとその他に分類されているものと共通している点も多いので、以降こちらを取り上げていきます(その他に分類されるものにも重要なものが多いのでまたの機会に。。)。

基本入力系以外における提供機能は、別記事で紹介します。

サンプルコード

実際にフォームで入力を受け取るための UI にはいろんなものがありますが、最も基本的なものの一つがテキスト入力です。 今回はテキスト入力を受け取り、その内容を出力する単純なプログラムを通して th:fieldth:object の提供機能を見ていきましょう。
つまり基本入力系の中でも input タグの type 属性が text のものを扱っていきます。

具体的には次のような入力画面と出力画面を作ることを考えてみます。

f:id:shin-kinoshita:20180904014409p:plain

例として 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 のレンダリングを確認しても、一致することが確認できます。
f:id:shin-kinoshita:20181010223803p:plain

これが th:fieldth:object による基本的な提供機能となります。
他の多くのタグや input の属性値でも 共通しています。

th:field と name, id, value 属性の併用

th:fieldth: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>

レンダリングの結果は次のようになります。
f:id:shin-kinoshita:20181010223503p:plain
この結果からそれぞれの属性についてみてみると、

  • name 属性
    値が name であることから、th:field 属性が優先。
  • id 属性
    値が manualId であることから、id 属性が優先。
  • value 属性
    値が 空の入力値 になっていることから、th:field が優先。

実は id 属性だけ優先度が違うのです。
実際フォームバインディングに使用されるのは name 属性value 属性 のみです(詳しくは後述します)。 id 属性は通常のセレクタとして使用されることを想定しているため、このような優先度になっているものと推測されます。

まとめ

今回は th:fieldth:object を使用するケースの中でも基本的なものを紹介してみました。 th:fieldth:object は構文的に使用しがちということを言いましたが、実際の挙動はタグや属性値によって異なっています。 しっかり挙動を押さえることは、短く可読性よく記述することにも繋がるかと思うので参考になればと思います。