1. Thymeleaf 적용
spring boot 2.6.1.RELEASE 버전
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>nz.net.ultraq.thymeleaf</groupId> <artifactId>thymeleaf-layout-dialect</artifactId> <version>3.0.0</version> </dependency>
2 . 사용법
표현식
① 변수 : ${ }
② 객체 변숫값 : *{ }
③메시지 : #{ }
④ 링크 : @{ }
예1)
@Getter @Setter
public class Board {
private int no;
private String title;
private String writer;
private LocalDateTime updateTime;
Board(){}
public Board(int no, String title, String writer) {
this.no = no;
this.title = title;
this.writer = writer;
this.updateTime = LocalDateTime.now();
}
@Controller
public class ThymeController {
@RequestMapping("/")
String indexPage(Model model){
Board board = new Board(1, "테스트 제목", "이종민2");
model.addAttribute("board", board);
return "index";
}
}
<tbody>
<tr th:object="${board}">
<td><span th:text="*{no}"></span></td>
<td><span th:text="*{title}"></span></td>
<td><span th:text="*{writer}"></span></td>
<td><span th:text="${#temporals.format(board.updateTime, 'yyyy-MM-dd HH:mm')}"></span></td>
</tr>
</tbody>
table의 tbody 부분을 수정했습니다. $로 변수를 가져오고 *를 통해서 선택적 변수를 가져옵니다. 날짜의 경우 LocalDateTime을 변환하기 위해서
temporals개체를 이용했습니다. 이 외에도 다양한 개체들이 존재합니다.
#dates : java.util.Date를 다루기 위해 사용.
#calendars : #dates와 비슷하지만 java.util.Calendar를 위해 사용.
#number : 숫자 객체를 형식화하기 위해 사용.
#strings : String객체를 위해 사용.(contains, startsWith, prepending/appending 등)
#objects : 일반적인 객체를 다룬다.
#bools : boolean을 위해 사용.
#arrays : 배열을 위해 사용.
#lists : 리스트를 위한 유틸리티 메소드.
#sets : set을 위한 유틸리티 메소드.
#maps : map을 위한 유틸리티 메소드.
#aggreates : Array 또는 Collenction에서 집계를 위한 메소드.
#ids : 반복될 수 있는 id 속성을 처리.
예2)
@Getter @Setter
public class ItemFormDto {
private Long id;
@NotBlank(message = "상품명은 필수 입력 값입니다.")
private String itemNm;
@NotNull(message = "가격은 필수 입력 값입니다.")
private Integer price;
@NotBlank(message = "이름은 필수 입력 값입니다.")
private Integer itemDetail;
@NotNull(message = "재고는 필수 입력 값입니다.")
private Integer stockNumber;
private ItemSellStatus itemSellStatus;
//1. 상품 저장 후 수정할 때 상품 이미지 정보를 저장하는 리스트
private List<ItemImgDto> itemImgDtoList=new ArrayList<>();
//2.상품의 이미지 아이디를 저장하는 리스트. 상품 등록 시에는 아직 상품의 이미지를 저장하지 않았기
//때문에 아무 값도 들어가 있지 않고 수정 시에 이미지 아이디를 담아둘 용도로 사용
private List<Long> itemImgIds=new ArrayList<>();
private static ModelMapper modelMapper =new ModelMapper();
//modelMapper 를 이용하여 엔티티 객체와 DTO 객체 간의 데이터를 복사하여 복사한 객체를 반환해주는 메소드
public Item createItm(){
return modelMapper.map(this, Item.class);
}
//modelMapper 를 이용하여 엔티티 객체와 DTO 객체 간의 데이터를 복사하여 복사한 객체를 반환해주는 메소드
public static ItemFormDto of(Item item){
return modelMapper.map(item, ItemFormDto.class);
}
}
@Controller
public class ItemController {
@GetMapping(value = "/admin/item/new")
public String itemForm(Model model){
model.addAttribute("itemFormDto", new ItemFormDto());
return "/item/itemForm";
}
}
itemFormDto 각 ①변수 : ${ } 로 사용 itemDetail 이 ②객체 변숫값 : *{ } 로 사용
th:each="num: ${#numbers.sequence(1,5)} 의 each 문에서 ${num } ①변수 : ${ } 로 사용
<form role="form" method="post" enctype="multipart/form-data" th:object="${itemFormDto}">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">상품 상세 내용</span>
</div>
<textarea class="form-control" aria-label="With textarea" th:field="*{itemDetail}"></textarea>
</div>
<p th:if="${#fields.hasErrors('itemDetail')}" th:errors="*{itemDetail}" class="fieldError">Incorrect data</p>
<div th:if="${#lists.isEmpty(itemFormDto.itemImgDtoList)}">
<div class="form-group" th:each="num: ${#numbers.sequence(1,5)}">
<div class="custom-file img-div">
<input type="file" class="custom-file-input" name="itemImgFile">
<label class="custom-file-label" th:text="상품이미지 + ${num}"></label>
</div>
</div>
</div>
<div th:if="${#strings.isEmpty(itemFormDto.id)}" style="text-align: center">
<button th:formaction="@{/admin/item/new}" type="submit" class="btn btn-primary">저장</button>
</div>
#lists : 리스트를 위한 유틸리티 메소드.
#sets : set을 위한 유틸리티 메소드.
#fields
form 태그에 포함된 action속성 값을 th:action속성 값으로 치환할 수 있으며, th:object속성 값에 Model속성 이름을 지정하여 이 태그 안에서 *{필드이름} 형식으로 사용할 수 있다.
th:field="*{필드 이름}"을 설정하면 이 HTML필드와 폼 객체(여기서는 CustomerForm)에 포함된 필드를 연결할 수 있고 HTML필드 값이 폼 객체의 해당 필드로 설정된다. 반대로 폼 객체의 필드 값이 Model에서 HTML필드 값을 설정된다.
입력 검사에서 오류가 발견됐을 경우에는 th:errorclass속성 값이 class속성에 설정된다. 필드에 오류가 있을 경우에만 특정 태그를 표시하고 싶다면 해당 태그에 th:if="${#fields.hasErrors('필드이름')"을 설정한다. th:errors="*{필드이름}"을 설정하면 태그 안에 있는 문자열을 대상 필드에 관련된 에러 메시지로 치환할 수 있다.
${#numbers.sequence(1,5)}
타임리프의 유틸리티 객체 #numbers.sequence(start,end) 를 이용하면 start 부터 end 까지 반복 처리를 할 수 잇습니다.
num 에는 1부터 5까지 숫자가 할당됨
<!-- 상품 이미지 정보를 담고 있는 리스트가 비어 있다면 상품을 등록하는 경우-->
<div th:if="${#lists.isEmpty(itemFormDto.itemImgDtoList)}">
<div class="form-group" th:each="num: ${#numbers.sequence(1,5)}">
<div class="custom-file img-div">
<input type="file" class="custom-file-input" name="itemImgFile">
<label class="custom-file-label" th:text="상품이미지 + ${num}"></label>
</div>
</div>
</div>
링크 : @{ }
@{/admin/item/new}
※ data-th-text, th:text 둘 중 아무거나 사용해도 상관없음
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping(value ="/thymeleaf")
public class ThymeleafExController {
@GetMapping("/ex00")
public String indexPage(Model model) {
Map<String, Object> info = new HashMap<>();
info.put("name", "kim");
info.put("age", 29);
List<String> datas = new ArrayList<>();
datas.add("red");
datas.add("orange");
datas.add("yellow");
model.addAttribute("info", info);
model.addAttribute("datas", datas);
return "thymeleaf/ex00";
}
@GetMapping(value = "/ex01")
public String ex01(Model model){
model.addAttribute("data", "타임리프");
model.addAttribute("htmlData", "<h1>타임리프</h1>");
return "thymeleaf/ex01";
}
/**
* th:text 예제
* @param model
* @return
*/
@GetMapping(value = "/ex02")
public String ex02(Model model){
ItemDto itemDto =new ItemDto();
itemDto.setItemDetail("상품상세설명");
itemDto.setItemNm("테스트상품1");
itemDto.setPrice(1000);
itemDto.setRegTime(LocalDateTime.now());
model.addAttribute("itemDto", itemDto);
return "thymeleaf/ex02";
}
/**
* th:each 예제
* @param model
* @return
*/
@GetMapping(value = "/ex03")
public String ex03(Model model){
List<ItemDto> ItemDtoList =new ArrayList<>();
for(int i=0; i<=10; i++){
ItemDto itemDto =new ItemDto();
itemDto.setItemDetail("상품상세설명"+i);
itemDto.setItemNm("테스트상품"+i);
itemDto.setPrice(1000*i);
itemDto.setRegTime(LocalDateTime.now());
ItemDtoList.add(itemDto);
}
model.addAttribute("ItemDtoList" ,ItemDtoList);
return "thymeleaf/ex03";
}
/**
* th:if, th:unless 예제
* @param model
* @return
*/
@GetMapping(value = "/ex04")
public String ex04(Model model){
List<ItemDto> itemDtoList =new ArrayList<>();
for(int i=1; i<=10; i++){
ItemDto itemDto=new ItemDto();
itemDto.setItemDetail("상품상세설명"+i);
itemDto.setItemNm("테스트상품"+i);
itemDto.setPrice(1000*i);
itemDto.setRegTime(LocalDateTime.now());
itemDtoList.add(itemDto);
}
model.addAttribute("itemDtoList", itemDtoList);
return "thymeleaf/ex04";
}
/**
* th:switch, th:case 예제
* @param model
* @return
*/
@GetMapping(value = "/ex05")
public String ex05(Model model){
List<ItemDto> itemDtoList =new ArrayList<>();
for(int i=1; i<=10; i++){
ItemDto itemDto=new ItemDto();
itemDto.setItemDetail("상품상세설명"+i);
itemDto.setItemNm("테스트상품"+i);
itemDto.setPrice(1000*i);
itemDto.setRegTime(LocalDateTime.now());
itemDtoList.add(itemDto);
}
model.addAttribute("itemDtoList", itemDtoList);
return "thymeleaf/ex05";
}
/**
* th:href
* @return
*/
@GetMapping("/ex06")
public String ex06(String param1, String param2, Model model){
model.addAttribute("param1", param1);
model.addAttribute("param2", param2);
return "thymeleaf/ex06";
}
}
1)값을 그대로 표출할 때
<th:block th:text="${text}"></th:block>
or
<th:block data-th-text="${text}"></th:block
or
<span>[[ ${text} ]]</span>
2) ${...} 표현식
${...} 표현식을 이용해 컨트롤러에서 전달받은 변수에 접근할 수 있으며 th:속성명 내에서만 사용
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>상품 데이터 출력 예제</h1>
<div>
상품명 : <span th:text="${itemDto.itemNm}"></span>
</div>
<div>
상품상세설명 : <span th:text="${itemDto.itemNm}"></span>
</div>
<div>
상품등록일 : <span th:text="${itemDto.regTime}"></span>
</div>
<div>
상품가격 : <span th:text="${itemDto.price}"></span>
</div>
</body>
</html>
3) @{...} 표현식
@{...} 표현식은 서버의 contextPath를 의미하며 @{/} 는 "/contextPath/" 를 의미
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>th:if</h1>
<h3>param1 :<span th:text="${param1}"></span></h3>
<h3>param2:<span th:text="${param2}"></span></h3>
<div>
<a th:href="@{/thymeleaf/ex01}">예제1</a>
</div>
<div>
<a th:href="@{https://www.thymeleaf.org}">thyemleaf </a>
</div>
<div>
<a th:href="@{/thymeleaf/ex06(param1='데이터1' ,param2='데이터2')}">전송 </a>
</div>
</body>
</html>
4)html 태그가 들어가는 데이터 값을 그대로 삽입
<th:block data-th-utext="${text}"></th:block> <!--html 태그가 들어가는 데이터 값을 그대로 삽입하고 싶을 때-->
<th:block th:utext="${htmlData}"></th:block>
5) 문자 합치기
<div th:text="|My name is ${info.name} !! |"></div>
<div th:text="'My name is '+ ${info.name} + ' !! '"></div>
<span th:text="|${username}님 환영합니다|"></span>
6) 비교 연산자
a)
<!-- 이항 연산자 -->
<div th:text="${info.name != 'kim'}"></div>
<div th:text="${info.age >= 30}"></div>
<!-- 삼항 연산자 -->
<div th:text="${info.name == 'kim' ? 'ok' : 'no'}"></div>
b)
package com.shop.constant;
public enum ItemSellStatus {
SELL, SOLD_OUT
} <td th:text="${item.itemSellStatus == T(com.shop.constant.ItemSellStatus).SELL} ? '판매중' : '품절'"></td>
7) th:value
<input type='text' th:value="${info.name}">
8) th:if, th:unless
<th:block th:if="${info.age < 30}">당신은 30대가 아닙니다.</th:block>
<th:block th:unless="${info.age >= 30}">당신은 30대입니다.</th:block><th:block data-th-if="${test == null}"></th:block>
<th:block data-th-unless="${test == null}"></th:block><!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>th:if, th:unless 예제</h1>
<table border="1" width="1200">
<thead>
<tr>
<td width="10%">순번</td>
<td>상품명</td>
<td>상품설명</td>
<td width="15%">가격</td>
<td>상품등록일</td>
</tr>
</thead>
<tbody>
<tr th:each="itemDto, status:${itemDtoList}">
<td>
<span th:text="${status.index}"></span>
(<span th:if="${status.even}" th:text="짝수"></span>
<span th:unless="${status.even}" th:text="홀수"></span>)
</td>
<td th:text="${itemDto.itemNm}"></td>
<td th:text="${itemDto.itemDetail}"></td>
<td>
<!-- IF CUSTOMER IS ANONYMOUS -->
<div th:if="${itemDto.price} ==5000" th:text="오천원">
</div>
<!-- ELSE -->
<div th:unless="${itemDto.price}==5000" th:text="${itemDto.price}">
</div>
</td>
<td th:text="${itemDto.regTime}"></td>
</tr>
</tbody>
</table>
</body>
</html>
9) th:switch, th:case
<th:block th:switch="${info.name}">
<div th:case="lee"> 당신은 이씨 입니다.</div>
<div th:case="kim"> 당신은 김씨 입니다.</div>
</th:block><th:block th:switch="${username}">
<span th:case="홍길동">홍길동님 환영합니다</span>
<span th:case="길동이">길동님 환영합니다</span>
</th:block><!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>th:switch, th:case 예제</h1>
<table border="1">
<thead>
<tr>
<td width="10%">순번</td>
<td>상품명</td>
<td>상품설명</td>
<td>가격</td>
<td>상품등록일</td>
</tr>
</thead>
<tbody>
<tr th:each="itemDto, status:${itemDtoList}">
<td th:switch="${status.index}">
<span th:case="2">이번</span>
<span th:case="5">오번</span>
<span th:case="*"><span th:text="${status.index}"></span>
</span>
</td>
<td>[[${itemDto.itemNm}]]</td>
<td th:text="${itemDto.itemDetail}"></td>
<td th:text="${itemDto.price}"></td>
<td th:text="${itemDto.regTime}"></td>
</tr>
</tbody>
</table>
</body>
</html>
10) th:each
<th:block th:each="data:${datas}">
<h1 th:text="${data}"></h1>
</th:block>
변수명 앞에 status 변수를 추가해 row에 대한 추가정보를 얻을 수 있다.
<th:block th:each="data,status:${datas}">
<h1 th:text="|${status.count} ${data}|"></h1>
</th:block>
status 속성 index : 0부터 시작 count : 1부터 시작 size : 총 개수 current : 현재 index의 변수 event/odd : 짝수/홀수 여부 first/last : 처음/마지막 여부
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>th:each 예제</h1>
<table border="1">
<thead>
<tr>
<td>순번</td>
<td>상품명</td>
<td>상품설명</td>
<td>가격</td>
<td>상품등록일</td>
</tr>
</thead>
<tbody>
<tr th:each="itemDto, status:${ItemDtoList}">
<td th:text="${status.index}"></td>
<td th:text="${itemDto.itemNm}"></td>
<td th:text="${itemDto.itemDetail}"></td>
<td th:text="${itemDto.price}"></td>
<td th:text="${itemDto.regTime}"></td>
</tr>
</tbody>
</table>
</body>
</html>
11) and, or 구문 ( if 조건 여러개 가능 )
<th:block th:if="${view == null and view == ''}">
<th:block th:if="${view != '홍길동' or view != '길동'}">
<th:block data-th-if="${view == null and view == ''}">
<th:block data-th-if="${view != '홍길동' or view != '길동'}">
12) LIST 형식으로 리턴 된 값을 6번 출력
<span data-th-each="i : ${#numbers.sequence(0,5)}" data-th-if="${i < #lists.size(LIST)}" data-th-text="${LIST[i]?.title}"></span>
13) Thymeleaf 문자열 비교
<th:block data-th-if="${#strings.equals(userName, testName)}"></th:block>
14) Thymeleaf 문자열, 배열(array), 리스트(list), 셋(sets)이 비어 있는지 확인
${#strings.isEmpty(title)}
${#strings.arrayIsEmpty(array)}
${#strings.listIsEmpty(list)}
${#strings.setIsEmpty(set)}
15 )map으로 리턴한 menu에서 menuArea 키 값이 존재하는지 확인
<th:block th:if="${not #maps.isEmpty(menu)} and ${#maps.containsKey(menu, 'menuArea')}"></th:block>
16) LIST 반복문을 돌리면서 첫번째 값일 경우 active 클래스 추가
<div th:each="i : ${#numbers.sequence(0,1)}" th:if="${i < #lists.size(LIST)}" th:classappend="${i == 0} ? 'active':''">
</div>
17) data-th-with를 사용하면 변수와 변수 값 지정이 가능
<th:block th:with="SIZE='123', view='/main'">
<a th:href="${view}"></a>
</th:block>
18) style 지정
<div th:style="|background: url(${url}) background-size: cover|"></div>
19) 현재 날짜 표출
<span th:text="${#dates.format(#dates.createNow(), 'yyyy-MM-dd HH:mm:ss')}"></span>
20) secutiry 인증 정보 여부
( 타임리프에서 로그인, 로그아웃에 대한 이벤트를 줄 수 있다. )
<th:block sec:authorize="isAuthenticated()"> 로그인 정보 있음 </th:block> <th:block sec:authorize="isAuthenticated() == false"> 로그인 정보 없음 </th:block>
21) security에서 인증받은 객체가 존재하는지 확인
<th:block th:if="${#authorization.getAuthentication() != null and #authorization.expression('isAuthenticated()')}">
</th:block>
22) property, yml 에서 값 가져와서 사용
<a th:href="${@environment.getProperty('app.url.logout')}"></a>
23)
<form th:action="@{/customers/create}" th:object="${customerForm}" method="post">
<input type="text" id="lastName" name="lastName" th:field="*{lastName}" th:errorclass="error-input" value="민" />
<span th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" class="error-messages">error!</span>
<form>태그에 포함된 action속성 값을 th:action속성 값으로 치환할 수 있으며, th:object속성 값에 Model속성 이름을 지정하여 이 태그 안에서 *{필드이름} 형식으로 사용할 수 있다.
th:field="*{필드 이름}"을 설정하면 이 HTML필드와 폼 객체(여기서는 CustomerForm)에 포함된 필드를 연결할 수 있고 HTML필드 값이 폼 객체의 해당 필드로 설정된다. 반대로 폼 객체의 필드 값이 Model에서 HTML필드 값을 설정된다.
입력 검사에서 오류가 발견됐을 경우에는 th:errorclass속성 값이 class속성에 설정된다.
필드에 오류가 있을 경우에만 특정 태그를 표시하고 싶다면 해당 태그에 th:if="${#fields.hasErrors('필드이름')"을 설정한다. th:errors="*{필드이름}"을 설정하면 태그 안에 있는 문자열을 대상 필드에 관련된 에러 메시지로 치환할 수 있다.
참조)
https://bamdule.tistory.com/216
https://s-yeonjuu.tistory.com/6
https://effectivecode.tistory.com/1056
https://jongminlee0.github.io/2020/03/12/thymeleaf/
https://velog.io/@leyuri/Thymeleaf-thfield-therrorclass-%EC%86%8D%EC%84%B1

















댓글 ( 4)
댓글 남기기