Thymeleaf

김수정·2020년 6월 10일
2

스프링/스프링부트

목록 보기
5/11

타임리프란?

Java 위에서 동작하는 클라이언트 템플릿 엔진입니다.

타임리프 설정

1. application.properties 설정

음.. 어플리케이션의 속성을 설정하는 파일? 뭘 의미하는 지는 아직 명확히 모르겠습니다. 타임리프만 설정하는 파일은 아닌 것 같습니다만 타임리프 설정을 여기에 할 수 있네요.

# Web
spring.thymeleaf.mode=HTML

2. 메인 class에서 ServletContextTemplateResolver 활용

ServletContextTemplateResolver 클래스를 사용해서 mode나 prefix등 설정을 하는 버전도 있네요.

네임스페이스

타임리프는 자신의 문법을 html에 혼용해서 씁니다.
html은 자신들이 아는 문법이 아니면 무시하므로 html로 열어도 레이아웃이 깨지지 않는 장점이 있습니다.
그러나 IDE에서 오류처럼 인식되는 불편함과(빨간색으로 쫘아악) 순수한 html파일로 가져가고(?)싶다면 방법이 있습니다.

1. IDE 불편함을 해소하기 위해서 네임스페이스를 달아줍니다.
타임리프의 명령은 다 th로 시작하므로 이것이 무엇을 의미하는지 알려주는 겁니다. 아래처럼요.

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

2. th로 사용하기 싫다. 순수한 html로 남겨달라.
원래 타임리프의 기본 표현은 아래와 같습니다.

표현식 th:[속성]="서버 전달 받은 값 또는 조건식"

그런데 이 속성을 html이 이해하는 속성으로 만들고 싶다면 앞에 data-를 붙이고 th:속성 대신 th-속성으로 적습니다. 즉, data-th-속성=""

속성 값

th:text
텍스트를 태그에 넣어줍니다.

th:utext
html코드를 인식하여 그에 맞게 태그 안에 내용을 넣어줍니다.

th:block
리액트의 fragment처럼, 안에 내용만 남고 자기 자신은 사라집니다.
여기서는 타임리프가 처리한 내용물만 남고 자기자신의 태그는 브라우저에 렌더링되지 않겠네요!

th:each

<tr th:each="owner : ${selections}"> <!-- ${selection}에 있는 객체가 owner로 맵핑됩니다. -->
  <td>
    <a th:href="@{/owners/__${owner.id}__}" th:text="${owner.firstName + ' ' + owner.lastName}"/></a>
  </td>
  <td th:text="${owner.age}"/>
  <td th:text="${owner.address}"/>
  <td th:text="${owner.city}"/>
  <td th:text="${owner.telephone}"/>
  <td><span th:each="pet : ${owner.pets}" th:text="${pet.name} "/></td>
</tr>

표현 구문

1) #{}
메시지식으로 사용합니다.
메시지 문구는 ~~~.properties 파일에서 메시지를 지정해놓거나, org.thymeleaf.messageresolver.StandardMessageResolver를 활용하거나 자체 구현할 수 있습니다.
~~.properties 파일에서 메시지 문구를 동적으로 하고 싶다면 {매개변수 순서}로 매개변수를 받을 수 있습니다.

<!-- home.html -->
<p th:utext="#{home.welcome('susu')}">
  Welcome to our grocery store, Sebastian Pepper!
</p>

in home.properties

home.welcome=Welcome to <b>fantastic</b> our grocery store, {0} (from default messages)!

2) ${}
변수식으로 사용합니다. ${{}}은 data형식의 스트링으로 바꿔줍니다.

<p th:text="${person}"></p> <!-- person 변수 부름 -->
<p th:text="${person.father.name}"></p> <!-- person객체의 father프로퍼티의 name 프로퍼티 부름 -->
<p th:text="${person['father']['name']}"></p> <!-- 위와 동일 -->
<p th:text="${person[0].name}"></p> <!-- 배열 변수의 0번째 데이터 객체의 name 프로퍼티 부름 -->
<p th:text="${createCompleteName('-')}"></p> <!-- createCompleteName메소드 부름 -->

3) *{}
th:object와 함께 쓰여서 해당 객체의 일부 프로퍼티만 나타내는 표현 구문입니다.

<div th:object="${session.user}"> <!-- 객체 명시 -->
  <p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p> <!-- ${}표현식에서 object를 쓰고 싶을 때 -->
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <!-- 원래 ${} 표현식 -->
  <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> <!-- *{} 표현식 -->
</div>

4) @{}
th:href와 함께 쓰여 링크식으로 사용합니다.

5) ~{}
레이아웃 템플릿 fragment와 함께 쓰입니다.

6) __
전처리 시스템. 타임리프보다 먼저 실행될 값입니다.

<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>

리터럴

텍스트 리터럴 '...'
+로 텍스트를 이어붙일 수 있습니다.
그런데 +로 붙이는 게 가독성이 안좋으니 리터럴 치환을 할 수 있습니다. 아래 두 식은 같습니다.

<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
<span th:text="|Welcome to our application, ${user.name}!|">

숫자 리터럴 숫자를 그대로 사용
+, -, *, /, % 연산 가능

부울 리터럴 true, false
>, <, >=, <=, !, ==, != 사용가능
조건식에서 ?:은 ?앞에 값이 true면 ?앞의 값을 사용하고 아니면 뒤의 값을 사용한다는 뜻입니다.

<tr th:class="${row.even}? 'even' : 'odd'"> <!-- ?뒤에 값이 하나면 false일 경우 null이 리턴됨 -->
  ...
</tr>

널 리터럴 null
값에 _를 넣으면 동작 안한다는 뜻입니다. 아래 두 식은 동ㅇ리하게 동작합니다.

<span th:text="${user.name} ?: _">no user authenticated</span>
<span th:text="${user.name} ?: 'no user authenticated'">...</span>
  1. ||
    리터럴 치환. javascript의 리터럴 템플릿과 같은 것 같다.

  2. <div th:utext="${msg}"></div>
    html을 출력한다. 다른 것들은 다 th:text였는데 얘만 th:utext이다.

2. 조건문

3. 반복문

주석

기본적인 html 주석 가능합니다.

<!-- comments -->

정적인 페이지에서는 주석이지만 타임리프 처리가 되면 브라우저에 노출되지 않는 주석입니다.

<!--/* comments */-->
<!--/*--><div>정적일 때만 나타나는 화면</div><!--*/-->

정적인 페이지에서는 주석이고 타임리프 처리 후 나타납니다.
th:block과 같이 사용하기 좋습니다.

<!--/*/ <div th:text="test">타임리프 처리 후 나타나는 화면</div> /*/-->
<!--/*/ <th:block> /*/-->
    <span>타임리프 처리 후 나타나는 화면</span>
<!--/*/ </th:block> /*/-->

Template Layout

블로그 검색하다 마땅한 게 보이지 않아서 공홈 문서를 통해 정리했습니다.

fragment 만들기

공통으로 쓸 마크업 조각을 만듭니다.
css selector처럼, js의 module처럼 구역을 나눠줍니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <!-- th변수를 정의해주는 것 같아요. 없어도 동작은 합니다. th부분이 IDE에서 빨갛게 될 뿐. -->
  <body>
    <div th:fragment="copy"> <!-- fragment selector를 만듭니다. -->
      &copy; 2011 The Good Thymes Virtual Grocery
    </div>
    
    <div id="copy2"> <!-- fragment selector 대신 id값으로도 사용 가능합니다. -->
      &copy; 2020 copy2
    </div>
  </body>
</html>

fragment 삽입하기

호스트 페이지(fragment를 삽입할 페이지)에 아래 3가지 방법으로 넣을 수 있습니다.

insert - 지정된 조각을 호스트 태그의 본문으로 삽입합니다.
replace - 호스트 태그를 지정된 조각으로 바꿉니다.
include - 호스트 태그의 본문으로 삽입하지만 조각의 내용만 삽입합니다.(타임리프3.0부터 비추)

3가지 방법 중 하나를 고른 뒤 fragment경로:: 선택자 형식 혹은 ~{}으로 감싸서(선택사항) 호출합니다.

<div th:insert="fragments/footer :: #copy2"></div> <!-- 아이디로 불러올 경우 -->
<div th:insert="fragments/footer :: copy"></div> <!-- fragment로 불러올 경우 -->
<div th:insert="~{fragments/footer :: copy}"></div> <!-- ~{}로 감쌀 경우 -->

fragment에서 parameter받기

함수처럼 ()를 이용해서 파라미터를 받아올 수 있습니다.
혹여 프래그먼트 페이지에서 파라미터를 지정하지 않았더라도 호스트 페이지에서 인자값을 파라미터 변수명과 짝지어서 넘겨줄 경우 프래그먼트 페이지에서 사용 가능합니다.

<!-- fragment page -->
<div th:fragment="frag (onevar,twovar)"> <!-- 함수처럼 ()에 parameter를 지정합니다. -->
    <p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>

<!-- host page -->
<div th:replace="::frag (${value1},${value2})">...</div> <!-- 인자 값 넣어서 호출 -->
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div><!-- 인자명이랑 매칭해서 넘겨줌. 이 경우, 파라미터의 순서는 중요하지 않습니다. -->
<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}"> <!-- 바로 위 코드와 동일한 기능을 하는 코드 -->

파라미터를 넘기지 않았을 때 오류를 발생시켜줄 때 th:assert설정을 하는 것 같네요.

<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>

fragment에서 tag받기

프래그먼트 페이지에서 해당 태그이름으로 파라미터를 지정해줍니다.
호스트 페이지에서는 ~{::tagName}형식으로 넘겨주면 replace/insert 안에서 사용한 실제 그 태그가 넘어가집니다.
아래 두 문법은 조건식과 함께 쓰일 때 빛을 발합니다.
~{}: 조건식이 참이면 tag를 넘기고 아니면 비어놓고 싶을 때
_: 조건식이 참이면 tag를 넘기고 아니면 원래 있던 대로 냅두고 싶을 때

<!-- fragment page -->
<footer th:fragment="take(p)"> <!-- tag 이름으로 파라미터를 지정 -->
  <th:block th:replace="${p}" />
</footer>

<!-- host page -->
<div th:replace="fragments/footer :: take (~{::p})"> <!-- tag 넘김 -->
  <p>테스트입니당?!</p>
</div>

<div th:replace="fragments/footer :: take (~{})"> <!-- tag 안넘길 때 -->
  <p>테스트입니당?!</p>
</div>

<div th:replace="fragments/footer :: take (_)"> <!-- fragment page에 원래 있던 것을 그대로 사용하겠다. -->
  <p>테스트입니당?!</p>
</div>

템플릿 제거

th:remove
태그를 제거하는 명령어입니다.
여기에는 여러가지 값이 존재합니다.

  • all: 포함 태그와 모든 하위 태그 삭제
  • body: 포함 태그 제거안하고 모든 하위 태그 삭제
  • tag: 포함 태그만 제거하고 하위 태그는 살려둠
  • all-but-first: 첫 번째 태그를 제외하고 포함 태그의 모든 하위 태그들 제거
  • none: 제거하지 마세요. 조건식에서 쓰이기 좋겠죠?
<!-- 1과 2는 같습니다. -->
<a href="/something" th:remove="${condition}? tag : none">Link text not to be removed</a> <!-- 1 -->
<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a> <!-- 2 -->

레이아웃 상속

html에다가 fragment를 명시하면 html이 통째로 바뀝니다!

<!-- fragment page-->
<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:replace="${title}">Layout Title</title>
</head>
<body>
    <h1>Layout H1</h1>
    <div th:replace="${content}">
        <p>Layout content</p>
    </div>
    <footer>
        Layout footer
    </footer>
</body>
</html>

<!-- host page-->
<!DOCTYPE html>
<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}">
<head>
    <title>Page Title</title>
</head>
<body>
<section>
    <p>Page content</p>
    <div>Included on page</div>
</section>
</body>
</html>

수정이력
2020.06.29~ 2020.06.30


참고
타임리프 공부
타임리프 주석, th:block
타임리프 레이아웃은 공홈참고

profile
정리하는 개발자

0개의 댓글