[Thymeleaf] 타임리프 기본 기능

hi·2022년 9월 13일
0

특징

서버 사이드 HTML 랜더링 (SSR)

  • 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용

내추럴 템플릿

  • 순수 HTML을 최대한 유지하는 특징
    : 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있고,
    서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인 가능

  • JSP를 포함한 다른 뷰 템플릿
    : 파일 자체를 그대로 웹 브라우저에서 열어보면 JSP 소스코드와 HTML이 섞여서 웹 브라우저에서 정상적인 HTML 결과를 확인 불가

스프링 통합 지원

  • 스프링과 자연스럽게 통합된다
  • 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원

기본 기능

사용 선언

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


텍스트 - text , utext

th:text

  • HTML 태그 속성 내 출력 : <span th:text="${data}">
  • HTML 콘텐츠 영역안에서 직접 데이터 출력 : [[${data}]]

Escape

  • HTML 문서는 < , > 같은 특수 문자를 기반으로 정의
    예시로 "Hello <b>Spring!</b>" 를 실행하면 <를 문자가 아닌 태그의 시작으로 인식한다.

  • 태그가 아닌 문자로 표현할 수 있는 방법을 HTML 엔티티라 하고,
    HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다.

  • 타임리프가 제공하는 th:text , [[...]]기본적으로 이스케이스(escape)를 제공

Unescape

  • 이스케이프 기능을 사용하지 않으려면 아래와 같이 사용
  • 실무에서는 escape를 사용하지 않아 많은 문제가 발생한다.
    꼭 필요한 경우에만 unescape 사용
th:text 👉 th:utext
[[...]] 👉 [(...)]

변수 표현식

${...}

  • 변수 표현식에는 스프링이 제공하는 표현식 SpringEL 사용 가능

SpringEL 표현식

  • 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!')}


URL 링크

@{...}

쿼리 파라미터

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

  • ( ) 에 있는 부분은 쿼리 파라미터로 처리

경로 변수

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

  • URL 경로상에 변수가 있으면 () 부분은 경로 변수로 처리

경로 변수 + 쿼리 파라미터

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

프로토콜 기준 표현

/hello : 절대 경로
hello : 상대 경로

참고
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#link-urls


Literals 리터럴

  • 리터럴 : 소스 코드상에 고정된 값을 말하는 용어

ex) 다음 코드에서 "Hello" 는 문자 리터럴, 10 , 20 는 숫자 리터럴이다.

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

타임리프에는 다음과 같은 리터럴이 존재

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

  • 문자 리터럴은 항상 '' 작은 따옴표로 감싸야 한다
    but , 공백 없이 쭉 이어진다면 따옴표 생략 가능

리터럴 대체

||

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 &gt; 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:* 로 지정한 속성으로 대체
  • HTML을 그대로 볼 경우 기존 속성이 사용되고,
    뷰 템플릿을 거치면 th:* 의 값으로 대체되어 동적으로 변경

속성 추가

th:attrappend : 속성 값 뒤에 값을 추가
th:attrprepend : 속성 값 앞에 값을 추가
th:classappend : class 속성에 추가

checked 처리

  • HTML에서는 속성 값에 상관없이 checked 처리가 됨
  • 타임리프는 th:checked 는 값이 false 인 경우 checked 속성 자체를 제거

반복

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 : 현재 객체


유틸리티 객체와 날짜

  • 문자, 숫자, 날짜, URI등을 편리하게 다루는 다양한 유틸리티 객체들을 제공

#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 주석

타임리프가 렌더링 하지 않고, 그대로 남겨둔다
<!-- -->

타임리프 파서 주석

타임리프의 주석으로, 렌더링에서 주석 부분을 제거
<!--/* */-->

타임리프 프로토타입 주석

HTML 파일을 그대로 열어보면 렌더링하지 않고(주석처리)
타임리프를 렌더링을 거치면 정상 렌더링 된다(주석처리 X)
<!--/*/ /*/-->


블록

<th:block>

  • HTML 태그가 아닌 타임리프의 유일한 자체 태그
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>
  • 보통 HTML 태그안에 속성으로 기능을 정의해서 사용하는데, 예시처럼 애매한 경우 사용
  • <th:block> 은 렌더링시 제거된다

타임리프 해석 X

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() 호출 값이다

자바스크립트 인라인 each

[# th:each ]

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

템플릿 조각

부분포함 insert , 부분포함 replace

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>
  • 공통 부분은 그대로 유지되고, 전달한 태그만 교체된다
  • 레이아웃은 그대로 두고, 필요한 코드 조각만 전달
  • html 전체를 변경할 수도 있다

0개의 댓글