공식 사이트 : https://www.thymeleaf.org/
공식 메뉴얼 - 기본 기능: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
공식 메뉴얼 - 스프링 통합 : https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
<html xmlns:th="http://www.thymeleaf.org">
을 html파일 상단에 선언하면 사용할 수 있다.
타임리프에서 문자 리터럴에서 주의해야할 점이 있는데, 바로 모든 문자열은
'(싱글)
으로 감싸져야한다는 것이다.
하지만 예외적으로 a-z, A-Z,0-9 으로만 이루어진 문자열은 감싸지 않아도 되지만, 띄어쓰기가 들어가거나 특수 문자 등이 들어가거나 변수와 문자열 합치기를 해야하는 경우에는 반드시'(싱글)
으로 감싸져 있어야한다.
혹은 위에서 말한|
으로 감싸져도 된다.
HTML Entity와 주의를 해야한다.
타임리프는 기본적으로 HTML 테그의 속성에 기능을 정의해서 동작한다.
<span th:text="${data}">원래 텍스트</span>
이런식으로 안에 th:
를 먼저쓰고 뒤에 text
나 object
등으로 어떤 값인지 구분을 하고, 그 값에 표현식을 넣는다.
아니면 html 태그 안에 값을 직접 넣고 싶다면,
<span>[[${data}]]</span>
이러한 방식으로 [[]]
안에 표현식을 써서 사용할 수도 있다.
data가 <b>text</b>
라고 상황을 두고 두 개를 비교해보자.
[타임리프]
<span th:text="${data}">원래 텍스트</span>
[결과] -> 소스보기
<span><b>text</b></span>
[타임리프]
<span th:utext="${data}">원래 텍스트</span>
[결과] -> 소스보기
<span><b>text</b></span>
Escape
html문서는<
,>
같은 특수 문자를 기반으로 정의 되기 때문에 html 텍스트를 보여줄 때 주의 해야하는데, 이때<
,>
와 같이 html 파일에서 사용하는 특수문자를 문자로 표현하는 문자를html entity
라고 한다.
이 html 파일에서 사용하는 특수문자를html entity
로 변경하는 것을escape
라고 한다.
기본적인 예시로는
<
-><
,>
->>
가 있고 이 외에도 많은html entity
가 있다.
위에서 말한
[[]]
안에 표현식을 써서 사용하는 방법은 text와 같고,[()]
를 사용하면 utext와 같다.
타임리프에서는 변수를 사용 할 때에는 ${,,,}
의 변수 표현식을 사용하는데, 여기 안에는 springEL
이라는 스프링이 제공하는 표현식을 사용할 수 있다.
SpringEL 다양한 표현식 사용
1. Object
user.username : user의 username을 프로퍼티 접근
user['username'] : 위와 같음 user.getUsername()
user.getUsername() : user의 getUsername() 을 직접 호출
2. List
users[0].username : List에서 첫 번째 회원을 찾고 username 프로퍼티 접근 -> list.get(0).getUsername()
users[0]['username'] : 위와 같음
users[0].getUsername() : List에서 첫 번째 회원을 찾고 메서드 직접 호출
3. Map
userMap['userA'].username : Map에서 userA를 찾고, username 프로퍼티 접근 map.get("userA").getUsername()
userMap['userA']['username'] : 위와 같음 userMap['userA'].getUsername() : Map에서 userA를 찾고 메서드 직접 호출
[타임리프]
<div th:with="userFirst=${users[0]}">
<p>1번째 유저 이름 : <span th:text="${userFirst.username}"></span></p>
</div>
${#request}
${#response}
${#session}
${#servletContext}
${#locale}
#message
: 메시지, 국제화 처리#uris
: URI 이스케이프 지원#dates
: java.util.Date 서식 지원 #calendars
: java.util.Calendar 서식 지원 #temporals
: 자바8 날짜 서식 지원#numbers
: 숫자 서식 지원#strings
: 문자 관련 편의 기능#objects
: 객체 관련 기능 제공#bools
: boolean 관련 기능 제공#arrays
: 배열 관련 기능 제공#lists
, #sets
, #maps
: 컬렉션 관련 기능 제공 #ids
: 아이디 처리 관련 기능 제공, 뒤에서 설명타임리프에서 링크를 생성할 때 @{...}
를 사용하면 된다.
@{/hello}
-> /hello
@{/hello(param1=${param1}, param2=${param2})}
-> /hello?param1=data1¶m2=data2
@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
-> /hello/data1/data2
@{/hello/{param1}(param1=${param1}, param2=${param2})}
-> /hello/data1?param2=data2
th:*
속성을 지정하면 기존 속성을 th:*
로 지정한 속성으로 대체한다. (없으면 만듬)[타임리프]
<input type="text" name="mock" th:name="userA" />
[결과]
<input type="text" name="userA" />
[타임리프]
<input type="text" class="text" th:attrappend="class=' large'" />
[결과]
<input type="text" class="text large" />
checked
사실checked
는true
,false
값을 구분하는 것이 아니라 속성 값이 존재하는지 하지 않는지를 구분하기때문에 처리가 까다로운데, 타임리프는th:checked="false"
라고 되어있으면 아예checked
값을 넣지 않는다.
[타임리프]
<tr th:each="user : ${users}">
<td th:text="${user.username}">유저이름</td>
</tr>
[결과]
<tr>
<td>username1</td>
<tr>
</tr>
<td>username2</td>
</tr>
반복시 오른쪽 컬렉션${users}
의 값을 하나씩 꺼내서 왼쪽 변수user
에 담아서 태그를 반복
반복 할 수 있는 객체 : Map
, List
,배열
, java.util.Iterable
, java.util.Enumeration
을 구현한 모든 객체를 반복에 사용할 수 있음
Map 도 사용할 수 있는데 이 경우 변수에 담기는 값은 Map.Entry 입니다.
첫번째 파라미터 이름
+ Stat
으로 접근 가능[타임리프]
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.index}">인덱스</td>
<td th:text="${user.username}">유저이름</td>
</tr>
[결과]
<tr>
<td>0</td>
<td>username1</td>
</tr>
<tr>
<td>1</td>
<td>username1</td>
</tr>
타임리프는 조건식을 비교했는데 맞지 않으면 아예 렌더링을 하지 않고 사라짐
[타임리프]
<span th:text="'결과1'" th:if="${21 lt 20}"></span>
<span th:text="'결과2'" th:unless="${21 gt 20}"></span>
[결과]
<span>결과2</span>
th:case="*"
는 만족하는 값이 없을때 사용하는 디폴트 값[타임리프]
<td th:switch="'10'">
<span th:case="10">10살</span>
<span th:case="20">20살</span>
<span th:case="*">기타</span>
</td>
[결과]
<td>
<span>10살</span>
</td>
<th:block>
은 HTML 태그가 아닌 타임리프의 유일한 자체 태그
원래 타임리프는 html태그 안에 속성으로 기능을 정의해서 사용하는데, 그렇게 하기 애매한 경우에 사용
이 태그는 렌더링시에는 사라진다.
[타임리프]
<th:block th:each="user : ${users}">
<div>
<div th:text="${user.index}"></div>
</div>
<div>
<div th:text="${user.name}"></div>
<div th:text="${user.age}"></div>
</div>
</th:block>
[결과]
<div>
<div>1</div>
</div>
<div>
<div>유저1</div>
<div>10</div>
</div>
<div>
<div>2</div>
</div>
<div>
<div>유저2</div>
<div>20</div>
</div>
타임리프는 자바스크립트에서 타임리프를 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.
적용하기 위해서는 <script th:inline="javascript">
를 적으면 된다.
"
로 감싸져서 들어감[타임리프]
<script th:inline="javascript">
var username = [[${user.username}]];
var age = [[${user.age}]];
//자바스크립트 내추럴 템플릿
var username2 = /*[[${user.username}]]*/ "test username";
//객체
var user = [[${user}]];
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
[결과]
<script>
var username = "userA";
var age = 10; //자바스크립트 내추럴 템플릿
var username2 = "userA"; //객체
var user = {"username":"userA","age":10};
var user1 = {"username":"userA","age":10};
var user2 = {"username":"userB","age":20};
var user3 = {"username":"userC","age":30};
</script>
템플릿 조각은 여러 페이지에서 공통적으로 사용하는 영역들을 해결하기 위해서 용한다. 예를 들어 footer와 header 등과 같은 부분은 여러 페이지에서 공통적으로 사용되면서 동시에 모든 페이지에서 똑같아야하는 부분등을 의미한다.
footer
를 따로 파일로 분리하고, 각 페이지에서 분리했던 footer
를 가져다 사용하는 방식이다.
[타임리프]
/template/fragment/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/main.html
<body>
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터 2')}"></div>
</body>
[결과]
<body>
<h2>부분 포함 insert</h2>
<div>
<footer> 푸터 자리 입니다.</footer>
</div>
<h2>부분 포함 replace</h2>
<footer> 푸터 자리 입니다.</footer>
<h1>파라미터 사용</h1>
<footer>
<p>파라미터 자리 입니다.</p>
<p>데이터1</p>
<p>데이터2</p>
</footer>
</body>
th:insert
: 이 속성이 적혀 있던 태그의 자식 태그로 들어옴th:replace
: 이 속성이 적혀 있던 태그가 사라지고, 그 위치로 들어옴또 파라미터를 사용 할 수 있고, 파라미터로는 태그도 들어 갈 수 있다.
이것을 이용해서 다양한 동적인 페이지를 만들 수 있다.
위에서는 공통된 부분을 부분별로 잘라내어 따로 저장하고 메인 페이지에서 이를 가져다 사용하는 방법이였다면, 이 방법은 오히려 반대로 한 html에 레이아웃을 짜놓고, 내용이 변경 되는 부분에만 내용을 넣어서 페이지를 구성하는 방식이다.
레이아웃을 짜놓을 파일에
<html th:fragment="layout(title, content)" xmlns:th="http://www.thymeleaf.org">
를 추가해 해당 페이지를 layout
이라고 정의하고, title
과content
를 받아서 레이아웃 안에 넣어 페이지를 보여준다.
[타임리프]
layout.html
<!DOCTYPE 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>
main.html
<!DOCTYPE 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>
[결과]
<!DOCTYPE html>
<html>
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
<footer> 레이아웃 푸터</footer>
</body>
</html>