필기노트

타임리프 기본 기능 본문

김영한 강의 요약

타임리프 기본 기능

우퐁코기 2023. 10. 23. 04:29
반응형
목차
1. 기본 표현식
2. 변수 - SpringEL
3. 타임리프 유틸리티 객체들
4. 리터럴
5. 속성 값 설정
6. 반복
7. 조건부 평가
8. 블록
9. 템플릿 조각
10. 템플릿 레이아웃

1. 기본 표현식

• 간단한 표현: 

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

   ◦ 선택 변수 표현식: *{...} 

   ◦ 메시지 표현식: #{...} 

   ◦ 링크 URL 표현식: @{...} 

   ◦ 조각 표현식: ~{...} 

• 리터럴 

   ◦ 텍스트: 'one text', 'Another one!',...

   ◦ 숫자: 0, 34, 3.0, 12.3,...

   ◦ 불린: true, false

   ◦ 널: null

   ◦ 리터럴 토큰: one, sometext, main,...

• 문자 연산:

   ◦ 문자합치기:+

   ◦ 리터럴 대체: |The name is ${name}|

• 산술 연산:

   ◦ Binary operators: +, -, *, /, %

   ◦ Minus sign (unary operator): -

• 불린 연산:

   ◦ Binary operators: and, or

   ◦ Boolean negation (unary operator): !, not

• 비교와 동등:

   ◦ 비교:>,<,>=,<=(gt,lt,ge,le)

   ◦ 동등 연산: ==, != (eq, ne)

• 조건 연산:

   ◦ If-then: (if) ? (then)

   ◦ If-then-else: (if) ? (then) : (else)

   ◦ Default: (value) ?: (defaultvalue)

• 특별한 토큰:

   ◦ No-Operation: _

 

텍스트 - text, utext

데이터를 출력할 때는 다음과 같이 th:text 를 사용하면 된다. 

<li>th:text 사용 <span th:text="${data}"></span></li>

 

HTML 테그의 속성이 아니라 HTML 콘텐츠 영역안에서 직접 데이터를 출력하고 싶으면 다음과 같이 [[...]] 를 사용하면 된다.

<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>

 


2. 변수 - SpringEL

@GetMapping("/variable")
public String variable(Model model) {
    User userA = new User("userA", 10);
    User userB = new User("userB", 20);
    List<User> list = new ArrayList<>();
    list.add(userA);
    list.add(userB);
    Map<String, User> map = new HashMap<>();
    map.put("userA", userA);
    map.put("userB", userB);
    model.addAttribute("user", userA);
    model.addAttribute("users", list);
    model.addAttribute("userMap", map);
    return "basic/variable";
}
  
@Data
static class User {
    private String username;
    private int age;
         
    public User(String username, int age) {
        this.username = username;
        this.age = age;
    } 
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>SpringEL 표현식</h1>
<ul>Object
    <li>${user.username} = <span th:text="${user.username}"></span></li>
</ul>
<ul>List
    <li>${users[0].username}    = <span th:text="${users[0].username}"></span></li>
</ul>
<ul>Map
    <li>${userMap['userA'].username} =  <span th:text="${userMap['userA'].username}"></span></li>
</ul>

</body>
</html>

Object 

user.username : user의 username을 프로퍼티 접근 -> user.getUsername()

 

List 

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

 

Map

userMap['userA'].username : Map에서 userA를 찾고, username 프로퍼티 접근 -> map.get("userA").getUsername()

 

지역 변수 선언 

th:with 를 사용하면 지역 변수를 선언해서 사용할 수 있다. 지역 변수는 선언한 테그 안에서만 사용할 수 있다.

<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
    <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p> 
</div>

 

3. 타임리프 유틸리티 객체들 

#message : 메시지, 국제화 처리 

#uris : URI 이스케이프 지원 

#dates : java.util.Date 서식 지원 

#calendars : java.util.Calendar 서식 지원 

#temporals : 자바8 날짜 서식 지원 

#numbers : 숫자 서식 지원 

#strings : 문자 관련 편의 기능 

#objects : 객체 관련 기능 제공 

#bools : boolean 관련 기능 제공 

#arrays : 배열 관련 기능 제공 

#lists , #sets , #maps : 컬렉션 관련 기능 제공 

#ids : 아이디 처리 관련 기능 제공, 뒤에서 설명

@GetMapping("/date")
public String date(Model model) {
    model.addAttribute("localDateTime", LocalDateTime.now());
    return "basic/date";
}
<h1>LocalDateTime</h1>
<ul>
    <li>default = <span th:text="${localDateTime}"></span></li>
    <li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime, 'yyyy-MM-dd HH:mm:ss')}"></span></li>
</ul>

<h1>LocalDateTime - Utils</h1>
<ul>
    <li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
    <li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
    <li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
</ul>

 


4. 리터럴

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

<span th:text="'hello'">

 

그런데문자를항상 '로감싸는것은너무귀찮은일이다.

공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해서 다음과 같이 작은 따옴표를 생략할 수 있다. 

<span th:text="hello">

 

중간에 공백이 있어서 하나의 의미있는 토큰으로도 인식되지 않는다.

<span th:text="hello world!"></span> (오류)

 

이렇게 ' 로 감싸면 정상 동작한다.

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

 

리터럴 대체

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

 


5. 속성 값 설정

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

속성 설정 

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

기존 속성이 없다면 새로 만든다. 

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

-> 타임리프 렌더링 후 <input type="text" name="userA" />

 

속성 추가

th:attrappend : 속성 값의 뒤에 값을 추가한다.

th:attrprepend : 속성 값의 앞에 값을 추가한다.

th:classappend : class 속성에 자연스럽게 추가한다.

 

checked 처리

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

 

HTML에서 checked 속성은 checked 속성의 값과 상관없이 checked 라는 속성만 있어도 체크가 된다. 이런 부분이 true , false 값을 주로 사용하는 개발자 입장에서는 불편하다. 

 

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

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

-> 타임리프 렌더링 후: <input type="checkbox" name="active" />

 


6. 반복

@GetMapping("/each")
public String each(Model model) {
    addUsers(model);
    return "basic/each";
}
  
private void addUsers(Model model) {
    List<User> list = new ArrayList<>();
    list.add(new User("userA", 10));
    list.add(new User("userB", 20));
    list.add(new User("userC", 30));

    model.addAttribute("users", list);
}
<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>

반복 기능 

<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 가 됩니다.

여기서는 user + Stat = userStat 이므로 생략 가능합니다.

 

반복 상태 유지 기능

index : 0부터 시작하는 값

count : 1부터 시작하는 값

size : 전체 사이즈

even , odd : 홀수, 짝수 여부( boolean )

first , last :처음, 마지막 여부( boolean )

current : 현재 객체

 


7. 조건부 평가

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

if, unless 

타임리프는 해당 조건이 맞지 않으면 태그 자체를 렌더링하지 않는다. 

만약 다음 조건이 false 인 경우 <span>...<span> 부분 자체가 렌더링 되지 않고 사라진다. 

<span th:text="'미성년자'" th:if="${user.age lt 20}"></span> 

 

switch 

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

 


8. 블록

<th:block> 은 HTML 태그가 아닌 타임리프의 유일한 자체 태그다.

<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> 은 렌더링시 제거된다.

 


9. 템플릿 조각

/resources/templates/template/fragment/footer.html

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

<body>

<footer th:fragment="copy"> 
    푸터 자리 입니다.
</footer>
  
<footer th:fragment="copyParam (param1, param2)">
    <p>파라미터 자리 입니다.</p>
    <p th:text="${param1}"></p> 
    <p th:text="${param2}"></p>
</footer>

</body>

</html>

th:fragment 가 있는 태그는 다른곳에 포함되는 코드 조각으로 이해하면 된다.

 

/resources/templates/template/fragment/fragmentMain.html

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

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

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

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

template/fragment/footer :: copy

template/fragment/footer.html 템플릿에 있는 th:fragment="copy" 라는 부분을 템플릿 조각으로 가져와서 사용한다는 의미이다.

 

footer.html 의 copy 부분

<footer th:fragment="copy">
    푸터 자리 입니다. 
</footer>

 

부분 포함 insert

<div th:insert="~{template/fragment/footer :: copy}"></div>

<h2>부분 포함 insert</h2> 
<div>
<footer>
    푸터 자리 입니다. 
</footer> 
</div>

th:insert 를 사용하면 현재 태그( div ) 내부에 추가한다.

 

부분 포함 replace

<div th:replace="~{template/fragment/footer :: copy}"></div>

<h2>부분 포함 replace</h2> 
<footer>
    푸터 자리 입니다. 
</footer>

th:replace 를 사용하면 현재 태그( div )를 대체한다.

 

부분 포함 단순 표현식

<div th:replace="template/fragment/footer :: copy"></div>

<h2>부분 포함 단순 표현식</h2> 
<footer>
    푸터 자리 입니다. 
</footer>

~{...} 를 사용하는 것이 원칙이지만 템플릿 조각을 사용하는 코드가 단순하면 이 부분을 생략할 수 있다.

 

파라미터 사용

다음과 같이 파라미터를 전달해서 동적으로 조각을 렌더링 할 수도 있다. 

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

<h1>파라미터 사용</h1> 
<footer>
<p>파라미터 자리 입니다.</p> 
<p>데이터1</p> 
<p>데이터2</p>
</footer>

 

 


10. 템플릿 레이아웃1

이전에는 일부 코드 조각을 가지고와서 사용했다면, 이번에는 개념을 더 확장해서 코드 조각을 레이아웃에 넘겨서 사용하는 방법에 대해서 알아보자. 

 

예를 들어서 <head> 에 공통으로 사용하는 css , javascript 같은 정보들이 있는데, 이러한 공통 정보들을 한 곳에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같이 사용하면 된다.

 

/resources/templates/template/layout/base.html

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

/resources/templates/template/layout/layoutMain.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<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> 
<body> 
메인 컨텐츠 
</body> 
</html>

결과

<html>
<head>
<title>메인 타이틀</title>
<!-- 공통 -->

<link rel="stylesheet" type="text/css" media="all" href="/css/awesomeapp.css">
<link rel="shortcut icon" href="/images/favicon.ico">
<script type="text/javascript" src="/sh/scripts/codebase.js"></script>

<!-- 추가 -->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/themes/smoothness/jquery-ui.css">

</head> 
<body> 
메인 컨텐츠 
</body> 
</html>

common_header(~{::title},~{::link}) 이 부분이 핵심이다. 

::title 은 현재 페이지의 title 태그들을 전달한다. 

::link 는 현재 페이지의 link 태그들을 전달한다. 

 

결과를 보자. 

메인 타이틀이 전달한 부분으로 교체되었다. 

공통 부분은 그대로 유지되고, 추가 부분에 전달한 <link> 들이 포함된 것을 확인할 수 있다. 

 

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

 

템플릿 레이아웃2

앞서 이야기한 개념을 <head> 정도에만 적용하는게 아니라 <html> 전체에 적용할 수도 있다.

/resources/templates/template/layoutExtend/layoutFile.html

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

/resources/templates/template/layoutExtend/layoutExtendMain.html

<!DOCTYPE html>
<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title},~{::section})}"
    xmlns:th="http://www.thymeleaf.org">
 <head>
<title>메인 페이지 타이틀</title> 
</head>
<body>
<section>
    <p>메인 페이지 컨텐츠</p>
    <div>메인 페이지 포함 내용</div> 
</section>
</body>
</html>

결과

<!DOCTYPE html>
<html>
<head>
<title>메인 페이지 타이틀</title> 
</head>
<body>
<h1>레이아웃 H1</h1>

<section>
<p>메인 페이지 컨텐츠</p> 
<div>메인 페이지 포함 내용</div> 
</section>

<footer> 
레이아웃 푸터
</footer>

</body>
</html>

layoutFile.html 을 보면 기본 레이아웃을 가지고 있는데, <html> 에 th:fragment 속성이 정의되어 있다. 이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용을 전달해서 부분부분 변경하는 것으로 이해하면 된다. 

 

layoutExtendMain.html 는 현재 페이지인데, <html> 자체를 th:replace 를 사용해서 변경하는 것을 확인할 수 있다. 결국 layoutFile.html 에 필요한 내용을 전달하면서 <html> 자체를 layoutFile.html 로 변경한다.

 


 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

반응형

'김영한 강의 요약' 카테고리의 다른 글

스프링 메시지, 국제화  (0) 2023.11.17
타임리프 스프링 통합과 폼  (0) 2023.11.03
타임리프 간단히 알아보기  (0) 2023.10.20
스프링 요청매핑, HTTP 요청, HTTP 응답  (0) 2023.10.11
스프링 로깅  (0) 2023.10.04
Comments