Tymeleaf 기본 기능
- Tymeleaf란?
- Tymeleaf의 기본 기능
1. Tymeleaf 소개
- tymeleaf 공식 사이트 : https://www.thymeleaf.org/
- tymeleaf 공식 메뉴얼 - 기본 기능 : https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
- tymeleaf 공식 메뉴얼 - 스프링 통합 : https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
Tutorial: Using Thymeleaf
1 Introducing Thymeleaf 1.1 What is Thymeleaf? Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text. The main goal of Thymeleaf is to provide a
www.thymeleaf.org
Tutorial: Thymeleaf + Spring
Preface This tutorial explains how Thymeleaf can be integrated with the Spring Framework, especially (but not only) Spring MVC. Note that Thymeleaf has integrations for both versions 3.x and 4.x of the Spring Framework, provided by two separate libraries c
www.thymeleaf.org
Thymeleaf
Integrations galore Eclipse, IntelliJ IDEA, Spring, Play, even the up-and-coming Model-View-Controller API for Java EE 8. Write Thymeleaf in your favourite tools, using your favourite web-development framework. Check out our Ecosystem to see more integrati
www.thymeleaf.org
1) 타임리프 특징
- 서버 사이드 HTML 렌더링(SSR)
- 타임리프는 서버에서 렌더링하여 완성된 HTML 파일을 로드해 주는데 클라이언트에서 요청할 때마다 각 상황에 맞는 HTML 파일을 넘겨주기 때문에 페이지가 여러 가지다
- 네츄럴 템플릿
- 타임리프로 작성한 파일은 HTML을 유지하기 때문에 웹 브라우저 파일을 직접 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과 확인이 가능하다.
- 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿이라고 한다.
- 스프링 통합 지원
- 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다.
2. Tymeleaf 기본 기능
1) 타임리프 사용
타임 리프를 사용하려면 선언을 먼저 해야하는데 선언은 다음과 같다
<html xmlns:th="http//www.thymeleaf.org">
2) 텍스트 - text, utext
th : text는 HTML의 콘텐츠에 데이터 출력할 때 사용한다. <span th:text="${data}">
HTML 태그의 속성이 아닌 콘텐츠 영역안에 직접 데이터를 출력하기를 원한다면 [[...]]을 사용하면 된다.
[BasicController.java]
@Controller
@RequestMapping("/basic")
public class BasicController {
@GetMapping("text-basic")
public String textBasic(Model model) {
model.addAttribute("data", "Hello Spring!");
return "basic/text-basic";
}
[/resources/templates/basic/text-basic.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
</ul>
</body>
</html>
소스를 보면 치환이 되어 나온다.
HTML 문서는 < , > 같은 특수 문자를 기반으로 정의가 되는데 주의해서 사용해야한다.
<b>태그를 이용하여 Spring! 단어를 진하게 만들어보려한다.
"Hello <b> Spring!</b>" 이렇게 입력 후 웹 브라우저에서 실행을 해보게 되면 웹 브라우저에서는 Hello <b>Spring!</b>가 그대로 보여지게 된다. 소스보기로 보면 Hello <b>Spring!</b> 이렇게 출력이 되는데
의도와는 다르게 <b> 태그가 그대로 나온다
-HTML 엔티티
웹 브라우저는 <를 HTML의 태그의 시작으로 인식을 한다. 그래서 <를 태그의 시작이 아닌 문자로 표현할 수 있는 방법이 필요한데 이를 HTML 엔티티라고 한다.
그리고 이런 식으로 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다.
th:text, [[...]]는 기본적으로 이스케이프를 제공하고 있다.
- < → <
- > → >
이스케이프를 사용하지 않으려면 어떻게 해야할까?
- th:text → th:utext
- [[...]] → [(...)]
[BasicController.java 추가]
@GetMapping("text-unescaped")
public String textUnescaped(Model model){
model.addAttribute("data", "Hello <b> Spring!</b>");
return "basic/text-unescaped";
}
[/resources/templates/basic/text-unescape.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>text vs utext</h1>
<ul>
<li>th:text = <span th:text="${data}"></span></li>
<li>th:utext = <span th:utext="${data}"></span></li>
</ul>
<h1><span th:inline="none">[[...]] vs [(...)]</span></h1>
<ul>
<li><span th:inline="none">[[...]] = </span>[[${data}]]</li>
<li><span th:inline="none">[(...)] = </span>[(${data})]</li>
</ul>
</body>
</html>
th:inline = "none"는 이 태그 안에서 타임리프가 해석하지 말라는 옵션이다.
3) 변수 - SpringEL
타임리프에서 변수 표현할 때에는 ${...}로 사용을 한다.
[BasicController.java 추가]
@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); //user 자체
model.addAttribute("users", list);
model.addAttribute("userMap", map);
return "basic/variable";
}
@Data
static class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
[/resources/templates/basic/variable.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
</body>
</html>
- Object
- user.username는 user의 username을 프로퍼티 접근한다. (user.getUsername())
- user['username']는 user.username과 같다.
- user.getUsername()는 user의 getUsername()을 직접 호출한다.
- List
- users[0].username는 List에서 첫 번째 회원을 찾고 username을 프로퍼티 접근한다. (list.get(0).getUsername())
- user[0]['username']은 user[0].username과 같다.
- user[0].getUsername()는 List에서 첫 번째 회원을 찾고 메서드를 직접 호출한다.
- Map
- userMap['userA'].username는 Map에서 userA를 찾고 username을 프로퍼티 접근한다.(map.get("userA").getUsername()
- userMap['userA']['username']은 위와 같다.
- userMap['userA'].getUsername()은 Map에서 userA 찾고 메서드를 직접 호출한다.
이렇게 다양한 방식을 지원하는데 사용함에 있어 차이는 없고 편한 방식 또는 일관성 있는 방식을 가지면 좋다.
지역 변수를 선언해서 사용하기 위해서는 th:with를 사용하면 되는데 지역변수는 선언한 태그 안에서만 사용할 수 있다.
4) 기본 객체들
[BasicController.java 추가]
@GetMapping("/basic-objects")
public String basicObjects(HttpSession session) {
session.setAttribute("sessionData", "Hello Session");
//HttpSession에 데이터 담아두고 타임리프에서 꺼낼 수 있음
return "basic/basic-objects";
//HttpSession 유저가 웹브라우저를 종료하기 전까지 데이터 유지
}
@Component("helloBean")
static class HelloBean {
public String hello(String data) {
return "hello" + data;
}
}
[/resources/templates/basic/basic-objects.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
</body>
</html>
편의 객체는 http://localhost:8080/basic/basic-objects?paramData=HelloParam
원래대로라면 값을 컨트롤러로 받아서 모델에 담고 모델에서 뷰템플릿으로 넘겨서 렌더링을 해야하지만 타임리프는
이런 자주쓰는 것들은 직접 불러쓸 수 있게 해준다. 이름 그대로 paramData를 부르면 HelloParam이 들어온다.
또한 스프링 빈에 직접 접근하는 것도 가능하다.
<li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
스프링 빈에 @helloBean이라 이름을 주면(Component Scan으로 helloBean이라 이름을 줌) 스프링 빈에 직접 접근하여 HelloBean에 있는 hello()가 호출된다. (Hello Spring!)
5) 유틸리티 객체와 날짜
유틸리티 객체는 대략 알아두고 필요에 따라 찾아서 사용하면 된다.
[BasicController.java 추가]
@GetMapping("/date")
public String date(Model model) {
model.addAttribute("localDateTime", LocalDateTime.now());
return "basic/date";
}
[/resources/templates/basic/date.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
</body>
</html>
6) URL 링크
타임리프에서 URL 생성을 할 때에는 @{...} 문법을 사용한다.
[BasicController.java 추가]
@GetMapping("/link")
public String link(Model model) {
model.addAttribute("param1", "data1");
model.addAttribute("param2", "data2");
return "basic/link";
}
[/resources/templates/basic/link.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
</body>
</html>
단순하게 URL을 표현하고자 하면 @{/hello}과 같이 사용해준다.
쿼리파라미터를 넣고 싶을때 @{/hello(param1 = ${param1}, param2 =${param2})}을 사용하게 되면
/hello?param1=data1&param2=data2 다음과 같이 처리된다. ()에 있는 부분이 쿼리파라미터로 처리가된다.
hello URL에 뭔가 더 넣고 싶을 때 /hello/data1/data2이런 식으로 하기를 원한다면
@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})} 이 식을 사용해준다.
${param1}, &{param2}가 각각 param1, param2에 바인딩 된다. URL 경로상에 변수가 있으면 () 부분은 경로 변수로 처리가된다.
경로 변수와 쿼리 파라미터를 같이 사용할 수도 있는데 /hello/data1?param2 =data2 이런 식으로 표현이 된다.
7) 리터럴(Literals)
소스 코드상 고정된 값을 리터럴이라고 한다.
타임리프에서 문자 리터럴은 항상 '(작은 따옴표)로 감싸야한다.
<span th:text=" 'hello' ">
하지만 너무 귀찮기 때문에 공백이 없이 쭉 쓴다면 하나의 의미 있는 토큰으로 작은 따옴표 생략이 가능하다.
룰 : A-Z , a-z , 0-9 , [] , . , - , _
[BasicController.java 추가]
@GetMapping("/literal")
public String literal(Model model) {
model.addAttribute("data", "Spring!");
return "basic/literal";
}
[/resources/templates/basic/literal.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>리터럴</h1>
<ul>
<!--주의! 다음 주석을 풀면 예외가 발생함-->
<!-- <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>
</ul>
</body>
</html>
8) 연산
타임리프 연산은 java와 크게 다르지 않는데 HTML안에서 사용하기 때문에 HTML 엔티티를 사용하는 부분에만 주의를 기울이면 된다.
[BasicController.java 추가]
@GetMapping("/operation")
public String operation(Model model) {
model.addAttribute("nullData", null);
model.addAttribute("data", "Spring!");
return "basic/operation";
}
[/resources/templates/basic/operation.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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 > 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>
</body>
</html>
비교연산 : HTML 엔티티 사용해야 하는 부분 주의
- > (gt), < (lt), >= (ge), <= (le), ! (not), == (eq), != (neq, ne)
조건식 : java 조건식과 유사
Elvis 연산자 : 조건식의 편의 버전 (데이터 없을 때 사용)
No-Operation : _인 경우 타임리프 태그 렌더링을 안 하는 것과 같다. 그래서 그냥 그대로 출력이 된다.
9) 속성 값 설정
타임리프는 주로 HTML 태그에 th:* 속성을 지정하는 방식으로 동작하는데 이렇게 적용을 하면 기존 속성을 대체하고
만약 기존 속성이 없다면 새로 생성한다.
[BasicController.java 추가]
@GetMapping("/attribute")
public String attribute() {
return "basic/attribute";
}
[/resources/templates/basic/attribute.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>속성 설정</h1>
<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>
</html>
타임리프가 th:* 부분을 렌더링해서 바꿔준다.
<input type="text" name="mock" th:name="userA" />
-> <input type ="text" name="userA" />
- th:attrappend는 속성 값의 뒤에 값을 추가해준다.
- th:attrprepend는 속성 값의 앞에 값을 추가해준다.
- th:classappend는 class 속성에 자연스럽게 알아서 추가해준다.
HTML에서는 <input type="checkbox" name="active" th:checked="false" />이 경우에도 checked 처리가 되어 버린다.
즉 checked 속성 값과 상관 없이 있으면 체크가 된다. 그래서 타임리프의 th:checked 같은 경우는 값이 false라면 checked 속성 자체를 제거한다.
10) 반복
[BasicController.java 추가]
@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);
}
[/resources/templates/basic/attribute.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>기본 테이블</h1>
<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>
<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>
</table>
</body>
</html>
타임리프에서 반복을 쓰고 싶을 땐 th:each를 사용하면 된다.
<tr th:each="user : ${users}">
반복시 오른쪽 컬렉션( ${users} )의 값을 하나씩 꺼내어 왼쪽 변수(user)에 담아서 태그를 반복한다.
List뿐만 아니라 Map도 사용가능한데 변수에 담기는 값은 Map.Entry이다.
<tr th:each="user, userStat : ${users}">
반복에서 두 번째 파라미터를 설정하게 되면 반복의 상태를 확인할 수 있는데 생략이 가능하다. 생략을 하게 되면 지정한 변수명(user) + Stat가 된다.
반복 상태 유지 기능
- index : 0부터 시작하는 값
- count : 1부터 시작하는 값
- size : 전체 사이즈
- even, odd : 홀,짝 여부(boolean)
- first, last : 처음, 마지막 여부(boolean)
- current : 현재 객체
11) 조건부 평가
[BasicController.java 추가]
@GetMapping("/condition")
public String condition(Model model) {
addUsers(model);
return "basic/condition";
}
[/resources/templates/basic/condition.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>if, unless</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>
<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>
</td>
</tr>
</table>
</body>
</html>
타임리프에는 조건식으로 if와 unless가 존재한다..
해당 조건이 만족하지 않으면 태그 자체를 렌더링하지 않는다. 다음 조건이 false인 경우 <span>...<span> 부분 자체가 렌더링 되지 않고 없어진다.
unless는 if와는 반대로 false일 경우 출력된다
switch
*은 만족하는 조건이 없을 경우 사용하는 디폴트이다.
12) 주석
[BasicController.java 추가]
@GetMapping("/comments")
public String comments(Model model) {
model.addAttribute("data", "Spring!");
return "basic/comments";
}
[/resources/templates/basic/comments.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>예시</h1>
<span th:text="${data}">html data</span>
<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>
/*/-->
</body>
</html>
- 표준 HTML 주석
- 자바 스크립트의 표준 HTML 주석은 타임리프가 렌더링하지 않고 그대로 남겨둔다.
- 타임리프 파서 주석
- 타임리프의 진짜 주석으로 렌더링에서 주석 부분을 제거한다.
- 타임리프 프로토타입 주석
- HTML 주석에 약간의 구문을 더한 것으로 HTML 파일을 웹 브라우저에서 그대로 열어보면 HTML 주석이기 때문에 웹 브라우저가 렌더링하지 않고 타임리프 렌더링을 거치면 정상적으로 렌더링 된다.
13) 블록
[BasicController.java 추가]
@GetMapping("/block")
public String block(Model model) {
addUsers(model);
return "basic/block";
}
[/resources/templates/basic/block.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
</body>
</html>
<th : block>는 HTML 태그가 아닌 타임리프의 유일 자체 태그이다.
타임리프 특성상 태그 속성으로 기능을 정의해서 사용하지만 애매한 경우에 사용한다.
block은 렌더링시에 제거된다.
14) 자바스크립트 인라인
[BasicController.java 추가]
@GetMapping("/javascript")
public String javascript(Model model) {
model.addAttribute("user", new User("userA", 10));
addUsers(model);
return "basic/javascript";
}
[/resources/templates/basic/javascript.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 자바스크립트 인라인 사용 전 -->
<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>
</body>
</html>
자바스크립트 인라인은 <script th:inline="javascript">로 사용하면 된다.
-텍스트 렌더링
var username = [[${user.username}]];
인라인 사용 전 -> var username = userA;
인라인 사용 후 -> var username = "userA";
userA가 변수명으로 사용되어 자바스크립트 오류가 발생한다. 숫자 age는 "가 필요 없기 때문에 정상 렌더링 된다.
인라인 사용 후 렌더링 결과를 보면 문자 타입의 경우 "를 포함해 준다. 추가로 자바 스크립트에서 문제가 될 수 있는 문자가 포함되어 있으면 이스케이프 처리도 해준다 (" -> \")
-자바스크립트 내추럴 템플릿
타임리프는 HTML 파일을 직접 열어도 동작하는 내추럴 템플릿 기능을 제공해준다. 인라인 기능을 사용하면 주석을 활용해서 사용이 가능하다.
var username2 = /*[[${user.username}]]*/ "test username";
인라인 사용 전 -> var username2 = /*userA*/ "test username";
인라인 사용 후 -> var username2 = "userA";
-객체
인라인 기능을 사용하면 객체를 JSON으로 자동 변환해준다.
var user = [[${user}]];
인라인 사용 전 -> var user = BasicController.User(username=userA, age=10); //객체의 toString() 호출된 값
인라인 사용 후 -> var user = {"username":"userA","age":10}; //JSON으로 변환
14) 템플릿 조각
웹 페이지 개발할 때는 공통영역(상단, 하단, 좌측 카테고리 등등)이 많기 때문에 이런 부분을 코드를 복사해서 사용하면 효율성이 떨어진다 그래서 타임리프는 이러한 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다.
[TemplateController.java]
@Controller
@RequestMapping("/template")
public class TemplateController {
@GetMapping("/fragment")
public String template() {
return "template/fragment/fragmentMain";
}
}
[/resources/templates/template/fragment/footer.html]
<!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가 있는 태그는 다른 곳에 포함되는 코드 조각으로 이해하면 쉽다.
[/resources/templates/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>
template/fragment/footer :: copy 는 th:fragment="copy"라는 부분을 템플릿 조각으로 가져와서 사용한다는 의미이다.
<footer th:fragment="copy">
푸터 자리 입니다.
</footer>
- 부분 포함 insert
- th:insert를 사용하면 현재 태그(div) 내부에 추가한다.
- 부분 포함 replace
- th:replace를 사용하면 현재 태그(div)를 대체한다.
- 부분 포함 단순 표현식
- ~{...}가 원칙이긴 하나 코드가 단순하면 생략 가능하다.
15) 템플릿 레이아웃 1
16) 템플릿 레이아웃 2
귀찮아서 못쓰겠음