Thymeleaf

Thymeleafについて

Thymeleaf(タイムリーフ)は Java の代表的なテンプレートエンジンで、SpringBoot アプリケーションを開発する際によく利用されます。
プログラムはJavaのクラスとして実装されています。Spring Boot自体にも、Thymeleafを利用する機能が用意されているので、別途Thymeleafのライブラリなどをインストールする必要もありません。

Thymeleaf の主な目的は、テンプレートの作成に対して優雅で保守性の高い方法を提供することです。

それを実現するために、Thymeleaf はナチュラルテンプレートというコンセプトを採用しており、
テンプレート内でJavaコードを埋め込むこともでき、高速で処理効率がいいです。

テンプレート
HTMLをベースにして記述されたソースコードを読み込み、レンダリング(整形)してWebページの表示を生成する機能。
単純に「HTMLのコードを読み込んで表示する」というだけでなく、必要に応じてさまざまな情報をHTML内にはめ込み、表示させるような機能を持っています。
これにより、プログラム内から画面の表示を操作することが可能になります。

Thymeleafは、タグの中に「th:○○」という特殊な属性を用意し、また${ }といった特殊な記号を使って値をはめ込むことで、HTMLのタグ構造に影響を与えることなく内容を記述できます。
HTMLのビジュアルエディタなどを使っても、表示が崩れたりすることもありません。

Thymeleafの様々な使い方

Thymeleafの基礎的な機能を学んでいくために、ベースとなるテンプレートファイルとクラスファイルを作成しましょう。

テンプレートファイル

「src/main/resources/templates」フォルダ内に
「thymeleaf」フォルダを作成し、「usage.html」を作成します。

・usage.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleafの様々な使い方</title>
</head>
<body>
	<div class="contents">
	<!-- 以下に追記します -->
	
	</div>
</body>
</html>
クラスファイル

「/com/cmps/spring」パッケージ直下に「thymeleaf」パッケージ作成し、その中に
「ThymeleafController」クラスファイルを作成、以下のソースを記述してください。

package com.cmps.spring.thymeleaf;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ThymeleafController {
    
    // 「/thymeleaf」へアクセスがあった場合
    @GetMapping("/thymeleaf")
    public String thymeleaf(Model model) {
        // 画面に出力するViewを指定
        return "thymeleaf/usage";
    }
}

以上の記述で「/thymeleaf」へアクセスがあった場合、thymeleafメソッドが実行されます。

GetMapping=GET通信は、基本的に「画面を表示する」ものであり、メソッドの戻り値にはthymeleafテンプレートファイルを指定します。
thymeleafの指定の仕方としては、src/main/resources/templatesから見たフォルダ・ファイル名を /(スラッシュ)区切りで記入します。
今回は上記で作成したusage.html(src/main/resources/templates/thymeleaf/usage.html)を表示したいので、
thymeleaf/usage” と記載しています。

 Javaのインスタンスやメソッドもそのまま使用可能

テンプレート内でJavaコードを埋め込むことができるThymeleafは、
Javaの変数や条件分岐などの制御構造をHTMLテンプレート内に埋め込むことができます。
これにより、動的なコンテンツを生成することができます。

Thymeleafの基本は、値を出力する(表示する)ということです。これは${○○}という形で記述されます。
この${}という書き方は「変数式」と呼ばれます。
変数式の中に記述されるのは、「OGNL」(Object-Graph Navigation Language)式という、Javaの値にアクセスするための式言語です。
Thymeleafに限らず、各種のライブラリやフレームワークなどで使われています。

先程作成したusage.htmlのdivタグ内に以下のソースを記述しましょう。

	<div class="contents">
	<!-- 以下に追記します -->
	<h2> Javaのインスタンスやメソッドもそのまま使用可能</h2>
	<p th:text="${new java.util.Date().toString()}"></p>
	
	</div>

以下のアドレスからアプリケーションにアクセスします。
URL:http://localhost:8080/thymeleaf

ここでは、th:textに、${new java.util.Date().toString()}というように値を指定しています。
変数式には、単に変数や値などを書くだけでなく、このようにインスタンスをnewしたり、
メソッドを呼び出したりするOGNLの式を書くこともできるのです。

ユーティリティオブジェクト(#~)

変数式では変数を記述してそのまま出力することができます。
この変数は、既に簡単なサンプルで動作を確認したように、
まずコントローラーで値を用意しておき、それをテンプレート側に出力する、というのが基本でした。

しかし、テンプレートで頻繁に利用されるクラスがあった場合、常に「コントローラーでこのクラスを変数として用意して…」とするのは面倒ですよね。

そこでThymeleafでは、よく使われるクラスを「#名前」という定数として変数式の中に直接記述して利用できるようにしました。これが「ユーティリティオブジェクト」です。

主なユーティリティオブジェクトは以下の通りです。

#stringsStringオブジェクト用のメソッド群
#numbers数値オブジェクトをフォーマットするためのメソッド群
Numberクラス
#bools真偽値評価用のメソッド群。
Booleanクラス
#datesjava.util.Dateオブジェクト用のメソッド群
Dateクラス
#temporalsJDK8以上のjava.timeAPIを利用して日付や時間を処理する
#objectsObjectクラスの定数
#arraysArrayクラスの定数
#listsListクラスの定数
#setsSetクラスの定数
#mapsMapクラスの定数

実際に使用してみましょう。
コントローラー側は特に修正の必要はなく、テンプレート(usage.html)を修正するだけでOKです。

	<h2>ユーティリティオブジェクト</h2>
	<p th:text="${#dates.format(new java.util.Date(), 'dd/MM/yyyy HH:mm')}"></p>
	<p th:text="${#numbers.formatInteger(1234,7)}"></p>
	<p th:text="${#strings.toUpperCase('Hello World')}"></p>

上記では日時と整数とテキストを決まった形に整形し表示しています。

日時の整形

#datesは、Dateクラスの定数です。
formatメソッドを使い、引数で作成しているDateを決まった形式で表示しています。ここでは、dd/MM/yyyy HH:mmという形でパターンを用意してあります。

なお、LocaldateTimeを使い表示をすると以下の様になる場合があります。

"2021-06-24T20:23:39.987941”

その場合は以下の様に埋め込みましょう。

th:text="*{#temporals.format(date, 'yyyy/MM/dd HH:mm')}"
整数の整形

#numbersは、Numberクラスの定数です。
formatIntegerメソッドは、整数を決まった桁数表記にします。第1引数の整数を第2引数の桁表示にして返します。

テキストの整形

#stringsは、Stringクラスの定数です。
toUpperCaseは、「Stringの操作」で取り扱った、引数のテキストをすべて大文字に変換するメソッドです。

公式サイト:Tutorial: Using Thymeleaf (ja)

テキストリテラルの記述

これまで記述したth:textの値を見ると、
「”~”(ダブルコーテーション)」の中に「’~'(シングルコーテーション)」で値が記述されていることが分かります。
これは、OGNLでテキストリテラルを記述する際の書き方です。

1つのテキストリテラルをそのまま表示する場合は、ダブルクォート内に直接テキストを書けばいいのですが、
いくつかのリテラルをつなぎ合わせるような場合は、ダブルクォート内に更にシングルクォートでテキストリテラルを記述することができます。

	<h2>テキストリテラルの記述</h2>
	<p th:text="'I my me mine'"></p>
	<p th:text="'I' + 'my' + 'me' + 'mine'"></p>

どちらも「I my me mine」を出力しているのですが、後者は「+」で文字列を繋ぎ合わせています。
このように、ダブルコーテーションの中でさらに式を使い値を組み立てることが出来ます。

条件分岐(th:if)

Thymeleafには条件式や繰り返しなどを行うための機能も用意されています。

基本的な構文から見ていきましょう。

th:if = "${条件}"

条件がtrueと判断された場合、タグおよび内部のタグが実行されます。

th:unless = "${条件}"

条件がfalseと判断された場合、タグおよび内部にあるタグを実行します。

th:ifはJava標準のif構文と勝手が少々異なり、true/falseの判断が真偽値だけではありません。

trueと判断されるのは以下のものです。
・true(boolean型)
・ゼロ以外の数値
・”0″、”off”、”no”といった値以外の文字列

falseと判断されるのは以下のものです。
・false
・数値のゼロ
・”0″、”off”、”no”といった文字列

offやnoなどの文字列を理解して利用する分には問題ありませんが、
意図せず判定・処理してしまわないように注意してください。

実際に使用してみましょう。以下のソースを記述してください。

    <h2>条件分岐</h2>
	<!--パラメータが存在する場合-->
	<p th:if="${param.id != null}" th:text="'idは「' + ${param.id[0]} + '」'"></p>
	<p th:if="${param.name != null}" th:text="'nameは「' + ${param.name[0]} + '」です。'"></p>

	<!--パラメータが存在しない場合-->
	<p th:if="${param.id == null}" th:text="'idは指定されていません。'"></p>
	<p th:if="${param.name == null}" th:text="'nameは指定されていません。'"></p>

記述した上でパラメータを持たせた場合とそうでない場合の画面を表示てみましょう。
パラメータあり:http://localhost:8080/thymeleaf?id=123&name=テスト太郎
パラメータなし:http://localhost:8080/thymeleaf

どうでしょうか。ちゃんと異なる文章が出力されたでしょうか。
パラメータが存在する場合はth:ifの条件に「param.○○ != null」が設定されています。
この条件がtrueの場合、つまりパラメータが送信されている場合のみ、このタグが表示されるようになります。

リンク式(@{~})

Webページでは、URLを指定したリンクもさまざまなところで利用されます。
リンクを指定する専用の式が用意されています。

@{ アドレス }

これは、<a>タグのhrefなどURLを指定するような属性で利用します。
例えば、@{/index}と記述すれば、/indexへのリンクを指定することができます。

「リンクならテキストとして書けばいいだろう」と思う人も多いかもしれませんが、
例えば、他の変数などと組み合わせてリンクのアドレスを指定するような場合には必要となってきます。

作成してみましょう。
・ThymeleafController.javaに追記

public String thymeleaf(Model model) {
        // リンク用サンプル 追記
        model.addAttribute("user_id", 999);

        // 画面に出力するViewを指定
        return "thymeleaf/usage";
    }

・usage.html

        <h2>リンク式とhref</h2>
	<p><a th:text="'次のページ'" th:href="@{/other-page}"></a>	 
	<p><a th:text="'詳細ページ'" th:href="@{/detail/{id}/(id = ${user_id})}"></a></p>

http://localhost:8080/thymeleaf
記述が完了したら上記リンクを開いてみましょう。

詳細ページのリンクに注目しましょう(画像下部。ブラウザ上ではリンクにマウスカーソルを合わせるとURLが表示されます)。
今回、テンプレートだけでなくコントローラの処理も修正しています。
コントローラーから「user_id」という名前で「999」という値を渡して、th:hrefで表示しています。

@{/detail/{id}/(id = ${user_id})}

処理の流れとしては以下のようになります。
@{/detail/{id}/(id = 999)}
 →変数${user_id}が展開される

@{/detail/999/}
 →{id}という文字列と値が置換される

href=”/detail/999/”
 → リンクのアドレスとして表示される

選択オブジェクトへの変数式(th:object、*{~})

変数式は、基本的にコントローラー側に用意した値をそのまま出力するだけのものです。
これは数値やテキストといったシンプルな値ならばいいのですが、
オブジェクトを利用するようになると扱いに困ってしまいます。

例えば ${object.name} といった具合にオブジェクトのプロパティやメソッドを指定して書けばいいですが、
オブジェクト名が変更されたりしたときにすべての名前をまた書き換えないといけないのは大変です。

このような場合にオブジェクトを指定し、その選択されたオブジェクト内の値を取り出すための専用の変数式が用意されています。
この変数式は、オブジェクトを扱う変数式の内部で利用します。

<div th:object = "${ オブジェクト }"
  <p th:text = "*{ プロパティ }">
</div>

タグに「th:object」という属性を使ってオブジェクトを設定します。
こうすることで、そのタグの内部で、*{ }による変数式が使えるようになります。
この「*(アスタリスク)」による変数式では、オブジェクト内のプロパティなどを名前だけで指定が可能です。

実際にやってみましょう

・usage.html

   <h2>選択オブジェクトへの変数式</h2>
	<table th:object="${user}" >
		<tr>
			<th>名前</th>
			<td th:text="*{name}"></td>
		</tr>
		<tr>
			<th>紙幣</th>
			<td th:text="*{bill}"></td>
		</tr>
	</table>

・ThymeleafController.java

@Controller
public class ThymeleafController {
    
    // 「/thymeleaf」へアクセスがあった場合
    @GetMapping("/thymeleaf")
    public String thymeleaf(Model model) {
        // リンク用サンプル
        model.addAttribute("user_id", 999);

        //  選択オブジェクト用サンプル 追記
        User user = new User("野口英世",1000); // 追記
        model.addAttribute("user",user);

        // 画面に出力するViewを指定
        return "thymeleaf/usage";
    }
}

// 以下を追記
class User{
    
    // コンストラクタ
    public User(String name, int bill) {
        this.name = name;
        this.bill = bill;
    }
    
    // 名前
    public String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    // 紙幣
    public int bill;
    
    public int getBill() {
        return bill;
    }
    
    public void setBill(int bill) {
        this.bill = bill;
    }
}

以下のリンクにアクセスしてみましょう。
URL:http://localhost:8080/thymeleaf

選択オブジェクト

コントローラー内の以下の箇所に注目してください。

//  選択オブジェクト用サンプル
User user = new User("野口英世",1000);
mav.addObject("user",user);

model.addAttribute(“user”,user);という形で、「user」という名前でインスタンス化、
Modelへ保管し、テンプレート側で利用しようとしています。

テンプレート側ではtableタグに「th:object」を使い、「${user}」を設定しています。
これでtableタグ内では「*{ }」でuser内のプロパティ等を直接扱えるようになります。
あとは、th:text=”*{name}” のように値を出力していくだけです。

リテラル置換(|~|)

上でシングルコーテーションを用いてテキストリテラルを記述できることをお伝えしましたが、
「リテラル置換」を用いれば、よりシンプルに書くことが可能です。

"| テキストの内容 |"

テキストの前後に「|」を付けて記述します。
普通のテキストと何が違うのかというと、この中に変数式を直接書き込むことができます。

・usage.html

	<h2>リテラル置換</h2>
	<div th:object="${user}">
		<p th:text = "|名前は *{name}、紙幣は *{bill}円です。|"></p>
	</div>

以下のリンクにアクセスしてみましょう。
URL:http://localhost:8080/thymeleaf

設定されたuserの「野口英世」と「1000」という値がそれぞれ出力され、
「名前は 野口英世、紙幣は 1000円です。」という文章が表示されたかと思います。

テキストリテラルの中に、*{name} と *{bill} という変数式が直接書き込まれています。
リテラル置換を使用することで、+記号でリテラルをつなぎ合わせる必要も無く、
自然なテキストに近い形で変数式を埋め込んで利用することができます。

多項分岐(th:switch)

if文のような「二者択一」ではなく、3つ以上に分岐させたい場合は「th:switch」を使用しましょう。

<○○ th:switch = "条件式">
 <▼▼ th:case = "条件式">..</▼▼>
 <▼▼ th:case = "条件式">..</▼▼>
</○○>

th:switch は指定された条件式の値をチェックし、
その内側にある th:case から同じ値のものを探してそのタグだけを出力します。

試してみましょう。
アクセスするのは今まで使っていたアドレスです。
・ThymeleafController.java

public String thymeleaf(Model model) {
        // リンク用サンプル
        model.addAttribute("user_id", 999);

        //  選択オブジェクト用サンプル
        User user = new User("野口英世",1000); 
        model.addAttribute("user",user);

        // 多項分岐用サンプル    追記
        model.addAttribute("num", 1);  // 追記

        // 画面に出力するViewを指定
        return "thymeleaf/usage";
    }

コントローラー側からは「num」という名前で、「1」という値を渡しています。

・usage.html

	<h2>多項分岐(switch)</h2>
	<div th:switch="${num}">
		<p th:case="1" th:text="|${num} は oneです。|"></p>
		<p th:case="2" th:text="|${num} は teoです。|"></p>
		<p th:case="3" th:text="|${num} は threeです。|"></p>
		<p th:case="4" th:text="|${num} は fourです。|"></p>
		<p th:case="*" th:text="|${num} - 不明です。|"></p>
	</div>

テンプレート側では、「num」の値によって表示される文章が異なるように
th:switch」が使用されています。

th:switch = “${num}”」により、divタグ内では
numの値と同じ「th:case」のpタグだけが表示されるようになります。

pタグ内には「th:case=”1″ th:text=”|${num} は oneです。|”」といったように
th:case の値と、th:text によるメッセージが用意されています。
これは「th:case=1だったら、${num}の値が1のときにこのタグが表示される」ということです。

ちなみにpタグの一番最後の「th:case=”*” 」ですが、これはワイルドカード(参照:正規表現)を使った指定です。
どのth:caseにも該当しなかった場合のデフォルトの処理を指定します。

繰り返し(th:each)

条件分岐ではなく、繰り返し用の属性th:each」もあります。

<○○ th:each = "変数 : ${コレクション}">
 ...${変数}を利用した表示
</○○>

th:each では、配列などを値として用意しますが、ただ配列だけを書くのではなく、
例えば “value : ${list}” といった具合に、新たな変数名と併せて記述します。
これにより、コロンの後にあるリストから順に値を取り出し、コロン前の変数に代入するようになります。

タグの中では、コレクションから値が代入された変数を利用して値の出力などが行なえます。

・usage.html

        <h2>繰り返し(th:each)</h2>
	<table>
		<tr>
			<th>名前</th>
			<th>紙幣</th>
		</tr>
		<tr th:each="data : ${users}" th:object="${data}">
			<td th:text="*{name}"></td>
			<td th:text="|*{bill} 円|"></td>
		</tr>
	</table>

trタグに th:each=”data : ${users}” という形で th:each が用意されています。
これで、users から順に値を取り出しては data に代入する、という繰り返し処理が実行されます。

また、th:object=”${data}” も記述されています。
これにより 繰り返しの内部でも オブジェクトが持つプロパティ名のみを *{ } で書けばいいようにしています。

・ThymeleafController.java

 public String thymeleaf(Model model) {
        // リンク用サンプル
        model.addAttribute("user_id", 999);
        
        //  選択オブジェクト用サンプル 
        User user = new User("野口英世",1000);
        model.addAttribute("user",user);
        
        // 多項分岐用サンプル
        model.addAttribute("num", 1);
        
        // 繰り返し用サンプル   追記
        ArrayList<User> users = new ArrayList<User>();
        users.add(user);
        users.add(new User("北里柴三郎", 1000));
        users.add(new User("津田梅子", 5000));
        users.add(new User("渋沢栄一", 10000));
        model.addAttribute("users",users);
        
        // 画面に出力するViewを指定
        return "thymeleaf/usage";
    }

コントローラーでは、まずArrayListインスタンスを作成し、User配列の形でデータを保管しています。
これをテンプレート側にusersとして渡して、表示させようとしています。

Thymeleafパーサーレベルのコメントブロック(<!–/* ~ */–>)

HTMLでは<!—->でコメントアウトが可能ですが、見かけ上は目に見えませんが実際のHTML上には残っており、ユーザーも検証ツールから確認が可能です。
Thymeleafでは、テンプレートファイルとして処理する際にHTML上に残さないことができるコメントアウト方法もあります。

		<h2>Thymeleafパーサーレベルのコメントブロック</h2>
		<!-- ただのコメントアウト -->
		<!--/* <p>見せられないよ!Thymeleafでのコメントアウト</p> */-->
		
		<!--/* -->
		<p>これも隠されます</p>
		<!-- */-->

検証ツールで確認してみましょう。

HTMLとしては残らない専用タグ(th:blockタグ)

Thymeleaf標準機能では唯一のタグ(要素)機能です。
th:blockタグはThymeleaf属性を活かすために機能であり、th:ifなどの属性は機能として実行しますが、HTMLタグとしては残りません。
属性は追加したいもののdivタグなどを追加すると見た目を壊してしまう場合などに便利です。

		<h2>th:blockタグ</h2>
		<th:block th:if="${num == 1}">
			<p th:text="${num == 1}"></p>
		</th:block>
		<th:block th:if="${num == 2}">
			<p th:text="${num == 2}"></p>
		</th:block>

検証ツールで確認してみましょう。

CSSファイルの読み込み

CSSを書き込み、テンプレートに反映させることも可能です。
実際にやってみましょう。

CSSファイルの配置

Spring Bootでは、JavaScriptやCSS などの静的リソースは src/main/resources/staticに配置します。
staticの直下に「css」フォルダを作成し、その中に「style.css」ファイルを配置しましょう。

結果としては以下の様な配置となります。
src/main/resources/static/css/style.css

HTMLファイルの編集

配置したCSSファイルを読み込んでみましょう。
HTMLファイル(テンプレートファイル)のheadタグ内に以下のように記述します。

<link th:href="@{/css/style.css}" rel="stylesheet">

th:hrefの右辺にて、static以下のディレクトリ(static フォルダからの相対パス)から指定すると読み込まれます。

現在、テンプレートファイルのbosyタグ直下のdivタグに「contents」というクラスがついているので
CSSファイルで編集して画面に変化をもたらしましょう。

CSSファイルの編集

CSSの書き方については以下の単元を参考にしましょう。
HTMLの基本的なタグ・簡易CSS

例ですが、背景色と境界線、内側に距離(padding)を設定してみました。

@charset "UTF-8";

.contents{
	background-color: aquamarine;
	border: 3px solid black;
	padding: 10px 30px;
}

以下の様になります。

この他にもタグにクラスを付けて装飾が出来るのでいろいろと試してみましょう。

Thymeleaf全般 参考サイト:
【初級編】Thymeleafの使い方 | 記事一覧 | SPIRAL ナレッジサイト
Thymeleafの基本的な使い方とその利点についての徹底解説 | 株式会社一創

タイトルとURLをコピーしました