Home 타임리프 기본기능
Post
Cancel

타임리프 기본기능

타임리프 소개

타임리프 공식사이트 기능 사용 docs

  • 서버 사이드 HTML 렌더링 (SSR)
    • 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용된다.
  • 네츄럴 템플릿
    • 순수 HTML을 최대한 유지한다.
    • HTML을 유지하기 때문에 브라우저에서 파일을 직접 열어도 되고, 서버를 통해 뷰템플릿을 거쳐 동적으로 변경된 결과를 얻을 수도 있다.
    • 반례로 JSP를 예시로 들면 브라우저에서 파일을 열면 JSP코드와 HTML이 뒤죽박죽 섞여 정상적인 결과를 볼 수 없다.
    • 이렇게 순수 HTML을 유지하면서 템플릿도 사용할 수 있는 것을 네츄럴 템플릿이라고 한다.
  • 스프링 통합 지원
    • 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다.

타임리프 기본 기능

타임리프의 사용선언

1
<html xmlns:th="http://www.thymeleaf.org">

기본 표현식

텍스트 - text, utext

1
th:text

텍스트를 출력하는 기능을 보자.

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

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

따로 태그가 아닌 컨텐츠 내에서 직접 출력하려면 아래와 같이 표현할 수도 있다. ‘[[ ]]’ 를 사용하면 된다.

1
2
<li>th:text 사용 버전 <span th:text="${data}"></span></li>
<li>직접 출력 = [[${data}]]</li>

Escape

HTML 문서는 <, > 같은 특수 문자를 기반으로 정의된다. 따라서 뷰 템플리승로 HTML 화면을 생성할 때는 출력하는 데이터에 이러한 특수문자가 있는 것을 주의해서 사용해야 한다.

1
"Hello <b>Spring!</b>"

를 model에 담아 보낸다고 하자. 목적은 Spring! 이라는 글자를 진하게 나오도록 하기 위함이다.

웹 브라우저에서는 진하게 나오지 않고 저 문자 그대로가 나오게 된다.

HTML 엔티티

웹 브라우저는 < 를 태그의 시작으로 인식하는데 태그의 시작이 아닌 문자로 표현할 수 있는 방법이 필요하다. 이것이 HTML 엔티티이다.

그리고 이렇게 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 escape 라고 한다.

타임리프가 제공하는 th:text, [[…]] 는 기본적으로 escape를 제공한다.

ex)

  • ’<’ : &lt
  • ’>’ : &gt

unescape

이러한 escape 기능을 사용하지 않으려면

1
<li><span th:inline="none">[[...]] = </span>[[${data}]]</li>

태그 안에서는 타임리프의 해석을 사용하지 않겠다는 의미이다.

** unescape는 정말 필수적일 때만 사용해야 한다.

변수 - SpringEL

1
${...}

model로 아래와 같이 넘겼다고 하자.

  • user : user객체 하나
  • users : user을 담은 리스트
  • userMap : user을 담은 맵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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>

위와 같은 방법들로 접근할 수 있다. 메서드를 호출할 수도 있고, 배열에 접근하듯 접근할 수도 있고 ‘.’으로 접근할 수도 있다.

지역 변수 선언

1
th:with

‘th:with’ 를 사용하면 변수처럼 선언해 사용할 수 있다.

1
<div th:with="first=${users[0]}">

이렇게 하면 div 태그 내에서는 users[0] 유저를 first라는 이름으로 사용할 수 있다.

편의 객체

  • HTTP 요청 파라미터 접근 : param
  • HTTP 세션 접근 : session
  • 스프링 빈 접근 : @
1
2
3
4
${param.paramData}
${session.sessionData}
${@helloBean.hello('Spring!')}
//hello는 빈 내부의 메서드 (호출 가능)

유틸리티 객체와 날짜

유틸리티 객체들은 아래와 같다.

  • #message : 메시지, 국제화 처리
  • #uris : URI 이스케이프 지원
  • #dates : java.util.Date 서식 지원
  • #calendars : java.tuil.Calendar 서식 지원
  • #temporals : 자바8 날짜 서식 지원
  • #numbers : 숫자 서식 지원
  • #strings : 문자 관련 편의 기능
  • #objects : 객체 관련 기능 제공
  • #bools : boolean 관련 기능 제공
  • #arrays : 배열 관련 기능 제공
  • #lists, #sets, #maps : 컬렉션 관련 기능 제공
  • #ids : 아이디 처리 관련 기능 제공

유틸리티 객체들은 대략 이런 것들이 있다고 알아두고 필요할 때 찾아 쓰면 된다.

유틸리티 객체 예시

하나만 예시를 들어본다.

1
2
<li>default = <span th:text="{localDataTime}"></span></li>
<li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDataTime, 'yyyy-MM-dd HH:mm:ss')}"</span></li>

URL 링크

1
@{...} 를 사용한다.
1
2
3
4
5
6
7
8
//hello?param1=data1&param2=data2
<a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a>

//hello/data1/data2
<a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a>

//hello/data1?param2=data2
<a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a>
  • ( )에 있는 부분은 쿼리파라미터로 처리
  • 경로상 ()에 있는 해당 변수가 있으면 경로 변수로 처리

참고

  • /hello : 절대 경로
  • hello : 상대 경로
    • 기존에 위치해있던 경로에서 뒤에 추가적으로 붙는 경로

리터럴

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

예를 들어 아래 코드에서 “Hello”는 문자리터럴, ‘10’, ‘20’은 숫자 리터럴이다.

1
2
String a = "Hello"
int a = 10 * 20

쉽지만 실수하는 경우가 많다.

타임리프에서 문자 리터럴은 항상 작은 따옴표로 감싸야 한다

1
2
<li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>  
<li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
하나하나 감싸거나 리터럴 대체를 사용해주어야 한다.

물론 text에 hello 하나만 쓸 경우 감싸지 않고 사용해도 허용해준다.

하지만 hello 뒤에 공백만 붙어도 따옴표로 감싸주어야된다.

“hello world”는 오류, “hello”는 허용

연산

자바와 크게 다르지않지만 HTML 엔티티를 사용하는 부분을 주의해야한다.

1
2
3
4
5
6
7
th:text="10 + 2" //12
th:text="10 % 2 == 0" //true

th:text="1 &gt; 10" // 1 > 10
th:text="1 != 10" //true

th:text="${data} ?: '데이터가 없습니다.'" //삼항연산 축약 버전

속성 값 설정

1
th:*

속성을 적용하면 기존 속성을 대체하고 기존 속성이 없으면 새로 만든다.

1
2
3
4
5
// class가 abcd로 대체
<input type="text" name="text" th:class="abcd">

//없으면 새로 만든다. (name=true) 속성 생성
<input type="checkbox" th:name="true">

속성을 추가할 수도 있다.

1
<input type="text" class="text" th:classappend="large">
  • th:classappend : class 속성에 자연스럽게 추가한다.

다른 추가 방법도 있지만 필요할 때 찾아쓰도록 한다.

checked 처리

타입이 체크박스인 상태에서 HTML은 checked 속성이 있으면 true이든 false이든 값에 상관없이 checked 처리해버린다. checked=”false” 여도 체크한다.

타임리프는 th:checked=”false”로 두면 checked 속성을 없애는 식으로 처리해 true, false 값을 이용할 수 있도록 해준다.

반복

1
th:each
1
2
3
4
<tr th:each="user : ${uesrs}">
	<td th:text="${user.username}"></td>
	<td th:text="${user.age}"></td>
</tr>

현재 반복의 상태를 알 수 있는 기능도 지원한다.

1
2
3
4
<tr th:each="user, userStat : ${users}">
	<td th:text="${userStat.count}"></td>
	<td th:text="${uesrStat.index}"></td>
</tr>

두 번째 파라미터를 설정해 현재 반복의 상태를 조회할 수 있다.

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

조건부 평가

if, unless(if의 반대)

1
2
3
th:if
th:unless
th:switch
1
2
th:if="${user.age lt 20}
th:unless="${user.age gt 10}
1
2
3
4
5
<td th:switch="${user.age}">
	<span th:case="10">10살</span>
	<span th:case="20">20살</span>
	<span th:case="*">기타</span>
</td>	

주석

표준 HTML 주석

1
2
3
<!--

-->

타임리프 파서 주석 (아예 주석처리)

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

타임리프 프로토타입 주석

1
2
3
4
5
<!--/*/
<span th:text="${data}">html data</span>
/*/-->

<span>Spring!</span> 이 남음

프로토타입 주석은 거의 사용하지 않지만 일단 기능 설명을 하면 타임리프 렌더링을 거치면 정상 렌더링이 되고, HTML 파일을 그대로 열어 보여주면 주석처리 되는 기능이다.

블록

1
th:block

HTML 태그가 아닌 타임리프의 유일한 자체태그이다.

1
2
3
4
5
6
7
8
9
<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>

위는 div 태그를 두 개 다 한번에 반복문 돌리고 싶은 경우이다.

만약 위쪽 div 태그에만 반복문을 설정했을 경우 당연히 위쪽에만 반복문이 돌게 된다. 아래는 오류가 발생할 것이다.

즉 어떠한 조건을 걸기 위해 그 부분을 하나의 블록으로 만들어주는 기능이다.

(안 쓰는 것이 좋지만 어쩔 수 없는 경우 사용한다.)

자바스크립트 인라인

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

1
<script th:inline="javascript">

아래와 같은 코드가 있다고 가정하자.

1
2
3
4
5
6
7
8
9
10
<script th:inline="javascript">  
	var username = [[${user.username}]];  
	var age = [[${user.age}]];  
	
	//자바스크립트 내추럴 템플릿  
	var username2 = /*[[${user.username}]]*/ "test username";  
	
	//객체  
	var user = [[${user}]];  
</script>	
  • var username (String)
    • 인라인 사용 전 : = userA
    • 인라인 사용 후 : = “userA”
      • 사용하지 않으면 문자열이 아닌 변수 이름이 그대로 남는다. 따라서 자바스크립트 오류가 발생하게 된다.
      • 숫자인 age의 경우는 오류가 발생하지 않기는 하다.
  • var username2 (자바스크립트 내추럴 템플릿)
    • 인라인 사용 전 : = /* userA */ “test username”
    • 인라인 사용 후 : = “userA”
      • 순수하게 그대로 해석해버린다. 주석처리 하지 않는 내추럴 템플릿 기능이 동작하지 않는 결과가 나타난다.
      • 인라인 사용 후는 기대한 대로 주석처리가 되어 결과가 나온다.
  • var user (객체)
    • 인라인 사용 전 : = BasicController.User(username=userA, age=10);
    • 인라인 사용 후 : = [“username” : “userA”, “age” : “10”]
      • 사용 전에는 객체의 toString()이 호출된 값이다.
      • 사용 후에는 객체를 JSON으로 변환해준다.

자바스크립트 인라인의 each 지원

1
2
3
4
5
<script th:inline="javascript">  
	[# th:each="user, stat : ${users}"]  
	var user[[${stat.count}]] = [[${user}]];  
	[/]  
</script>

위와 같이 사용하면 된다.

user1 = user1 객체 user2 = user2 객체 가 들어가게 된다.

템플릿 조각

웹 페이지를 개발할 때는 각 페이지들이 모두 사용하는 공통 영역이 많다. (상단 메뉴 등등)

예를 들어 상단 영역이나 하단 영역, 좌측 카테고리 등등 여러 페이지에서 함께 사용하는 영역들이 있다.

이런 부분을 코드를 복사해 사용한다면 변경 시 여러 페이지를 다 수정해야하므로 상당히 비효율적이다.

타임리프는 이런 문제를 해결하기 위해 템플릿 조각과 템플릿 레이아웃 기능을 제공한다.

footer.html

fragementMain.html

footer.html 의 <footer th:fragment 로 되어있는 부분을 보자.

fragment의 이름을 설정할 수 있고,

다른 html에서 위와 같은 방식으로 가져다 쓸 수 있는 것이다. 즉 어떠한 부분을 메서드처럼 정의해서 가져다 쓸 수 있는 방식이다.

맨 아래의 copyParam은 위와 같이 매개변수를 쓰듯 사용할 수도 있다.

fragmentMain.html에서 매개변수를 넣고 그 매개변수를 바탕으로 원하는 HTML을 보여줄 수 있다.

템플릿 레이아웃

위에서는 일부 코드 조각을 가져와 사용했다면 이번에는 개념을 확장해 코드 조각을 레이아웃에 넘겨 사용하는 방법에 대해 알아본다.

예를 들어 head 태그에 공통으로 사용하는 css, javascript 같은 정보들이 있는데 이러한 공통 정보를 한 곳에 모아두고 공통으로 사용한다.

하지만 각 페이지마다 필요한 정보를 더 추가해 사용하고 싶을 수 있다. 아래와 같이 사용하면 된다.

base.html

layoutMain.html

1
(~{::title}, ~{::link})

이 부분은 위처럼 매개변수 값을 넣는 느낌이 아닌 title 태그, link 태그를 넣는 것이다.

그래서 base의 common_header 메서드(?)를 실행하면

  • title 태그는 매개변수로 전달된 부분으로 replace되는 것이고
  • 공통 부분은 그대로 유지되고
  • th:block 이 부분에 link 태그들이 추가되게 되는 것이다.

이러한 방식은 앞서 배운 템플릿 조각을 조금 더 적극적으로 사용하는 방식이다. 레이아웃 개념을 두고 그 레이아웃에 필요한 코드 조각을 전달해 완성하는 것으로 이해하면 된다.

템플릿 레이아웃의 확장

위의 개념을 헤드 태그 정도에만 적용하는 것이 아닌 html 전체에 적용할 수 있다.

layoutFile.html

layoutExtendMain.html

결과

Main에서는 사실상 태그 값들을 넘겨주는 역할이고,

레이아웃의 모양(양식)은 layoutFile.html에서 정한다.

공통된 양식을 100개를 쓴다고 하면 양식을 사용하는 각 페이지들이 값을 변경해야 할 때 값만 변경해주면 된다.

This post is licensed under CC BY 4.0 by the author.