[Spring Boot Tutorial] 6. Thymeleaf layout 설정하기

2019. 10. 30. 17:45Spring/Spring Boot Tutorial

반응형

Thymeleaf Layout Dialect

이전 게시글
[Spring Boot Tutorial] 5. Thymeleaf 템플릿 엔진 이용하기


공통 구성요소들(header, nav, footer)를 공유하고, 컨텐츠 관련요소(section)만 변경하고 싶을 때 layout을 사용하면 중복 코드를 최소화 할 수 있습니다.

Thymeleaf를 이용하여 layout을 설정하는 단계는 아래와 같습니다.

layout 설정하는 단계

  1. layout 템플릿 만들고 재정의할 컨텐츠 요소는 layout:fragment로 정의
  2. layout 템플릿을 layout:decorator를 이용하여 상속받고, 컨텐츠 요소만 override
  3. 중복적으로 추가할 블럭이나, link, script 태그와 같은 요소를 th:insert로 삽입하기

단계를 진행하기에 앞서, thymeleaf 템플릿엔진에서 layout표현법(Layout Dialect)을 사용하기 위해 pom.xml에 아래의 의존 라이브러리를 추가합니다.

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

1. layout.html 레이아웃 파일 만들기

이전 과정에서 만들었던 레이아웃에서,
<header>, <nav>, <footer>요소를 레이아웃 파일에 정의하고,
<section> 태그는 layout:decorator로 상속받는 파일에서 override 할 것입니다.

45


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
	<meta name="description" content="Welcome to jiniworld!">
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">

	<title>demo</title>

	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
	<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"/>
	<link href="https://fonts.googleapis.com/css?family=Noto+Sans+KR:300,400,500,700,900&amp;subset=korean" rel="stylesheet">
	<link rel="stylesheet" type="text/css" th:href="@{/static/css/common.css}"/>
</head>
<body>
	<header>
		<h1>
			<a th:href="@{/v}"><img th:src="@{/static/img/like.png}" width="50" height="auto" alt="demo page" id="btn_home" /></a>
		</h1>
		<ul>
			<li sec:authorize="isAuthenticated()">
				<span sec:authentication="principal.username"></span> 님 반가워요!
			</li>
			<li><form id="logoutFrm" th:action="@{/logout}" method="post" style="display: inline-block;">
					<a href="#" onclick="document.getElementById('logoutFrm').submit()" data-toggle="tooltip" data-placement="logout" title="Logout"><i class="fa fa-power-off"></i></a>
				</form></li>
		</ul>
	</header>
	<nav class="navbar navbar-expand-lg navbar-light" id="nav_area">
		<button class="navbar-toggler" type="button" data-toggle="collapse"
			data-target="#nav_menu_area" style="margin: 3px;">
			<span class="navbar-toggler-icon" style="width: 1em; height: auto;"></span>
		</button>
		<div class="collapse navbar-collapse" id="nav_menu_area">
			<ul class="navbar-nav" id="nav_menu" style="margin: 0 auto; width: 1140px;">
				<li class='nav-item'  th:classappend="${currentPage eq 'home'} ? 'active' : ''" th:attr="data-href=@{/v}"></li>
				<li sec:authorize="hasRole('ROLE_ADMIN')" class='nav-item' th:classappend="${currentPage eq 'user'} ? 'active' : ''" th:attr="data-href=@{/v/users}">회원</li>
			</ul>
		</div>
	</nav>
	<section layout:fragment="f-content"></section>
	<footer>
		<p>
			JINI WORLD - DEMO
		</p>
	</footer>

	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
	<script>
		$("#nav_menu_area #nav_menu li").on("click", function() {
			$("#nav_menu_area #nav_menu li").removeClass("active");
			$(this).addClass("active");
			location.href = $(this).data('href');
		});
	</script>
</body>
</html>

main.htmluser.html<section> 영역을 제외한 나머지 공통 요소를 layout에 작성했습니다.
기존의 코드와의 차이점은, html 태그에 layout xml 네임스페이스를 추가한 것과(ln 4), <section> 태그에 layout:fragment 속성을 추가하여 재정의할 수 있는 구역(ln 44)을 지정할 수 있다.

참고 ※ 위의 코드에서는 <section> 태그 내부가 비어있지만, 만약 내부에 내용을 작성할 경우 이 레이아웃을 이용하는 다른 페이지에서 재정의를 따로 하지 않으면 layout에서 작성된 내용이 그대로 출력됩니다.


2. layout:decorator로 레이아웃 상속받고, layout:fragment override

페이지를 렌더링할 때 이용할 레이아웃 템플릿은 html 태그의 layout:decorator 속성에 설정하고
layout:fragment 속성이 설정되어있는 override할 요소 내부에 코드를 작성한다.

<!DOCTYPE html>
<html lang="ko"
	xmlns:th="http://www.thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorator="cmm/layout">

<section layout:fragment="f-content">
	<div class="container">
		<div class="d-title">
			<h2>thymeleaf tutorial 과정</h2>
		</div>
		<div class="d-content">
			<div class="row">
				<ul>
					<li>thymeleaf 관련 configuration 설정하기</li>
					<li>thymeleaf로 웹 페이지 구성하기</li>
					<li>JSP vs thymeleaf 비교하기</li>
					<li>thymeleaf layout 구성하기</li>
				</ul>
			</div>
		</div>
	</div>
</section>

html 태그의 layout:decorator 속성에 사용하고자 하는 레이아웃 파일 경로를 작성합니다. (ln 6)
이때, 경로위치는 thymeleaf 템플릿 prefix로 설정한 경로를 기준으로 합니다.

이전과정에서 우리는 prefix를 classpath:/templates/ 로 설정했었죠.

46

layout파일을 cmm 폴더에 위치하기 때문에 'cmm/layout'이라고 작성합니다.

그리고, override할 section영역을 작성합니다. (ln 8~24)


3. 중복으로 이용될 코드블럭 정의

th:fragment 사용 예

  1. 레이아웃 영역 중 override 가능한 영역을 설정
  2. 여러 페이지에서 공통적으로 사용할 코드영역 설정

th:fragment는 여러 페이지에서 필요에 따라 포함시킬 코드영역을 정의할 때에도 사용할 수 있다.


layout 템플릿에 작성했던 css, js import 부분을 th:fragment로 정의해보자.

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

<th:block th:fragment="f-header">
	<meta name="description" content="Welcome to jiniworld!">
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">

	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
	<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"/>
	<link href="https://fonts.googleapis.com/css?family=Noto+Sans+KR:300,400,500,700,900&amp;subset=korean" rel="stylesheet">
	<link rel="stylesheet" type="text/css" th:href="@{/static/css/common.css}"/>
</th:block>

<th:block th:fragment="f-footer">
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
	<script>
		$("#nav_menu_area #nav_menu li").on("click", function() {
			$("#nav_menu_area #nav_menu li").removeClass("active");
			$(this).addClass("active");
			location.href = $(this).data('href');
		});
	</script>
</th:block>

</html>

cmm/block 위치에 f-header, f-footer 라는 이름으로 fragment를 생성했습니다.


<head>
  <title>demo</title>
  <th:block th:insert="cmm/block :: f-header"></th:block>
</head>
<body>

...

  <th:block th:insert="cmm/block :: f-footer"></th:block>
</body>

layout에서 공통으로 사용할 meta, link, script 태그들을 제거하고
th:insert 속성을 설정한 <th:block> 요소를 삽입합니다.

cmm/block 파일에 있는 f-header라는 fragment를 head 태그 내에 insert 하고(ln 3)
body태그 맨 마지막 줄에 f-footer fragment를 insert 합니다.(ln 9)


++ tip

th:fragment를 잘 활용하면, 중복 코드를 효율적으로 줄일 수 있다.

1) <head> 내의 <title>만 재정의 가능!

위의 layout 페이지의 <head> 태그를 아래와 같이 변경한다.

<head>
	<title>
		<th:block layout:fragment="f-title">demo</th:block>
	</title>
	<th:block th:insert="cmm/block :: f-header"></th:block>
</head>

f-title fragment만 재정의 함으로써, 페이지의 title을 변경할 수 있다.

<!DOCTYPE html>
<html lang="ko"
  xmlns:th="http://www.thymeleaf.org"
  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
  layout:decorator="cmm/layout">

  <th:block layout:fragment="f-title">main</th:block>

  <section layout:fragment="f-content">
    <div class="container">
      <div class="d-title">
      	<h2>thymeleaf tutorial 과정</h2>
      </div>
      <div class="d-content">
      	<div class="row">
        <ul>
          <li>thymeleaf 관련 configuration 설정하기</li>
          <li>thymeleaf로 웹 페이지 구성하기</li>
          <li>JSP vs thymeleaf 비교하기</li>
          <li>thymeleaf layout 구성하기</li>
        </ul>
      	</div>
      </div>
    </div>
  </section>

<head> 태그 영역중 <title>만 재정의한 main.html 코드이다.
ln 8 과같이 f-title fragment를 override하는 것으로 타이틀을 변경할 수 있다.

이 원리를 잘 파악한다면, 타임리프를 이용하여 문서구조를 만들 때 중복코드를 대량 단축할 수 있을것입니다.

2) 페이지별 개별적 script 작성 팁

만일, main.html나 user.html에서 jquery를 이용하려면 사전에 jquery라이브러리를 import해야합니다.

보통 javascript import태그는 body태그 맨 하단에 작성하죠.
그렇다면, section 영역보다 아래에 작성되는 부분입니다.

이런 경우, layout에 f-footer fragment를 insert 했던 부분 아래에 frgment를 하나 더 삽입하면 매우 깔끔하게 처리가 됩니다.

<th:block th:insert="cmm/block :: f-footer"></th:block>
<th:block layout:fragment="f-script"></th:block>

만일, 특정 페이지에서만 이용될 javascript를 작성하고자 할 때에는 f-script fragment를 overide하여 그 태그내의 <script>태그에 javascript코드를 작성하면 됩니다.


GitHub에서 demo 프로젝트를 다운받아 볼 수 있습니다.

728x90
반응형