Thymeleaf 페이지 레이아웃

JinKyung·2023년 4월 11일
0

SpringBoot 쇼핑몰

목록 보기
10/12

웹사이트를 만들 때 header, footer, menu 등 공통적인 페이지 구성 요소들이 있다. Thymeleaf의 페이지 레이아웃 기능을 사용해 공통 요소 관리를 해보자.

Thymeleaf Layout Dialect

하나의 레이아웃을 여러 페이지에 똑같이 적용 가능. 공통으로 적용되는 레이아웃을 미리 만들어놓고 현재 작성 중인 페이지만 레이아웃에 끼워넣으면 된다.

pom.xml

<dependency>
	<groupId>nz.net.ultraq.thymeleaf</groupId>
	<artifactId>thymeleaf-layout-dialect</artifactId>
	<version>3.1.0</version>
</dependency>

thymeleaf-layout-dialect 라이브러리 설치 완료 후 다음과 같이 templates 아래에 fragments와 layouts 폴더를 생성하자. 각 폴더 아래에 html 파일도 사진과 같이 추가해주자.

footer.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <!-- 다른 페이지에 포함시킬 영역을 th:fragment로 선언해준다.-->
  <div th:fragment="footer">
      footer 영역입니다.
  </div>
</html>

header.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <!-- 다른 페이지에 포함시킬 영역을 th:fragment로 선언해준다.-->
  <div th:fragment="header">
      header 영역입니다.
  </div>
</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">
  <title>Title</title>

  <th:block layout:fragment="script"></th:block>
  <th:block layout:fragment="css"></th:block>
</head>
<body>
  <!-- th:replace 속성은 해당 속성이 선언된 html 태그를 다른 html 파일로 치환. fragments 폴더 아래 header.html 파일의 "th:fragment=header" 영역을 가져옴 -->
  <div th:replace="fragments/header::header"></div>

  <!-- layout에서 변경되는 영역을 fragment로 설정. -->
  <div layout:fragment="content">

  </div>

  <!-- header와 마찬가지로 fragments 폴더 아래에 footer.html의 "th:fragment=footer" 영역을 가져옴 -->
  <div th:replace="fragments/footer::footer"></div>
</body>

thymeleafEx07.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
     xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
     layout:decorate="~{layouts/layout1}">
     
     <!-- layout1.html 파일의 <div layout:fragment="content"> 영역에 들어가는 영역 -->
     <div layout:fragment="content">
       본문 영역입니다.
     </div>
</html>

ThymeleafExController.java

	@GetMapping(value = "/ex07")
    public String thymeleafExample07() {
        return "thymeleafEx/thymeleafEx07";
    }

thymeleafEx07.html 파일에는 따로 header 영역과 footer 영역을 지정하지 않았지만, 작성한 내용인 layout1.html 파일에 포함되어 출력됨을 볼 수 있다. 이처럼 공통영역은 레이아웃으로 만들어 놓고 작성하는 페이지의 content만 변경하면 공통으로 들어가는 내용을 쉽게 관리할 수 있다!!

폴더 구조가 헷갈려서 정리

  • fragments: 레이아웃에서 공통적으로 쓸 부분들의 html 코드 생성
  • layouts: 공통적으로 쓸 header, footer 같이 공통적인 코드 적용 및 전체 layout 구조 세팅
  • (현재 예제에서는) thymeleafEx: 실제 각각 페이지별로 다르게 적용될 content

Bootstrap 적용하기

  1. header.html 수정
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!-- 다른 페이지에 포함시킬 영역을 th:fragment로 선언해준다.-->
<div th:fragment="header">
    <nav class="navbar navbar-expand-sm bg-primary navbar-dark">
        <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <a class="navbar-brand" href="/">Shop</a>

        <div class="collapse navbar-collapse" id="navbarTogglerDemo03">
            <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                <li class="nav-item">
                    <a class="nav-link" href="/admin/item/new">상품 등록</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/admin/items">상품 관리</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/cart">장바구니</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/orders">구매이력</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/members/login">로그인</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/members/logout">로그아웃</a>
                </li>
            </ul>
            <form class="form-inline my-2 my-lg-0" th:action="@{/}" method="get">
                <input name="searchQuery" 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>
  1. footer.html 수정
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!-- 다른 페이지에 포함시킬 영역을 th:fragment로 선언해준다.-->
<div th:fragment="footer" class="footer">
    <footer class="page-footer font-small cyan darken-3">
        <div class="footer-copyright text-center py-3">
            2020 Shopping Mall Example WebSite
        </div>
    </footer>
</div>
</html>
  1. layout1.css 추가
    resources/static/css/layout1.css
html {
    position: relative;
    min-height: 100%;
    margin: 0;
}
body {
    min-height: 100%;
}
.footer {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    padding: 15px 0;
    text-align: center;
}
.content{
    margin-bottom:100px;
    margin-top: 50px;
    margin-left: 200px;
    margin-right: 200px;
}
  1. layout 수정
  • 정적파일의 상대주소는 resources/static 기준
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

  <!-- CSS only -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  <link th:href="@{/css/layout1.css}" rel="stylesheet">

  <!-- JS, Popper.js, and jQuery -->
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

  <th:block layout:fragment="script"></th:block>
  <th:block layout:fragment="css"></th:block>
</head>
<body>
  <!-- th:replace 속성은 해당 속성이 선언된 html 태그를 다른 html 파일로 치환. fragments 폴더 아래 header.html 파일의 "th:fragment=header" 영역을 가져옴 -->
  <div th:replace="fragments/header::header"></div>

  <!-- layout에서 변경되는 영역을 fragment로 설정. -->
  <div layout:fragment="content" class="content">

  </div>

  <!-- header와 마찬가지로 fragments 폴더 아래에 footer.html의 "th:fragment=footer" 영역을 가져옴 -->
  <div th:replace="fragments/footer::footer"></div>
</body>

0개의 댓글