
타임리프란
타임리프는 서버 사이드 렌더링을 지원하는 뷰 템플릿의 종류 중 하나로 이외에 뷰 템플릿으로는 JSP등이 있다.
타임리프의 가장 큰 특징으로는 순수 HTML을 최대한 유지한다는 점이다.
정적인 HTML코드의 원래 모습을 유지하면서도 동적인 HTML코드를 제공한다. 이러한 특징으로 인해 후에 서버사이드 렌더링을 하지않더 라도 HTML코드를 직접 파일로 열어볼 수 있는 장점이 있다.
이러한 특징을 네츄럴 템플릿이라고한다.
또한 Spring 프레임워크에서 ThymeLeaf를 전적으로 지원하기때문에 Spring프레임워크와 ThymeLeaf의 높은 호환성을 갖는 특징이 있다.
타임리프에 대해 실습을 하기위해서는 반드시 Controller를 통해 View Template을 응답해야만 Tymeleaf의 기능을 사용할 수 있으며 실습할 수 있다.
예를 들어서 아래의 타임리프 문법이 포함된 HTML을 일반 절대주소로 파일을 열면
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span th:text="${data}">이 곳에 표현됨</span><br>
<span>[[${data}]]</span>
</body>
</html>

위와 같이 타임리프가 적용되지 않음을 알 수 있다.
따라서 아래코드의 Controller를 통해 View를 응답해야만 Thymeleaf실습을 진행할 수 있다.
@Controller
@RequestMapping("/basic")
public class BasicControllerTest {
@RequestMapping("text")
public String basic(Model model){
model.addAttribute("data", "ThymeLeaf");
return "basic/text";
}
타임리프를 사용한다는 것은 Spring과 MVC에 대한 학습이 충분하다고 판단하고
이 포스팅에서는 Thymeleaf포스팅이기 때문에 기본적인 Controller에 대한 코드는 설명또는 작성하지 않도록하겠다.
만약 Spring MVC에 대한 학습이 부족하다고 생각든다면 아래 링크를 참고하자!
Spring MVC - @RequestMapping
@RequestMapping은 어떤 역할을 수행하고 어떤 기능을 가지고 있는지 알아보자 @RequestMapping @RequestMapping은 단순하게 설명하자면 영단어 뜻 그대로 요청을 매핑해주는 역할을 수행하지만 이 어노테이
lee-dev-log.tistory.com
Spring MVC - 프레임워크 사용 전과 후 & @Controller
Spring MVC - Dispatcher Servlet과 View Resolver Spring MVC의 구조를 깊이 있게 알기위해서는 기존의 MVC패턴의 전체 구조를 알고 이해하는 것이 도움이 될 것이다. Spring MVC의 범위는 매우크고 기능 또한 매우
lee-dev-log.tistory.com
JSP와 JSP가 사장되는 이유 + MVC패턴을 사용하는 이유
JSP등장 이전 JSP의 등장 이전에는 클라이언트에게 동적인 코드를 제공하기위해서 Servlet내부에서 서비스 코드를 작성하여 HTML코드와 함께 응답을 해주었다. 클라이언트에게 동적인 화면을 제공
lee-dev-log.tistory.com
Spring MVC - Dispatcher Servlet과 View Resolver
Spring MVC의 구조를 깊이 있게 알기위해서는 기존의 MVC패턴의 전체 구조를 알고 이해하는 것이 도움이 될 것이다. Spring MVC의 범위는 매우크고 기능 또한 매우 많기때문에 기본 베이스 없이 Spring MV
lee-dev-log.tistory.com
텍스트 - text, utext
타임리프에서는 가장 간단한 텍스트를 출력하는 기능을 제공한다. 코드로 살펴보자
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span th:text="${data}">이 곳에 표현됨</span><br>
<span>[[${data}]]</span>
</body>
</html>
실행결과
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span>ThymeLeaf</span><br>
<span>ThymeLeaf</span>
</body>
</html>
th:text="${data}"
: span의 여는태그와 닫는 태그 사이에 data
의 값을 넣는다.
\[\[${data}\]\]
: 위와 동일하며, th:text
문법보다는 조금 더 간단하게 사용할 수 있다.
escape란
HTML태그는 <, >와 같은 특수문자를 주로 사용하여 HTML태그임을 브라우저에게 알려준다.
<h1>태그를 예시로 살펴보면 브라우저는 이 태그를 통해서 h1태그임을 알고 그에 맞는 태그를 적용(제목)하여 브라우저에 출력하게 된다.만약에 블로그를 작성하는데 작성자가 일반 문자열로 `<h1>`이라는 태그를 입력하게 되면 브라우저는 이를 HTML태그로 인식하게 되는데 이는 작성자의 의도와는 다르게되고, 이로인해 HTML이 깨지는 현상이 발생될 수 있다.
이를 막기위해서 브라우저는 <,>와 같은 HTML에 사용되는 특수문자들을 Escape처리하는 것을 지원한다.만약
<h1>
이라는 문자열을 Escape처리하여 HTML 엔티티로 변경한다면 그 결과로는<h1>
이다.
때문에 만약 타임리프를 통해 HTML태그가 적용된 HTML을 출력하고 싶다면 아래와 같이 코드를 짜야한다.
@RequestMapping("escape")
public String escape(Model model) {
model.addAttribute("data", "<b>ThymeLeaf</b>");
return "basic/escape";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span th:text="${data}">글씨 굵게하기 처리</span><br>
<span th:utext="${data}">글씨 굵게하기 처리</span><br>
<span th:inline="none"></span>[[${data}]]<br>
<span th:inline="none"></span>[(${data})]
</body>
</html>
th:utext
를 통해 escape처리되지 않은 HTML을 보냄으로써 실제 브라우저에서는 태그가 적용된 것을 확인할 수 있다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span><b>ThymeLeaf</b></span><br>
<span><b>ThymeLeaf</b></span><br>
<span></span><b>ThymeLeaf</b><br>
<span></span><b>ThymeLeaf</b>
</body>
</html>

Spring EL표현식
타임리프에서 컨트롤러부터 받은 Model에 대한 값을 사용하는 표현식이다.
이 부분은 설명보다 코드를 보는 것이 훨씬 쉽게 이해할 수 있을 것이다.
@RequestMapping("springEL")
public String springEL(Model model){
User user = new User("hong", 10);
model.addAttribute("user", user);
List<User> userList = new ArrayList<>();
userList.add(new User("lee", 20));
userList.add(new User("kim", 30));
model.addAttribute("userList", userList);
Map<String, User> userMap = new HashMap<>();
userMap.put("park", new User("park", 10));
userMap.put("song", new User("song", 25));
userMap.put("choi", new User("choi", 20));
model.addAttribute("userMap", userMap);
return "basic/springEL";
}
model에 객체와 List, Map컬렉션을 담아 전송하였다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>User 객체1</h1>
<span th:text="${user.username}"></span><br>
<span th:text="${user.age}"></span><br>
<h1>User 객체2</h1>
<span th:text="${user.getUsername()}"></span><br>
<span th:text="${user.getAge()}"></span><br>
<h1>UserList1</h1>
<span th:text="${userList[0].username}"></span><br>
<span th:text="${userList[0].age}"></span><br>
<h1>UserList2</h1>
<span th:text="${userList[1].getUsername()}"></span><br>
<span th:text="${userList[1].getAge()}"></span><br>
<h1>UserMap1</h1>
<span th:text="${userMap.get('park').username}"></span><br>
<span th:text="${userMap.get('park').age}"></span><br>
<h1>UserMap2</h1>
<span th:text="${userMap.get('park').getUsername()}"></span><br>
<span th:text="${userMap.get('park').getAge()}"></span><br>
</body>
</html>
위와 같이 Model추가된 데이터를 각각의 방식으로 표현할 수 있다.
뿐만 아니라 '지역 변수' 개념도 지원하며 아래 코드와 같다.
<h1>지역 변수사용</h1>
<div th:with="userPark=${userList[0]}">
<span th:text="${userPark.age}"></span>
</div>
userPark
의 지역변수의 라이프 사이클은 <div></div>
내부에서만 사용 가능하다.
유틸리티 객체
유틸리티 객체에 대해서는 아래에 나와있는 객체들이 있다는 정도만 알아두고 실제 필요로해질 때에는 아래 링크에 있는 Thymeleaf공식 문서를 참고하여 사용하도록하자.
message
: 메시지, 국제화 처리uris
: URI 이스케이프 지원dates
:java.util.Date
서식을 지원calendars
:java.util.Calendar
서식을 지원numbers
: 숫자서식 지원strings
: 문자 관련 편의 기능 지원objects
: 객체 관련 기능 제공bools
: boolean 관련 기능 제공arrays
: 배열관련 기능 제공lists, sets, maps
: 컬렉션 관련 기능 제공
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expression-utility-objects
URL 링크
@{link}
문법을 사용하여 css link, javascrpit link등과 같은 링크를 표현할 수 있다.
@GetMapping("link")
public String link(Model model) {
model.addAttribute("query1", "queryData1");
model.addAttribute("query2", "queryData2");
return "basic/link";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a th:href="@{/hello}">기본 링크</a><br>
<a th:href="@{/hello(query1=${query1}, query2=${query2})}">쿼리 파라미터 형식</a><br>
<a th:href="@{/hello/{query1}/{query2}(query1=${query1}, query2=${query2})}">경로 변수형식</a><br>
<a th:href="@{/hello/{query1}(query1=${query1}, query2=${query2})}">쿼리 파람 + 경로 변수</a>
</body>
</html>
특수문자들이 여러개 사용될 뿐만아니라 중첩되어 사용되기 때문에 하나씩 천천히 살펴보자.
가장 기본적인 @{link}
사용방식으로 태그가 적용된 기본링크를 클릭하면 "/hello"로 URL을 변경한다.
<a th:href="@{/hello}">기본 링크</a><br>

쿼리 파라미터 형식을 또한 지원하며 실행결과를 먼저 살펴보자
<a th:href="@{/hello(query1=${query1}, query2=${query2})}">쿼리 파라미터 형식</a><br>

1. (query1=${query1} , query2=${query2})
- ${query1}과 ${query2}는 model담긴 실제 값 즉 queryData1,2를 의미
2. (query1=queryData1 , query2=queryData2)
로 치환
3. query1=queryData1&query2=queryData2
형태로 완전한 쿼리 파라미터 형식으로 변경
4. /hello?query1=queryData1&query2=queryData2
다음으로는 Path Varialbe형식의 주소 표현지원이다.
PathVariable은 이전에 Srping MVC패턴을 공부하며 사용해본 적이 있다.
Get방식으로 HTTP 메시지를 전송할 때 URL창에 담겨있는 변수를 사용하는 것으로 여기서 사용하는 경로 변수형식 또한 매우 유사하다.
<a th:href="@{/hello/{query1}/{query2}(query1=${query1}, query2=${query2})}">경로 변수형식</a><br>

/{query1}/{query2}의 2가지 변수영역이 있으며 이 변수에는 ()안에 있는 model로부터 받은 실제 데이터의 값으로 바인딩 된다.
따라서 /hello/{query1}/{query2}이 부분과 (query1=${query1}, query2=${query2})영역을 나누어서 본다면 보다 더 이해하기 쉬울 것이다.
위에서 다룬 쿼리파라미터 형식과 경로변수 형식을 섞어서 사용할수도 있다.
<a th:href="@{/hello/{query1}(query1=${query1}, query2=${query2})}">쿼리 파람 + 경로 변수</a>

리터럴
타임리프에서 문자 리터럴은 자바와 다르게 'string'과 같이 지원한다.
또한 문자열 리터럴을 표현하는 과정 중, 공백이 들어간 문자열을 표현하기위해서는 다음과같이 표현되어야한다.
'hello' + ' world'
하지만 이 방법은 너무 귀찮기때문에
|hello world| 와 같은 방식으로 대체할 수 있다.
이 부분은 타임리프를 사용하며 많이 실수하는 부분으로 주의하도록하자. " " -> ' '
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span th:text="|hello world|"></span><br>
<span th:text="|hello ${data}|"></span>
</body>
</html>
실행결과
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span>hello world</span><br>
<span>hello thymeleaf</span>
</body>
</html>
연산
타임리프에서 사용하는 연산자는 자바와 매우 유사하다.
<h3>산술 연산</h3>
<span th:text="10 + 2">덧셈 결과 출력</span><br>
<span th:text="10 % 2 == 0">참 or 거짓 출력</span><br>
<h3>비교 연산</h3>
<span th:text="1 > 10">true or false</span><br>
<span th:text="1 > 10">true or false</span><br>
<span th:text="1 >= 10">true or false</span><br>
<span th:text="1 ge 10">true or false</span><br>
<span th:text="1 == 10">true or false</span><br>
<span th:text="1 != 10">true or false</span><br>
산술 연산자는 산술 연산에 대한 결과를 보여주고, 비교 연산은 true나 false의 값을 반환하여 표현한다.
실행결과
<h3>산술 연산</h3>
<span>12</span><br>
<span>true</span><br>
<h3>비교 연산</h3>
<span>false</span><br>
<span>false</span><br>
<span>false</span><br>
<span>false</span><br>
<span>false</span><br>
<span>true</span><br>
타임리프의 연산파트에서 주의 깊게 봐야할 곳이라면 Elvis연산과 No-Operation연산이다.
<h3>조건식</h3>
<span th:text="(10 % 2 == 0)? '짝수':'홀수'">삼항 연산자</span><br>
<span th:text="${data}?:'데이터가 없습니다.'">Elvis 연산자</span><br>
<span th:text="${nullData}?:'데이터가 없습니다.'">Elvis 연산자</span><br>
먼저 타임리프의 비교연산자는 자바의 삼항 연산자와 똑같은 모양이다.
Elvis연산자는 주로 데이터의 null여부를 검사하기위해 사용되며 위에 코드에서는 'data'에 값이 있으면 'data'를 그대로 보여주고 데이터가 없다면 '데이터가 없습니다'를 출력한다.
실행결과
<h3>조건식</h3>
<span>짝수</span><br>
<span>thymeleaf</span><br>
<span>데이터가 없습니다.</span><br>
마지막으로 No-Operation연산자이다.
<h3>No-Operation</h3>
<span th:text="${data}?: _">No operation</span><br>
<span th:text="${nullData}?: _">null이면 정적 HTML을 그대로 출력</span><br>
'_'로 No-Operation연산을 사용할 수 있으며 데이터의 null여부를 확인하고 null일 경우 HTML내부에 들어있는 값을 그대로 사용한다.
즉, 타임리프를 적용하지 않는 것이다. 말 그대로 No Operation이다.
<h3>No-Operation</h3>
<span>thymeleaf</span><br>
<span>null이면 정적 HTML을 그대로 출력</span><br>
타임리프 태그 속성 설정
속성변경
아래 코드와 같이 지금까지 타임리프를 통해 기존의 정적인 HTML코드는 바꾸지 않으면서 서버 사이드 렌더링 될 때 HTML코드를 임의로 변경해왔었다.
<span th:text="대체되는 span">기존 span</span>
HTML의 문자만 바꾸는 것이 아니라 태그 내부에 있는 속성 또한 타임리프를 통해 변경할 수 있다.
아래 코드는 input태그의 'name'속성을 th:name="userA" 코드를 통해 userA라는 name속성의 값을 부여한다.
<h3>속성 변경</h3>
<input type="text" name="name" th:name="userA">
실행결과
<h3>속성 변경</h3>
<input type="text" name="userA">
name속성이 "userA"로 변경되어있는 것을 확인할 수 있다.
속성추가
속성 변경뿐만아니라 임의로 속성을 추가할수도 있다.
<h3>속성 추가</h3>
<input type="text" class="text" th:attrappend="class=' add-class'"><br>
<input type="text" class="text" th:attrprepend="class='add-class'"><br>
<input type="text" class="text" th:classappend="add-class"><br>
속성 변경에는 크게 3가지 방법이 있으며 아래의 실행결과를 통해 어떻게 사용되는지 알 수 있다.
<h3>속성 추가</h3>
<input type="text" class="text add-class"><br>
<input type="text" class="add-classtext"><br>
<input type="text" class="text add-class"><br>
여기서 주의할 점이라면 HTML코드에서 class속성은 공백을 기준으로 class값을 분류하기 때문에
'add-classtext'의 같은경우에는 class속성이 한 개이고, 'text add-class'는 'text'와 'add-class' 두 개의 속성 값을 가지고 있는 것이다.
타임리프에서는 이렇게 공백을 하나하나 넣어주는 불편함을 해결해주기 위해 th:classappend속성을 지원하여 자연스럽게 공백을 타임리프에서 넣어주도록 하였다.
checked처리
HTML은 checked의 속성 값에 'false','true'에 구분할 것 없이 checked라는 속성이 있기만하면 값에 상관없이 checked를 체크해서 보여주었다.
<h3>checked 처리</h3>
HTML checked 속성 없음<input type="checkbox" name="active" /><br>
HTML checked 값없이 속성만 있음<input type="checkbox" name="active" checked/><br>
HTML checked false<input type="checkbox" name="active" checked="false" /><br>
HTML checked true<input type="checkbox" name="active" checked="true" /><br>

타임리프에서는 checked속성에 대해 명확하게 체크를 on/off할 수 있도록 지원하고 이를 통해 개발자는 효율적으로 서비스를 제공할 수 있다.
<h3>checked 처리 thymeleaf</h3>
thymeleaf true<input type="checkbox" name="active" th:checked="true"><br>
thymeleaf false<input type="checkbox" name="active" th:checked="false">

반복문 - each
타임리프에서는 반복되는 문장을 편리하게 처리하기위해 자바의 for each문과 유사한 형태로 반복문을 제공한다.
<table border="1">
<tr>
<th>username</th>
<th>age</th>
</tr>
<tr th:each="user : ${userList}">
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
</tr>
</table>
Controller로부터 받은 userList를 순회하며 user에 값을 넣어가며 반복을 실행한다.
반복문은 table태그 뿐만아니라 div, li등 다양한 태그에서 사용할 수 있다.
<ul>
<li th:each="user : ${userList}" th:text="${user.username}"></li>
</ul>
실행결과
<table border="1">
<tr>
<th>username</th>
<th>age</th>
</tr>
<tr>
<td>userA</td>
<td>20</td>
</tr>
<tr>
<td>userB</td>
<td>30</td>
</tr>
<tr>
<td>userC</td>
<td>35</td>
</tr>
</table>
<ul>
<li>userA</li>
<li>userB</li>
<li>userC</li>
</ul>
each에 내장되어있는 Stat객체
each를 사용할 때 현재 반복문에 대한 상태를 알 수 있는 객체(Stat)를 제공한다.
Stat객체를 사용하기위해서는 아래 코드와 같이 userStat로 변수명을 맞춰서 사용하거나 맞추지 않을 경우에는 따로 변수명을 설정해주어야한다.
//Stat변수명을 따로 선언해주기
<tr th:each="user, userStat : ${users}">
<table border="1">
<tr>
<th>count</th>
<th>username</th>
<th>age</th>
<th>etc</th>
</tr>
<tr th:each="user:${userList}">
<td th:text="${userStat.count}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
<td>
index = <span th:text="${userStat.index}"></span><br>
count = <span th:text="${userStat.count}"></span><br>
size = <span th:text="${userStat.size}"></span><br>
짝수여부(true,false) = <span th:text="${userStat.even}"></span><br>
홀수여부(true,false) = <span th:text="${userStat.odd}"></span><br>
처음인덱스인지(true,false) = <span th:text="${userStat.first}"></span><br>
마지막인데스인지(true,false) = <span th:text="${userStat.last}"></span><br>
current(현재 객체) = <span th:text="${userStat.current}"></span><br>
</td>
</tr>
</table>

조건부 평가
타임리프는 자바의 if와 같이 조건식도 지원하며 타임리프에서는 if와 unless(if의 반대)로 사용된다.
<table border="1">
<tr>
<th>count</th>
<th>username</th>
<th>age</th>
</tr>
<tr th:each="user : ${userList}">
<td th:text="${userStat.count}"></td>
<td th:text="${user.username}"></td>
<td>
<span th:text="${user.age}"></span>
<span th:text="'미성년자'" th:if="${user.age lt 20}">lt = '<' </span>
<span th:text="'미성년자'" th:unless="${user.age ge 20}">ge = '>=' </span>
</td>
</tr>
</table>
따라서 아래와 같은 결과가 출력된다
List<User> list = new ArrayList<>();
list.add(new User("userA", 15));
list.add(new User("userB", 15));
list.add(new User("userC", 35));
<table border="1">
<tr>
<th>count</th>
<th>username</th>
<th>age</th>
</tr>
<tr>
<td>1</td>
<td>userA</td>
<td>
<span>15</span>
<span>미성년자</td>
</tr>
<tr>
<td>2</td>
<td>userB</td>
<td>
<span>15</span>
<span>미성년자</td>
</tr>
<tr>
<td>3</td>
<td>userC</td>
<td>
<span>35</span>
</td>
</tr>
</table>
만약 if나 unless조건에 모두 충족하지 않는다면 해당되는 태그 자체를 렌더링하지 않아 age가 35인것은 HTML그대로 출력되는 것을 확인할 수 있다.
주석처리
타임리프에서는 타임리프 별도의 주석을 제공한다.
HTML표준 주석
기존에 HTML에서 사용되던 주석으로 이 때 브라우저 소스보기를 통해 HTML주석을 확인할 수 있다.
<h3>HTML표준 주석</h3>
<!--<span th:text="${data}">HTML</span>-->

타임리프 파서 주석
타임리프에서 가장 많이 사용되는 주석으로 다음과 같이 주석처리하면 서비스를 이용하는 클라이언트에게도 보이지 않게된다.
<h3>타임리프 파서 주석</h3>
<!--/*-->
<span th:text="${data}">HTML</span>
<!--*/-->
//한 줄로도 처리 가능
<!--/* [[${data}]] */-->

타임리프 프로토타입 주석
잘 사용되지 않는 주석이지만 프로토타입 주석은 타임리프를 통해 렌더링 할 경우 ${data}의 값이 정상적으로 처리되는 특이한 주석이다.
HTML파일로 그대로 열 경우에는 주석처리된 것을 알 수 있다.
<h3>타임리프 프로토타입 주석</h3>
<!--/*/
<span th:text="${data}">HTML</span>
/*/-->

블록
<th:block> 태그는 타임리프에서 유일한 자체 태그로 아래 코드에서 알 수 있듯이 HTML에는 th:block이라는 태그가 존재하지 않는다.
때문에 타임리프에 장점인 '네츄럴 템플릿'을 만족하기 위해서는 사용하는 것을 권하지는 않지만, 아래 예제처럼 table, div등으로 반복하기에 애매할 경우에 사용할 수 있다.
<th:block th:each="user:${userList}">
<div>
이름 <span th:text="${user.username}"></span>
나이 <span th:text="${user.age}"></span>
</div>
<div>
요약 <span th:text="${user.username} + ' \ ' + ${user.age}"></span>
</div>
</th:block>
자바스크립트 인라인
타임리프는 자바스크립트를 편리하게 사용할 수 있는 자바스크립트 인라인 기능까지 제공한다.
//자바스크립트 인라인 사용전
<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>
코드만 봐서는 어떤게 다른점인지 알 수 없다 실행결과를 살펴보자
실행결과
//사용전
<script>
var username = userA;
var age = 10;
var username2 = /*userA*/ "test username";
var user = hello.thymeleaftest.basic.BasicControllerTest$User@7670cf50;
</script>
//사용후
<script>
var username = "userA";
var age = 10;
var username2 = "userA";
var user = {"username":"userA","age":10};
</script>
문자열타입의 리터럴을 보면 "userA"로 문자열을 표시하여 명확하게 문자열 리터럴임을 알려주는 기능을 제공하며
네츄럴 템플릿 기능도 명확하게 제공한다는 것을 알 수 있다.
그리고 가장 큰 차이점으로는 객체를 반환할 때 JSON형식으로 제공하기때문에 개발자 입장에서는 매우 편리하게 타임리프를 통해 자바스크립트를 사용할 수 있다.
템플릿 조각 - Fragment
웹 페이지를 개발할 때 '네이게이션 바', '쇼핑몰의 품목 리스트' 등과 같이 공통적인 영역이 있는데 이렇게 중복되는 코드들은 하나의 컴포넌트로 만들어서 관리하면 유지보수가 매우 용이할 것이다. 이러한 유지보수의 편의를 위해 타임리프는 템플릿 조각이라는 fragment속성을 지원한다.
Fragment - insert
먼저 Controller에서는 fragmentMain View를 반환한다.
@Controller
@RequestMapping("/template")
public class TemplateController {
@GetMapping("fragment")
public String template() {
return "template/fragment/fragmentMain";
}
}
컨트롤러에서 반환하는 fragmentMain View이다. 즉, 이 페이지가 브라우저에서 보여지게 된다.
fragmentMain.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:insert="~{template/fragment/footer :: copy}">footer(.html은 생략)</div> //~{}는 생략가능
</body>
</html>
아래는 템플릿 조각으로 함수를 호출해서 사용하듯이 아래 템플릿 View를 호출해서 사용할 수 있다.
footer.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<footer th:fragment="copy">
footer.html의 푸터 영역
</footer>
</body>
</html>
실행결과
fragmentMain.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div><footer>
footer.html의 푸터 영역
</footer></div>
</body>
</html>
실행결과를 통해 알 수 있듯이
//footer.html의 footer영역코드
<footer th:fragment="copy">
footer.html의 푸터 영역
</footer>
위에서 선언된 footer태그는 copy라는 fragment속성을 갖게되며 fragmentMain페이지에서
<div th:insert="~{template/fragment/footer :: copy}">footer</div>
이 코드를 통해 호출되어 <footer>태그가 통째로 삽입되는 것을 확인할 수 있다.
이 방법이 fragment의 insert방식이다.
Fragment - replace
위에서 다루어본 insert는 th:fragemnt코드가 포함된 <footer>태그를 통째로 호출한 곳에 삽입하였다면
replace는 말그대로 호출한 곳의 태그를 대체해버린다.
//fragmentMain.html
<body>
<div th:replace="~{template/fragment/footer :: copy}"></div> //~{}는 생략가능
</body>
//footer.html
<footer th:fragment="copy">
footer.html의 푸터 영역
</footer>
실행결과
<footer>
footer.html의 푸터 영역
</footer>
footerMain.html에 있는 div태그를 footer태그로 대체된 것을 확인할 수 있다.
Fragment - 파라미터 사용
//footer.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<footer th:fragment="copyParam(param1, param2)">
<p>파라미터 자리 입니다.</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
</body>
</html>
//footerMain.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:replace="template/fragment/footer :: copyParam('데이터', '데이터2')"></div>
</body>
</html>
파라미터 값을 넣어 템플릿 조각을 동적으로 사용할수도 있다.
템플릿 레이아웃
템플릿 레이아웃개념은 위에서 사용한 템플릿 조각에 확장된 개념으로 이전에는 조각 조각들을 라이브러리 가져오듯이 사용했다면 템플릿 레이아웃은 큰 틀을 가져와서 사용자가 원하는 부분은 바꿔가며 사용하는 개념이다. 코드로 살펴보자
//layoutMain.html
//Main페이지로 이 페이지가 호출되어 클라이언트에게 보여짐
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="template/layout/base :: common_header(~{::title}, ~{::link})">
<meta charset="UTF-8">
<title>메인 타이틀</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smothness/jquery-ui.css}">
<!-- 결론적으로 base 컴포넌트를 사용하긴하지만, title과 link는 Main페이지의 것들을 사용할것이라는 의미-->
</head>
<body>
메인 컨텐츠
</body>
</html>
아래 코드가 핵심 코드이며 layout폴더안에 base.html파일안에 common_header라는 변수로 매핑하여 현재 html파일의 title과 link값을 넘긴다는 것을 의미한다.
<head th:replace="template/layout/base :: common_header(~{::title}, ~{::link})">
fragment속성으로 common_header를 layoutMain.html파일로부터 받아와 ${title}의 값을 '메인 페이지'로 바꾸고,
임의의 th:block을 통해 layoutMain으로부터 받아온 link들도 ${links}에 매핑하여 링크들을 가져온다.
//base.html
//컴포넌트 역할을 수행
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title,links)">
<meta charset="UTF-8">
<title th:replace="${title}">레이아웃 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" media="all" type="text/css" 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>
<body>
</body>
</html>
실행결과
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>메인 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" media="all" type="text/css" href="/css/awesomeapp.css">
<link rel="shortcut icon" href="/images/favicon.ico">
<script type="text/javascript" src="/sh/scripts/codebase.js"></script>
<!-- 추가 -->
<link rel="stylesheet" href="/css/bootstrap.min.css"><link rel="stylesheet" href="/themes/smothness/jquery-ui.css">
</head>
<body>
메인 컨텐츠
</body>
</html>
이해하는데 시간이 좀 걸렸던 파트였다.
간단하게 생각하기위해 순서대로 살펴보자
1. 컨트롤러에서 main페이지 호출
2. <head th:replace="template/layout/base :: common_header(~{::title}, ~{::link})">
이 코드로 main페이지의 title과 link를 모두 base의 common_header부분으로 전달
3. <title th:replace="${title}">레이아웃 타이틀</title> 이 코드로 메인페이지로 부터 받은 title을 치환하여 적용
4. <th:block th:replace="${links}" /> 이 코드로 메인페이지로부터 받은 links파라미터 들을 치환하여 적용
5. 메인페이지에서 base로부터 받아온 값들을 포함하여 View로 반환
필자도 당장은 억지로 이렇게 이해한듯하다..
사실 html페이지가 전달되고 반환되는 개념은 없을 것이라고 생각한다.
이 부분은 웹 페이지를 직접 구현해보면 확실하게 이해할 수 있을듯하다.
위 정리한 내용들은 모두 인프런에서 김영한님의 강의를 듣고 학습한 내용을 스스로 정리한 것임을 밝힙니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1#
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!