thymeleaf 기본 기능

김태하·2023년 7월 30일
3
post-thumbnail

📌 1. thymeleaf란?

1-1. 타임리프 특징

  • 서버 사이드 HTML 렌더링 (SSR)
    : 타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용된다.
  • 네츄럴 템플릿
    : 타임리프는 순수 HTML을 최대한 유지하면서 뷰 템플릿도 사용할 수 있는 특징이 있다.
  • 스프링 통합 지원
    : 타임리프는 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다.

1-2. 타임리프 사용 선언

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

📌 기본 표현식

2-1. 텍스트 - text, utext

  • HTML의 콘텐츠(content)에 데이터를 출력할 때 th:text 사용
    ex) <span th:text="${data}">

  • HTML 테그의 속성이 아니라 HTML 콘텐츠 영역안에서 직접 데이터를 출력하고 싶을 때 [[...]] 사용
    ex) 컨텐츠 안에서 직접 출력하기 = [[${data}]]

만약 Hello Spring!과 같이 Spring! 부분에 <b></b> 태그를 넣는다면 어떻게 될까?
소스를 보게 되면 Hello &lt;b&gt;Spring!&lt;/b&gt; 와 같이 나오게 된다.

그렇다면 이를 어떻게 해결해야 할까?
다음과 같은 두가지 방법이 있다.

  • th:text -> th:utext
  • [[...]] -> [(...)]

이렇게 작성 후 실행해 보면 정상적으로 수행되는 것을 확인할 수 있다.

2-2. 변수 - SpringEL

  • 변수 표현식 : ${...}

SpringEL의 다양한 표현식

  1. Object
  • user.username : user의 username을 프로퍼티 접근 -> user.getUsername()
  • user['username'] : 위와 같음 -> user.getUsername()
  • user.getUsername() : user의 getUsername() 을 직접 호출
  1. List
  • users[0].username : List에서 첫 번째 회원을 찾고 username 프로퍼티 접근 -> list.get(0).getUsername()
  • users[0]['username'] : 위와 같음
  • users[0].getUsername() : List에서 첫 번째 회원을 찾고 메서드 직접 호출
  1. Map
  • userMap['userA'].username : Map에서 userA를 찾고, username 프로퍼티 접근 -> map.get("userA").getUsername()
  • userMap['userA']['username'] : 위와 같음
  • userMap['userA'].getUsername() : Map에서 userA를 찾고 메서드 직접 호출

2-3. 기본 객체들

타임리프는 기본적으로 다양한 객체들을 제공한다.
그 중 편의 객체는 다음과 같다.

  • HTTP 요청 파라미터 접근: param
    예) ${param.paramData}
  • HTTP 세션 접근: session
    예) ${session.sessionData}
  • 스프링 빈 접근: @
    예) ${@helloBean.hello('Spring!')

2-4. 유틸리티 객체와 날짜

다양한 객체가 존재하는데 자세한 내용은 다음 링크에서 볼 수 있다.

2-5. URL 링크

타임리프에서 URL을 생성할 때는 @{...} 문법을 사용하면 된다.

  • 단순한 URL
    @{/hello} -> /hello

  • 쿼리 파라미터
    @{/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
    경로 변수와 쿼리 파라미터를 함께 사용할 수 있다.

예시)

<ul>
 <li><a th:href="@{/hello}">basic url</a></li>
 <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query 
param</a></li>
 <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=$
{param2})}">path variable</a></li>
 <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=$
{param2})}">path variable + query parameter</a></li>
</ul>

2-6. 리터럴 (literal)

타임리프는 다음과 같은 리터럴이 있다.

  • 문자: 'hello'
  • 숫자: 10
  • 불린: true , false
  • null: null
  1. 타임리프에서 문자 리터럴은 항상 ' (작은 따옴표)로 감싸야 한다.
    예시) <span th:text="'hello'">

  2. 만약 공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해서 다음과 같이 작은 따옴표를 생략할 수 있다.
    룰 : A-Z, a-z , 0-9 , [] , . , - , _
    예시) <span th:text="hello">

2-7. 연산

  • 비교연산 : HTML 엔티티를 사용해야 하는 부분을 주의하자,
    >(gt), < (lt), >= (ge), <= (le), ! (not), == (eq), != (neq, ne)

  • 조건식 : 자바의 조건식과 유사하다.

  • Elvis 연산자 : 조건식의 편의 버전

  • No-Operation : _ 인 경우 마치 타임리프가 실행되지 않는 것 처럼 동작한다. 이것을 잘 사용하면 HTML의 내용 그대로 활용할 수 있다. 마지막 예를 보면 데이터가 없습니다. 부분이 그대로 출력된다.

예시)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<ul>
    <li>산술 연산
        <ul>
            <li>10 + 2 = <span th:text="10 + 2"></span></li>
            <li>10 % 2 == 0 = <span th:text="10 % 2 == 0"></span></li>
        </ul>
    </li>
    <li>비교 연산
        <ul>
            <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 == 10 = <span th:text="1 == 10"></span></li>
            <li>1 != 10 = <span th:text="1 != 10"></span></li>
        </ul>
    </li>
    <li>조건식
        <ul>
            <li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)?'짝수':'홀수'"></span></li>
        </ul>
    </li>
    <li>Elvis 연산자
        <ul>
            <li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가없습니다.'"></span></li>
            <li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?:'데이터가 없습니다.'"></span></li>
        </ul>
    </li>
    <li>No-Operation
        <ul>
            <li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다.</span></li>
            <li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li>
        </ul>
    </li>
</ul>
</body>
</html>

실행 결과)

📌 속성 값 설정

3-1. 타임리프 태그 속성(Attribute)

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

3-2. 속성 설정

th:* 속성을 지정하면 타임리프는 기존 속성을 th:* 로 지정한 속성으로 대체한다. 기존 속성이 없다면 새로 만든다.
<input type="text" name="mock" th:name="userA" />
-> 타임리프 렌더링 후 <input type="text" name="userA" />

3-3. 속성 추가

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

3-4. checked 처리

HTML에서는 <input type="checkbox" name="active" checked="false" />
-> 이 경우에도 checked 속성이 있기 때문에 checked 처리가 되어버린다.

타임리프의 th:checked 는 값이 false 인 경우 checked 속성 자체를 제거한다.
<input type="checkbox" name="active" th:checked="false" />
-> 타임리프 렌더링 후 : <input type="checkbox" name="active" />

예시 코드)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>속성 설정</h1>
<input type="text" name="mock" th:name="userA" />
<h1>속성 추가</h1>
    - 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/>
<h1>checked 처리</h1>
    - 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/>
</body>
</html>

📌 반복

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

4-1. 반복 기능

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

  • 반복시 오른쪽 컬렉션( ${users} )의 값을 하나씩 꺼내서 왼쪽 변수( user )에 담아서 태그를 반복 실행한다.
  • th:each 는 List 뿐만 아니라 배열, java.util.Iterable , java.util.Enumeration 을 구현한 모든 객체를 반복에 사용할 수 있다. Map 도 사용할 수 있는데 이 경우 변수에 담기는 값은 Map.Entry다.

4-2. 반복 상태 유지

<tr th:each="user, userStat : ${users}">
반복의 두번째 파라미터를 설정해서 반복의 상태를 확인 할 수 있다.
두번째 파라미터는 생략 가능한데, 생략하면 지정한 변수명( user ) + Stat 가 된다.
여기서는 user + Stat = userStat 이므로 생략 가능하다.

4-3. 반복 상태 유지 기능

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

예시 코드)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>기본 테이블</h1>
<table border="1">
    <tr>
        <th>username</th>
        <th>age</th>
    </tr>
    <tr th:each="user : ${users}">
        <td th:text="${user.username}">username</td>
        <td th:text="${user.age}">0</td>
    </tr>
</table>
<h1>반복 상태 유지</h1>
<table border="1">
    <tr>
        <th>count</th>
        <th>username</th>
        <th>age</th>
        <th>etc</th>
    </tr>
    <tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">username</td>
        <td th:text="${user.username}">username</td>
        <td th:text="${user.age}">0</td>
        <td>
            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>
        </td>
    </tr>
</table>
</body>
</html>

실행 화면)

📌 조건부 평가

  • if, unless
    타임리프는 해당 조건이 맞지 않으면 태그 자체를 렌더링하지 않는다.
    만약 다음 조건이 false 인 경우 <span>...<span> 부분 자체가 렌더링 되지 않고 사라진다.
    <span th:text="'미성년자'" th:if="${user.age lt 20}"></span>

  • switch
    * 은 만족하는 조건이 없을 때 사용하는 디폴트이다

예시 코드)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>if, unless</h1>
<table border="1">
    <tr>
        <th>count</th>
        <th>username</th>
        <th>age</th>
    </tr>
    <tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">1</td>
        <td th:text="${user.username}">username</td>
        <td>
            <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>
        </td>
    </tr>
</table>
<h1>switch</h1>
<table border="1">
    <tr>
        <th>count</th>
        <th>username</th>
        <th>age</th>
    </tr>
    <tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">1</td>
        <td th:text="${user.username}">username</td>
        <td th:switch="${user.age}">
            <span th:case="10">10살</span>
            <span th:case="20">20살</span>
            <span th:case="*">기타</span>
        </td>
    </tr>
</table>
</body>
</html>

실행 화면)

📌 자바스크립트 인라인

타임리프는 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.
자바스크립트 인라인 기능은 다음과 같이 적용하면 된다.
<script th:inline="javascript">

var username2 = /*[[${user.username}]]*/ "test username";

  • 인라인 사용 전 -> var username2 = /*userA*/ "test username";
  • 인라인 사용 후 -> var username2 = "userA";





출처 : 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

4개의 댓글

comment-user-thumbnail
2023년 7월 30일

이런 유용한 정보를 나눠주셔서 감사합니다.

1개의 답글
comment-user-thumbnail
2023년 8월 7일

유용한 글 잘 보고 갑니다 😀

1개의 답글