서버 사이드 HTML 랜더링 (SSR)
내추럴 템플릿
순수 HTML을 최대한 유지하는 특징
: 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있고,
서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인 가능
JSP를 포함한 다른 뷰 템플릿
: 파일 자체를 그대로 웹 브라우저에서 열어보면 JSP 소스코드와 HTML이 섞여서 웹 브라우저에서 정상적인 HTML 결과를 확인 불가
스프링 통합 지원
사용 선언
<html xmlns:th="http://www.thymeleaf.org">
th:text
<span th:text="${data}">
[[${data}]]
HTML 문서는 < , > 같은 특수 문자를 기반으로 정의
예시로 "Hello <b>Spring!</b>"
를 실행하면 <
를 문자가 아닌 태그의 시작으로 인식한다.
태그가 아닌 문자로 표현할 수 있는 방법을 HTML 엔티티라 하고,
HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다.
타임리프가 제공하는 th:text , [[...]]
는 기본적으로 이스케이스(escape)를 제공
th:text 👉 th:utext
[[...]] 👉 [(...)]
${...}
Object
user.username
user['username']
user.getUsername()
List
users[0].username
list.get(0).getUsername()
users[0]['username']
users[0].getUsername()
Map
userMap['userA'].username
map.get("userA").getUsername()
userMap['userA']['username']
userMap['userA'].getUsername()
th:with
ex)
<<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
${#request}
${#response}
${#session}
${#servletContext}
${#locale}
편의 객체도 제공
HTTP 요청 파라미터 접근 : param
ex) ${param.paramData}
HTTP 세션 접근: session
ex) ${session.sessionData}
스프링 빈 접근: @
ex) ${@helloBean.hello('Spring!')}
@{...}
@{/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
프로토콜 기준 표현
/hello : 절대 경로
hello : 상대 경로
참고
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#link-urls
ex) 다음 코드에서 "Hello" 는 문자 리터럴, 10 , 20 는 숫자 리터럴이다.
String a = "Hello"
int a = 10 * 20
타임리프에는 다음과 같은 리터럴이 존재
문자: 'hello'
숫자: 10
불린: true , false
null: null
||
ex)
<span th:text="|hello ${data}|">
ex)
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
👇
<span th:text="|Welcome to our application, ${user.name}!|">
HTML안에서 사용하기 때문에 HTML 엔티티 사용
<span th:text="10 + 2"></span>
<span th:text="10 % 2 == 0"></span>
<span th:text="1 > 10"></span>
<li>1 gt 10 = <span th:text="1 gt 10"></span>
>
gt
<
lt
>=
ge
<=
le
!
not
==
eq
!=
neq, ne
조건식 1
th:if="${param.status}"
해당 조건이 참이면 실행
거짓이면 태그 자체를 렌더링하지 않아 사라짐
조건식 2
자바 조건식과 유사
<span th:text="(10 % 2 == 0)? '짝수':'홀수'"></span>
Elvis 연산자
조건식의 편의 버전
<span th:text="${data}?: '데이터가 없습니다.'"></span>
No-Operation
_
인 경우 마치 타임리프가 실행되지 않는 것 처럼 동작
<span th:text="${data}?: _">데이터가 없습니다.</span>
th:*
th:attrappend
: 속성 값 뒤에 값을 추가
th:attrprepend
: 속성 값 앞에 값을 추가
th:classappend
: class 속성에 추가
th:each
<tr th:each="user : ${users}">
ex)
<tr th:each="user : ${users}">
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
</tr>
items 컬렉션 데이터가 item 변수에 하나씩 포함 ,
반복문 안에서 item 변수 사용 가능
List 뿐만 아니라 배열, java.util.Iterable , java.util.Enumeration을 구현한 모든 객체를 반복에 사용 가능
Map의 경우 변수에 담기는 값은 Map.Entry
<tr th:each="user, userStat : ${users}">
지정한 변수명 + Stat
반복 상태 유지 기능
index : 0부터 시작하는 값
count : 1부터 시작하는 값
size : 전체 사이즈
even , odd : 홀수, 짝수 여부( boolean )
first , last : 처음, 마지막 여부( boolean )
current : 현재 객체
#message
: 메시지, 국제화 처리
#uris
: URI 이스케이프 지원
#dates
: java.util.Date 서식 지원
#calendars
: java.util.Calendar 서식 지원
#temporals
: 자바8 날짜 서식 지원
#numbers
: 숫자 서식 지원
#strings
: 문자 관련 편의 기능
#objects
: 객체 관련 기능 제공
#bools
: boolean 관련 기능 제공
#arrays
: 배열 관련 기능 제공
#lists
, #sets
, #maps
: 컬렉션 관련 기능 제공
#ids
: 아이디 처리 관련 기능 제공, 뒤에서 설명
자바8의 LocalData, LocalDateTime , Instant 사용시 추가 라이브러리 필요,
스프링 부트 타임리프 사용시에는 해당 라이브러리 자동으로 추가, 통합
thymeleaf-extras-java8time
자바8 날짜용 유틸리티 객체 : #temporals
ex)
<span th:text="${#temporals.format(localDateTime, 'yyyy-MM-dd HH:mm:ss')}"></
span>
🔎
타임리프 유틸리티 객체
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expression-utilityobjects
유틸리티 객체 예시
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-b-expressionutility-objects
타임리프가 렌더링 하지 않고, 그대로 남겨둔다
<!-- -->
타임리프의 주석으로, 렌더링에서 주석 부분을 제거
<!--/* */-->
HTML 파일을 그대로 열어보면 렌더링하지 않고(주석처리)
타임리프를 렌더링을 거치면 정상 렌더링 된다(주석처리 X)
<!--/*/ /*/-->
<th:block>
ex)
<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:inline="none"
<script th:inline="javascript">
자바스크립트에서 타임리프 사용 가능
var username = [[${user.username}]];
인라인 사용 전 -> var username = userA;
인라인 사용 후 -> var username = "userA";
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);
인라인 사용 후 -> var user = {"username":"userA","age":10};
사용 전은 객체의 toString() 호출 값이다
[# th:each ]
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
th:fragment=""
th:insert="~{ :: }"
th:fragment=""
부분을 th:insert="~{ :: }"
으로 가져와서 사용ex)
🔎 /resources/templates/template/fragment/footer.html
<footer th:fragment="copy">
가져가서 사용되는 부분 1
</footer>
<footer th:fragment="copyParam (param1, param2)">
<p>가져가서 사용되는 부분 2</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
🔎 /resources/templates/template/fragment/fragmentMain.html
<div th:insert="~{template/fragment/footer :: copy}">
</div>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터
2')}">
</div>
th:insert
: 현재 태그 내부에 추가
th:replace
: 현재 태그를 대체
th:fragment="common_header(title,links)"
으로
th:replace="template/layout/base :: common_header(~{::title},~{::link})"
의 title, link 태그를 전달
ex)
🔎 공통파일 template/layout/base.html
<head th:fragment="common_header(title,links)">
<!-- 추가 -->
<title th:replace="${title}">레이아웃 타이틀</title>
<!-- 공통 -->
///
<!-- 추가 -->
<th:block th:replace="${links}" />
</head>
🔎 교체할파일 template/layout/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>