Thymeleaf 문법 정리
Thymeleaf 사용 선언
1
2
<!-- 맨 윗줄에 선언 -->
<html xmlns:th="http://www.thymeleaf.org">
텍스트 출력
1
2
3
4
5
6
7
<!-- 이스케이프가 제공되는 표현 -->
<li><span th:text="${data}"></span></li>
<li>다른 텍스트와 함께 사용 = [[${data}]]</li>
<!-- 이스케이프가 제공되지 않는 표현 -->
<li><span th:utext="${data}"></span></li>
<li><span th:inline="none">[(...)] = </span>[(${data})]</li>
th:inline="none"
은 타임리프 표현식([(...)]
)을 해석하지 말라는 뜻이다.
Escape 와 HTML 엔티티
Controller 에서 넘어온 데이터에는 특수문자가 포함될 수 있다. 그러한 특수문자 중 HTML 에서 사용하는 특수문자를 별도의 문자로 표현한 것을 HTML 엔티티 라고 하며, 특수문자를 HTML 엔티티로 변경하는 과정을 이스케이프(escape) 라고 한다.
- model에 담긴 데이터 :
Hello <b>Spring!</b>
- 의도한 표현 : Hello Spring!
- 출력 :
Hello <b>Spring!</b>
- 소스보기 :
Hello <b>Spring!</b>
여기서
<
를 HTML 엔티티라고 하고<
를<
로 변경하는 것을 이스케이프라고 한다.
변수 표현식 : ${…}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Object -->
<li th:text="${user.username}"></li>
<li th:text="${user['username']}"></li>
<li th:text="${user.getUserName()}"></li>
<!-- List -->
<li th:text="${user[0].username}"></li>
<li th:text="${user[0]['username']}"></li>
<li th:text="${user[0].getUserName()}"></li>
<!-- Map -->
<li th:text="${user['userA'].username}"></li>
<li th:text="${user['userA']['username']}"></li>
<li th:text="${user['userA'].getUserName()}"></li>
타임리프 변수 : th:with
1
2
3
<div th:with="first=${users[0]}">
<p>첫번째 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
기본 객체
${#request}
- 3.0 버전 이상에서는 제공하지 않음${#response}
- 3.0 버전 이상에서는 제공하지 않음${#session}
- 3.0 버전 이상에서는 제공하지 않음${#servletContext}
- 3.0 버전 이상에서는 제공하지 않음${#locale}
${param}
- request 파라미터 접근 (Controller 에@RequestParam
을 해주지 않아도 타임리프가 쿼리스트링을 param 객체에 담아서 넘겨준다.)${session}
- 세션 접근${@helloBean.hello('Spring!')}
- 스프링 빈 접근
3.0 이상에서 제공하지 않는 객체는 직접 model 에 넣어줘야 한다.
유틸리티 객체
필요할 때 찾아보고 사용하기. 링크 참조
Java 8 날짜 : ${#temporals}
1
2
3
4
5
6
7
8
9
10
11
12
13
<li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime,'yyyy-MM-dd HH:mm:ss')}"></span></li>
<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>
URL 링크 : @{...}
1
2
3
4
<li><a th:href="@{/hello}">basic url</a></li>
<li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">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>
리터럴과 리터럴 대체
타임리프에서 문자는 항상 '
(작은 따옴표) 로 감싸야 한다.
하지만 룰로 연결된 문자라면 작은 따옴표를 생략할 수 있다. (룰 : A-Z
, a-z
, 0-9
, []
, .
, -
, _
)
혹은 리터럴 대체를 사용하면 된다.
1
2
3
4
5
6
7
<!-- 주석을 풀면 예외가 발생함
<li>"hello world!" = <span th:text="hello world!"></span></li>
-->
<li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li>
<li>'hello world!' = <span th:text="'hello world!'"></span></li>
<li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>
<li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
연산
산술연산
1 2
<li>10 + 2 = <span th:text="10 + 2"></span></li> <li>10 % 2 = <span th:text="10 % 2"></span></li>
비교연산
1 2 3
<li>1 gt 10 = <span th:text="1 gt 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>
조건식
1
<li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)?'짝수':'홀수'"></span></li>
Elvis 연산자
1
<li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?: '데이터가 없습니다.'"></span></li>
No-Operation(
_
)1 2 3 4 5
<li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li> <!-- 출력 ${nullData}?: _ = 데이터가 없습니다. -->
No-Operation 은 데이터가 null 이면 타임리프 속성을 무시한다.
속성 설정 : th:*
타임리프는 th:*
로 속성을 지정하면 기본 HTML 속성을 대체한다. 이 때, 기존 속성이 없다면 새로 만든다.
1
2
3
4
5
6
7
8
<!-- 기존 속성 뒤에 추가 -->
<input type="text" class="A" th:attrappend="class=' large'"/>
<!-- 기존 속성 앞에 추가 -->
<input type="text" class="A" th:attrprepend="class='large '"/>
<!-- class 추가 -->
<input type="text" class="A" th:classappend="large"/>
<!-- checked 처리 -->
<input type="checkbox" name="active" th:checked="true"/>
반복 : th:each
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<tr th:each="user, userStat : ${users}">
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
<td>
<span th:text="${userStat.index}"></span> <!-- 0부터 시작하는 값 -->
<span th:text="${userStat.count}"></span> <!-- 1부터 시작하는 값 -->
<span th:text="${userStat.size}"></span> <!-- 전체 사이즈 -->
<span th:text="${userStat.even}"></span> <!-- 홀수 여부 (boolean) -->
<span th:text="${userStat.odd}"></span> <!-- 짝수 여부 (boolean) -->
<span th:text="${userStat.first}"></span> <!-- 처음 여부 (boolean) -->
<span th:text="${userStat.last}"></span> <!-- 마지막 여부 (boolean) -->
<span th:text="${userStat.current}"></span><!-- 현재 객체 -->
</td>
</tr>
반복상태변수(userStat) 명이 반복변수(user) + Stat 와 같으면 반복상태변수(userStat) 는 생략할 수 있고, ...+Stat
로 사용하고 싶지 않다면 새롭게 정의하면 된다.
조건문 : th:if , th:unless , th:switch
1
2
3
4
5
6
7
8
9
<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>
th:if
는 조건이 참일 때 태그를 렌더링하고, th:unless
는 조건이 거짓일 때 태그를 렌더링한다.
1
2
3
4
5
6
7
8
9
<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>
주석
1
2
3
4
5
6
7
8
9
10
11
12
<p>타임리프 한줄 주석</p>
<!--/* <span>[[${data}]]</span> */-->
<p>타임리프 여러줄 주석</p>
<!--/*-->
<span th:text="${data}">data</span>
<!--*/-->
<p>타임리프 프로토타입 주석</p>
<!--/*/
<span th=text="${data}">data</span>
/*/-->
프로토타입 주석의 경우 HTML 파일로 보면 주석이지만 서버에서 렌더링 시 정상 출력되는 특이한 주석이다.
블록 : <th:block>
속성이 아닌 태그임에 주의
1
2
3
4
5
6
7
8
9
<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>
<th:block>
태그안에 포함된 것들을 하나로 묶어주는 역할을 한다. 조건, 반복 시 태그안에 포함된 것들이 함께 처리된다.
자바스크립트 인라인
실행 전
1
2
3
4
5
6
7
8
<script th:inline="javascript">
var username = [[${user.username}]];
var age = [[${user.age}]];
//자바스크립트 내추럴 템플릿
var username2 = /*[[${user.username}]]*/ "test username";
//객체
var user = [[${user}]];
</script>
실행 후
1
2
3
4
5
6
7
8
<script>
var username = "userA";
var age = 10;
//자바스크립트 내추럴 템플릿
var username2 = "userA";
//객체
var user = {"username":"userA","age":10};
</script>
JSP 처럼 ${…}
에 '
를 붙여줄 필요가 없으며, 객체는 자동으로 object 로 변환해준다.
실행 전
1
2
3
4
5
6
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]]
[/]
</script>
실행 후
1
2
3
4
5
6
7
8
<!-- 자바스크립트 인라인 each -->
<script>
var user1 = {"username":"userA","age":10}
var user2 = {"username":"userB","age":20}
var user3 = {"username":"userC","age":30}
</script>
each
를 통해 반복처리도 가능하다.
템플릿 조각 : th:fragment , ~{…}
템플릿 조각은 다른 html 파일에서 사용하기 위한 컴포넌트 같은 역할을 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
<!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>
th:fragment
가 적용된 태그는 다른 html 에서 호출해서 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>
th:insert
: 적용된 태그의 자식에 fragment 를 적용.th:replace
: 적용된 태그를 fragment 로 대체.
예제1
1
2
3
4
5
6
7
8
9
10
11
12
<head th:fragment="common_header(title, links)">
<!-- title replace -->
<title th:replace="${title}">Title</title>
<!-- common area -->
<link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomapp.css}">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
<!-- custom area -->
<th:block th:replace="${links}"/>
</head>
1
2
3
4
5
6
7
8
<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>
main contents
</body>
예제2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
th:fragment="layout(title, content)">
<head>
<title th:replace="${title}">Title</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<div th:replace="${content}">
<p>레이아웃 컨텐츠</p>
</div>
<footer>
레이아웃 푸터
</footer>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
th:replace="template/layoutExtend/layoutFile::layout(~{::title},~{::section})">
<head>
<title>Title</title>
</head>
<body>
<section>
<p>메인 페이지 콘텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
</body>
</html>
타임리프 레이아웃 적용 시 보통 위 예제들처럼 fragment 를 가져올 때 내가 원하는 태그만 넘겨 레이아웃을 적용한다.