Thymeleaf Fragment (Fragment ๋๋๊ธฐ)
Fragment๋ก ๋ ์ด์์์ ๋๋๊ณ ๊ฐ ํ์ด์ง์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ Fragment๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ๋๊ธธ์๋ ์๋ค.
Thymeleaf Fragment - 1 : Fragment๋ฅผ ๋๋๊ธฐ
Thymeleaf Fragment - 2 : ํ๋ผ๋ฏธํฐ ์ ๋ฌ, ์ฌ์ฉํ๊ธฐ
Thymeleaf Fragment ๋๋๊ธฐ
๊ฐ๋ฐํ๊ฒฝ์ Springboot + Thymeleaf๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์๊ณ
ํ๋ก์ ํธ์์ Thymeleaf๋ฅผ ์ฌ์ฉํ๊ธฐ์ํด Thymeleaf ์์กด์ฑ ์ฃผ์ ,
Web์ ์ฌ์ฉํ๊ธฐ ์ํด Spring-boot-starter-web ์์กด์ฑ์ ์ฃผ์ ๋ฐ์๋ค.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Fragment ๊ตฌ์กฐ๋ ์๋์ ๊ฐ์ด ๋๋์ด ์์ฑํ์๋ค.
- header (html ํค๋ ์์ญ)
- nav (์๋จ์ ๋ฉ๋ด ์์ญ)
- footer (ํ๋จ์ footer ์์ญ)
๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ
๊ฐ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ ํ์ด์ง๋ fragments ๋๋ ํ ๋ฆฌ์ ์์ฑํ๊ณ
๋ค๋ฅธ ํ์ด์ง์์ ํด๋น fragment(๊ณตํต๋ถ๋ถ)๋ฅผ ๋ถ๋ฌ์์ ์ฌ์ฉ ํ ์ ์๋๋ก ์ค์ ํ ๊ฒ์ด๋ค.
๋จผ์ header.html์์๋ ํ์ด์ง์ head ๋ถ๋ถ์์ ํด๋นํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
๊ทธ๋ฆฌ๊ณ Thymeleaf๋ฅผ ์ฌ์ฉํ๋ค๋ ์ ์ธ์ผ๋ก html ํ๊ทธ์ xmlns:th="http://www.thymeleaf.org"๋ฅผ ์ถ๊ฐํ๋ค.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="fragment-header">
<title>Thymeleaf Fragment</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</head>
</html>
head ๋ถ๋ถ์ fragment๋ก ์ ์ธํ๋ ค๋ฉด headํ๊ทธ์ th:fragment="fragment๋ช "์ ์์ฑํด์ค๋ค.
์ ์ธํ fragment๋ช ์ผ๋ก ๋ค๋ฅธ ํ์ด์ง์์ ํด๋น fragment๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
th:fragment๋ ํด๋น ๋ถ๋ถ์ fragment๋ก ์ ์ธํ๋ค๋ ์๋ฏธ
๋ค๋ฅธ ํ์ด์ง ํ๋ฉด์์๋ ์ด ์ ์ธํ fragment๋ฅผ ๊ฐ์ ธ์๋ณด์.
index.html ์์ fragment-header๋ฅผ ๊ฐ์ ธ์ค๋ ค๋ฉด th:replace๋ก fragment๋ฅผ ๋ถ๋ฌ์จ๋ค.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="/fragments/header.html :: fragment-header"></div>
</head>
<body>
Index Page
</body>
</html>
Index ํ์ด์ง์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํด fragment๋ก ์ ์ธํด๋์ fragment-header๋ฅผ ๊ฐ์ ธ์ค๋ ค๋ฉด
head ๋ถ๋ถ์ th:replace="fragment ์์น :: fragment๋ช " ์ ํธ์ถํ์ฌ fragment๋ฅผ ๋ถ๋ฌ์ฌ์ ์๋ค.
th:replace๋ ํด๋น ํ๊ทธ์ fragment๋ก ์ ์ธํ ์ฝ๋๋ก ๋ฐ๊พผ๋ค๋ ์๋ฏธ
์ 2๊ฐ์ง ์ฝ๋๋ฅผ ์์ฑ ํ ์คํํ๋ค ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ด์ด์ ํ์ธํด๋ณด๋ฉด head๋ถ๋ถ์ fragment-header์์ ์์ฑํ ์ฝ๋๋ฅผ ๊ฐ์ ธ์จ๊ฑธ ํ์ธ ํ ์ ์๋ค.
head์ ๊ฐ์ ๋ฐฉ์์ผ๋ก footer์ nav๋ ์์ฑํด๋ณด์.
footer.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<footer th:fragment="fragment-footer" class="footer">
<div class="row justify-content-center">
<img class="mb-2" alt="" width="20" src="data:image/png;base64,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">
<small class="d-block mb-3 ml-3 text-muted">©GHSONG 2020</small>
</div>
</footer>
</html>
nav.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<div th:fragment="fragment-nav">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">์๋จ๋ฉ๋ด</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#" th:href="@{/index}">๋ฉ์ธ <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/index2}">์ค์ </a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
</div>
</html>
๊ณตํต์ผ๋ก ์ฌ์ฉ๋ fragment๋ฅผ ์ ์ธ ํ ํ์ด์ง์์ fragmet๋ฅผ ๋ถ๋ฌ์จ๋ค.
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="/fragments/header.html :: fragment-header"></div>
</head>
<body>
<div th:replace="/fragments/nav.html :: fragment-nav"></div>
<!-- ํ์ด์ง ์์ค -->
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Index</h1>
<p class="lead">ghsong tistory</p>
</div>
</div>
<!-- ํ์ด์ง ์์ค -->
<footer th:replace="/fragments/footer.html :: fragment-footer"></footer>
</body>
</html>
fragment๋ก ํ๋ฉด์ ๊ตฌ์ฑํ๊ณ ๊ฐ ํ์ด์ง์ ์์ค๋ฅผ ์์ฑํ๋ฉด ๋ ์ด์์์ ๋๋์ ์๋ค.
๋ค๋ฅธ ํ์ด์ง๋ ์์ฑํ์ฌ ๋น๊ตํด๋ณด์.
index2.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="/fragments/header.html :: fragment-header"></div>
<style>
.jumbotron {background-color: aqua !important;}
</style>
</head>
<body>
<div th:replace="/fragments/nav.html :: fragment-nav"></div>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Index2</h1>
<p class="lead">ghsong tistory</p>
</div>
</div>
<div th:replace="/fragments/footer.html :: fragment-footer"></div>
</body>
</html>
index2 ํ์ด์ง์์๋ ๊ณตํต head ๋ถ๋ถ ๋ง๊ณ ๋ ์ด ํ์ด์ง์์๋ง ์ฌ์ฉํ ์ฝ๋๋ฅผ ๋ฃ์๋๋ฐ
head ๋ถ๋ถ์ fragment ์ ์ธ๋ถ๋ถ ์๋์ <style> ํ๊ทธ๋ฅผ ์์ฑํ๋ฉด ํด๋น ํ์ด์ง์์๋ง ์ฌ์ฉํ style์ ์ถ๊ฐ ํ ์ ์๋ค.
์ด style ํ๊ทธ๋ index ํ์ด์ง์์๋ ์ ์ฉ๋์ง ์๋๋ค.
index.html ํ๋ฉด
index2.html ํ๋ฉด
์ด์ฒ๋ผ Thymeleaf๋ฅผ ์ฌ์ฉํ ๋ fragment๋ฅผ ์ฌ์ฉํ์ฌ th:fragmet๋ก ์ ์ธ, th:replace๋ก ํธ์ถํ์ฌ ๋ ์ด์์์
๋ณด๋ค ์ฝ๊ฒ ๋๋ ์ ์๋ค.
git source : https://github.com/sgoho01/Thymeleaf-Fragments/tree/fragment