타임리프 기본 문법
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>
'spring' 카테고리의 다른 글
Validation (0) | 2022.02.03 |
---|---|
메시지 국제화 (0) | 2022.02.01 |
스프링 mvc 웹 페이지 만들기 (0) | 2022.01.26 |
서블릿부터 스프링 mvc까지의 변화 과정 (2) | 2022.01.22 |
tomcat을 이용한 Servlet에 대한 설명과 spring에서의 사용 (0) | 2022.01.08 |
블로그의 정보
kiwi
키위먹고싶다