6.テンプレート
学習目標
Modelでコントローラーからテンプレートに値を渡せるth:textでタグの中身に値を埋め込めるth:eachでリストの要素を繰り返し表示できるth:if・th:classで、表示の有無や見た目を条件で切り替えられる
1. Thymeleaf
Section titled “1. Thymeleaf”テンプレート に書いた HTML は、そのままレスポンスになります。本一覧のように、データベースの内容によって変わる値を表示するには、テンプレートの中に値を埋め込む仕組みが必要です。
Thymeleaf は、HTML のタグに専用の属性を加えて、外部から渡された値を埋め込むテンプレートエンジンです。属性は th: で始まり、HTML として有効な記法で書かれます。Thymeleaf を通さず単独でブラウザーに表示しても崩れず、デザイナーがおおよその完成イメージを確認できる設計です。
1-1. 値の埋め込み
Section titled “1-1. 値の埋め込み”タグの中身に値を埋め込むには、th:text 属性を書きます。
<タグ th:text="${式}">プロトタイプ表示用の文字</タグ>th:text は、タグの中身を ${...} の値で置き換えます。${...} の中には、外部から渡された値の属性名を書きます。title という属性名で こころ が渡されているとき、<span th:text="${title}">坊っちゃん</span> は <span>こころ</span> に置き換わります。値を渡す方法は Model による受け渡し で扱います。
タグの中身に書いた文字(坊っちゃん の部分)は、Thymeleaf が値で置き換える前の仮の表示です。テンプレートを単独でブラウザーに表示したとき完成イメージに近い見た目になるよう、実際の値らしい文字を書いておきます。
本のタイトルを 1 つ埋め込む books/sample.html です。
<!DOCTYPE html><html lang="ja" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Spring Books</title></head><body> <h1>本のサンプル</h1> <p>タイトル: <span th:text="${title}">坊っちゃん</span></p></body></html><html> の xmlns:th="http://www.thymeleaf.org" は、IDE が th: を未知の属性として警告しないようにするための宣言で、Thymeleaf の処理自体には影響しません。
title の値はコントローラーが用意します。値が こころ のときの出力は次のようになります。
<h1>本のサンプル</h1><p>タイトル: <span>こころ</span></p>1-2. Model による受け渡し
Section titled “1-2. Model による受け渡し”${title} の title は、コントローラーがテンプレートに渡します。値の受け渡しに使うのが Model(org.springframework.ui.Model)です。@GetMapping を付けたメソッドの引数に Model を書くと、Spring Boot がそのインスタンスを渡します。
テンプレートに渡したい値は、Model の addAttribute で属性名を付けて格納します。
model.addAttribute("属性名", 値);本の表示用に BookController を作り、/books/sample を担当する sample メソッドで title を Model に格納します。
package com.example.bookstore;
import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;
@Controllerpublic class BookController { @GetMapping("/books/sample") public String sample(Model model) { model.addAttribute("title", "こころ"); return "books/sample"; }}addAttribute("title", "こころ") で、title という属性名で こころ を格納します。テンプレート側の ${title} は、この属性名で格納された値を参照します。
ブラウザーで /books/sample を開くと、${title} が こころ に置き換わった HTML がレスポンスとして返ります。
Model に入れた値は、サーブレットの リクエストスコープ と同じく、その 1 回のリクエストの処理が終わるまでの間だけ有効です。次のリクエストには引き継がれません。複数のリクエストをまたいで値を保持する仕組みは、セッション で扱います。
2. 一覧の表示
Section titled “2. 一覧の表示”本一覧のように、同じ形の行を要素の数だけ表示するには、コントローラーが本のリストを Model に格納し、テンプレートが th:each でそのリストを繰り返します。
2-1. リストの受け渡し
Section titled “2-1. リストの受け渡し”リストの要素となる、タイトル・価格・在庫を持つ Book クラスを作ります。
package com.example.bookstore;
public class Book { private final String title; private final int price; private final int stock;
public Book(String title, int price, int stock) { this.title = title; this.price = price; this.stock = stock; }
public String getTitle() { return title; }
public int getPrice() { return price; }
public int getStock() { return stock; }}BookController に、/books を担当する books メソッドを追加します。固定の List<Book> を Model に格納し、ビュー名を返します。
package com.example.bookstore;
import java.util.List;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;
@Controllerpublic class BookController { @GetMapping("/books/sample") public String sample(Model model) { model.addAttribute("title", "こころ"); return "books/sample"; }
@GetMapping("/books") public String books(Model model) { List<Book> books = List.of( new Book("こころ", 473, 5), new Book("吾輩は猫である", 693, 0) ); model.addAttribute("books", books); return "books"; }}model.addAttribute("books", books) で、books という属性名でリストを格納します。テンプレート側の ${books} は、この属性名で格納されたリストを参照します。
java.util.List の import が加わります。
2-2. th:each による繰り返し
Section titled “2-2. th:each による繰り返し”本の数だけ同じ形の行を並べるには、繰り返したいタグに th:each 属性を付けます。
<タグ th:each="要素の名前 : ${リスト}"> ... 要素ごとに出力する HTML ...</タグ>${リスト} に繰り返すリスト、要素の名前 に 1 要素を受け取る変数名を指定します。th:each を付けたタグが、リストの要素数だけ繰り返されます。
ジャンル名のリスト(小説・技術書)を genres として渡し、<li> に th:each を付けます。
<ul> <li th:each="genre : ${genres}" th:text="${genre}">ジャンル</li></ul>生成結果:
<ul> <li>小説</li> <li>技術書</li></ul>本の一覧では、要素が Book オブジェクトになり、各要素から ${book.title} で値を取り出します。一覧を表示する books.html です。
<!DOCTYPE html><html lang="ja" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>書籍一覧</title></head><body> <h1>書籍一覧</h1> <table> <tr><th>タイトル</th><th>価格</th></tr> <tr th:each="book : ${books}"> <td th:text="${book.title}">こころ</td> <td><span th:text="${book.price}">473</span>円</td> </tr> </table></body></html>th:each="book : ${books}" は、books の 1 要素を book として受け取り、その <tr> を要素数だけ繰り返します。${book.title} はその本のタイトル、${book.price} は価格に置き換わります。価格に続く「円」は通常の HTML テキストで、<span> の外に書いています。
${book.title} の .title は、Book の getTitle() の戻り値で置き換わります。.フィールド名 の形で、getter を経由して値が取り出されます。
ブラウザーで /books を開くと、リストの 2 冊が 2 行に展開された HTML が表示されます。
<h1>書籍一覧</h1><table> <tr><th>タイトル</th><th>価格</th></tr> <tr><td>こころ</td><td><span>473</span>円</td></tr> <tr><td>吾輩は猫である</td><td><span>693</span>円</td></tr></table>本が何件でも books の要素数に応じて行数が変わるため、books.html を書き換える必要はありません。BookController が用意するリストの中身が、そのまま表に並びます。
2-3. th:if による表示の切り替え
Section titled “2-3. th:if による表示の切り替え”リストの要素が 0 件のとき、th:each は何も繰り返さず、表のヘッダーだけが残ります。0 件のときは、空の表の代わりに、データがないことを伝えるメッセージを表示します。表示するタグを条件で切り替えるには、th:if を使います。
<タグ th:if="${条件式}">条件が真のときに表示する内容</タグ>th:if を付けたタグは、${...} の中身が真のときだけ出力され、偽のときは出力されません。リストが空かどうかは、Thymeleaf の組み込みオブジェクト #lists.isEmpty() で判定します。
<!DOCTYPE html><html lang="ja" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>書籍一覧</title></head><body> <h1>書籍一覧</h1> <p th:if="${#lists.isEmpty(books)}">本がまだ登録されていません。</p> <table th:if="${not #lists.isEmpty(books)}"> <tr><th>タイトル</th><th>価格</th></tr> <tr th:each="book : ${books}"> <td th:text="${book.title}">こころ</td> <td><span th:text="${book.price}">473</span>円</td> </tr> </table></body></html>books が空のときは <p> のメッセージだけが表示され、空でないときは <table> だけが表示されます。not で条件を反転して、対になる 2 つのタグに別々の条件を付けています。
2-4. th:class による属性の設定
Section titled “2-4. th:class による属性の設定”一覧に在庫を表示し、在庫が 0 の本を目立たせます。th:class は class 属性を設定し、値は三項演算子 条件 ? A : B で条件によって変えられます。
<タグ th:class="${条件} ? 'クラス名' : ''">books.html の表に在庫の列を加え、各行に在庫が 0 のとき sold-out クラスを付けます。
<table> <tr><th>タイトル</th><th>価格</th><th>在庫</th></tr> <tr th:each="book : ${books}" th:class="${book.stock == 0} ? 'sold-out' : ''"> <td th:text="${book.title}">こころ</td> <td><span th:text="${book.price}">473</span>円</td> <td th:text="${book.stock}">5</td> </tr></table>th:class="${book.stock == 0} ? 'sold-out' : ''" は、book.stock が 0 の行に sold-out クラスを付け、0 でなければ付けません。CSS で .sold-out { color: #999; } のように定義すると、在庫切れの行だけ見た目を変えられます。th:text がタグの中身を設定するのに対し、th:class のように th: に属性名を続けると、その属性を設定できます。th:href(リンク先)など、ほかの属性も同じ形で設定できます。