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)
댓글 남기기