20
Help us understand the problem. What are the problem?

posted at

updated at

Thymeleafチートシート (ver3.0.11)

どうも、おはこんばんは。
spring、thymeleafを使ったことがないメンバーでspringboot×thymeleafのプロジェクト開発をしています.

今回学んだことを忘れない内にThymeleaf.ver3.0.11(2018年10月29日)チュートリアルを参考に自分なりにまとめていきます。間違っている可能性もあるので気になる点等あればコメントください。質問あれば答えます!
ver3.0.12のリリースノートが2020年12月21日に出ていたので気になる人はこちらを。
https://www.thymeleaf.org/releasenotes.html#thymeleaf-3.0.12

まずコントローラーはこれを前提とします。

HelloController.java
@controller
public class HelloController {
    @GetMapping("/")
    public String index(Model model){
        model.addAttribute("message", "Hello");
        return "index";
    }
}

値の表示

index.html
<span th:text="${message}"></span>

Helloが表示される。
th:textでテキストを表示できる。
htmlの属性にth:を付ければ属性値でthymeleaf式が使えるようになる。

結合

index.html
<span th:text="${message} + ' Spring Boot'"></span>

Hello Spring Bootが表示される。

リテラル置換

index.html
<span th:text="|${message} Spring Boot|"></span>

Hello Spring Bootが表示される。
|・・・|でそのまんま置換。結合するための「+」や「’」を省略できて、可読性が高いので好き。
一部分だけに使うこともできる。
th:text="|${message}| + 'Spring Boot'"

プロパティへのアクセス

Usersというbeanクラスに
idとnameプロパティが作られているとする。
コントローラーで渡せば画面からアクセスできる。

HelloController.java
model.addAttribute("users",new Users());
index.html
<span th:text="${users.name}"></span>

UsersクラスのgetName()を呼び出している。
一応getterさえあればメンバ変数がなくても呼び出せるようだ。
キャメル型でないgetname()はダメでnot foundとエラーが出る。
lombokを導入していれば@Getter@Dataを使えばgetterを省略できるからおすすめ。

選択変数式

index.html
<div th:object="${users}">
    <span th:text="*{name}"></span>
    <span th:text="*{id}"></span>
</div>

th:objectを使うことでそのブロック内でオブジェクトを省略できる。
*{・・}でth:objectを参照する。
${users.name}⇒*{name}となり、書くのが楽になる。

${・・}は変数式
*{・・}は選択変数式という。

ローカル変数の使用

index.html
<div th:object="${users}">
    <div th:with="x='名前は',y=*{name}">
        <span th:text="|${x}${y}です|"></span>
    </div>
</div>

th:withで変数を作成でき、ブロック内でのみ有効となる。
一度にx,yのように複数定義が可能。
spanタグでリテラル置換を使っているが、
th:withでも次のように使用できた(試したら動いた・・)。
th:with="x=|名前は*{name}です|"

算術演算子

index.html
<span th:text="1 + 1"></span>

2が表示される。
使用できる演算子:+、-、*、/、%

if文と比較演算子

index.html
<div th:if="${users.id} gt 2">
    <span>Hello</span>
</div>

th:ifでif文をいつものように使える。
「>」は&gt;と書くが、
文字列エイリアスのgtを使った方がシンプルだ。
使用できる演算子:gt(>)、lt(<)、ge(>=)、le(<=)、not(!)

if文と等価演算子

index.html
<div th:if="${users.id} == 2">
    <span>Hello</span>
</div>

等価演算子に関しては比較演算子と違いそのまま使える。
使用できる演算子:eq(==)、neq/ne(!=)。

条件分岐について

<div th:if="${users.id} == 2">
<div th:if="${users.id == 2}">

上記2行目のように全体を囲っても書けるが、変数式と選択変数式を比較するときは、使う式をどちらかに揃える必要がある為注意が必要。

index.html
<div th:object="${users}">
    <div th:with="x=1">
        <!--OK-->
        <div th:if="${users.id == x}">
        <div th:if="*{id == 1}">
        <!--NG-->
        <div th:if="*{id == x}">
    </div>
</div>

th:ifは真偽値条件のみを評価するわけではない。単一値だけの評価時は次の場合trueになる。
値がnullではない場合:

  • booleanのtrue
  • 0以外の数値
  • 0以外の文字
  • “false”でも“off”でも“no”でもない文字列
  • 真偽値でも、数値でも、文字でも文字列でもない場合

(値がnullの場合はth:ifはfalseと評価します)

else

elseはthymeleafにないので
th:unlessを使うか演算子を反転させる。

index.html
<div th:if="${users.id} == 2">
    <span>私だ</span>
</div>

<!--th:unlessでelseの代わり-->
<div th:unless="${users.id} == 2">
    <span>私じゃない</span>
</div>
<!--演算子反転でelseの代わり-->
<div th:if="${users.id} != 2">
    <span>私じゃない</span>
</div>

三項演算子

Usersクラスにboolean型のプロパティflagを作成したとする。

index.html
<!--(if) ? (true)-->
<span th:text="${users.flag} ? 'ONです'"></span>
<!--(if) ? (true) : (false)-->
<span th:text="${users.flag} ? 'ONです' : 'OFFです'"></span>
<!--(value) ?: (defaultvalue)-->
<span th:text="${users.name} ? '設定されていません'"></span>

3つ目の式はデフォルト式といって
\${users.name}がnullじゃなかったら${users.name}の値が表示され、
nullだったら「設定されていません」が表示される。

個人的にデフォルト式は、ローカル変数を設定したとき変数名が間違っていてもエラーがでないので少し気を付けないといけないきがす。

※検証:flag_th:wishでも使える。th:with="変数=条件? true : false"

処理なしトークン

index.html
<!--(value) ?: (defaultvalue)-->
<span th:text="${users.name} ? _">設定されていません</span>

通常タグ間の文字列よりth:textが優先されるので書いても意味がないが、
アンダースコア「_」を使うことで処理をなかったことにできる。
これをデフォルト式で使うと、${users.name}がnullならth:textをなかったことにして、「設定されていません」が表示される。

繰り返し文

Usersクラスにリンゴ、ミカン、バナナが格納された
配列fruitsが用意されていたとする。

index.html
<div th:object="${users}">
    <div th:each="fruits :*{fruits}">
        <span th:text="${fruits}"></span>
    </div>
</div>

リンゴミカンバナナと表示される。
th:eachでループさせることが可能。つまりfor文と同じ。
式は拡張for文のようにth:each="反復変数名 :配列"で書ける。
反復変数はブロック内でのみ有効なローカル変数。

繰り返しステータスの保持

ステータス変数はth:each属性の中で定義され、次のデータを保持している

  • indexプロパティー:0から始まる現在のインデックス
  • countプロパティー:1から始まる現在のインデックス
  • sizeプロパティー:要素数
  • currentプロパティー:現在の要素オブジェクト
  • even/odd真偽値プロパティー:現在の繰り返し処理が、偶数か奇数か
  • first真偽値プロパティー:現在の繰り返し処理が最初かどうか
  • last真偽値プロパティー:現在の繰り返し処理が最後かどうか
index.html
<div th:object="${users}">
    <div th:each="fruits ,stat :*{fruits}">
        <span th:text="${fruits}"></span>
        <span th:text="|${stat.count}回目|"></span>
    </div>
</div>

th:each="反復変数,ステータス変数:配列"で書ける。

プリプロセッシング

thymeleaf式にネストして式を書きたいとき、
先に中の式を評価する必要がある。
例えば、json等の多次元配列を表示するときに使ったりする。

index.html
<div th:object="${users}">
    <div th:each="Data :*{Data}">
        <span th:text="${Data[__${stat.index}__].id}"></span>
    </div>
</div>

__${}__の形をプリプロセッシング式という。
アンダースコア2つで囲むと先に評価してくれる。
もちろん選択変数式:*{}でも利用可能。

以下のように無意味に付けても問題はないが
二重で付けるとエラーになったので注意。

index.html
<!--無問題-->
<span th:text="__${message}__"></span>
<!--エラー-->
<span th:text="__${Data[__${stat.index}__].id}__">

switch

index.html
<div th:switch="${user.role}">
  <p th:case="'admin'">administrator</p>
  <p th:case="'manager'">manager</p>
  <p th:case="*">どれでもない</p>
</div>

th:switchを使う。
どれでもなければth:case="*"になる。

特定の属性でthymeleafを使う

例えば、onclick="alert('値');"という形で
thymeleafで値を取得したいとき。

index.html
<input type="button" th:attr="onclick='alert(\'' + ${message} + '\');'">

特定の属性にth:attrを使えばthymeleafで記述できるが
th:attr="onclick='alert(\'' + ${message} + '\');'"
って複雑。ただのonclickでここまで複雑になるのか。。
「thymeleaf onclick」や動的で検索すると
この式が上位ヒットしてしまう。
ヒット記事:Thymeleaf3で属性値へ動的に値を埋め込み&テキストを追加したい場合

実は、もっと簡潔に書ける。

index.html
<input type="button" th:onclick="|alert('__${message}__')|">

th:を付けるだけでthymeleafが使用可能となる。
それとリテラル置換と、
プリプロセッシングを使用している。

検証:値を${users.id}にすればプリプロしなくてもエラーは起こらないがgetterの戻り値が宣言時の型と違えばエラーとなる。
プリプロすれば自動変換してくれるのかエラーはなくなった。
個人的にとりあえずプリプロにしておけば良いと思う。

古い情報に注意:th:attrの形式からth:onclickだけ有効にしたパターンを記事で見るがthymeleaf.ver3以降ではエラーになるので注意。
th:onclick="'alert(\'' + ${message} + '\');'"

th:blockタグ

index.html
<!--ブラウザソースを確認すると<span>Hello</span>だけが出力されてる-->
<th:block th:if="*{id} == 2">
    <span>Hello</span>
</th:block>
index.html
<!--ブラウザソースを確認するとHelloだけが出力されてる-->
<th:block th:text="Hello"></th:block>

th:blockタグは画面に出力されない。
htmlタグを使いたくないときに使用したりする。

アンエスケープテキスト

index.html
<span th:utext="${message}"></span>

th:textだと「<」や「>」が「&lt;」や「&gt;」に置き換えられるため、scriptとして動作することを防ぐ。
th:utextにすることでエスケープ(サニタイズ)を無効にできる。
※ただし、XSS(クロスサイトスクリプティング)に注意。

${message}の値がHello<br>Spring<br>Bootなら<br>が有効となり改行される。

インライン化

index.html
<span th:text="${message}"></span>
<span>[[${message}]]</span>

[[・・・]]で囲むことでタグ外に記述できる。
インライン化はデフォルトで有効の為、th:inlineは必要ない。(適当に必要と書いている記事もあるが個人的には不要なコードはただ邪魔である・・)。
個人的にはインラインの方が直感的で好き。
th:textを含むタグの入れ子は解釈されなかったりと動きが読めないことがあるので好きじゃない。

アンエスケープ(th:utextの)場合は[(・・・)]

リンク

http://localhost:8080/data?id=(users.idの値)&name=ABC
のようなリンクを作成したいとき

index.html
<!--1.クエリパラメータを動的に-->
<a href="a.html"
   th:href="@{http://localhost:8080/use/data(id=${users.id},name='ABC')}">リンク</a>

<!--2.さらにパスパラメータuseを動的にする-->
<a th:href="@{http://localhost:8080/{use}/data(id=${users.id},name='ABC',use=${use})}">リンク</a>

<!--3.さらにURLを省略する-->
<a th:href="@{/{use}/data(id=${users.id},name='ABC',use=${use})}">リンク</a>
  1. hrefを記載しておくとthymeleafが使えない環境で有効になる。なくても良い。
    th:hrefでthymeleafが使えるので値を動的にできる。

  2. パスパラメータを動的にするにはクエリ部分に追加すれば良い。
    パスパラメータを{}で囲い、パス=${値}で代入される。
    {}で使用されたものはクエリには含まれない。
    (クエリに含めず直接プリプロ式__${use}__でも動いたので個人的にはこの方がわかりやすそう。)

  3. /始まりでコンテキストパスまでを省略可能。
    application.propertiesにserver.servlet.context-path=/api/v1が設定されていれば 「http://localhost:8080/api/v1 」を省略する。

インクルード

外部ファイルを作成し共通化したいコードを使いまわす。

fragment.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <div th:fragment="header">
        ヘッダー
    </div>

    <div th:fragment="footer">
        フッター
    </div>
</body>
</html>
index.html
<div th:insert="~{fragment::header}"></div>
<div th:insert="fragment::footer"></div>
出力結果index.html
<div>
    <div>
        ヘッダー
    </div>
</div>
<div>
    <div>
        フッター
    </div>
</div>

th:insert="~{ファイル名 :: th:fragmentの属性値}"
th:fragmentで作成した部分をインクルードできる。
・ファイル名はtemplateからのパス。
~{}を付けるかは任意(index.htmlの1行目参考)。
・内部ファイルを参照したい時は"::th:fragment属性値"or"this::th:fragment属性値"

th:fragmentを使わない

th:fragmentを使わずcssセレクタのようにid,class属性で呼べる。

index.html
<div class="header" id="header">
    ヘッダー
</div>

<div th:insert="::#header"></div>
<div th:insert="::.header"></div>

th:insert、th:replace、th:includeの違い

th:insertth:replaceth:include3つの類似するタグがある。

index.html
<span th:fragment="hello">こんにちは</span>

1<div th:insert="::hello"></div><!--divタグ内に挿入する-->
2<div th:replace="::hello"></div><!--divタグごと置換-->
3<div th:include="::hello"></div><!--divタグ内にコンテンツのみを挿入する-->
出力結果index.html
1<div>
    <span>こんにちは</span>
</div>
2<span>こんにちは</span>
3<div>
    こんにちは
</div>

th:insertth:includeの違いはコンテンツの外枠を含めるかどうか。
ここでいうhelloのあるspanタグのこと。

th:includeはver3.0より非推奨となった為、th:insertが追加された経緯がある。th:insertでコンテンツのみを挿入したい場合th:blockを使用すれば同じ結果になる。

index.html
<th:block th:fragment="hello">こんにちは</th:block>

fragmentへ引数を渡す

fragment.html
<div th:fragment="header(m)">
    <p>[[${m}]]</p>
    <p>[[${users.id}]]</p>
</div>
index.html
<div th:insert="fragment::header(${message})"></div>

参照先で親の変数も使用可能(fragment.htmlの2行目参考)。

fragmentへタグごと渡す

画面固有のタグごと渡せば簡単に外部ファイルベースで構築できる。
headタグ共通化のサンプル。

fragment.html
<head th:fragment="common_title(t)">
<meta charset="UTF-8">
<title th:replace="${t}">各画面のタイトル</title>
</head>
index.html
<head th:replace="fragment :: common_title(~{::title})">
<title>index画面</title>
</head>

index.htmlで画面固有の値のみを記述する。
ファイル名:: th:fragment属性値(~{::タグ名})でタグごと渡せる。
th:fragmentでtitleを受け取り、th:replaceで置換する。
fragment.htmlで完成されたheadタグをindex.html上へ置換し完成。

ユーティリティオブジェクト

サンプル.html
<!--現在日時の日付オブジェクトを作成する-->
<p th:text="${#dates.createNow()}"></p>
<!--リストが空かどうかをチェック-->
<p th:if="${#lists.isEmpty(users.list)}"></p>
<!--もちろん選択変数式でも記述可能-->
<p th:if="*{#lists.isEmpty(list)}"></p>

ユーティリティオブジェクトは紹介するにはボリュームが多すぎるので割愛します。

thymeleafコメント

<!--htmlのコメントブロック
パースされる為、内容が間違っていればエラーが発生する-->
<!-- <p th:text="${message}"></p> -->

<!--パーサーレベルのコメントブロック
パースされない為、内容が間違っていてもエラーは発生しない-->
<!--/* <p th:text="${message}"></p> */-->

<!--プロトタイプのみのコメントブロック
エディタ等のファイル上でのみコメントアウト。パース後アンコメントされ出力される-->
<!--/*/ <p th:text="${message}"></p> /*/-->

th:fieldについて

th:fieldはbeanクラスのプロパティを設定してやるとid,name,th:valueに展開される。
input,select,option,textareaタグで使用できる。

index.html
<div th:object="${users}">
<input type="text" th:field="*{name}">
<input type="text" id="name" name="name" th:value="*{name}">
</div>

nameプロパティの初期値が空の場合。
出力結果:<input type="text" id="name" name="name" value="">
2行目、3行目は同意なので同じ出力結果となる。

th:field使用時の注意点はnameとvalueはth:fieldの値が優先される。
idのみ上書き可能。

index.html
<div th:object="${users}">
<input type="text" th:field="*{name}" th:value="あ" id="abc">
</div>

上記出力結果:<input type="text" id="abc" name="name" value="">

th:fieldにはローカル変数を使用できない(おそらく...)。
よくあるエラーパターンはth:each内で使おうとするとき。

index.html
<div th:object="${users}">
    <div th:each="f ,stat :*{fruitsList}">
        <input type="text" th:field="${f.name}">
    </div>
</div>

反復変数fはローカル変数の為エラーになる。
th:field="*{fruitsList[__$stat.index__].name}"or
th:field="${users.fruitsList[__$stat.index__].name}にすれば良い。

またインデックスを含むときのth:fieldのid、nameの展開形式が違う為注意。
<input ~ id="fruits0" name="fruits[0]"

ドロップダウンのサンプル

セレクト.html
<div th:object="${users}">
    <select name="fruits">
        <option th:each="f :*{fruitsList}" th:value="${f}">[[${f}]]
        </option>
    </select>
</div>
<!--Java側で初期値を設定したい場合はfruitsに値を入れて
th:selected="*{fruits} == ${value}"とかにすれば良い。-->
データリスト.html
<div th:object="${users}">
<input type="text" name="name" autocomplete="off" list="keywords">
     <datalist id="keywords">
        <option th:each="fruits :*{fruitsList}" th:value="${fruits}">[[${fruits}]]
        </option>
    </datalist>
</div>

JavaScriptでthymeleafを扱う

scriptタグ内でもthymeleafを使える。

基本.html
<script th:inline="javascript">
    var message = [[${message}]];
</script>

scriptタグにth:inline="javascript"を追加すると
インライン式で簡単に値の取得が可能となる。
 

静的対応.html
<script th:inline="javascript">
    var message = /*[[${message}]]*/ "メッセージ";
</script>

html単体で表示するときはvar message = [[${message}]];だとエラーが出るのでその対策。
thymeleaf使用時は[[${message}]]、html単体などthymeleaf未使用時はメッセージが有効になる。
 

XML対応.html
<script th:inline="javascript">
/*<![CDATA[*/
    var message = [[${message}]];
/*]]>*/
</script>

xmlで記述するときはCDATAセレクションで囲えば良い。
html5しか使っていなければ関係はない。
(html5は21年に廃止されたので現在はHTML Living standardがデファクトスタンダードだが...)
 

thymeleaf式対応.html
<script th:inline="javascript">
    [#th:block th:unless="${message}"]
        var username = [[${message}]];
    [/th:block]
</script>

thymeleaf式を書き込むこともできる。
開始タグは[#・・・]で囲む。
終了タグは[/th:block]を短縮して[/]と記述することもできる。

Javaメソッドの呼び出し

コントローラ.java
Data Data = new Data(); //DataはgetAメソッドを持つとする。
model.addAttribute("Data",Data);
index.html
<p th:text="${Data.getA('a')}"></p>

オブジェクトにインスタンスを追加すれば所持するメソッドを使用可能。

@beanNameを使う例
Thymeleafは、@beanNameでBeanアクセスを許可するのでそれを使う。

Config.java
@Configuration
public class Config {
    @Bean
    public Data Data() {
        return new Data();
    }
}
index.html
<p th:text="${@Data.getA('a')}"></p>

@BeanでDataクラスをSpringのDIコンテナ管理下に登録している。
なので、わざわざビューを返すときにオブジェクトへ追加する必要がなくなる。
@Configurationを付けるとSpring起動時に自動で探しにくる。
なので、独立したファイルでまとめて管理できる。
Beanにアクセスするときは${@Data.getA('a')}のように@を付ける。

参考

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html
https://casual-tech-note.hatenablog.com/entry/2018/10/10/224250

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
20
Help us understand the problem. What are the problem?