kiwi

타임리프 기본 문법

by 키위먹고싶다

 

타임리프

 

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

네츄럴 템플릿

스프링 통합 지원


텍스트 -text, utext

 

데이터를 출력할때는 th:text를 사용

태그 속성이 아니라 html콘텐츠 영역안에 직접 사용하고 싶으면

[[...]]사용

 

escape와 unescape

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
    <li>th:text 사용 <span th:text="${data}"></span></li>
    <li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
</ul>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>text vs utext</h1>
<ul>
    <li>th:text = <span th:text="${data}"></span></li>
    <li>th:utext = <span th:utext="${data}"></span></li>
</ul>
<h1><span th:inline="none">[[...]] vs [(...)]</span></h1>
<ul>
    <li><span th:inline="none">[[...]] = </span>[[${data}]]</li>
    <li><span th:inline="none">[(...)] = </span>[(${data})]</li>
</ul>
</body>
</html>

text vs utext

  • th:text = Hello <b>Spring!</b>
  • th:utext = Hello Spring!

[[...]] vs [(...)]

  • [[...]] = Hello <b>Spring!</b>
  • [(...)] = Hello Spring!

SpingEl

@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>
    <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>
</body>
</html>

SpringEL 표현식

  • ${user.username} = userA
  • ${user['username']} = userA
  • ${user.getUsername()} = userA
  • ${users[0].username} = userA
  • ${users[0]['username']} = userA
  • ${users[0].getUsername()} = userA
  • ${userMap['userA'].username} = userA
  • ${userMap['userA']['username']} = userA
  • ${userMap['userA'].getUsername()} = userA

지역 변수 선언

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

지역 변수 - (th:with)

처음 사람의 이름은 userA


기본 객체

@GetMapping("/basic-objects")
public String basicObjects(HttpSession session) {
    session.setAttribute("sessionData", "Hello Session");
    return "basic/basic-objects";
}

@Component("helloBean")
static class HelloBean {
    public String hello(String data) {
        return "Hello " + data;
    }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>식 기본 객체 (Expression Basic Objects)</h1>
<ul>
    <li>request = <span th:text="${#request}"></span></li>
    <li>response = <span th:text="${#response}"></span></li>
    <li>session = <span th:text="${#session}"></span></li>
    <li>servletContext = <span th:text="${#servletContext}"></span></li>
    <li>locale = <span th:text="${#locale}"></span></li>
</ul>
<h1>편의 객체</h1>
<ul>
    <li>Request Parameter = <span th:text="${param.paramData}"></span></li>
    <li>session = <span th:text="${session.sessionData}"></span></li>
    <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
</ul>
</body>
</html>

식 기본 객체 (Expression Basic Objects)

  • request = org.apache.catalina.connector.RequestFacade@254d4c47
  • response = org.apache.catalina.connector.ResponseFacade@5b1893c2
  • session = org.apache.catalina.session.StandardSessionFacade@6cb193d6
  • servletContext = org.apache.catalina.core.ApplicationContextFacade@6e2c6cf6
  • locale = ko_KR

편의 객체

  • Request Parameter = HelloParam
  • session = Hello Session
  • spring bean = Hello Spring!

request의 데이터를 얻기 위해서 request.getParameter("data")해줘야 하는데 

접근이 불편해서 요청 파라미터 접근, 세선접근, 스프링빈 접근등의  편의 객체를 사용한다. 


날짜 객체

@GetMapping("/date")
public String data(Model model){

    model.addAttribute("localDateTime", LocalDateTime.now());
    return "basic/date";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<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.monthName(localDateTime)} = <span th:text="${#temporals.monthName(localDateTime)}"></span></li>
    <li>${#temporals.monthNameShort(localDateTime)} = <span th:text="${#temporals.monthNameShort(localDateTime)}"></span></li>
    <li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeek(localDateTime)} = <span th:text="${#temporals.dayOfWeek(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeekName(localDateTime)} = <span th:text="${#temporals.dayOfWeekName(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeekNameShort(localDateTime)} = <span th:text="${#temporals.dayOfWeekNameShort(localDateTime)}"></span></li>
    <li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
    <li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
    <li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
    <li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>
</ul>
</body>
</html>

LocalDateTime

  • default = 2022-01-26T20:11:35.312083500
  • yyyy-MM-dd HH:mm:ss = 2022-01-26 20:11:35

LocalDateTime - Utils

  • ${#temporals.day(localDateTime)} = 26
  • ${#temporals.month(localDateTime)} = 1
  • ${#temporals.monthName(localDateTime)} = 1월
  • ${#temporals.monthNameShort(localDateTime)} = 1월
  • ${#temporals.year(localDateTime)} = 2022
  • ${#temporals.dayOfWeek(localDateTime)} = 3
  • ${#temporals.dayOfWeekName(localDateTime)} = 수요일
  • ${#temporals.dayOfWeekNameShort(localDateTime)} = 
  • ${#temporals.hour(localDateTime)} = 20
  • ${#temporals.minute(localDateTime)} = 11
  • ${#temporals.second(localDateTime)} = 35
  • ${#temporals.nanosecond(localDateTime)} = 312083500

url링크 

@GetMapping("link")
public String link(Model model){
    model.addAttribute("param1", "date1");
    model.addAttribute("param2", "date2");
    return "basic/link";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>URL 링크</h1>
<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>
</body>
</html>

URL 링크

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

http://localhost:8082/hello/date1?param2=date2 ==> () 이거는 자동으로 쿼리파라미터로 치환 , 그런데 param1은 경로 변수 되어있어서 경로변수로 치환되고, param2만 쿼리 파라미터로 됨. 

 


리터럴

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>리터럴</h1>
<ul>
    <!--주의! 다음 주석을 풀면 예외가 발생함-->
<!--     <li>"hello world!" = <span th:text="hello world!"></span></li>-->
    <li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li>
    <li>'hello world!' = <span th:text="'hello world!'"></span></li>
    <li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>
    <li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
</ul>
</body>
</html>

"hello"는 가능하지만

"hello wrold!"는 불가능함. 띄어쓰기 있으면 반드시 ' ' 로 감싸기. 

 


 

 

 

스프링 부트는 타임리프 템플릿 엔진을 스프링에 등록하고 타임리프용 뷰 리졸버를 스프링 빈으로 등록한다. 

Gradle은 타임리프와 관련된 라이브러리를 다운로드 받고, 스프링부트는 앞서 설명한 타임리프와 관련된 스프링 빈을 자동으로 등록해준다. 

 

입력 폼 처리

 

등록

@GetMapping("/add")
public String addForm(Model model){
    model.addAttribute("item", new Item());
    return "basic/addForm";
}

빈 객체 생성해서 넘겨주기. 거추장스러워 보이지만 빈객체 생성하는 것은 비용이 많이 들지 않음. 

<form action="item.html" th:action th:object="${item}" method="post">
    <div>
        <label for="itemName">상품명</label>
        <input type="text" id="itemName" th:field="*{itemName}" class="form-control"
               placeholder="이름을 입력하세요">
    </div>
    <div>
        <label for="price">가격</label>
        <input type="text" id="price" th:field="*{price}" class="form-control"
               placeholder="가격을 입력하세요">
    </div>
    <div>
        <label for="quantity">수량</label>
        <input type="text" id="quantity" th:field="*{quantity}" class="form-control"
               placeholder="수량을 입력하세요">
    </div>
    <hr class="my-4">
    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-primary btn-lg" type="submit">상품
                등록</button>
        </div>
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg"
                    onclick="location.href='items.html'"
                    th:onclick="|location.href='@{/basic/items}'|" type="button">취소</button>
        </div>
    </div>
</form>

th:object(form에서 사용할 객체 지정) th:filed 해서 넘긴 item을 사용하면 id와 name을 직접 똑같은 이름으로 지정해준다. '${item.itemName} == *{itemName}' 과 같음. 

 

 

수정

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 수정 폼</h2>
    </div>
    <form action="item.html" th:action th:object="${item}" method="post">
        <div>
            <label for="id">상품 ID</label>
            <input type="text" id="id" class="form-control" th:field="*{id}" readonly>
        </div>
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" class="form-control" th:field="*{itemName}">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" class="form-control" th:field="*{price}">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" class="form-control" th:field="*{quantity}">
        </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">저장
                </button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='item.html'"
                        th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|" type="button">취소</button>
            </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

 상품 수정 폼에서 필드 사용하면 id, name, value값을 모두 없앨수 있다. 자동으로 처리해줌. 

 


 

체크박스

package hello.itemservice.domain.item;

import lombok.Data;
import java.util.List;

@Data
public class Item {

    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;

    private Boolean open;   //판매여부
    private List<String> regions;   //등록지역
    private ItemType itemType;  //상품 종류
    private String deliveryCode;    //배송 방식

    public Item() {

    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

 

<!-- single checkbox -->
<div>판매 여부</div>
<div>
    <div class="form-check">
        <input type="checkbox" id="open" name="open" class="form-check-input">
        <label for="open" class="form-check-label">판매 오픈</label>
    </div>
</div>

체크박스를 선택 안하면 open이 false가 아니라 null이 넘어온다. -> 서버로 값 자체를 안넘김. 

<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" th:value="on" /> <!-- 히든 필드 추가 -->
<label for="open" class="form-check-label">판매 오픈</label>

히든 필드를 추가 하고 name앞에 '_'넣어주면 체크박스 선택이 안됐다는거를 알고 false로 설정. 

open에 값이 있다 > 체크박스 체크 > _open확인 안함 > open이 on이라서 true

open에 값이 없다 > 체크박스 체크 안함 > _open확인 > on이라는 결과가 넘어옴. > _open이 on이라서 false 

 

<div>
    <div class="form-check">
        <input type="checkbox" id="open" name="open" th:field="*{open}" class="form-check-input">
        <label for="open" class="form-check-label">판매 오픈</label>
    </div>
</div>

th:field사용하면 hidden도 자동으로 생기게 해줘서 처리함. 매우 편리!! 

 

@ModelAttribute("regions")  //모든 컨트롤러에 다 담김 model.addAttribute("regions", regions);
public Map<String, String> regions(){
    Map<String, String> regions = new LinkedHashMap<>();    //해쉬맵은 순서 보장 안함.
    regions.put("SEOUL", "서울");
    regions.put("BUSAN", "부산");
    regions.put("JEJU", "제주");
    return regions;
}
<!-- multi checkbox -->
<div>
    <div>등록 지역</div>
    <div th:each="region : ${regions}" class="form-check form-check-inline">
        <input type="checkbox" th:field="*{regions}" th:value="${region.key}"
               class="form-check-input">
        <label th:for="${#ids.prev('regions')}"
               th:text="${region.value}" class="form-check-label">서울</label>
    </div>
</div>
<!-- multi checkbox -->
  <div>
  <div>등록 지역</div>
  <div class="form-check form-check-inline">
  <input type="checkbox" value="SEOUL"
  class="form-check-input" id="regions1" name="regions"><input type="hidden" name="_regions" value="on"/>
  <label for="regions1"
  class="form-check-label">서울</label>
  </div>
  <div class="form-check form-check-inline">
  <input type="checkbox" value="BUSAN"
  class="form-check-input" id="regions2" name="regions"><input type="hidden" name="_regions" value="on"/>
  <label for="regions2"
  class="form-check-label">부산</label>
  </div>
  <div class="form-check form-check-inline">
  <input type="checkbox" value="JEJU"
  class="form-check-input" id="regions3" name="regions"><input type="hidden" name="_regions" value="on"/>
  <label for="regions3"
  class="form-check-label">제주</label>
  </div>
  </div>

멀티 체크 박스에서 중요한건 name은 같아야 하지만 id는 달라야 함. each루프는 임의로 아이디 뒤에 1,2,3 숫자를 붙여줌. 

 

라디오 버튼

<!-- radio button -->
<div>
    <div>상품 종류</div>
    <div th:each="type : ${itemTypes}" class="form-check form-check-inline">
        <input type="radio" th:field="*{itemType}" th:value="${type.name()}"
               class="form-check-input">
        <label th:for="${#ids.prev('itemType')}" th:text="${type.description}"
               class="form-check-label">
            BOOK
        </label>
    </div>
</div>
@ModelAttribute("itemTypes")
public ItemType[] itemTypes(){
    return ItemType.values();
}
package hello.itemservice.domain.item;

public enum ItemType {

    BOOK("도서"), ENUM("음식"), ETC("기타");

    private final String description;

    ItemType(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}

 

셀렉트 박스

@ModelAttribute("deliveryCodes")
public List<DeliveryCode> deliveryCodes(){
    List<DeliveryCode> deliveryCodes = new ArrayList<>();
    deliveryCodes.add(new DeliveryCode("FAST", "빠른 배송"));
    deliveryCodes.add(new DeliveryCode("NORMAL", "일반 배송"));
    deliveryCodes.add(new DeliveryCode("SLOW", "느린 배송"));

    return deliveryCodes;
}
package hello.itemservice.domain.item;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class DeliveryCode {

    private String code;
    private String displayName;
}
<!-- SELECT -->
<div>
    <div>배송 방식</div>
    <select th:field="*{deliveryCode}" class="form-select">
        <option value="">==배송 방식 선택==</option>
        <option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
                th:text="${deliveryCode.displayName}">FAST</option>
    </select>
</div>

 

블로그의 정보

kiwi

키위먹고싶다

활동하기