Spring Boot / View / Thymeleaf / テンプレート / レイアウト

Spring Boot / View / Thymeleaf / テンプレート / レイアウト

つまりコントローラー側が全体を呼び出すのではなく、 内容のコンテンツ側を選ぶとそれに合わせたガワをコンテンツ側主体で選択できる仕組み。

つまり HTML の大枠はほとんどの画面で同じなのだからそれを共通化してあとで、重要なコンテンツ部分だけハメましょうということ。

【注!】準備

このレイアウトという機能はそもそも Thymeleaf の標準機能として存在してなくて、拡張を入れる必要がある。

Maven Repository: nz.net.ultraq.thymeleaf » thymeleaf-layout-dialect

build.gradle にはこう書く

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-thymeleaf')
    implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '2.3.0'
}

これをやっておかないと、エラーは出ないが動作しないという動きをするのでハマる。

基本

レイアウト用テンプレートの用意

とりあえずガワとなるレイアウト用テンプレートを common/layout1.html として用意する。 中身はなんでもいいだろう

調べたらやたら行数が多い複雑なサンプルばっかりだったので単純にする。

<!DOCTYPE html>
<html
    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <title>unko</title>
    </head>
    <body>
        <header>this is header</header>
        <div layout:fragment="contents"></div>
        <footer>this is footer</footer>
    </body>
</html>

コンテンツ部分をHTMLの中へ埋め込みヘッダーとフッターで挟み込む狙いだ。

title が unko になっていることを確認。

コンテンツ側テンプレート用意

埋め込まれるコンテンツ側のテンプレートを用意する。 ここで注目なのはコンテンツ側も普通に完全な HTML として用意するということになっている。

<!DOCTYPE html>
<html
    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorate="~{common/layout1}">
    <head>
        <title>taitoru</title>
    </head>
    <body>
        <div layout:fragment="contents">
            <p>this is contents body</p>
        </div>
    </body>
</html>

ポイントは、layout:decorate で使いたいレイアウトを指定していること、 title が taitoru になっていること。対象部分に contents と印をつけていること。 この印が、レイアウト側のテンプレートの中身と対応している。

結果

実行結果はこうなる。

<!DOCTYPE html>
<html>
    <head>
    <title>taitoru</title>
    </head>
    <body>
        <header>this is header</header>
        <div>
            <p>this is contents body</p>
        </div>
        <footer>this is footer</footer>
    </body>
</html>

レイアウト側でlayout:fragment=“contents” と付けられた要素が、 コンテンツ側で layout:fragment=“contents” と付けられた要素と完全に交換される。中身とかじゃない。つまりレイアウト側は開発時に想定するダミーの何かを書き込んでおけばいい。これはこれで HTML として成立しているのでシステムが存在してなくてもいい。

埋め込んだ部分が差し替わるのはわかるが、よく見ると title も差し替わっている。 すなわちこの機能は、「HTMLを理解している」逆言うと HTML 専用ともいえる。

要素の追加

レイアウト側をこのようにして

<!DOCTYPE html>
<html
    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <title>unko</title>
        <link rel="stylesheet" type="text/css" href="css/hoge.css" />
    </head>
    <body>
        <header>this is header</header>
        <div layout:fragment="contents"></div>
        <footer>this is footer</footer>
    </body>
</html>

コンテンツ側をこのようにすると

<!DOCTYPE html>
<html
    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorate="~{common/layout1}">
    <head>
        <title>taitoru</title>
        <meta name="description" content="aaaa">
        <link rel="stylesheet" type="text/css" href="css/piyo.css" />
    </head>
    <body>
        <div layout:fragment="contents">
            <p>this is contents body</p>
        </div>
    </body>
</html>

結果がこうなる

<!DOCTYPE html>
<html>
    <head>
        <title>taitoru</title>
        <link rel="stylesheet" type="text/css" href="css/hoge.css" />
    <meta name="description" content="aaaa">
    <link rel="stylesheet" type="text/css" href="css/piyo.css" />
    </head>
    <body>
        <header>this is header</header>
        <div>
            <p>this is contents body</p>
        </div>
        <footer>this is footer</footer>
    </body>
</html>

このように要素が追記されて出力される。

レイアウト側が部品を引き込む

よくあるのは、分割されているヘッダーとかフッターをレイアウトが読み込むというパターン

これはレイアウトの機能を使わずに Thymeleaf 標準の機能を使えばよい。

header.html というファイル名のヘッダー用テンプレートを用意して、その必要なブロックを th:fragment で印をつけておく

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title></title>
    </head>
    <body>
        <header th:fragment="header">
            this is header.
        </header>
    </body>
</html>

レイアウト側ではそれを引き込めばいい

<!DOCTYPE html>
<html
    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>hogehoge</title>
    </head>
    <body>
        <header th:include="common/header::header"></header>
        <div layout:fragment="contents"></div>
    </body>
</html>

th:include を使うとその指定されている要素の直下に指定の要素がインサートされる。

そのパラメータ指定は templates ディレクトリからのパスを指定し、コロン2つに続いて fragment で名付けした名前を指定すればいい。この場合なら common ディレクトリにある header.html の中の header で名付けられているブロックがそこへ引き込まれる。

th:replace を使うと、その要素の直下ではなく、その要素と完全に入れ替わる。

コンテンツ側からレイアウト側へパラメータを送り込む

コンテンツ側からこのように書く

<html th:with="hoge='piyo'">

そうするとその名前でレイアウト側で引き出すことができる。

[[${hoge}]]  <!-- piyo -->

現在のページ名とか渡してメニューの状態を切り替えたりしたいときはこれを使うだろう。

この変数的なモノのスコープは、そのつけた要素に閉じているようで、最外の html タグにつければグローバルに振る舞うし もっと下位の要素に付加するとその要素内でしか見れないようだ。

複数のパラメータをつけたいなら

<html th:with="hoge='piyo', fuga='moge'">

このようのコンマで区切ればよい

スコープが合致するなら、レイアウトが引き込んでいるような別のテンプレートからも参照することができる。

java/spring/spring_boot/view/thymeleaf/template/layout.txt · 最終更新: 2021-06-25 18:53 by ore