서버 사이드 HTML 렌더링 (SSR)
타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용
네츄럴 템플릿
타임리프는 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있음
타임리프로 작성한 파일은 HTML을 유지하기 때문에 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있음
타임리프로 작성된 파일은 해당 파일을 그대로 웹 브라우저에서 열어도 정상적인 HTML 결과를 확인할 수 있음 > 이 경우 동적으로 결과가 렌더링 되지는 않지만 HTML 마크업 결과가 어떻게 되는지 파일만 열어도 바로 확인 가능
스프링 통합 지원
타임리프는 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원
타임리프 사용 선언
<html xmlns:th="http://www.thymeleaf.org">
<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
</ul>
"Hello <b>Spring!</b>"
로 <b>
태그를 사용해서 Spring! 단어가 진하게 나오게 하고 싶을 때
Hello <b>Spring!</b>
Hello <b>Spring!</b>
<
부분이 <
로 변경되어 화면에 문자 그대로 출력됨 > HTML 엔티티
HTML 엔티티
<
를 태그의 시작이 아니라 문자로 표현할 수 있는 방법이스케이프 (Escape)
언이스케이프 (Unescape)
<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>
기본으로는 항상 escape 처리를 해야하고 필요할때만 unescape를 사용
타임리프에서 변수를 사용할 때는 변수 표현식을 사용 ${...}
변수 표현식에는 스프링 EL(스프링이 제공하는 표현식)을 사용할 수 있음
<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
타임리프는 기본 객체들을 제공
자주 사용하는 것들은 thymeleaf가 사용하기 편리하게 제공
필요할 때 메뉴얼에서 찾아서 사용하기
날짜 사용 예시
<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>
<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>
컨트롤러에서 받은 localDateTime을 원하는대로 형식을 바꾸어 사용
타임리프에서 URL을 생성할 때는 @{...}
사용
단순한 URL
@{/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
경로를 만드는 부분과 데이터가 있는 부분이 분리되어있어 유지보수 하기 좋음
리터럴 : 소스 코드상에서 고정된 값 (문자, 숫자, 불린, null)
문자
<span th:text="'hello'">
<span th:text="hello"
<span th:text="hello world!"></span>
> 중간에 공백이 있어서 오류<span th:text="'hello world!'"></span>
> ' 로 감싸면 정상 동작<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>
리터럴 대체 문법을 쓰면 한번에 깔끔하게 변수를 넣을 수 있음
<span th:text="|hello ${data}|">
HTML안에서 사용하기 때문에 HTML 엔티티를 사용하는 부분만 주의하면 됨
<span th:text="${data}?: '데이터가 없습니다.'"></span>
_
인 경우 마치 타임리프가 실행되지 않는 것 처럼 동작<span th:text="${nullData}?: _">데이터가 없습니다.</span>
속성 설정
<h1>속성 설정</h1>
<input type="text" name="mock" th:name="userA" />
<input type="text" 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/>
checked 처리
<input type="checkbox" name="active" checked="false" />
> 이 경우에도 checked 속성이 있기 때문에 checked 처리가 되어버림<input type="checkbox" name="active" th:checked="false" />
<input type="checkbox" name="active" />
타임리프에서 반복은 th:each 를 사용, 추가로 반복에서 사용할 수 있는 여러 상태 값을 지원
반복 기능
<tr th:each="user : ${users}">
<tr th:each="user : ${users}">
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
</tr>
반복 상태 유지
<tr th:each="user, userStat : ${users}">
<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>
<td th:switch="${user.age}">
<span th:case="10">10살</span>
<span th:case="20">20살</span>
<span th:case="*">기타</span>
</td>
<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span>
-->
표준 HTML 주석
자바스크립트의 표준 HTML 주석은 타임리프가 렌더링 하지 않고, 그대로 남겨둠
<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]] 한 줄일 때*/-->
<!--/*-->
<span th:text="${data}">html data</span> 여러줄일 때
<!--*/-->
타임리프 파서 주석
타임리프의 진짜 주석, 렌더링에서 주석 부분을 제거
페이지 소스 보기를 해도 보여지지 않음
<h1>3. 타임리프 프로토타입 주석</h1>
<!--/*/
<span th:text="${data}">html data</span>
/*/-->
타임리프 프로토타입 주석
HTML 주석에 약간의 구문을 더한것
HTML 파일을 그대로 열어보면 주석처리가 되지만, 타임리프를 렌더링 한 경우에만
보이는 기능
<th:block>
은 HTML 태그가 아닌 타임리프의 유일한 자체 태그<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>
<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}]];
</script>
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
template/fragment/footer.html
템플릿에 있는 th:fragment="copy"
라는 부분을 템플릿 조각으로 가져와서 사용한다는 의미부분 포함 insert
<div th:insert="~{template/fragment/footer :: copy}"></div>
태그를 유지한 상태로 안에 가져와서 넣음, 현재 태그 내부에 추가
부분 포함 replace
<div th:replace="~{template/fragment/footer :: copy}"></div>
태그 자체를 가져온 것으로 교체, 현재 태그를 대체
부분 포함 단순 표현식
<div th:replace="template/fragment/footer :: copy"></div>
템플릿 조각을 사용하는 코드가 단순하면 ~{..} 부분 생략 가능
파라미터 사용
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div></body>
footer.html 의 copyParam 부분
<footer th:fragment="copyParam (param1, param2)">
<p>파라미터 자리 입니다.</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
파라미터를 전달해서 동적으로 조각을 렌더링 할 수도 있음
<head>
정도에만 적용하는게 아니라 <html>
전체에 적용할 수도 있음layoutMain.html
<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>
::title
은 현재 페이지의 title 태그들을 template/layout/base 파일의 common_header에 전달::link
는 현재 페이지의 link 태그들을 전달base.html
<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>