Thymeleaf Basic 1

Thymeleaf는 서버에서 HTML을 동적으로 랜더링하는 SSR 템플릿 엔진이다.

Thymeleaf는 내츄럴 템플릿으로 HTML 파일 기반으로 동작하며, 동적 HTML 랜더링을 수행하게 된다. 랜더링을 거치지 않은 경우, html을 그대로 읽을 수 있으며 랜더링을 수행하게 되면 생성된 html을 반환하게 된다.

thymeleaf는 스프링과 통합해서 사용하기 좋은 템플릿 엔진으로 다양한 기능을 제공한다.

Configuration

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

html에 태그에 위의 thymeleaf 선언을 추가한다.

Text & Utext

Controller

@GetMapping("/text-basic")
public String textBasic(Model model) {
    model.addAttribute("data", "Hello Spring!");
    return "basic/text-basic";
}   

basic/text-basic.html

<ul>
    <li><span th:text="${data}"></span></li>
    <li>[[${data}]]</li>
</ul>

${} 방식을 이용해서 model로 전달한 객체에 접근 할 수 있다.

태그 본문에 내용을 추가하기 위해 th:text를 이용한다.

HTML 영역에 직접 출력하기 위해 [[..]] 을 활용한다.

HTML Entity

html에서는 <, > 를 태그의 시작과 끝으로 보기 때문에 이러한 의미를 가지는 특수문자에 대한 처리가 필요로한다. 그래서 이러한 특수문자를 문자열 형태로 표현될 수 있도록 해야하는 데 이것이 HTML 엔티티이다.

이렇게 HTML 엔티티로 변경하는 것을 escape 라고 한다.

그래서, 예를 들어 ```Hello Spring!`` 와 같이 전달받은 경우 기존에는 진하게 표시하기 위해 실행하지만 이를 Hello <b>Spring!</b> 와 같이 인식해서 태그가 아닌 문자열로 인식되도록 escape을 제공한다.

th:text, [[]] 방식은 escape가 기본 전략이다.

그러면, 태그로 인식하려고 하면 어떻게 해야하나?

th:utext, [()] 를 사용하게 되면 html entity로 변경되지 않고 태그로 인식되어서 실행된다.

하지만, escape를 사용하지 않아서, html이 정상적으로 랜더링되지 않는 문제가 종종 발생하기 때문에 escape를 기본으로 적용하고, 최소한의 경우에만 unescape를 이용한다.

Variable

Controller

@GetMapping("/variable")
    public String variable(Model model){
        User userA = new User("userA", 10);
        User userB = new User("userB", 20);

        List<User> list=new ArrayList<>();
        list.add(userA);
        list.add(userB);

        Map<String,User> map=new HashMap<>();
        map.put("userA", userA);
        map.put("userB", userB);

        model.addAttribute("user", userA);
        model.addAttribute("users", list);
        model.addAttribute("userMap", map);

        return "basic/variable";
    }

basic/variable.html

<h1>SpringEL 표현식</h1>
<ul>Object
  <li>${user.username} = <span th:text="${user.username}"></span></li>
  <li>${user['username']} = <span th:text="${user['username']}"></span></li>
  <li>${user.getUsername()} = <span th:text="${user.getUsername()}"></span></li>
</ul>
<ul>List
  <li>${users[0].username} = <span th:text="${users[0].username}"></span></li>
  <li>${users[0]['username']} = <span th:text="${users[0]['username']}"></span></li>
  <li>${users[0].getUsername()} = <span th:text="${users[0].getUsername()}"></span></li>
</ul>
<ul>Map
  <li>${userMap['userA'].username} = <span th:text="${userMap['userA'].username}"></span></li>
  <li>${userMap['userA']['username']} = <span th:text="${userMap['userA']['username']}"></span></li>
  <li>${userMap['userA'].getUsername()} = <span th:text="${userMap['userA'].getUsername()}"></span></li>
</ul>
<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
  <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>

전달받은 변수의 타입에 따라 다양한 방식으로 필드에 접근 할 수 있다.

user객체가 있을때, user.username(필드값 접근), user['username'](directory 접근 방식), user.getUserName() 과 같이 다양한 방식으로 변수를 접근 할 수 있다.

이러한 표현 방식을 SpringEL에서 제공한다.

th:with을 이용하면 지역변수를 선언해서 사용할 수 있다. 사용범위는 해당 태그 하위에 존재하는 태그이다.

## Basic Objects

> Controller

```java
@GetMapping("/basic-objects")
public String basicObjects(HttpSession session) {
    session.setAttribute("sessionData", "Hello Session");
    return "basic/basic-objects";
}
@Component("helloBean")
static class HelloBean {
    public String hello(String data) {
    return "Hello " + data;
    }
}

basic/basic-objects.html

<h1>식 기본 객체 (Expression Basic Objects)</h1>
<ul>
  <li>request = <span th:text="${#request}"></span></li>
  <li>response = <span th:text="${#response}"></span></li>
  <li>session = <span th:text="${#session}"></span></li>
  <li>servletContext = <span th:text="${#servletContext}"></span></li>
  <li>locale = <span th:text="${#locale}"></span></li>
</ul>
<h1>편의 객체</h1>
<ul>
  <li>Request Parameter = <span th:text="${param.paramData}"></span></li>
  <li>session = <span th:text="${session.sessionData}"></span></li>
  <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></
  li>
</ul>

기본적으로, request, response, session 객체 등과 같은 HttpServlet과 관련된 객체를 제공해준다.

추가로, 쿼리 파라미터에 접근할 수 있는 param 객체, session에 접근할 수 있는 session 객체, Spring Bean에 접근할 수 있도록 @ 연산자 지원

Utility Object

#message, #uris, #dates, #calendars ... 등과 같이 문자,숫자,날짜,등을 편리하게 다루기 위한 여러 객체들을 제공한다.

link:utility_objects

날짜를 표현하는 #temporal 객체에 대해 알아보자

자바 8의 LocalDate, LocalDateTime, Instant을 이용하려면 thymeleaf-extras-java8time 라이브러리가 요구되는데, 이는 Spring boot에서 Thymeleaf 라이브러리를 설정했을 때, 자동으로 설치된다.

Controller

@GetMapping("/date")
public String date(Model model) {
    model.addAttribute("localDateTime", LocalDateTime.now());
    return "basic/date";
}

basic/date.html

<h1>LocalDateTime</h1>
<ul>
    <li>default = <span th:text="${localDateTime}"></span></li>
    <li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime,'yyyy-MM-dd HH:mm:ss')}"></span></li>
</ul>
<h1>LocalDateTime - Utils</h1>
<ul>
    <li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
    <li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
    <li>${#temporals.monthName(localDateTime)} = <span th:text="${#temporals.monthName(localDateTime)}"></span></li>
    <li>${#temporals.monthNameShort(localDateTime)} = <span th:text="${#temporals.monthNameShort(localDateTime)}"></span></li>
    <li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeek(localDateTime)} = <span th:text="${#temporals.dayOfWeek(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeekName(localDateTime)} = <span th:text="${#temporals.dayOfWeekName(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeekNameShort(localDateTime)} = <span th:text="${#temporals.dayOfWeekNameShort(localDateTime)}"></span></li>
    <li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
    <li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
    <li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
    <li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>
</ul>

URL 링크

Controller

@GetMapping("/link")
public String link(Model model){
    model.addAttribute("param1", "data1");
    model.addAttribute("param2", "data2");
    return "basic/link";
}

basic/link.html

<h1>URL 링크</h1>
<ul>
  <li><a th:href="@{/hello}">basic url</a></li>
  <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li>
  <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li>
  <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li>
</ul>

링크를 표현하기 위해 @{}을 이용한다.

경로 변수를 표현해주기 위해서는 경로 변수에 {}표기를 추가해준다. 경로 변수와 쿼리 파라미터를 혼용해서 사용한 것도 가능하다.

리터럴

thymeleaf 에서 문자는 항상 ‘‘을 이용해서 감싸줘야한다. 하지만, 공백이 없는 문자열에 대해서는 ‘’ 을 생략해도 된다.

<span th:text="'hello'">
<span th:text="hello">

하지만 공백이 포함된 문자열에 ‘’ 표기를 하지 않은 경우, 하나의 토큰으로 인식할 수 없기 때문에 오류가 발생한다.

<span th:text="hello world!"></span><!-- 잘못된 표기 -->
<span th:text="'hello world!'"></span><!-- 정확한 표기 -->
아니면, 아래와 같이   을 이용한 리터럴 대체 문법을 사용하면 이러한 문자열, 변수, 공백에 대한 처리를 자동으로 수행된다.
<span th:text="|hello ${data}|"></span>

연산

Controller

@GetMapping("/operation")
public String operation(Model model) {
    model.addAttribute("nullData", null);
    model.addAttribute("data", "Spring");
    return "basic/operation";
}

basic/operation.html

<ul>
  <li>산술 연산
    <ul>
      <li>10 + 2 = <span th:text="10 + 2"></span></li>
      <li>10 % 2 == 0 = <span th:text="10 % 2 == 0"></span></li>
    </ul>
  </li>
  <li>비교 연산
    <ul>
      <li>1 > 10 = <span th:text="1 &gt; 10"></span></li>
      <li>1 gt 10 = <span th:text="1 gt 10"></span></li>
      <li>1 >= 10 = <span th:text="1 >= 10"></span></li>
      <li>1 ge 10 = <span th:text="1 ge 10"></span></li>
      <li>1 == 10 = <span th:text="1 == 10"></span></li>
      <li>1 != 10 = <span th:text="1 != 10"></span></li>
    </ul>
  </li>
  <li>조건식
    <ul>
      <li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)? '짝수':'홀수'"></span></li>
    </ul>
  </li>
  <li>Elvis 연산자
    <ul>
      <li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가 없습니다.'"></span></li>
      <li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?:'데이터가 없습니다.'"></span></li>
    </ul>
  </li>
  <li>No-Operation
    <ul>
      <li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다.</span></li>
      <li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li>
    </ul>
  </li>
</ul>

기본적인 산술 연산 및 논리 연산을 제공한다. 단, ‘<’ 는 &lt, lt로 해석되고, ‘>’는 &gt와 gt로 변환되어 해석된다.

아래의 연산자는 elvis 연산자라고 한다.

<span th:text="${data}?: '데이터가 없습니다.'"></span>

만약 data에 값이 있으면 data 변수의 값을 출력하고, 없으면 대체 문자열이 출력되게 된다.

아래의 연산자는 No-Operation 연산이다.

<span th:text="${data}?: _">

만약 data의 값이 있게 되면 data 변수의 값을 출력하게 되고, data 값이 없으면 th 태그가 사라지게 되고(th 태그가 사라지고 연산이 이루어 지지 않아서 No-Operation이라고 한다.), html 태그만 남게 된다.

속성 값 변경

Controller

@GetMapping("/attribute")
public String attribute(){
    return "basic/attribute";
}

basic/attribute.html

<input type="text" name="mock" th:name="userA" />
<h1>속성 추가</h1>
- th:attrappend = <input type="text" class="text" th:attrappend="class=' large'" /><br/>
- th:attrprepend = <input type="text" class="text" th:attrprepend="class='large '" /><br/>
- th:classappend = <input type="text" class="text" th:classappend="large"/><br/>
<h1>checked 처리</h1>
- checked o <input type="checkbox" name="active" th:checked="true" /><br/>
- checked x <input type="checkbox" name="active" th:checked="false" /><br/>
- checked=false <input type="checkbox" name="active" checked="false" /><br/>
</body>

위와 같이 th:xxx을 이용하게 html의 속성값을 바꿀 수 있는 것 확인할 수 있다.

또한, attrappend, attrprepend을 이용해서 html 속성에 값을 추가할 수 있는데, attrappend는 기존 값 뒤에 추가하고, attrprepend을 이용하면 기존값 앞에 추가한다.

classappend을 이용해서 class 속성에 값을 추가할 수 있다.

원래 checkbox type에서 html 태그에 checked 속성이 존재하기만 하면 해당 checkbox는 체크 되게 된다. 설령, checked에 False가 설정되어 있다 해도, 체크로 설정되게 된다. 이는 개발자가 의도하지 않은 바이다. 그래서 thymeleaf에서는 th:checked을 이용해서 체크 yes/no 를 지정할 수 있다.

반복

Controller

@GetMapping("/each")
public String each(Model model){
    addUsers(model);
    return "basic/each";
}
private void addUsers(Model model){
    List<User> list = new ArrayList<>();
    list.add(new User("userA", 10));
    list.add(new User("userB", 20));
    list.add(new User("userC", 30));

    model.addAttribute("users", list);
}

basic/each.html

<h1>반복 상태 유지</h1>
<table border="1">
  <tr>
    <th>count</th>
    <th>username</th>
    <th>age</th>
    <th>etc</th>
  </tr>
  <tr th:each="user, userStat : ${users}">
    <td th:text="${userStat.count}">username</td>
    <td th:text="${user.username}">username</td>
    <td th:text="${user.age}">0</td>
    <td>
      index = <span th:text="${userStat.index}"></span>
      count = <span th:text="${userStat.count}"></span>
      size = <span th:text="${userStat.size}"></span>
      even? = <span th:text="${userStat.even}"></span>
      odd? = <span th:text="${userStat.odd}"></span>
      first? = <span th:text="${userStat.first}"></span>
      last? = <span th:text="${userStat.last}"></span>
      current = <span th:text="${userStat.current}"></span>
    </td>
  </tr>

th:each를 이용해서 객체에 대한 반복으로 수행할 수 있다. 또한, Stat 이라는 반복 상태 객체를 이용해서 반복문을 수행하는 과정에서 다양한 정보를 이용할 수 있다. 두번째 파라미터를 생략하게 되면 첫번째 파라미터로 전달된 변수에 Stat을 붙인 형태로 제공하게 된다.

Properties Description
index 0부터 시작하는 값
count 1부터 시작하는 값
size 전체 사이즈
even , odd 홀수, 짝수 여부( boolean )
first , last 처음, 마지막 여부( boolean )
current 현재 객체

Condition

Controller

@GetMapping("/condition")
public String condition(Model model) {
    addUsers(model);
    return "basic/condition";
}

basic/condition.html

  <tr>
    <th>count</th>
    <th>username</th>
    <th>age</th>
  </tr>
  <tr th:each="user, userStat : ${users}">
    <td th:text="${userStat.count}">1</td>
    <td th:text="${user.username}">username</td>
    <td>
      <span th:text="${user.age}">0</span>
      <span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
      <span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
    </td>
  </tr>
</table>
<h1>switch</h1>
<table border="1">
  <tr>
    <th>count</th>
    <th>username</th>
    <th>age</th>
  </tr>
  <tr th:each="user, userStat : ${users}">
    <td th:text="${userStat.count}">1</td>
    <td th:text="${user.username}">username</td>
    <td th:switch="${user.age}">
      <span th:case="10">10살</span>
      <span th:case="20">20살</span>
      <span th:case="*">기타</span> <!-- default -->
    </td>
  </tr>

자바의 if/else 문과 switch 문과 동작방식이 유사하다. 조건을 만족하는 태그 이외 나머지 태그는 랜더링 되지 않는다.

Comments

Controller

@GetMapping("/comments")
public String comments(Model model){
    model.addAttribute("data", "Spring!");
    return "basic/comments";
}

basic/comments.html

<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span>
-->
<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]] */-->
<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->
<h1>3. 타임리프 프로토타입 주석</h1>
<!--/*/
<span th:text="${data}">html data</span>
/*/-->
  1. <!-- -->
    • 표준 html 주석은 주석 처리 된채로 랜더링만 하지 않고 남겨 둔다.
  2. <!-- /* */-->
    • thymeleaf의 파서 주석은 thymeleaf에서 제공하는 주석 방식이므로 해당 주석 내용을 제거한다.
  3. <!-- /*/ /*/ -->
    • thymeleaf 프로타입 주석은 thymeleaf로 랜더링 되었을때만 해당 주석 내용을 랜더링 할 수 있도록 한다. html에 직접 접근하게 되면 주석처리되어 보이지 않지만, 템플릿 엔진을 거치게 되면 랜더링을 수행해서 주석 안의 내용이 랜더링된다.

Block

Controller

@GetMapping("/block")
public String block(Model model){
    addUsers(model);
    return "basic/block";
}

basic/block.html

<th:block th:each="user : ${users}">
  <div>
    사용자 이름1 <span th:text="${user.username}"></span>
    사용자 나이1 <span th:text="${user.age}"></span>
  </div>
  <div>
    요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
  </div>
</th:block>

위와 같이 2개 이상의 태그를 동시에 반복문을 수행해야 하는 경우 해당 태그들을 묶는 태그를 선언하는 대신에 블록을 활용해서 태그들을 묶어서 each와 같은 연산을 수행할 수 있다. th:block는 랜더링 되면 사라진다.

JavaScript In-line

Controller

@GetMapping("/javascript")
public String javascript(Model model){
    model.addAttribute("user", new User("userA", 10));
    addUsers(model);
    return "basic/javascript";
}

basic/javascript.html

<script>
var username = [[${user.username}]];
var age = [[${user.age}]];
//자바스크립트 내추럴 템플릿
var username2 = /*[[${user.username}]]*/ "test username";
//객체
var user = [[${user}]];
</script>
<!-- 자바스크립트 인라인 사용 후 -->
<script th:inline="javascript">
var username = [[${user.username}]];
var age = [[${user.age}]];
//자바스크립트 내추럴 템플릿
var username2 = /*[[${user.username}]]*/ "test username";
//객체
var user = [[${user}]];
</script>
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>

자바 스크립트에서 문자열은 ““로 감싸서 저장해야되며, 숫자의 경우 그대로 사용한다.

객체를 설정할때에도 JSON 형태로 반환해줘야다.

아래와 같이 설정하게 되면, 위의 작업들이 자동으로 이루어진다.

<script th:inline="javascript">

또한 자바스크립트 내추럴 템플릿 기능을 제공하는데,

var username2 = /*[[${user.username}]]*/ "test username";

위와 같이 코드를 구성하면 thymeleaf 랜더링 이후 아래와 같이 랜더링된다.

var username2="userA"

자바스크립트 인라인 each

<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>

위와 같이 자바스크립트 인라인에서 each를 지원한다.

위의 실행 결과 아래와 같이 객체들이 출력되는 것을 확인할 수 있다.

<script>
var user1 = {"username":"userA","age":10};
var user2 = {"username":"userB","age":20};
var user3 = {"username":"userC","age":30};
</script>

템플릿 조각

웹 페이지에는 여러 공통 부분이 존재하는데, 헤더, 푸터, 좌측 카테고리, 등 다양한 부분이 공통적으로 사용된다. 그래서 이러한 부분을 템플릿 조각 형태로 해당 페이지에 포함시켜서 페이지를 구현할 수 있다.

Controller

@GetMapping("/fragment")
public String template() {
    return "template/fragment/fragmentMain";
}

footer.html

<footer th:fragment="copy">
    푸터 자리 입니다.
</footer>
<footer th:fragment="copyParam (param1, param2)">
    <p>파라미터 자리 입니다.</p>
    <p th:text="${param1}"></p>
    <p th:text="${param2}"></p>
</footer>

template/fragment/fragmentMain.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>부분 포함</h1>
        <h2>부분 포함 insert</h2>
        <div th:insert="~{template/fragment/footer :: copy}"></div>
        <h2>부분 포함 replace</h2>
        <div th:replace="~{template/fragment/footer :: copy}"></div>
        <h2>부분 포함 단순 표현식</h2>
        <div th:replace="template/fragment/footer :: copy"></div>
        <h1>파라미터 사용</h1>
        <div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터
        2')}"></div>
    </body>
</html>

fragmentMain.html에서는 footer.html에 있는 코드조각(th:fragment)을 이용해서 웹페이지를 생성하는 것을 확인할 수 있다.

th:insert을 이용하면 해당 태그 안에 템플릿 조각이 포함되게 되며, th:replace을 이용하며 해당 태그가 템플릿 조각으로 대체된다. fragment을 지정할 때는 ~{}표현을 사용하지만 간단한 경우 생략해도 된다.

또한, fragment에 인자를 전달해서 해당 인자를 이용해서 동적으로 랜더링을 수행할 수 있다.

템플릿 레이아웃

위의 템플릿 조각 방식이 생성하고자 하는 페이지에 fragment을 불러와서 만드는 것이 었다면, 레이아웃은 템플릿 조각들로 구성되어 있는 레이아웃에 특정 부분을 추가하는 것이다. 즉, 공통영역을 미리 구성해 놓은 레이아웃에 각 페이지 별로 추가되는 부분을 추가하면 된다.

Controller

@GetMapping("/layout")
    public String layout() {
    return "template/layout/layoutMain";
}

base.html

<head th:fragment="common_header(title,links)">
  <title th:replace="${title}">레이아웃 타이틀</title>
  <!-- 공통 -->
  <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/
awesomeapp.css}">
  <link rel="shortcut icon" th:href="@{/images/favicon.ico}">
  <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
<!-- 추가 -->
<th:block th:replace="${links}" />
</head>

위와 같이 head 태그에 대한 레이아웃을 구성하고, 공통영역과 추가되는 부분을 분리했다. 추가되는 부분은 인자로 전달된 태그를 채워 넣는다.

template/layout/layoutMain.html

<head th:replace="template/layout/base :: common_header(~{::title},~{::link})">
    <title>메인 타이틀</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
<body>
메인 컨텐츠
</body>

위와 같이 하게 되면 head 부분이 레이아웃으로 대체 되게 된다.

이때, ::title은 현재 페이지 내 title 태그를 의미하고, ::link 링크 태그를 의미한다. 이처럼 fragment 에 태그를 인자로 전달할 수 있는 것을 확인할 수 있다.

레이아웃에 필요한 정보(태그)를 전달해서 페이지를 개발하는 방식이다.

탬플릿 레이아웃의 확장

위에서는 head 태그에 대해서만 레이아웃을 적용했는데, html 태그 전체에 대한 레이아웃을 구성할 수 있다.

Controller

@GetMapping("/layoutExtend")
public String layoutExtends() {
    return "template/layoutExtend/layoutExtendMain";
}

layoutFile.html

<html th:fragment="layout (title, content)" xmlns:th="http://
www.thymeleaf.org">
    <head>
        <title th:replace="${title}">레이아웃 타이틀</title>
    </head>
    <body>
        <h1>레이아웃 H1</h1>
        <div th:replace="${content}">
        <p>레이아웃 컨텐츠</p>
        </div>
        <footer>
        레이아웃 푸터
        </footer>
    </body>
</html>

/template/layoutExtend/layoutExtendMain.html

<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title},~{::section})}" xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>메인 페이지 타이틀</title>
    </head>
    <body>
    <section>
        <p>메인 페이지 컨텐츠</p>
        <div>메인 페이지 포함 내용</div>
    </section>
    </body>
</html>

References

link: inflearn

link:springmvc

link:thymeleaf doc1

link:thymeleaf doc2

댓글남기기