Spring Boot で Thymeleaf 使い方メモ

# Thymeleaf とは
- 読み方は「タイムリーフ」
- テンプレートエンジンの１つ
- Spring Boot で使用が推奨されている
- 純粋な HTML としてテンプレートを記述することができる

# 動作環境
Spring Boot 上で Spring MVC のビューテンプレートエンジンとして動かすことを前提で調べる。

# Hello World
```groovy:build.gradle
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.6.RELEASE'
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.springframework.boot:spring-boot-devtools'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
}

ext['thymeleaf.version'] = '3.0.7.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.2'

jar.baseName = 'thymeleaf'
```

- `spring-boot-devtools` は、コード変更をすぐに反映できるようにするツール？で、 Thymeleaf の利用に直接は関係ないが、便利なので入れている
- 2017年8月現在、 Spring Boot で Thymeleaf を有効に（依存関係に `spring-boot-starter-thymeleaf` を追加）すると ver 2.x 系が採用される
- しかし、 Thymeleaf は現在 ver 3.x 系になっている
- Spring Boot で利用する Thymeleaf を 3.x にするには `'thymeleaf.version'` と `'thymeleaf-layout-dialect.version'` を拡張プロパティ（`ext`）に追加して指定する
- バージョン番号は、 `'thymeleaf.version'` は `org.thymeleaf:thymeleaf-spring4` のバージョンを
- `'thymeleaf-layout-dialect.version'` は `nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect` のバージョンをそれぞれ指定する
    - [Maven Repository: org.thymeleaf » thymeleaf-spring4](https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring4)
    - [Maven Repository: nz.net.ultraq.thymeleaf » thymeleaf-layout-dialect](https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect)

```text:フォルダ構成
|-build.gradle
`-src/main/
  |-java/sample/thymeleaf/
  |  |-Main.java
  |  `-web/
  |    `-HelloController.java
  |
  `-resources/
    |-application.properties
    `templates/
       `-hello.html
```

```properties:application.properties
spring.thymeleaf.mode=HTML
```

- デフォルトだと HTML5 モードで動作する
- Thymeleaf の ver 3.x からは HTML モードの利用が推奨されるため、 HTML5 モードだと警告メッセージがコンソールに出力される
- この設定を入れておけば、 HTML モードで動作するので警告が出なくなる

```java:Main.java
package sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
```

```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello Thymeleaf!!");
        return "hello";
    }
}
```

- Spring Boot のデフォルト設定だと、クラスパス以下の `/resources/templates/【Controller の戻り値】.html` でテンプレートファイルが探索される
- 上の実装の場合は `src/main/resources/templates/hello.html` がテンプレートファイルとして利用されることになる

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="${message}"></h1>
    </body>
</html>
```

- テンプレートファイルは純粋な HTML として記述できる
- Thymeleaf 用の属性は `xmlns:th="http://www.thymeleaf.org"` で名前空間を宣言して利用できる

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/142f8f8f-ae56-5aa9-0873-78c0f69f7a66.jpeg)

# テキストの埋め込み
```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="'hello world'"></h1>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/95b4552d-864c-2396-f0f9-e3a3175c11f1.jpeg)

**説明**

```html:hello.html
<h1 th:text="'hello world'"></h1>
```

- `th:text="【出力する値】"` とすることで、そのタグのテキスト要素を出力できる
- `【出力する値】` には Thymeleaf 独自の式言語を使用できる
    - 基本的に他の式言語（SpEL や EL式 とか）と同じ感じ

## インライン処理
```html:hello.html
<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1>[['hello world!!']]</h1>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/4124c0c4-1ca5-03c8-ff07-ff35fb0b41ce.jpeg)

**説明**

- `[[【出力する値】]]` で、 `th:text` を使わずに直接テンプレート上に値を出力することができる

# 変数の出力
```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("modelValue", "Model Value!!");
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1>[[${modelValue}]]</h1>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/95564f88-2546-88dc-459d-696c21a0349c.jpeg)

**説明**

```html:hello.html
<h1>[[${modelValue}]]</h1>
```

- 実行時のコンテキストに保存されている値をテンプレートに埋め込むには `${【変数名】}` という式を利用する

```java:HelloControlloer.java
    public String hello(Model model) {
        model.addAttribute("modelValue", "Model Value!!");
```

- コンテキストへの値の保存は、 Spring MVC のコントローラ引数で `Model` クラスを受け取り、 `addAttribute()` メソッドで行う

# 式の構文
## リテラル
```html
<!-- 文字列 -->
<span th:text="'some text'"></span>
<!-- 数値 -->
<span th:text="123"></span>
<!-- 真偽値 -->
<span th:text="true"></span>
<!-- null -->
<span th:text="null"></span>
```

## 文字列連結
```html
<span th:text="'some text' + '!!'"></span>
```

- `+` で文字列の連結が可能

## リテラル置換
```html
<span th:text="|Hello ${message}|"></span>
```

- `|` で括った文字列リテラルの中で `${}` を使えば、文字列リテラルの中に変数を埋め込むことができる

## 算術演算子
```html
<span th:text="(30 * 20 + 10 / 5 - 1) % 3"></span>
```

- `*`, `/`, `+`, '-', '%' が使用できる
- 意味は Java のと同じ感じ

## 論理演算子
```html
<span th:text="true
                and false
                or true
                and not true
                or !false"></span>
```

- `and` または `or` が使用できる
- `not` または `!` で否定できる

## 比較と等価
```html
<span th:text="1 < 10"></span>
<span th:text="1 > 10"></span>
<span th:text="1 <= 10"></span>
<span th:text="1 >= 10"></span>
<span th:text="1 == 10"></span>
<span th:text="1 != 10"></span>
```

- `<`, `>`, `<=`, `>=` が使用可能
- それぞれ Java の演算子と同じ
- `==`, `!=` は `equals()` を使った比較に置き換えられる（文字列の比較も可）

## 条件演算子
```html
1: <span th:text="true　? 'a'"></span><br>
2: <span th:text="false ? 'b'"></span><br>
3: <span th:text="true  ? 'c': 'C'"></span><br>
4: <span th:text="true　?: 'd'"></span><br>
5: <span th:text="false ?: 'e'"></span><br>
6: <span th:text="null　?: 'f'"></span><br>
```

**出力結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/1b7d89f3-8b6f-4396-b27c-2b5601c8b0fc.jpeg)

**`【条件】 ? 【値】`**

- 【条件】 が `true` の場合に【値】が評価される
- `false` の場合は空になる

**`【条件】 ? 【値１】 : 【値２】`**

- 【条件】が `true` の場合は【値１】が、 `false` の場合は【値２】が評価される

**`【オブジェクト】 ?: 【値】`**

- 【オブジェクト】が `null` の場合に【値】が評価される
- `null` 以外の場合は【オブジェクト】がそのまま評価される

# SpEL 式
`${...}` の中は SpEL (Spring Expression Language)という式言語によって評価される。
この SpEL を使うと、オブジェクトに簡潔にアクセスできる。

## 標準の式言語は OGNL
素の Thymeleaf のリファレンスを見ると、式言語は OGNL が使われていると説明が書いてある。
しかし、 Spring と統合した場合は SpEL が代わりに使用されるようになる。

[Tutorial: Thymeleaf + Spring](http://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html)

> 2 The SpringStandard Dialect
>  
> Use Spring Expression Language (Spring EL or SpEL) as a variable expression language, instead of OGNL. 
> Consequently, all ${...} and *{...} expressions will be evaluated by Spring’s Expression Language engine. 
> 
> （翻訳）
> Spring Expression Language (Spring EL または SpEL) を変数の式言語として OGNL の代わりに使用します。
> 従って、全ての `${...}` と `*{...}` の式は Spring の式言語エンジンによって評価されます。

## フィールド・プロパティ・メソッドの参照
```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("hoge", new Hoge());
        return "hello";
    }
    
    public static class Hoge {
        public int publicField = 1;
        
        public int publicMethod() {return 2;}
        
        public int getPublicValue() {return 3;}
    }
}
```

```html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:text="${hoge.publicField}"></div>
        <div th:text="${hoge['publicField']}"></div>
        <div th:text="${hoge.publicMethod()}"></div>
        <div th:text="${hoge.publicValue}"></div>
        <div th:text="${hoge['publicValue']}"></div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/e2b3265b-2803-f366-9fce-eb7ee6d89fba.jpeg)


**説明**

- `public` なフィールドやメソッドなら直接参照が可能
- `getXxx()` というメソッドなら、プロパティとして `objectName.xxx` でアクセスできる（メソッドが `public` である必要がある）
- フィールドとプロパティなら `objectName['名前']` のように角括弧で参照することも可能

## Map の参照
```java:HelloController.java
package sample.thymeleaf.web;

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

import java.util.HashMap;

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        HashMap<String, String> map = new HashMap<>();
        map.put("message", "Hello World!!");

        model.addAttribute("map", map);
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:text="${map.message}"></div>
        <div th:text="${map['message']}"></div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/97527274-cacd-c1b4-6e8d-6c9a66cb55a7.jpeg)

**説明**

- `Map` の場合、 `map.キー` で値を参照できる
- また `map['キー']` のように角括弧で参照することもできる

## List の参照
```java:HelloConteroller.java
package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList(1, 2, 3));
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:text="${list[0]}"></div>
        <div th:text="${list[1]}"></div>
        <div th:text="${list[2]}"></div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/379e99cf-34dd-9972-3fbb-93b7ef4fe9bc.jpeg)

**説明**

- `List` や配列の要素にアクセスするには、 `list[インデックス]` とする

# 属性に値を設定する
```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <span th:class="'hello' + 'world'">hoge</span>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/ede908de-ca0a-ce79-a3f9-68cb5d2a84fc.jpeg)

**説明**

- `th:属性名` という形で属性を記述することで、式の処理結果を属性にセットすることができる
- v3.x から？は任意の属性に対してこの方法が使えるっぽい
    - [5.6 Setting the value of any attribute (default attribute processor)](http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-the-value-of-any-attribute-default-attribute-processor)

# リンクURL
```properties:application.properties
server.contextPath=/thymeleaf
```

- 動作確認のために Spring Boot をコンテキストパスつきで起動するよう設定

```html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li>
                @{foo/bar} = [[@{foo/bar}]]
            </li>
            <li>
                @{/foo/bar} = [[@{/foo/bar}]]
            </li>
            <li>
                @{~/foo/bar} = [[@{~/foo/bar}]]
            </li>
            <li>
                @{http://localhost:8080/foo/bar} = [[@{http://localhost:8080/foo/bar}]]
            </li>
            <li>
                @{//localhost:8080/foo/bar} = [[@{//localhost:8080/foo/bar}]]
            </li>
        </ul>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/083c7097-930c-b6a2-53d2-00f1cfad201e.jpeg)

**説明**

- `@{...}` という式を使うと、コンテキストパスなどを上手く補完した形式でパスを構築できる

|種類|説明|
|---|---|
|`foo/bar`|通常の相対パス|
|`/foo/bar`|コンテキストパスからの相対パス|
|`~/foo/bar`|サーバーのルートからの相対パス（`/foo` が他のコンテキストパスになる）|
|`http://xxx/xxx`|絶対パス|
|`//xxx/xxx`|プロトコル相対パス|

通常は `<a>` タグの `href` 属性などに指定するので、

```html
<a th:href="@{/foo/bar}">/foo/bar</a>
```

のように設定することになる。

## クエリパラメータを設定する
```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("paramValue", "PARAM VALUE");
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        [[@{/foo/bar(hoge='HOGE', paramValue=${paramValue})}]]
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/99c6bbef-3114-036b-bdbb-99a637870cb4.jpeg)

**説明**

- パスにクエリパラメータを設定するには、パスの末尾に丸括弧（`()` ）で括ったキーバリューの組み合わせを記述する
- 複数指定する場合は、カンマ区切りで列挙する
- URL エンコーディングは自動的に行われる

## パスに変数を埋め込む
```html:hello.html
<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        [[@{/foo/{pathValue}/bar(pathValue=123)}]]
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/3ee07a9a-5a82-783e-0fa5-ce5bcf42b725.jpeg)

**説明**

- 末尾で `()` を使って宣言した変数をパスの中で `{}` で括った形で利用することができる
- この場合、そのパラメータはクエリパラメータにはセットされず、パスの一部としてだけ利用される


# 暗黙的に参照できるオブジェクト
コントローラの `Model` でセットした値以外にも、デフォルトでアクセスできるオブジェクトが存在する。

```html
<span th:text="${#httpServletRequest}"></span>
<span th:text="${#httpSession}"></span>
<span th:text="${param}"></span>
<span th:text="${session}"></span>
<span th:text="${application}"></span>
```

- `#httpServletRequest` は `HttpServletRequest` オブジェクトそのもの
- `#httpSession` は、 `HttpSession` オブジェクトそのものを参照する
- `param` はリクエストパラメータを保持したオブジェクト
- `session` は `HttpSession` に保存された属性情報
- `application` は `ServletContext` に保存された属性情報
- `param`, `session`, `application` は `Map` のように参照することができる
    - `param['parameterName']` など

## `#` の有る無しの違い
- `httpServletRequest` は `#` が頭についていて、 `session` や `param` はついていない
- この違いは、そのオブジェクトがコンテキスト上のどこに保存されているかによる
- `#` がついていない場合、その値は `#vars` という暗黙の変数から検索される

# オブジェクトの選択
```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("hoge", new Hoge());
        return "hello";
    }
    
    public static class Hoge {
        public String name = "hogeee";
        public String value = "HOGEEE";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:object="${hoge}">
            <div>name = [[*{name}]]</div>
            <div>value = [[*{value}]]</div>
        </div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/20cde07f-adaf-2c33-42e1-70051f62949f.jpeg)

**説明**

```html:hello.html
        <div th:object="${hoge}">
            <div>name = [[*{name}]]</div>
            <div>value = [[*{value}]]</div>
        </div>
```

- `th:object` でオブジェクトを指定すると、そのタグの子要素の中では `*{}` という形式でそのオブジェクトのフィールドやプロパティを直接参照できるようになる

# ローカル変数の宣言
```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:with="message = 'Hello World!!'">
            message = [[${message}]]
        </div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/1545e3db-d9fa-9d91-3dd2-6e15aea667c7.jpeg)

**説明**

```html:hello.html
        <div th:with="message = 'Hello World!!'">
            message = [[${message}]]
        </div>
```

- `th:with` の中で宣言した変数は、そのタグの子要素の中だけで有効な変数になり、 `${}` 式の中で参照できるようになる

# 条件による表示・非表示の切り替え
```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("flag", true);
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:if="${flag}">flag is true</h1>
        <h1 th:if="${!flag}">flag is false</h1>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8cd7ed9f-9fc6-7ef1-8f39-7f981c7049a5.jpeg)

**説明**

- `th:if="【条件】"` を使うと、【条件】が**真と評価される値**のときだけ表示されるようになる

## 真・偽と評価される値
```java:HelloController.java
package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("booleanTrue", true);
        model.addAttribute("booleanFalse", false);
        model.addAttribute("numericZero", 0);
        model.addAttribute("numericOne", 1);
        model.addAttribute("charZero", '0');
        model.addAttribute("stringEmpty", "");
        model.addAttribute("stringZero", "0");
        model.addAttribute("stringOff", "off");
        model.addAttribute("stringNo", "no");
        model.addAttribute("stringFalse", "false");
        model.addAttribute("anyObject", new Object());
        model.addAttribute("nullValue", null);
        
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:if="${booleanTrue}">booleanTrue</li>
            <li th:if="${booleanFalse}">booleanFalse</li>
            <li th:if="${numericZero}">0</li>
            <li th:if="${numericOne}">numericOne</li>
            <li th:if="${charZero}">charZero</li>
            <li th:if="${stringEmpty}">stringEmpty</li>
            <li th:if="${stringZero}">stringZero</li>
            <li th:if="${stringOff}">stringOff</li>
            <li th:if="${stringNo}">stringNo</li>
            <li th:if="${stringFalse}">stringFalse</li>
            <li th:if="${anyObject}">anyObject</li>
            <li th:if="${nullValue}">nullValue</li>
        </ul>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/2c033743-f001-433e-96dd-702e67ce5d5e.jpeg)


**<font color="red">偽と評価される値</font>**

- `boolean` の `false`
- 数値の `0`
- `null`
- 文字列の `"false"`, `"off"`, `"no"`

**<font color="blue">真と評価される値</font>**

- 「偽と評価される値」以外全て
- 例
    - `boolean` の `true`
    - 数位値の `1`
    - 文字列の `"0"`
    - 空文字
    - `null` でない任意のオブジェクト

リファレンスを読むと、「文字列の `"0"` 以外の値は `true` と評価される」と書かれている。
つまり、「文字列の `"0"` は `false`」と評価されるような気がするが、実際に試すと `true` と評価される。
バグ？


# 繰り返し処理
```java:HelloController.java
package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList("hoge", "fuga", "piyo"));
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="element : ${list}">[[${element}]]</li>
        </ul> 
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8a8acd4e-0597-88d6-c487-aa09f0b766e4.jpeg)

**説明**

- `th:each="【各要素を格納する変数名】 : ${【繰り返し処理するオブジェクト】}"` で、タグを繰り返し出力することができる
- 【各要素を格納する変数名】は、繰り返しの出力の中でのみ有効な変数になる
- `Iterable` を実装したクラスであれば、 `th:each` で繰り返し処理することができる

## Map の繰り返し
```java:HelloController.java
package sample.thymeleaf.web;

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

import java.util.HashMap;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        HashMap<String, String> map = new HashMap<>();
        map.put("hoge", "HOGE");
        map.put("fuga", "FUGA");
        map.put("piyo", "PIYO");

        model.addAttribute("map", map);
        
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="entry : ${map}">
                key=[[${entry.key}]], value=[[${entry.value}]]
            </li>
        </ul>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/f8f8d99c-d8fb-b10c-1d29-e07ca35d23d0.jpeg)

**説明**

- `Map` を繰り返し処理した場合、 `Map.Entry` が各要素として繰り返し処理される

## List, Map 以外を繰り返し処理した場合
```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="element : 'text'">[[${element}]]</li>
        </ul>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/b2db3fb6-b8cd-e74a-faf2-848268866c2a.jpeg)

**説明**

- `Iterable` を実装していない、かつ `Map` 以外のオブジェクトを `th:each` で繰り返し処理した場合、その値を唯一持つ `List` として扱われる

## 繰り返しのステータスの参照
```java:HelloController.java
package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList("hoge", "fuga", "piyo"));
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="element, status : ${list}">
                index = [[${status.index}]],
                count = [[${status.count}]],
                size = [[${status.size}]],
                current = [[${status.current}]],
                even = [[${status.even}]],
                odd = [[${status.odd}]],
                first = [[${status.first}]],
                last = [[${status.last}]]
            </li>
        </ul>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/d5692adf-194d-a67e-228f-2c330ebacd3a.jpeg)

**説明**

- `th:each="【各要素】, 【ステータス変数】 : 【繰り返す対象】"` のように、各要素変数の宣言に続けて `, 変数名` とすることで、各ループの状態を保持した変数を使えるようになる
    - ステータス変数の宣言を省略した場合は、 `【各要素】Stat` という名前の変数でステータス変数が準備される
    - `th:each="element : ${list}"` とした場合は `elementStat` でステータス変数を参照できる
- ステータスには、現在のループの状態に関する情報が格納されている
- ステータスに保存されている情報は以下の通り

|変数名|意味|
|-----|----|
|`index`|現在のループインデックス（`0` はじまり）|
|`count`|現在のループカウント（`1` はじまり）|
|`size`|繰り返し対象オブジェクトのサイズ|
|`current`|現在の繰り返し要素|
|`even`|現在の要素が偶数かどうか（真偽値）|
|`odd`|現在の要素が奇数かどうか（真偽値）|
|`first`|現在の要素が先頭かどうか（真偽値）|
|`last`|現在の要素が末尾かどうか（真偽値）|

# th:block
```java:HelloController.java
package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList("hoge", "fuga", "piyo"));
        
        return "hello";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <th:block th:each="element : ${list}">
            <h2>[[${element}]]</h2>
        </th:block>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8937486c-65fc-0cc3-e447-d40fbf538678.jpeg)

**説明**

```html:hello.html
        <th:block th:each="element : ${list}">
            <h2>[[${element}]]</h2>
        </th:block>
```

- `<th:block>` タグは、レンダリング後からは完全に消去される
- Thymeleaf の処理（`th:if` とか）は書けるので、処理は書くためだけに `<div>` タグを増やすといったことが不要になる

# フラグメントを埋め込む
## 他のテンプレートを埋め込む
```text:フォルダ構成
`-src/main/
  |-java/sample/thymeleaf/
  | `-FragmentController.java
  |
  `-resources/templates/fragment/
    |-fragment.html
    `-embedded.html
```

```java:FragmentController.java
package sample.thymeleaf.web;

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

@Controller
public class FragmentController {
    
    
    @GetMapping("/fragment")
    public String fragment() {
        return "fragment/fragment";
    }
}
```

```html:embedded.html
<h2>embedded</h2>
```

```html:fragment.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div id="foo"
             th:insert="fragment/embedded"></div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/c709204b-ef5c-5254-6f28-3ee16ba43572.jpeg)

**説明**

```text:フォルダ構成
`-src/main/
  `-resources/templates/fragment/
    |-fragment.html
    `-embedded.html
```

```html:fragment.html
        <div id="foo"
             th:insert="fragment/embedded"></div>
```

- 他のテンプレートを埋め込むには `th:insert` を使用する
    - v2.x の頃は `th:include` があったが、 v3.x では非推奨となっている
- 値には、埋め込むテンプレートを指定する
    - ここで指定する値は、コントローラクラスの戻り値からテンプレートファイルを解決するときと同じロジックが適用されるので、それに合わせる
- `th:insert` の値では式を利用することもできる（`th:insert="fragment/${name}"`）

## 埋め込むフラグメントを指定する
```html:embedded.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <span th:fragment="hoge">hoge</span>
        <span th:fragment="fuga">fuga</span>
    </body>
</html>
```

```html:fragment.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div id="foo"
             th:insert="fragment/embedded :: fuga"></div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/7251ec6b-5730-88c9-15a6-c09e820c5cdb.jpeg)

**説明**

```html:embedded.html
        <span th:fragment="hoge">hoge</span>
        <span th:fragment="fuga">fuga</span>
```

- 埋め込む側に `th:fragment` 属性でフラグメントの名前を指定する

```html:fragment.html
        <div id="foo"
             th:insert="fragment/embedded :: fuga"></div>
```

- `th:insert` で、 `【テンプレート名】 :: 【埋め込むフラグメント名】` と指定することで、指定したテンプレートの指定したフラグメントだけを埋め込むことができる

## th:replace で完全に置き換える
```html:embedded.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <span id="hoge" th:fragment="hoge">
            HOGE
        </span>
    </body>
</html>
```

```html:fragment.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div id="foo"
             th:insert="fragment/embedded :: hoge"></div>
        <div id="bar"
             th:replace="fragment/embedded :: hoge"></div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/5a577111-f42b-5fbf-d7e5-d386ee43f458.jpeg)

**説明**

- `th:insert` の場合、埋め込み元（`fragment.html`）側のタグの中に、埋め込み対象（`embedded.html`）のタグの中身（`th:fragment` を付与したタグの子要素）が埋め込まれる
- `th:replace` の場合、埋め込み元（`fragment.html`）側のタグが、埋め込み対象（`embedded.html`）のタグそのもの（`th:fragment` を付与したタグそのもの）で置き換えられる

## フラグメントにパラメータを渡す
```html:embedded.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <span th:fragment="hoge(p1, p2)">
            p1 = [[${p1}]], p2 = [[${p2}]]
        </span>
    </body>
</html>
```

```html:fragment.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div th:insert="fragment/embedded :: hoge('HOGE', 'FUGA')"></div>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/b4ea3009-ff25-1023-6721-ca07ca5ccde9.jpeg)

**説明**

```html:embedded.html
        <span th:fragment="hoge(p1, p2)">
            p1 = [[${p1}]], p2 = [[${p2}]]
        </span>
```

- フラグメント名の宣言の後ろで、メソッド引数のような形式でフラグメントが受け取るパラメータを宣言できる

```html:fragment.html
        <div th:insert="fragment/embedded :: hoge('HOGE', 'FUGA')"></div>
```

- 埋め込む側は、フラグメント名の後ろにメソッド呼び出しのように引数を渡すことができる
- 引数は名前指定でも可能で、 `hoge(p1='HOGE', p2='FUGA')` のようにもできる

# Thymeleaf Layout Dialect
フラグメントは個々のページに共通部品を埋め込む形だった。
今度は逆に、共通レイアウト（ヘッダーやフッター）に対して自ページを埋め込む。

この処理自体は標準機能だけでは実現できず、 `thymeleaf-layout-dialect` という拡張機能を入れる必要がある。

```groovy:build.gradle
ext['thymeleaf.version'] = '3.0.7.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.2'
```

この `'thymeleaf-layout-dialect.version'` がソレ。

```text:フォルダ構成
`-src/main/
  |-java/sample/thymeleaf/web/
  |  `-LayoutController.java
  |
  `-resources/templates/layout/
     |-layout.html
     `-content.html
```

```java:LayoutController.java
package sample.thymeleaf.web;

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

@Controller
public class LayoutController {
    
    @GetMapping("/layout")
    public String method() {
        return "layout/content";
    }
}
```

```html:layout.html
<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <meta charset="UTF-8" />
        <title>Layout File</title>
    </head>
    <body>
        <h1>Common Layout</h1>
        <section layout:fragment="content">
        </section>
    </body>
</html>
```

```html:content.html
<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">
    <body>
        <section layout:fragment="content">
            Content
        </section>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/2741c3b5-741c-30c4-3564-802eaa9f0d48.jpeg)

**説明**

```java:LayoutController.java
    @GetMapping("/layout")
    public String method() {
        return "layout/content";
    }
```

- テンプレートとして指定するのは `content.html` の方にする

```html:layout.html
<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <meta charset="UTF-8" />
        <title>Layout File</title>
    </head>
    <body>
        <h1>Common Layout</h1>
        <section layout:fragment="content">
        </section>
    </body>
</html>
```

- 名前空間として `xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"` を宣言
- `layout:fragment` で各ページの情報を埋め込む場所を定義する

```html:content.html
<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">
    <body>
        <section layout:fragment="content">
            Content
        </section>
    </body>
</html>
```

- `layout:decorate` でレイアウトを定義したテンプレートを指定する
- 値には v3.x で追加されたフラグメント式（fragment expression）を利用する
- フラグメント式は `~{}` という形式で記述し、テンプレートの位置を指定する
    - フラグメント式の書式は、 `th:insert` で指定したものとだいたい一緒
    - 詳細は [公式ドキュメントを](http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#flexible-layouts-beyond-mere-fragment-insertion)
- ちなみにフラグメント式の `~{}` を使わなくても一応動くが、警告メッセージがコンソールに出力される
    - メッセージによると、将来は使えなくなるらしい

## title の構築
```html:layout.html
<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <meta charset="UTF-8" />
        <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Layout</title>
    </head>
    <body>
        <h1>Common Layout</h1>
        <section layout:fragment="content">
        </section>
    </body>
</html>
```

```html:content.html
<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">
    <head>
        <title>Content</title>
    </head>
    <body>
        <section layout:fragment="content">
            Content
        </section>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/ac4e8068-9a97-87a7-f3eb-f4c6d4c6a2b5.jpeg)

**説明**

```html:layout.html
        <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Layout</title>
```

- `<title>` の内容を、レイアウトファイルのものとコンテンツファイルのものとの組み合わせにすることができる
- レイアウトファイル側の `<title>` に `layout:title-pattern` 属性を設定し、値の中で組み合わせ後の `<title>` の形を定義する
- `$LAYOUT_TITLE` はレイアウトファイルの `<title>` を参照し、 `$CONTENT_TITLE` はコンテンツファイル側の `<title>` を参照する

```html:content.html
    <head>
        <title>Content</title>
    </head>
```

- コンテンツファイル側は、普通に `<title>` を宣言するだけ

```html:生成されたHTMLのtitle
<title>Layout - Content</title>
```

- レイアウトファイルの `<title>` とコンテンツファイルの `<title>` が組み合わされた値になっている

# コメント
```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <!-- standard html comment -->
        <!--/* -->
            <h1 th:text="'parser level comment'"></h1>
        <!-- */-->
        <!--/*/
            <h1 th:text="'prototype comment'"></h1>
        /*/-->
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/c0cc4e33-893f-4690-e693-70563966297f.jpeg)

**説明**

- Thymeleaf のコメントは３種類存在する

## 通常の HTML コメント
```html:hello.html
        <!-- standard html comment -->
```

- これは、普通の HTML コメントになる
- レンダリング後もそのまま出力され、 HTML 上もただのコメントとして扱われる

## パーサーレベルのコメント
```html:hello.html
        <!--/* -->
            <h1 th:text="'parser level comment'"></h1>
        <!-- */-->
```

- `<!--/*` から始まり `*/-->` までの間が対象となる
- このコメントは、レンダリング時に完全に除去され、 HTML 上には存在しなくなる
- `<!--/*-->` と `<!--*/-->` で括ることもできるので、ブラウザで開いたときは表示されるが、サーバー上で動かすと無くなる、といったことが実現できる

## プロトタイプのみのコメント
```html:hello.html
        <!--/*/
            <h1 th:text="'prototype comment'"></h1>
        /*/-->
```

- `<!--/*/` から `/*/-->` までが対象となる
- ブラウザで直接開いた場合は単純な HTML のコメントとして扱われるが、 Thymeleaf のレンダリングは対象になる
- プロトタイプとしてはコメントとして扱うが、実際にサーバー上で動かすときは Thymeleaf に処理させたい、といったときに使う

# メッセージリソース
```text:フォルダ構成
`-src/main/
  |-java/sample/thymeleaf/
  |  |-web/
  |  |  `-HelloController.java
  |  `-Main.java
  |
  `-resources/
     |-messages/
     |  `-Messages_ja.properties
     `-templates/
        `-hello.html
```

```java:Main.java
package sample.thymeleaf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ResourceBundleMessageSource;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
    
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages/Messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}
```

```properties:Messages_ja.properties
foo.message=おはよう世界
bar.message=さよなら世界
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="#{foo.message}"></h1>
        <h1 th:text="#{bar.message}"></h1>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/e710e099-a7f9-9ac0-bfc6-281be874c907.jpeg)

**説明**

```java:Main.java
import org.springframework.context.support.ResourceBundleMessageSource;

...

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages/Messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
```

- Spring が扱うリソースバンドルファイルを指定している
- エンコーディングを `UTF-8` にしているので、日本語をそのままメッセージファイルに書ける

```html:hello.html
        <h1 th:text="#{foo.message}"></h1>
        <h1 th:text="#{bar.message}"></h1>
```

- メッセージを Thymeleaf のテンプレートから参照するには `#{}` という式を利用する
- 中に表示したいメッセージのキーを指定する

## メッセージにパラメータを埋め込む
```properties:Messages_ja.properties
foo.message=おはよう{0}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="#{foo.message('せかい')}"></h1>
        <h1 th:text="#{foo.message('世界')}"></h1>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/e05dc968-f042-20fc-452b-5e672f412d85.jpeg)

**説明**

```html:hello.html
        <h1 th:text="#{foo.message('せかい')}"></h1>
        <h1 th:text="#{foo.message('世界')}"></h1>
```

- メッセージにプレースホルダがある場合は、 `#{キー名(パラメータ...)}` というふうにメソッド引数のような感じでパラメータを渡すことができる

# Springビーン
```java:MySpringBean.java
package sample.thymeleaf;

import org.springframework.stereotype.Component;

@Component
public class MySpringBean {
    
    public String hello() {
        return "Hello MySpringBean!!";
    }
}
```

```html:hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="${@mySpringBean.hello()}"></h1>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/4914d432-4223-980a-9119-4c40dd46e744.jpeg)

**説明**

- `${}` は SpEL 式として評価されるので、 Spring の Bean の参照も可能になっている
- Spring の Bean を参照するには、 `@ビーン名` とする

# Spring MVC との Form データの連携
```java:MyForm.java
package sample.thymeleaf.web;

public class MyForm {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
```

- Form クラスはデフォルトだとプロパティアクセスになるので、 Getter, Setter が必須

```java:FormController.java
package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(MyForm form) {
        System.out.println("form.value=" + form.getValue());
        return "form";
    }
}
```

- 初期表示時に `Model.addAttribute()` で `MyForm` のオブジェクトを登録

```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <input type="text" th:field="*{value}" />
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/972095b9-16f3-75fd-798e-3640ded330c9.jpeg)

適当に文字を入力して Submit ボタンをクリック。

```text:サーバーコンソール出力
form.value=test
```

**説明**

```html:form.html
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <input type="text" th:field="*{value}" />
            <input type="submit" value="Submit" />
        </form>
```

- Java 側の Form オブジェクト（`MyForm`）と HTML の `<form>` タグを紐づけるには `th:object` タグでコントローラで登録した Form オブジェクトを指定する
    - （`th:object` 使わなくてもなんか動いたけど、ドキュメントには必須と書かれていた...）
- 各入力項目と Form のプロパティとのマッピングには `th:field` 属性を使用する

## チェックボックスのラベル
```java:MyForm.java
package sample.thymeleaf.web;

public class MyForm {
    private boolean checked;

    public boolean isChecked() {
        return checked;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }
}
```

```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <label th:for="${#ids.next('checked')}">checked</label>
            <input type="checkbox" th:field="*{checked}" />
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/085b5288-a438-e02e-255f-1ca5e17e2ee6.jpeg)

**説明**

- `th:field` を使うと、 `id` 属性は Thymeleaf によって自動的に付与される
- 特にループしながら複数のチェックボックスを作成している場合は `for` を指定するのが難しくなる
- これをサポートするため、 `th:for` で `${#ids.next('【紐づけたいプロパティの名前】')}` と指定することで、 `for` 属性値に対象のプロパティの `id` 属性値が設定される
- チェックボックス項目が `<label>` の前にある場合は `${#ids.prev()}` を使う

## ラジオボタン
```java:MyForm.java
package sample.thymeleaf.web;

import java.util.LinkedHashMap;
import java.util.Map;

public class MyForm {
    private String selectedValue = "piyo";
    
    public Map<String, String> radioButtons() {
        Map<String, String> radioButtons = new LinkedHashMap<>();
        radioButtons.put("hoge", "HOGE");
        radioButtons.put("fuga", "FUGA");
        radioButtons.put("piyo", "PIYO");
        
        return radioButtons;
    }

    public String getSelectedValue() {
        return selectedValue;
    }

    public void setSelectedValue(String selectedValue) {
        this.selectedValue = selectedValue;
    }
}
```

```java:FormController.java
package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(MyForm form) {
        System.out.println("form.selectedValue=" + form.getSelectedValue());
        return "form";
    }
}
```

```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div th:each="radioButton : *{radioButtons()}">
                <label th:for="${#ids.next('selectedValue')}" th:text="${radioButton.value}"></label>
                <input type="radio" th:field="*{selectedValue}" th:value="${radioButton.key}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/4ed25e52-6010-ad9f-7222-458f3a494010.jpeg)

**説明**

```java:MyForm.java
    private String selectedValue = "piyo";
    
    public Map<String, String> radioButtons() {
        Map<String, String> radioButtons = new LinkedHashMap<>();
        radioButtons.put("hoge", "HOGE");
        radioButtons.put("fuga", "FUGA");
        radioButtons.put("piyo", "PIYO");
        
        return radioButtons;
    }
```

```html:form.html
            <div th:each="radioButton : *{radioButtons()}">
                <label th:for="${#ids.next('selectedValue')}" th:text="${radioButton.value}"></label>
                <input type="radio" th:field="*{selectedValue}" th:value="${radioButton.key}" />
            </div>
```

- ラジオボタンと Form をマッピングするには、次の２つを用意する
    - ラジオボタンを構築するための情報（`raidoButtons()`）
        - 今回はサクっと作るために `LinkedHashMap` を使ったが、実際はラベルと値を格納する専用の入れ物クラスを用意して `List` とかに詰めると思う
    - 選択された値を格納するプロパティ（`selectedValue`）
- `radioButtons()` の結果を `th:each` で回しながらラジオボタンを構築していく
- ラジオボタンを Form とマッピングするときは、 `th:field` だけでなく `th:value` も指定が必須となる
    - その項目が選択されたときの値が必要なので
- 初期値を選択したい場合は、対応するプロパティに選択値を設定しておく（`selectedValue = "piyo"`）
    - 値を設定しなかったら、全ての項目が未選択の状態になる

## ドロップダウンリスト
```java:MyForm.java
package sample.thymeleaf.web;

import java.util.LinkedHashMap;
import java.util.Map;

public class MyForm {
    private String selectedValue;
    
    public Map<String, String> options() {
        Map<String, String> radioButtons = new LinkedHashMap<>();
        radioButtons.put("hoge", "HOGE");
        radioButtons.put("fuga", "FUGA");
        radioButtons.put("piyo", "PIYO");
        
        return radioButtons;
    }

    public String getSelectedValue() {
        return selectedValue;
    }

    public void setSelectedValue(String selectedValue) {
        this.selectedValue = selectedValue;
    }
}
```

```java:FormController.java
package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(MyForm form) {
        System.out.println("form.selectedValue=" + form.getSelectedValue());
        return "form";
    }
}
```

```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <select th:field="*{selectedValue}">
                <option th:each="option : *{options()}"
                        th:value="${option.key}"
                        th:text="${option.value}">
                </option>
            </select>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/f3f980e5-5be6-db57-0057-8eb92a427a2a.jpeg)

**説明**

- ドロップダウンリストを動的に生成するには、 `<option>` を `th:each` でループさせればいい
- `th:field` は `<select>` タグのほうに記述する
- `<option>` には `th:value` の指定が必須になる

## 動的フィールド
行を動的に追加・削除できるような入力項目の作り方

```java:MyForm.java
package sample.thymeleaf.web;

import java.util.ArrayList;
import java.util.List;

public class MyForm {
    private List<Row> rows = new ArrayList<>();
    
    public void appendRow() {
        this.rows.add(new Row());
    }
    
    public void removeRow(int index) {
        this.rows.remove(index);
    }

    public List<Row> getRows() {
        return rows;
    }

    public void setRows(List<Row> rows) {
        this.rows = rows;
    }

    public static class Row {
        private String value;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}
```

```java:FormController.java
package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping(params="appendRow")
    public String appendRow(MyForm form) {
        form.appendRow();
        this.printRows(form);
        return "form";
    }
    
    @PostMapping(params="removeIndex")
    public String submit(MyForm form, @RequestParam int removeIndex) {
        form.removeRow(removeIndex);
        this.printRows(form);
        return "form";
    }
    
    private void printRows(MyForm form) {
        List<MyForm.Row> rows = form.getRows();
        for (int i = 0; i < rows.size(); i++) {
            MyForm.Row row = rows.get(i);
            System.out.println("i=" + i + ", row.value=" + row.getValue());
        }
    }
}
```

```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <table border="1">
                <tr>
                    <th>No</th>
                    <th>User input</th>
                    <th>Remove Row</th>
                </tr>
                <tr th:each="row, loop : *{rows}">
                    <th th:text="${loop.count}"></th>
                    <th>
                        <input type="text" th:field="*{rows[__${loop.index}__].value}" />
                    </th>
                    <th>
                        <button type="submit" name="removeIndex" th:value="${loop.index}">
                            Remove
                        </button>
                    </th>
                </tr>
            </table>
            
            <input type="submit" name="appendRow" value="Append Row" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/5f982b10-b389-20f0-c8c3-3f51be5fe346.gif)

**説明**

```html:form.html
<tr th:each="row, loop : *{rows}">
    <th th:text="${loop.count}"></th>
    <th>
        <input type="text" th:field="*{rows[__${loop.index}__].value}" />
    </th>
    <th>
        <button type="submit" name="removeIndex" th:value="${loop.index}">
            Remove
        </button>
    </th>
</tr>
```

- 動的に増減するフィールドを紐づける場合は、 `th:field` の指定をすこし工夫する必要がある
    - 単純に `${row.value}` とするとエラーになる
- `rows` をインデックス指定で各要素にアクセスする必要がある
- さらに、インデックスの指定は**プリプロセッシング**の仕組みを利用する
- プリプロセッシングとは通常の式よりも先に評価させることができる仕組みで、 `__` で括ることで記述する
    - `__${loop.index}__` の部分
- これにより、 `*{rows[__${loop.index}__].value}` は `*{rows[0].value}` と評価されてから `*{}` の中がさらに評価されるようになる
- 単純に `*{rows[loop.index].value}` としても動きそうだが、残念ながら動かない
- これは、 SpEL 式がリストのインデックスを指定する括弧の中で式を指定できない仕様になっていることに起因する

```java:FormController.java
    @PostMapping(params="appendRow")
    public String appendRow(MyForm form) {
        form.appendRow();
        this.printRows(form);
        return "form";
    }
    
    @PostMapping(params="removeIndex")
    public String submit(MyForm form, @RequestParam int removeIndex) {
        form.removeRow(removeIndex);
        this.printRows(form);
        return "form";
    }
```

- `Append Row` ボタンがクリックされたのか、 `Remove` ボタンがクリックされたのかの識別は、ボタンに `name` 属性を指定し、そのパラメータの有る無しでコントローラのメソッド呼び出しを切り替えるようにしている
- マッピング用のアノテーション（`@PostMapping`, `@GetMapping` など）の `params` で、「そのパラメータがあれば実行」というのを制御できる

# バリデーション
Spring MVC は Bean Validation を使った入力チェックの仕組みを用意している。
Thymeleaf は、このエラー結果を確認したり、エラーメッセージを表示するための仕組みを用意している。

```java:MyForm.java
package sample.thymeleaf.web;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

public class MyForm {
    @Size(min=3)
    private String text;
    @Min(100)
    private Integer number;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }
}
```

```java:FormController.java
package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(@Validated MyForm form, BindingResult result) {
        System.out.println("********************************************************");
        System.out.println("form = " + form);
        System.out.println("result = " + result);
        System.out.println("********************************************************");
        return "form";
    }
}
```

```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div>
                <input type="text" th:field="*{text}" />
                <ul th:each="error : ${#fields.errors('text')}">
                    <li th:text="${error}"></li>
                </ul>
            </div>
            <div>
                <input type="text" th:field="*{number}" />
                <ul th:each="error : ${#fields.errors('number')}">
                    <li th:text="${error}"></li>
                </ul>
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/5c69ea26-d22b-c96b-b2f7-bf5d1b7082fb.jpeg)

**説明**

```java:MyForm.java
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

...

    @Size(min=3)
    private String text;
    @Min(100)
    private Integer number;
```

- Form のフィールドを Bean Validation のアノテーションで注釈し、チェック内容を定義
- Bean Validation の使い方については[こちら](http://qiita.com/opengl-8080/items/3926fbde5469c0b330c2)を参照

```java:FormController.java
import org.springframework.validation.annotation.Validated;

...

    @PostMapping
    public String submit(@Validated MyForm form, BindingResult result) {
```

- コントローラメソッドの引数で、フォームオブジェクトを `@Validated` でアノテートすることで、入力チェックが有効になる

```html:form.html
  <div>
      <input type="text" th:field="*{text}" />
      <ul th:each="error : ${#fields.errors('text')}">
          <li th:text="${error}"></li>
      </ul>
  </div>
  <div>
      <input type="text" th:field="*{number}" />
      <ul th:each="error : ${#fields.errors('number')}">
          <li th:text="${error}"></li>
      </ul>
  </div>
```

- 各プロパティで発生したエラーメッセージは `${#fields.errors('【プロパティ名】')}` でアクセスできる
- 結果はリストになっているので、ループで回せば各エラーメッセージを取得できる

## 項目がエラーかどうか確認する
```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div>
                <input type="text" th:field="*{text}" />
                <span th:if="${#fields.hasErrors('text')}">エラーです！</span>
            </div>
            <div>
                <input type="text" th:field="*{number}" />
                <span th:if="${#fields.hasErrors('number')}">エラーです！</span>
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/cef83323-8759-8f42-6293-876f5ded7dfb.jpeg)

**説明**

- `${#fields.hasErrors('【プロパティ名】')}` で、そのプロパティにエラーがあるかどうかを確認できる

## エラーがある場合のスタイルを指定する
```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div>
                <input type="text" th:field="*{text}" class="always-assigned" th:errorclass="error-style" />
            </div>
            <div>
                <input type="text" th:field="*{number}" class="always-assigned" th:errorclass="error-style" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/35a33496-3686-4b19-f79c-47bf2c69aa34.jpeg)

**説明**

- `th:errorclass` 属性を指定すると、そのプロパティにエラーになったときだけ指定された class 属性が追加される
- エラー時のスタイルを適用したい場合は、 `${#fields.hasErrors('text') ? 'error-style'}` のようにするよりかは、シンプルで分かりやすくなる

## エラーが１つでもあるかどうか確認する
```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <h3 th:if="${#fields.hasAnyErrors()}">エラーがあります</h3>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/ad90fe57-796f-432b-0100-0da477cf21ca.jpeg)

**説明**

```html:form.html
<h3 th:if="${#fields.hasAnyErrors()}">エラーがあります</h3>
```

- `#fields.hasAnyErrors()` で、エラーが１つでも存在するかどうかを確認できる
- `#fields.hasErrors('*')` または `#fields.hasErrors('all')` としても同じ結果を得られる

## 全てのエラーメッセージを取得する
```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <ul>
                <li th:each="error: ${#fields.allErrors()}">
                    [[${error}]]
                </li>
            </ul>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/6f9fd659-105f-c40c-bc5b-5a12528a00ce.jpeg)

**説明**

- `#fields.allErrors()` で全てのエラーメッセージを取得できる
- これは `#fields.errors('*')` または `#fields.errors('all')` とした場合も同じ動きになる

## グローバルエラー
特定のプロパティに紐づいていないエラーをグローバルエラーと呼ぶ（たぶん）。

相関チェックを行う独自バリデーションを作成して、グローバルエラーを発生させる。

```java:MyValidation.java
package sample.thymeleaf.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = MyValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyValidation {
    String message() default "エラーです！";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
```

```java:MyValidator.java
package sample.thymeleaf.validation;


import sample.thymeleaf.web.MyForm;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MyValidator implements ConstraintValidator<MyValidation, MyForm> {
    @Override
    public void initialize(MyValidation constraintAnnotation) {
        System.out.println("MyValidator initialize");
    }

    @Override
    public boolean isValid(MyForm value, ConstraintValidatorContext context) {
        System.out.println("MyValidator isValid");
        if (value == null) {
            return true;
        }

        Integer number = value.getNumber();
        if (number == null) {
            return true;
        }
        
        String text = value.getText();
        
        if (number == 500) {
            return "500".equals(text);
        }
        
        return true;
    }
}
```

- `number` が `500` だったら、 `text` も `"500"` であることをチェックするバリデータ
- 独自バリデータの作成方法については[こちら](http://qiita.com/opengl-8080/items/3926fbde5469c0b330c2#%E8%87%AA%E4%BD%9C%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%81%A8%E5%88%B6%E7%B4%84%E3%82%A2%E3%83%8E%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)を参照

```java:MyForm.java
package sample.thymeleaf.web;

import sample.thymeleaf.validation.MyValidation;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

@MyValidation
public class MyForm {
    @Size(min=3)
    private String text;
    @Min(100)
    private Integer number;

    ...
}
```

- `MyForm` を `@MyValidation` でアノテート

```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <h3>[[${#fields.hasGlobalErrors()}]]</h3>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

`number` を `500` 以外にしてエラーにする

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/51814ba2-c853-3bad-e2ce-094b53d98323.jpeg)

`number` を `500` にしてエラーにする

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/3d294326-621a-d6b4-fb7e-d73d1fd02d62.jpeg)

**説明**

```html:form.html
<h3>[[${#fields.hasGlobalErrors()}]]</h3>
```

- `#fields.hasGlobalErrors()` でグローバルエラーがあるかどうかを確認できる
- これは `#fields.hasErrors('global')` でも同じ結果になる

### グローバルエラーのメッセージを取得する
```html:form.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <h3>[[${#fields.globalErrors()}]]</h3>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/9d2d65b1-61e1-4266-6810-13f89fd05664.jpeg)

**説明**

- `#fields.globalErrors()` でグローバルエラーのメッセージを全て取得できる
- これは `#fields.errors('global')` とした場合も同じ結果になる

## エラーメッセージの変更
```java:Main.java
package sample.thymeleaf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ResourceBundleMessageSource;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
    
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.addBasenames("messages/Messages", "messages/validation-messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}
```

```java:MyWebConfig.java
package sample.thymeleaf;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MyWebConfig extends WebMvcConfigurerAdapter {
    private MessageSource messageSource;

    public MyWebConfig(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource);
        return validator;
    }
}
```

```properties:src/main/resources/messages/validation-messages_ja.properties
javax.validation.constraints.Min.message = {value} 以上で入力してください
```

**実行結果**

![thymeleaf.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/888cac70-0c40-1b53-3713-3ddafb14cd7d.jpeg)

**説明**

- Bean Validation の仕様上、クラスパス直下に `ValidationMessages` をベース名とするプロパティファイルを配置すれば、デフォルトのメッセージを上書きすることができる
- ただし、プロパティファイルを native2ascii でエンコーディングしないといけなかったりでちょっと不便
- Spring の `MessageSource` の仕組みを利用すれば、 UTF-8 でメッセージを書けるので便利

```java:Main.java
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.addBasenames("messages/Messages", "messages/validation-messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
```

- `addBasenames()` で Bean Validation 用のメッセージファイルを追加

```properties:validation-messages_ja.properties
javax.validation.constraints.Min.message = {value} 以上で入力してください
```

- キーが何なのかは、デフォルトのメッセージファイルを調べればいい
- 依存関係の中に Hibernate Validator が入っているので、その jar の中にある `ValidationMessages.properties` を見ればデフォルトメッセージの一覧が確認できる

```properties:ValidationMessages.properties
javax.validation.constraints.AssertFalse.message = must be false
javax.validation.constraints.AssertTrue.message  = must be true
javax.validation.constraints.DecimalMax.message  = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message  = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message      = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Future.message      = must be in the future
javax.validation.constraints.Max.message         = must be less than or equal to {value}
javax.validation.constraints.Min.message         = must be greater than or equal to {value}
javax.validation.constraints.NotNull.message     = may not be null
javax.validation.constraints.Null.message        = must be null
javax.validation.constraints.Past.message        = must be in the past
javax.validation.constraints.Pattern.message     = must match "{regexp}"
javax.validation.constraints.Size.message        = size must be between {min} and {max}

org.hibernate.validator.constraints.CreditCardNumber.message        = invalid credit card number
org.hibernate.validator.constraints.EAN.message                   = invalid {type} barcode
org.hibernate.validator.constraints.Email.message                   = not a well-formed email address
org.hibernate.validator.constraints.Length.message                  = length must be between {min} and {max}
org.hibernate.validator.constraints.LuhnCheck.message               = The check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod10Check.message              = The check digit for ${validatedValue} is invalid, Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod11Check.message              = The check digit for ${validatedValue} is invalid, Modulo 11 checksum failed
org.hibernate.validator.constraints.ModCheck.message                = The check digit for ${validatedValue} is invalid, ${modType} checksum failed
org.hibernate.validator.constraints.NotBlank.message                = may not be empty
org.hibernate.validator.constraints.NotEmpty.message                = may not be empty
org.hibernate.validator.constraints.ParametersScriptAssert.message  = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.Range.message                   = must be between {min} and {max}
org.hibernate.validator.constraints.SafeHtml.message                = may have unsafe html content
org.hibernate.validator.constraints.ScriptAssert.message            = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.URL.message                     = must be a valid URL

org.hibernate.validator.constraints.br.CNPJ.message                 = invalid Brazilian corporate taxpayer registry number (CNPJ)
org.hibernate.validator.constraints.br.CPF.message                  = invalid Brazilian individual taxpayer registry number (CPF)
org.hibernate.validator.constraints.br.TituloEleitoral.message      = invalid Brazilian Voter ID card number
```




# 参考
- [Tutorial: Using Thymeleaf (ja)](http://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf_ja.html)
- [Tutorial: Thymeleaf + Spring](http://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html)
- [Spring Boot 1.4でThymeleaf 3.0系を使うための設定方法 - Qiita](http://qiita.com/kazuki43zoo/items/da64a68b9805e512cdc9)
- [Spring Boot 1.4でThymeleaf 3を使う - Java EE 事始め！](http://masatoshitada.hatenadiary.jp/entry/2016/08/10/132137)
- [Support Thymeleaf 3 · Issue #4393 · spring-projects/spring-boot](https://github.com/spring-projects/spring-boot/issues/4393#issuecomment-233131972)
- [Introduction · Thymeleaf Layout Dialect](https://ultraq.github.io/thymeleaf-layout-dialect/)