Skip to content
Playground

6.テンプレート

学習目標

  • Model でコントローラーからテンプレートに値を渡せる
  • th:text でタグの中身に値を埋め込める
  • th:each でリストの要素を繰り返し表示できる
  • th:ifth:class で、表示の有無や見た目を条件で切り替えられる

テンプレート に書いた HTML は、そのままレスポンスになります。本一覧のように、データベースの内容によって変わる値を表示するには、テンプレートの中に値を埋め込む仕組みが必要です。

Thymeleaf は、HTML のタグに専用の属性を加えて、外部から渡された値を埋め込むテンプレートエンジンです。属性は th: で始まり、HTML として有効な記法で書かれます。Thymeleaf を通さず単独でブラウザーに表示しても崩れず、デザイナーがおおよその完成イメージを確認できる設計です。

タグの中身に値を埋め込むには、th:text 属性を書きます。

<タグ th:text="${式}">プロトタイプ表示用の文字</タグ>

th:text は、タグの中身を ${...} の値で置き換えます。${...} の中には、外部から渡された値の属性名を書きます。title という属性名で こころ が渡されているとき、<span th:text="${title}">坊っちゃん</span><span>こころ</span> に置き換わります。値を渡す方法は Model による受け渡し で扱います。

タグの中身に書いた文字(坊っちゃん の部分)は、Thymeleaf が値で置き換える前の仮の表示です。テンプレートを単独でブラウザーに表示したとき完成イメージに近い見た目になるよう、実際の値らしい文字を書いておきます。

本のタイトルを 1 つ埋め込む books/sample.html です。

templates/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>

${title}title は、コントローラーがテンプレートに渡します。値の受け渡しに使うのが Modelorg.springframework.ui.Model)です。@GetMapping を付けたメソッドの引数に Model を書くと、Spring Boot がそのインスタンスを渡します。

テンプレートに渡したい値は、ModeladdAttribute で属性名を付けて格納します。

model.addAttribute("属性名", 値);

本の表示用に BookController を作り、/books/sample を担当する sample メソッドで titleModel に格納します。

src/main/java/com/example/bookstore/BookController.java
package com.example.bookstore;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public 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 回のリクエストの処理が終わるまでの間だけ有効です。次のリクエストには引き継がれません。複数のリクエストをまたいで値を保持する仕組みは、セッション で扱います。

本一覧のように、同じ形の行を要素の数だけ表示するには、コントローラーが本のリストを Model に格納し、テンプレートが th:each でそのリストを繰り返します。

リストの要素となる、タイトル・価格・在庫を持つ Book クラスを作ります。

Book.java
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 に格納し、ビュー名を返します。

BookController.java
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;
@Controller
public 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.Listimport が加わります。

本の数だけ同じ形の行を並べるには、繰り返したいタグに 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 です。

templates/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 は、BookgetTitle() の戻り値で置き換わります。.フィールド名 の形で、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 が用意するリストの中身が、そのまま表に並びます。

リストの要素が 0 件のとき、th:each は何も繰り返さず、表のヘッダーだけが残ります。0 件のときは、空の表の代わりに、データがないことを伝えるメッセージを表示します。表示するタグを条件で切り替えるには、th:if を使います。

<タグ th:if="${条件式}">条件が真のときに表示する内容</タグ>

th:if を付けたタグは、${...} の中身が真のときだけ出力され、偽のときは出力されません。リストが空かどうかは、Thymeleaf の組み込みオブジェクト #lists.isEmpty() で判定します。

templates/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>
<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 つのタグに別々の条件を付けています。

一覧に在庫を表示し、在庫が 0 の本を目立たせます。th:class は class 属性を設定し、値は三項演算子 条件 ? A : B で条件によって変えられます。

<タグ th:class="${条件} ? 'クラス名' : ''">

books.html の表に在庫の列を加え、各行に在庫が 0 のとき sold-out クラスを付けます。

templates/books.html
<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(リンク先)など、ほかの属性も同じ形で設定できます。