본문 바로가기
MVC with 김영한

타임리프(thymeleaf)

by wonseok99 2021. 11. 15.

[ 타임리프의 특징 ]

  1. 서브 사이트 HTML 렌더링(SSR)
    백엔드 서버에서 HTML 동적으로 렌더링하는 용도이다.
  2. 내츄럴 템플릿
    타임리프는 순수 HTML을 최대한 유지하는 특징이 있다! 따라서 타임리프로 작성한 파일은 웹 브라우저에서 파일을 직접 열어도
    내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 걸치면 동적으로 변경된 결과를 확인할 수 있다.
    이렇게 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 특징을 네츄럴 템플릿이라고 한다.

  3. 스프링 통합지원
    타임리프는 스프링과 자연스럽게 통합되어 편리한 기능들을 제공한다.

 

[ 타임리프의 기본 기능 ]

타임리프 사용 선언:
<html xmlns:th="http://www.thymeleaf.org">

 

 

텍스트 출력

타임리프는 기본적으로 HTML태그의 속성에 기능을 정의하여 동작한다. HTML의 콘텐츠의 데이터를 출력할 때는
th:text 를 사용한다.

<span th:text="${data}">

- Escape
웹 브라우저는 "<, >"을 HTML 태그의 시작과 끝으로 인식한다. 따라서 "<"를 문자로 표현하는 방법이 필요한데, 이러한 표현 방식을 HTML 엔티티라고 한다.

그리고 특수문자를 HTML 엔티티로 변경하는것을 Escpae라고 한다.
예를들어, text를 강조하고 싶은데 <b></b> 구문이 Escape 기능을 통해 다른 문자로 변환된다.
ex) "<" → "&lt;"

타임리프가 제공하는 th:text, [[...]]는 기본적으로 Escape가 제공된다. (실제 태그를 렌더링하지 않는다)

- Unsecape
렌더링 된 텍스트, Escape기능을 사용하지 않으려면!
th:text -> th:utexet
[[...]] [(...)]

 

 

변수 출력

타임리프에서 변수를 사용할 때는 변수 표현식 ${...} 을 사용한다.

프로퍼티 접근 : 객체내의 속성에 접근

- Object

Object.username : object의 username을 프로퍼티 접근 → object.getUsername(); //  
Object['username'] → object.getUsername();
Object.getUsername → object의 getUsername()을 직접 호출

- List
List[0].username : List에서 0번째 index의 회원을 찾고 username 프로퍼티 접근 → list.get(0).getUsername()

- Map
Map['userA'].username : Map에서 Key값 'userA'를 찾아 username 프로퍼티 접근 → map.get("userA").getUsername()

- 지역변수 선언
th:with 를 사용하면 지역 변수를 선언할 수 있다. 지역변수는 선언한 태그내에서만 유효하다.
ex) th:with = "localvariable = ${user[0]}"  //  th:text = "localvariable.username"

 

 

URL 링크

타임리프에서 URL 링크를 사용할 때는 @{...} 문법을 사용한다.

- 단순한 URL

@{/hello} → /hello

 

- 쿼리 파라미터

@{/hello(param1=${param1}, param2=${param2})}
→ /hello?param1=data1&param2=data2

( ... ) 안에 있는 부분은 쿼리 파라미터로 처리된다.

 

- 경로 변수

@{/hello/{param1}(param1=${param1}, param2=${param2})}
→ /hello/data1?param2=data2

경로 변수(data1) 와 쿼리 파라미터를 함께 사용할 수 있다.

 

 

리터럴

소스코드상에 고정된 값을 말하는 용어이다.

  • 문자 : 'hello'
  • 숫자 : 10
  • 불린 : true, false
  • null : null

타임리프에서 문자 리터럴은 항상 '(작은 따옴표)로 감싸야 한다.
리터럴이 공백없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지하여 작은 따옴표를 생략할 수 있다.

- 오류 케이스

<span th:text="hello world!"></span>

문자 리터럴은 원칙상 '로 감싸야 하지만 중간의 공백때문에 하나의 의미있는 토큰으로 인식되지 않는다.

 

<span th:text="'hello world!'"></span>

'로 감싸게되면 오류해결

 

- 예제 케이스

<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>

| 를 사용하면 리터럴을 대체하여 사용할 수 있다.

 

 

연산


- 비교연산

<li>1 > 10 = <span th:text="1 &gt; 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 == 1 = <span th:text="1 == 10"></span></li>
<li>1 != 1 = <span th:text="1 != 10"></span></li>

HTML엔티티를 사용해야 하는 부분을 주의하자. 
ex) th:utext 혹은 [(...)] Unescape문을 사용하면 "<", ">" 기호를 사용할 수 있다.

 

- Elvis 연산자

<li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가없습니다.'"></span></li>
<li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?:'데이터가 없습니다.'"></span></li>

${ ... }? : " ... " 인 경우에 ${ ... }가 null이라면  :의 오른쪽을 출력. 아니라면 왼쪽을 출력

 

- No-Operation

<li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다.</span></li>
<li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li>

"_" 인 경우에는 마치 타임리프가 실행되지 않는 것 처럼 동작시킨다. (렌더링 X)

 

 

- 속성 값 설정

타임리프는 주로 HTML 태그에 th:* 속성을 지정하는 방식으로 동작한다. th:* 로 속성을 적용하면 기존 속성을 대체한다.
기존 속성이 없으면 새로 만든다.

 

- 속성 설정

<input type="text" name="mock" th:name="userA" />
→ <input type="text" name="userA" />

th:* 속성을 지정하면 타임리프는 기존 속성을 th:* 로 지정한 속성으로 대체한다.
기존 속성이 없다면 새로 만든다.

 

- 속성 추가

- 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/>

th:attrappend : 속성 값의 뒤에 값을 추가한다.
th:attrprepend : 속성 값의 앞에 값을 추가한다.
th:classappend : class 속성에 자연스럽게 추가한다.

"class= ' large'" →  class에 속성 값에 따라 ' large'를 추가한다.

 

- checked 처리

- 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/>

HTML은 checked 속성만 있어도 속성 값이 체크된다.

<input type="checkbox" name="active" th:checked="false" />
→ <input type="checkbox" name="active" />

타임리프의 th:checked 는 값이 false인 경우 checked 속성 자체를 제거한다.

 

 

반복

타임리프에서 반복은 th:each를 사용한다. 추가로 반복에서 사용할 수 있는 여러 상태 값을 지원한다.

 

- 반복 기능

<tr th:each="user : ${users}">

반복시에 오른쪽 컬렉션 ${users} 의 값을 하나씩 꺼내서 왼쪽 변수 user에 담아 태그를 반복 실행한다.

* th:each List 뿐만 아니라 배열, java.util.Iterable , java.util.Enumeration 을 구현한 모든 객체를 반복에 사용할 수 있다. Map 도 사용할 수 있는데 이 경우 변수에 담기는 값은 Map.Entry 이다.

 

- 반복 상태 유지

<tr th:each="user, userStat : ${users}">

반복의 두번째 파라미터를 설정하여 반복의 상태를 확인할 수 있다.
두번째 파라미터는 생략 가능한데, 생략하면 자동으로 지정한 변수면 첫 번째 파라미터(user) + Stat으로 설정이 된다.

 

- 반복 상태 유지 기능

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>

- index : 0부터 시작하는 값
- count : 1부터 시작하는 값
- size : 전체 사이즈
- even , odd : 홀수, 짝수 여부( boolean )
- first , last :처음, 마지막 여부( boolean )
- current : 현재 객체

 

 

 

조건부 평가

타임리프 조건식 if, unless(if의 반대)

 

- if, unless

<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>

타임리프는 if의 해당 조건을 만족하지 못하면 태그 자체를 렌더링 하지 않는다.
다음 조건이 false인 경우 <span>...</span> 자체가 렌더링 되지않고 사라진다.

 

- switch

<td th:switch="${user.age}">
	<span th:case="10">10살</span> 
	<span th:case="20">20살</span> 
	<span th:case="*">기타</span>

* 는 디폴트 값이다. (만족하는 조건이 없을 때)

 

 

주석

 

1. 표준 HTML 주석

<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span> 
-->

자바스크립트의 표준 HTML 주석은 타임리프가 렌더링 하지 않고, 그대로 남겨둔다.

 

2. 타임리프 파서 주석

<h1>2. 타임리프 파서 주석</h1> 
<!--/* [[${data}]] */-->

<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->

타임리프 파서 주석은 타임리프의 진짜 주석이다. 렌더링에서 주석 부분을 제거한다. (렌더링 자체를 하지 않는다)

 

3. 타임리프 프로토타입 주석

<h1>3. 타임리프 프로토타입 주석</h1>

<!--/*/
<span th:text="${data}">html data</span> 
/*/-->

해당 HTML 파일을 웹 브라우저에서 그대로 열어보면 이 부분을 렌더링 하지 않고 주석으로 표현한다.
하지만 타임리프 렌더링을 거치게되면 이 부분이 정상적으로 렌더링 된다.

한마디로 HTML을 그대로 열어보면 주석처리, 타임리프 렌더링을 거치면 정상적으로 렌더링

 

 

자바스크립트 인라인

타임리프는 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바 스크립트 인라인 기능을 제공한다.

<script th:inline="javascript">

 

자바스크립트 인라인을 사용했을 때 해결되는 문제들!

 

 

- 텍스트 렌더링

var username = [[${user.username}]];
인라인 사용 전 → var username = userA;
인라인 사용 후 → var username = "userA";

인라인 사용 후 렌더링 결과를 보면 문자 타입인 경우 "를 포함해준다. 사용 전은 userA라는 변수 이름을 그대로 가져온다.
추가로 JS에서 문제가 될 수 있는 문자가 포함되어 있으면 이스케이프 처리도 해준다. 
ex) "\"A\""  

 

- 자바스크립트 내추럴 템플릿

var username2 = /*[[${user.username}]]*/ "test username";
인라인 사용 전 → var username2 = /*userA*/ "test username";
인라인 사용 후 → var username2 = "userA";

인라인 사용 전 결과는 순수하게 그대로 해석하여 내추럴 템플릿 기능이 동작하지 않으며 ("test usernmae"을 그대로 출력한다)

 

- 객체

var user = [[${user}]];
인라인 사용 전 → var user = BasicController.User(username=userA, age=10);
인라인 사용 후 → var user = {"username":"userA","age":10};

인라인 사용 전 결과는 객체의 toString()을 호출하지만, 인라인 후에는 객체를 JSON 형태로 변환하여준다.

 

 

템플릿 조각

Java에서 필요한 기능이 공통되는 부분을 메소드로 따로 정의하여 부분적으로 사용하듯이, 웹 페이지를 개발할 때에도 공통 영역이 많다.
<Header>, <Footer> 등등..
타임리프에서는 이런 문제를 해결하기 위해 템플릿 조각레이아웃 기능을 지원한다.


- ~/template/frament/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>

th:fragment가 있는 태그는 다른곳에 포함되는 코드의 조각이다.

 

 

- insert, replace, Parameter

<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>

th:insert 를 사용하면 현재 태그(div) 내부에 해당 코드조각을 추가한다.

 

<h2>부분 포함 단순 표현식</h2>
<div th:replace="template/fragment/footer :: copy"></div>

th:replace 를 사용하면 현재 태그(div)를 대처한다.
또한 부분 포함은 ~{ ... } 를 사용하는것이 원칙이지만  템플릿 조각을 사용하는 코드가 단순하면 이를 생략하여 실행할 수 있다.

 

<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터 2')}"></div>

파라미터를 전달하여 동적으로 코드 조각을 렌더링 할 수도 있다.

 

 

템플릿 레이아웃

이전의 템플릿 조각을 가지고 사용을 했다면, 개념을 더 확장하여 코드 조각을 레이아웃에 넘겨 사용하는 방법이 있다.
예를들어 <head>에 공통으로 사용하는 정보들을 한 곳에 모아두고, 각 페이지마다 필요한 정보를 더 추가해서 사용할 수 있다.

한마디로 Base가 되는 메인 레이아웃을 두고, 그 레이아웃에 필요한 코드 조각을 전달한다.

 

- Base

<html xmlns:th="http://www.thymeleaf.org">
<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>

 

 

- ~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>

th:replace를 사용하여 template/layout/base로 <head>를 대체한다.
[:: title] 은 현재 페이지의 title 태그를, [:: link] 는 현재 페이지의 link 태그들을 전달한다.

 

 

<!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>

레이아웃 확장 개념을 <head> 정도에만 적용하는게 아니라 <html> 전체에 적용할 수도 있다.
위와 같이 <html> 태그에 th:framgent 속성이 정이 되어있다. 이 레이아웃 파일을 기본으로 하되 본이이 필요한 내용이나 태그만 부분적으로 변경하여 사용하는 것이다.

 

상황에 따라 부분적으로 태그만 변경할 수 있고, layout 전체를 fragment로 활용하여 변경할 수도 있다.

 

 

 

타임리프의 입력폼 처리

 

- 커맨드 객체 지정

th:object

*{ ... } 선택변수 식을 활용하여 th:object 에서 선택한 객체에 접근한다.



- th:field

[Befor rendering]
<input type="text" th:field="*{itemName}" />

[After rendering]
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />

HTML 태그의 'id', 'name', 'value'를 자동으로 처리해준다.

 

 

- th:value

[Before rendering]
<input type="checkbox" th:field="*{regions}" th:value="${region.key}" class="form-check-input">
 
[After Check and Rendering]
<input type="checkbox" value="SEOUL"class="form-check-input" disabled id="regions1" name="regions"
checked="checked">
<input type="hidden" name="_regions" value="on"/>

체크박스의 th:field 값과 th:value 값을 비교하여 같다면 자동으로 checked 설정을 해준다

 

 

 

타임리프 체크박스

checked 와 hidden field(_open) 을 알아서 다 처리해준다!

'MVC with 김영한' 카테고리의 다른 글

Bean Validation  (0) 2021.12.14
Validation  (0) 2021.12.06
요청 매핑 핸들러 어뎁터  (0) 2021.10.22
HTTP 요청과 응답  (0) 2021.10.20
로깅(logging)  (0) 2021.10.17