Thymeleaf でフラグメント処理: th:fragment, th:replace, th:insert, th:remove="tag"

## 概要

- Thymeleaf にて th:fragment で定義したフラグメントを th:replace や th:insert で埋め込むサンプルコードを書いて挙動を確認する
- Thymeleaf 3.0 から非推奨となった th:include の代替として th:remove="tag" を利用する
- 今回の動作確認環境: OpenJDK 11.0.2 + Thymeleaf 3.0.11

## Thymeleaf のフラグメント機能

th:fragment 属性を指定した要素を、別の箇所に th:insert や th:replace で埋め込むことができる機能。

[Tutorial: Using Thymeleaf \(ja\) - 8.1 テンプレートフラグメントのインクルード](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%83%95%E3%83%A9%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%89)

> 他のテンプレートのフラグメントを別のテンプレートにインクルードしたいという場合がよくあります。フッターやヘッダー、メニューなどです。
> 
> これを実現するために、インクルード可能な「フラグメント」をThymeleafに定義する必要があります。その定義にはth:fragment属性を使用します。

## フラグメントを埋め込む側の構文

th:fragment で定義したフラグメントを th:insert や th:replace で埋め込むことができる。

```
th:insert="テンプレート名 :: フラグメント名"
```

```
th:replace="テンプレート名 :: フラグメント名"
```

[Tutorial: Using Thymeleaf \(ja\) - フラグメント仕様構文](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html#%E3%83%95%E3%83%A9%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E4%BB%95%E6%A7%98%E6%A7%8B%E6%96%87)

> "~{templatename::selector}"：templatenameという名前のテンプレートからマークアップセレクターで指定されたフラグメントをインクルードします。selectorは単なるフラグメント名でも良いので、上記の~{footer :: copy}のように~{templatename::fragmentname}としてシンプルに指定することもできます。

[Tutorial: Using Thymeleaf \(ja\) - フラグメント仕様構文](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html#%E3%83%95%E3%83%A9%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E4%BB%95%E6%A7%98%E6%A7%8B%E6%96%87)

> 取り囲んでいる~{...}はth:insert/th:replaceでは任意です。

## th:insert と th:replace の違い

[Tutorial: Using Thymeleaf \(ja\) - th:insertとth:replaceの違い（とth:include）](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html#thinsert%E3%81%A8threplace%E3%81%AE%E9%81%95%E3%81%84%E3%81%A8thinclude)

> th:insertとth:replaceの違いは何でしょうか（それと3.0から推奨されなくなったth:include）？
> ・th:insertが一番シンプルです：指定されたフラグメントを単純にホストタグのボディとして挿入します。
> ・th:replaceは指定されたフラグメントでホストタグを実際に置換します。
> ・th:includeはth:insertと似ていますがフラグメントをインサートするのではなく、フラグメントのコンテンツだけをインサートします。

## th:remove="tag" について

フラグメントに th:remove="tag" を指定することで、フラグメントの子要素のみを埋め込むことができる。
Thymeleaf 3.0 から非推奨となった th:include と同等の処理をするためには、th:fragment 属性を指定した要素に th:remove="tag" を追加すればよい。

[Tutorial: Using Thymeleaf \(ja\) - 8.4 テンプレートフラグメントの削除](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%83%95%E3%83%A9%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AE%E5%89%8A%E9%99%A4)

> th:remove は値によって5つの異なる振る舞いをします：
> ・all：この属性を含んでいるタグとその全ての子を削除します。
> ・body：この属性を含んでいるタグは削除せずに、全ての子を削除します。
> ・tag：この属性を含んでいるタグは削除しますが、子は削除しません。
> ・all-but-first：最初の子以外の全ての子を削除します。
> ・none：何もしません。この値は、動的な評価を行う場合に有用です。

## サンプルコード

### th:fragment によるフラグメントの定義

フラグメントを定義するサンプルコード (myfragment.html)。
テンプレート名はファイル名から myfragment となる。

th:remove="tag" の有無で2パターンを記述している。

```myfragment.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>断片置き場</title>
</head>
<body>

<!-- フラグメント名は my-fragment -->
<div class="my-fragment-class" th:fragment="my-fragment" th:object="${myMap}">
    <p th:text="'Name: ' + *{name}"></p>
    <p th:text="'Message: ' + *{message}"></p>
</div>

<!-- フラグメント名は my-fragment-remove-tag -->
<div class="my-fragment-class" th:fragment="my-fragment-remove-tag" th:object="${myMap}" th:remove="tag">
    <p th:text="'Name: ' + *{name}"></p>
    <p th:text="'Message: ' + *{message}"></p>
</div>

</body>
</html>
```

### th:insert で埋め込む

th:insert を使用するサンプルコード (myinsert.html)。

```myinsert.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>挿入</title>
</head>
<body>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment -->
<div class="my-insert-class" th:insert="myfragment :: my-fragment"></div>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment-remove-tag -->
<div class="my-insert-class" th:insert="myfragment :: my-fragment-remove-tag"></div>

</body>
</html>
```

th:insert でフラグメントを埋め込んだ実行結果を示す。
th:remove="tag" 無しのフラグメントの場合は、th:fragment 属性を持つ要素ごと挿入されている。
th:remove="tag" 有りのフラグメントの場合は、th:fragment 属性を持つ要素が埋め込まれず、子要素だけが埋め込まれる。

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>挿入</title>
</head>
<body>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment -->
<div class="my-insert-class"><div class="my-fragment-class">
    <p>Name: Alice</p>
    <p>Message: Hello, world</p>
</div></div>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment-remove-tag -->
<div class="my-insert-class">
    <p>Name: Alice</p>
    <p>Message: Hello, world</p>
</div>

</body>
</html>
```

### th:replace で埋め込む

th:replace を使用するサンプルコード (myreplace.html)。

```myreplace.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>置換</title>
</head>
<body>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment -->
<div class="my-replace-class" th:replace="myfragment :: my-fragment"></div>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment-remove-tag -->
<div class="my-replace-class" th:replace="myfragment :: my-fragment-remove-tag"></div>

</body>
</html>
```

th:replace でフラグメントを埋め込んだ実行結果を示す。
th:remove="tag" 無しのフラグメントの場合は、th:fragment 属性を持つ要素ごと置換されている。
th:remove="tag" 有りのフラグメントの場合は、th:fragment 属性を持つ要素が埋め込まれず、子要素だけが埋め込まれて置換される。

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>置換</title>
</head>
<body>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment -->
<div class="my-fragment-class">
    <p>Name: Alice</p>
    <p>Message: Hello, world</p>
</div>

<!-- テンプレート名 :: フラグメント名 -->
<!-- myfragment :: my-fragment-remove-tag -->

    <p>Name: Alice</p>
    <p>Message: Hello, world</p>


</body>
</html>
```

### フラグメントの挙動確認に使用した Java のコード

```java
package com.example;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

import java.util.ArrayList;
import java.util.HashMap;

public class MyApp {

  public static void main(String[] args) {

    // ITemplateResolver インターフェースの一実装
    // テンプレートファイルを読み込むためのリゾルバクラス
    ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
    resolver.setPrefix("templates/");
    resolver.setSuffix(".html");
    resolver.setTemplateMode("HTML5");

    // テンプレート処理のメインクラスにリゾルバをセットする
    TemplateEngine engine = new TemplateEngine();
    engine.setTemplateResolver(resolver);

    // IContext インターフェースの一実装
    // ロケールや、テンプレートに埋め込むためのコンテキスト変数を持つ
    Context context = new Context();

    // コンテキスト変数として Map オブジェクトをセットする
    HashMap<String, String> myMap = new HashMap<String, String>();
    myMap.put("name", "Alice");
    myMap.put("message", "Hello, world");
    context.setVariable("myMap", myMap);

    // テンプレート処理を実行
    String myreplace = engine.process("myreplace", context);
    System.out.println("---------- myreplace ----------");
    System.out.println(myreplace);

    // テンプレート処理を実行
    String myinsert = engine.process("myinsert", context);
    System.out.println("---------- myinsert ----------");
    System.out.println(myinsert);
  }
}
```

## 参考資料

- [Tutorial: Using Thymeleaf \(ja\)](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html)