【springBoot + thymeleaf】各画面で利用できる共通レイアウトファイルの作り方と使い方 【layout.html】

springBootアプリ構築の際に避けては通れない各画面共通テンプレートを作成する作業手順についてまとめました。自分の備忘録のためのメモしておきます。

なお、この記事では

<font color="yellow">**・ヘッダー**</font>
<font color="yellow">**・フッター**</font>
<font color="yellow">**・headタグ内の記述(読み込むcssファイルやjsファイルなどの共通化)**</font>

この3点を共通テンプレート化する手順を解説しています。

## 開発環境
開発環境は以下のとおりです

・springBoot V3.3.0
・Java 17

それではいってみましょう！

## 作成するファイル
この記事では以下2点のファイルを作成します。

### 1.layout.html
共通で使用するテンプレート情報を記述する側のファイル

### 2.index.html
共通テンプレートであるlayout.htmlを呼び出して利用する側のファイルです。

## 重要なthymeleafタグ
以下の2つのタグがとっても重要

<font color="yellow">**・th:fragment**</font>
<font color="yellow">**・th:replace**</font>

### 1.th:fragment
共通テンプレート側のlayout.htmlで使用します。fragmentは 「断片」 という意味です。

### 2.th:replace 
テンプレートを利用する側のindex.htmlで使用します。 th:fragment タグを利用して記述された内容を呼び出す際に利用するイメージです。

## layout.htmlの記述(共通テンプレートファイル)
まずはヘッダーとフッターを各画面で呼び出すための記述を行います。
上で述べた <font color="yellow">**th:fragment**</font> を利用して以下のように記述します。


```layout.html
<!DOCTYPE html>
<html lang="jp" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>layout.htmlです</title>
</head>

<!--/* 共通で利用するヘッダーを定義する */-->
<!--/* th:fragment="ヘッダーの名前" という形式で記述する */-->
<header th:fragment="common_header">
    <div style="background-color: gray;">
        <p>共通ヘッダー</p>
    </div>
</header>

<!--/* 共通で利用するフッターを定義する */-->
<!--/* th:fragment="フッターの名前" という形式で記述する */-->
<footer th:fragment="common_footer">
    <div>
        <p>© 2024 Common Footer</p>
    </div>
</footer>
```

ヘッダーは背景色がグレーのヘッダー。フッターはコピーライトマークとテキストを表示する簡単なフッターです。
th:fragmentを利用して記述していますが、ポイントは <font color="yellow">**「th:fragment＝"パーツ(断片)の名前"」** </font> と記述する点です。
ここでつけたパーツ名はテンプレートを呼び出す側のファイルで使用します。


## index.htmlの記述(共通テンプレート利用側)
テンプレート利用側のindex.htmlファイルで、先程のヘッダーとフッターを呼び出します。
<font color="yellow">**「th:replace」** </font>タグを使用して呼び出します。

```index.html
<!DOCTYPE html>
<html lang="jp" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>index.htmlです</title>
</head>
<body>

  <!--/* replaceタグを利用してレイアウト部品を呼び出す */-->
  <!--/* th:replace="~{テンプレートファイルの名前(拡張子なし) :: パーツ名}" という記述で呼び出す */-->
  <header th:replace="~{layout :: common_header}"></header>

  <h1>index.htmlのコンテンツです</h1>

  <!--/* フッターも同様の記述形式で呼び出す */-->
  <footer th:replace="~{layout :: common_footer}"></footer>

</body>
</html>
```

ここでのポイントは部品を呼び出す際の記述方法です。replaceタグに続いて、

<font color="yellow">**~{共通テンプレートファイルの名前(拡張子無し) :: パーツ名}** </font> 

という記述で共通レイアウトファイルに記述した部品を呼び出します。thymeleafでは変数の呼び出しなどで「＄」を使うことが多いですが、「~{ :: }」 という少し変わった記法なので注意が必要です。

上記のコードを記述した後、index.htmlにアクセスすると以下のように表示されているはずです。

![example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283820/336ba80e-1c3d-eb03-61c9-db3647ffa182.png)

layout.htmlに記述したヘッダーとフッターの内容がindex.html上に表示されていますね！


## headタグ部分の共通テンプレート化
ヘッダーやフッターに続いて、headタグ内の記述もテンプレート化します。
headタグ内に記述する内容といえば、

<font color="yellow">**・ページのタイトル**</font>
<font color="yellow">**・読み込むcssファイル**</font>
<font color="yellow">**・読み込むjsファイル**</font>

などを記述するケースが多いと思います。なのでこの内容を共通テンプレート化して別ファイルで呼び出してみます。
th:fragment を使用しつつ、以下のような記述でテンプレート化が可能です。

なお、読み込む css と js ファイルは、プロジェクトにダウンロードした bootStrap ファイルを例に記述してます。

```layout.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<!--/* fragment="テンプレート名(共通利用タグの名前, ..., ...)" */-->
<head th:fragment="common_header(title, links, scripts)">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <!--/* 1.タイトル部分 */-->
    <title th:text="${title}+' | Tset App'">各ページタイトル | Tset App</title>
    
    <!--/* 2.css部分 */-->
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">

    <!--/* 3.js部分  */-->
    <script th:src="@{/js/bootstrap.min.js}"></script>

    <!-- 各画面において別途読み込むファイルを管理するための記述 -->
    <!-- 読み込むファイルが存在しない場合はblockの記述自体が存在しない形式になる -->
    <th:block th:replace="${links} ?: _" />
    <th:block th:replace="${scripts} ?: _" />
    
</head>
<body>
...
...
```

ポイントですが、headタグ部分で、

<font color="yellow">**th:fragment="テンプレート名(共通利用するタグ名, ..., ...)"**</font> 
と記述する形式になります。
今回は 

・titleタグ
・cssファイル
・jsファイル

この3つを共通化するため、引数もコンマ区切りで3つ記載しています。
テンプレート化する時の注意ですが、共通で利用するファイルのパスは絶対パスで記述します。相対パスだと、このテンプレートを呼び出す側のファイルで、対象のcssとjsファイルを正しく読み込めず、うまく機能しません。
また、テンプレート利用側のファイルで別途読み込むcssやjsファイルがある場合は、最後記述してある「th:block」タグ部分の記述も追加で行います。

では、次にテンプレート利用側の記述です。

```index.html
<!DOCTYPE html>
<html lang="jp">
<head th:replace="~{layout :: common_header('index画面', ~{::links}, ~{::scripts})}">
</head>
<body>
...
...
```

テンプレートを呼び出すために

<font color="yellow">**replace="~{テンプレートファイル名 :: テンプレート名('ページタイトル',  ~{::links}, ~{::scripts})}"**</font> 

という形式の記述で呼び出します。
各ページのタイトルは引数部分に設定する文字列で指定可能です。

また、追加で各画面独自のcssやjsを読み込む際は、テンプレートの呼び出し側のファイルでも、「th:block」を利用して、以下のように記述します。

```index.html
<!DOCTYPE html>
<html lang="jp">
<head th:replace="~{layout :: common_header('index画面', ~{::links}, ~{::scripts})}">

    <!--/* ページ個別css */-->
    <th:block th:fragment="links">
        <link rel="stylesheet" th:href="@{/css/index.css}">
    </th:block>

    <!--/* ページ個別js */-->
    <th:block th:fragment="scripts">
        <script th:src="@{/js/index.js}"></script>
    </th:block>

</head>
<body>
...
...
```

以上でheadタグの共通化も完了です。便利な共通テンプレートは積極的に利用していきましょう！