LoginSignup
2
4

More than 1 year has passed since last update.

#19 Spring layoutを用いて画面パーツを共通化する

Last updated at Posted at 2022-11-26

#19 Spring layoutを用いて画面パーツを共通化する

今回はThymeleafでレイアウトを作成できるライブラリ(layout)を用いて画面のパーツを共通化します。
header, footer, sidebarなど、どの画面でも用いる部分はページ毎に書いていると面倒くさいので別で作り、各画面でそれらと紐付け呼び出します。

前提条件

この記事はSpringの最低限の知識が必要になります。
また、なるべく分かりやすく書くつもりですが、この記事の目的は自分の勉強のアウトプットであるため、所々説明は省略します。
さらに、今回はBootstrapというレイアウトのフレームワークを使用しているため、分からない箇所はBootstrapの公式サイトを参考にしてください。

参考にしたサイト

以前自分が書いたBootstrapの機能まとめ

構築環境

1. 各バージョン
Spring Boot ver 2.7.5
jquery ver 3.6.1
bootstrap ver 5.2.2
webjars-locator ver 0.46
thymeleaf-layout-dialect ver 3.0.0

2. 依存関係
image.png
依存関係は上記の画像になりますが、今回の記事ではこれに加えthymeleaf-layout-dialectを追加します。

3. ツリー構成
image.png

成果物

image.png

今回行うこと

今回は以下の流れに沿って進めていきます。

  1. thymeleaf-layout-dialectの追加
  2. 共通レイアウト(layout.html)の作成
  3. ヘッダー部分(header.html)の作成
  4. サイドバー部分(sidebar.html)の作成
  5. コンテンツ部分(list.html)の作成
  6. 各コントローラの作成
  7. layout:replaceとlayout:fragmentの違い
     1. layout:replace
     2. layou:fragment

1. thymeleaf-layout-dialectの追加

Thymeleafでレイアウトを作成するためのライブラリを追加します。
pom.xmlのdependenciesタグ内に以下のコードを追加します。

pom.xml
    <!-- 1部省略 -->
	<dependencies>
		<!-- thymeleaf-layout-dialect -->
		<dependency>
			<groupId>nz.net.ultraq.thymeleaf</groupId> 
			<artifactId>thymeleaf-layout-dialect</artifactId>
		</dependency>
    <!-- 1部省略 -->
    <dependencies>

2. 共通レイアウト(layout.html)の作成

layout.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- 共通CSS読み込み -->
<link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.css}">
<link rel="stylesheet" th:href="@{/css/layout/layout.css}">
<!-- 共通JS読込 -->
<script th:src="@{/webjars/jquery/jquery.min.js}" defer></script>
<script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}" defer></script>
<title></title>
</head>
<body>
	<!-- ヘッダー -->
	<nav layout:replace="~{layout/header::header}"></nav>
	<!-- サイドバー -->
	<div class="container-fluid">
		<div class="row">
			<nav class="col-sm-1 bg-light sidebar pt-2">
				<div layout:replace="~{layout/sidebar::sidebar}"></div>
			</nav>
		</div>
	</div>
	<main class="position-relative position">
		<!-- コンテンツ -->
		<div class="container-fluid">
			<div class="row">
				<div class="col-sm-10 offset-sm-1">
					<div layout:fragment="~{list}"></div>
				</div>
			</div>
		</div>
	</main>
</body>
</html>

content="width=divice-width:コンテンツをデバイスの幅に合わせる
initial-scale=1:初期倍率1倍
shrink-to-fit=no":IOSの古いバージョンによるバグをなくすために利用されている(現在ではあまり使われていない)

参考文献

ここでのポイントは2つあります。

  1. htmlタグに「xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" 」を読込み
  2. layout:replaceで読み込むファイルのパスとキーを指定

2.の「layout:replaceで読み込むファイルのパスとキーを指定」に関して、ファイルパスはsrc/main/resources/templatesからの相対パスを指定します。キーには任意の値を設定します。今回、header.htmlはsrc/main/resources/templates/layout/header.htmlにあるため、layout/headerと記述している。

3. ヘッダー部分(header.html)の作成

header.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<head>
</head>
<body>
<nav layout:fragment="header" class="navbar navbar-expand-lg navbar-light bg-primary color fixed-top fs-4">
  <div class="container-fluid">
    <a class="navbar-brand fa fa-house" th:href="@{#}"> Home</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav me-auto">
        <li class="nav-item">
          <a class="nav-link" th:href="@{#}">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" th:href="@{#}">Service</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" th:href="@{#}">Comapany</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" th:href="@{#}">Access</a>
        </li>
      </ul>
      <ul class="navbar-nav align-items-center">
      	<li class="nav-item align-middle text-white">こんにちは〇〇さん</li>
      	<li class="nav-item">
      		<a class="nav-link" th:href="@{/login/login}">
      			<img th:src="@{/img/kkrn_icon_user_12.png}" alt="アイコン画像"  th:width="50px" th:height="50px">
      		</a>
      	</li>
      </ul>
    </div>
  </div>
</nav>
</body>
</html>

ここでのポイントは2つあります。

  1. layout.decorateでどのHTMLファイルに組み込むかを設定
  2. layout.fragmentで組み込む箇所を指定

1.の「layout.decorateでどのHTMLファイルに組み込むかを設定」に関して、先ほどと同様にsrc/main/resources/templatesからの相対パスを指定します。index.htmlはsrc/main/resources/templates/index.htmlになるので、layout:decorate="~{layout/layout}"となります。

2.の「layout.fragmentで組み込む箇所を指定」に関して、先ほどindex.htmlで設定したキー名を組み込みたい箇所に指定します。layout.htmlで設定した"~{layout/header::header}"とlayout:fragment="header"を一致させます。

4. サイドバー部分(sidebar.html)の作成

行うことは3. ヘッダー部分(header.html)の作成と同様です。

  1. head部分にlayout:decorate="~{layout/layout}"を記述
  2. layout:fragment="sidebar"で紐付け
sidebar.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<head>
</head>
<body>
	<div layout:fragment="sidebar" class="bg-light">
		<ul class="navbar-nav nav-pills">
			<li class="nav-item">
				<a class="nav-link fs-4" th:href="@{'/user/list'}">ユーザ一覧</a>
			</li>
		</ul>
	</div>
</body>
</html>

5. コンテンツ部分(list.html)の作成

こちらもheader.htmlsidebar.htmlと同様なので説明は割愛します。

list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorate="~{layout/layout}">
<head>
<title>ユーザー一覧</title>
<!-- 個別CSS読込 -->
<link rel="stylesheet" th:href="@{/css/user/list.css}">
</head>
<body>
	<div layout:fragment="~{list}">
		<div class="header border-bottom">
			<h1 class="h2">ユーザ一覧画面</h1>
		</div>
    <div>
</body>
</html>

6. 各コントローラの作成

各コントローラの作成は今回は割愛します。

7. layout:replaceとlayout:fragmentの違い

メインのレイアウトlayout.htmlから各画面を紐付けるためにheader.htmlsidebar.htmllayout:replace=を用いたのに対して、list.htmllayout:fragment="list"を用いました。
この2つはどちらも画面を紐付けるための方法ですが、この2つには違いがあります。

1. layout:replace

layout.html
<!--レイアウト側のHTMLサンプル-->
<div layout:replace="~{layout/sample::sample}" class="sample1"></div>
sample.html
<!-- コンテンツ側のHTMLサンプル -->
<div layout:fragment="sample" class="sample2">
    <p>Hello World</p>
<div>
<!-- layout.replaceで生成されたHTML -->
<div class="sample2">
    <p>Hello World</p>
</div>

コンテンツ側のHTMLでレイアウト側のHTMLが置き換えられる(sample1sample2に置き換えれたかのよう)

2. layou:fragment

layout.html
<!--レイアウト側のHTMLサンプル-->
<div layout:fragment="~{sample}" class="sample1"></div>
sample.html
<!-- コンテンツ側のHTMLサンプル -->
<div layout:fragment="sample" class="sample2">
    <p>Hello World</p>
<div>
<!-- layout.replaceで生成されたHTML -->
<div class="sample1 sample2">
    <p>Hello World</p>
</div>

コンテンツ側のHTMLをレイアウト側のHTMLに追加する(sample1sample2の両要素を持つ)

最後に

layout:replaceとlayout:fragmentの違いを理解して使い分けることが重要!!!
例えば、コンテンツ画面で個別のCSSファイルを読み込ませたい場合はlayout:fragmentを用いる
(layout:replaceを用いるとレイアウト側のHTMLのheadで上書きされてしまうから)

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4