[Thymleaf] 타임리프의 특징과 기본 기능
타임리프의 특징
1. 서버 사이드 HTML 렌더링
타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용된다
2. 네츄럴 템플릿
타임리프는 순수 HTML을 최대한 유지하려는 특징이 있다
타임리프로 작성한 파일은 HTML을 유지하기 때문에 웹 브라우저에서 파일을 열어도 내용을 확인할 수 있다
3. 스프링 통합 지원
타임리프는 스프링과 자연스럽게 통합되고 다양한 기능을 편리하게 사용할 수 있게 지원한다
타임리프 기본 표현식
기본 표현식 | 포맷 |
변수 표현식 | ${...} |
선택 변수 표현식 | *{...} |
메시지 표현식 | #{...} |
링크 URL 표현식 | @{...} |
조각 표현식 | ~{...} |
텍스트 - text, utext
타임리프는 HTML에서 사용하는 특수문자를 escape하는 기능을 제공한다
이러한 escape 기능을 사용하지 않으려면 태그 내부에서는 utext를, 태그 외부에서는 [()]를 사용하면 된다
var1 = "Hello <b>Spring!</b>";
HTML | 렌더링 후 출력 |
<p th:text="${var1}"> contents </p> | Hello <b>Spring!</b> |
<p th:utext="${var1}"> contents > | Hello Spring! |
HTML | 렌더링 후 출력 |
<p> [[${var1}]] </p> | Hello <b>Spring!</b> |
<p> [(${var1})] </p> | Hello Spring! |
타임리프 속성 설정
타임리프는 th:* 속성을 지정하면 기존 속성을 th:*로 지정한 속성으로 대체한다
만약 기존 속성이 없다면 새로 만든다
HTML | 렌더링 후 HTML |
<p class="field-error" th:class="global-error"> ... </p> | <p class="global-error"> ... </p> |
<p class="field-error" th:type="text"> ... </p> | <p class="field-error" type="text"> ... </p> |
타임리프 반복 설정
타임리프는 iterable 컬렉션에서 반복을 지원하는 th:each 기능을 사용한다
다음은 User 리스트를 순회하며 출력하는 코드이다
public String each(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(list);
return "basic/each";
}
<table border="1">
<tr>
<th>username</th>
<th>age</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
</tr>
</table>
타임리프 조건문 설정
타임리프는 조건문으로 if, unless, switch, case 문법을 제공한다
타임리프는 해당 조건이 맞지 않으면 태그 자체를 렌더링하지 않는다
다음은 그 예시이다
<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>
<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>
</td>
</tr>
SpringEL 표현식
타임리프는 변수를 사용할 때 변수 표현식 (${...}) 을 사용한다
Spring은 변수 표현식 내부에서 객체를 사용하듯 쓸 수 있는 표현식을 제공한다
Object
HTML 문서 | 동작 |
${user.username} | user.getUsername() 호출 |
${user['username']} | user.getUsername() 호출 |
${user.getUsername()} | user.getUsername() 호출 |
List
HTML 문서 | 동작 |
${users[0].username} | users[0].getUsername() 호출 |
${users[0]['username']} | users[0].getUsername() 호출 |
${users[0].getUsername()} | users[0].getUsername() 호출 |
Map
HTML 문서 | 동작 |
${userMap['userA'].username} | userMap.get("userA").getUsername() |
${userMap['userA']['username']} | userMap.get("userA").getUsername() |
${userMap['userA'].getUsername()} | userMap.get("userA").getUsername() |
지역 변수 선언
th:with 를 사용하면 지역 변수를 사용할 수 있다
해당 지역 변수는 선언한 태그 내부에서만 사용할 수 있다
<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 [[${first.username}] 입니다</p>
</div>
Thymeleaf 기본 객체
자주 사용하는 객체들은 model에 담지 않아도 사용할 수 있도록 Spring이 제공한다
기본 객체 | 동작 |
${#request} | HttpServletRequest 객체 호출 |
${#response} | HttpServletResponse 객체 호출 |
${#session} | HttpSession 객체 호출 |
${#servletContext} | ServletContext 객체 호출 |
${#locale} | locale 객체 호출 |
URL 링크
Thymeleaf에서 URL 링크를 생성하려면 @{...} 문법을 사용하면 된다
기본 링크 표현
a 태그 | 링크 |
<a th:href="@{/hello}"> basic link </a> | http://localhost:8080/hello |
경로 표현식
a 태그 | 링크 |
<a th:href="@{/hello/{a}/{b}(a=${dir1},b=${dir2})}"> ... | http://localhost:8080/hello/d1/d2 (dir1=d1, dir2=d2) |
복합 표현식
a 태그 | 링크 |
<a th:href="@{/hello/{a}(a=${dir1},b=${dir2})}"> ... | http://localhost:8080/hello/d1?b=dir2 (dir1=d1) |
템플릿 Fragment
여러 웹 페이지의 공통 영역을 표현해주는 템플릿 Fragment 기능이 있다
~{...} 문법을 사용하면 이를 활용할 수 있다
물론 ~{...} 문법을 사용하는 것이 원칙이지만 템플릿 코드가 간단하다면 이를 생략할 수 있다
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy">푸터 자리 입니다.</footer>
<footer th:fragment="copyParam (param1, param2)">
<p>파라미터 자리 입니다.</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
</body>
</html>
렌더링 전 | 렌더링 후 |
<div th:insert="~{/temp/frag/footer :: copy}"></div> | <div><footer th:fragment="copy"></footer></div> |
<div th:replace="~{/temp/frag/footer :: copy}"></div>
|
<footer th:fragment="copy"></footer> |
<div th:replace="~{/temp/frag/footer :: copyParam ('데이터 1', '데이터 2')}"></div>
|
<footer th:fragment="copyParam ('데이터 1') (데이터 2)"></footer> |
템플릿 레이아웃
템플릿은 파라미터를 받아 데이터를 동적으로 렌더링할 수 있다
여기에 파라미터로 단순히 데이터가 아닌 태그를 받을 수도 있다
이 개념을 단순 태그가 아닌 전체에 적용할수도 있다
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<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>
</html>
<html xmlns:th="http://www.thymeleaf.org">
<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>
상단의 문서를 렌더링하면 다음과 같은 결과가 나온다
<!DOCTYPE html>
<html>
<head>
<title>메인 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" type="text/css" media="all" 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/smoothness/jquery-ui.css">
</head>
<body>
메인 컨텐츠
</body>
</html>