타임리프는 스프링과 통합을 위한 다양한 기능을 편리하게 제공합니다.
스프링 통합으로 추가되는 기능들
- 스프링의 SpringEL 문법 통합
- ${@myBean.doSomething()} 처럼 스프링 빈 호출 지원
- 편리한 폼 관리를 위한 추가 속성
- th:object (기능 강화, 폼 커맨드 객체 선택)
- th:field , th:errors , th:errorclass
- 폼 컴포넌트 기능
- checkbox, radio button, List 등을 편리하게 사용할 수 있는 기능 지원
- 스프링의 메시지, 국제화 기능의 편리한 통합
- 스프링의 검증, 오류 처리 통합
- 스프링의 변환 서비스 통합(ConversionService)
입력 폼 처리
th:object | 커맨드 객체를 지정 |
*{...} | 선택 변수 식 (th:object 에서 선택한 객체에 접근) |
th:field | HTML 태그의 id , name , value 속성을 자동으로 처리 |
th:field 필드 사용시 렌더링 전과 후
- 렌더링 전
<input type="text" th:field="*{itemName}" />
- 렌더링 후
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />
렌더링 후를 보면 HTML 태그의 id , name , value 속성을 자동으로 처리 해준 것을 볼 수 있습니다.
등록 폼
th:object 를 적용하려면 먼저 해당 오브젝트 정보를 넘겨주어야 합니다.
등록 폼이기 때문에 데이터가 비어있는 빈 오브젝트를 만들어서 뷰에 전달합니다.
Item 객체
@Data
public class Item {
private Long id; // 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;
}
}
Controller
@Controller
@RequestMapping("/form/items")
@RequiredArgsConstructor
public class FormItemController {
private final ItemRepository itemRepository;
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
@PostMapping("/add")
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
return "redirect:/form/items/{itemId}";
}
}
addForm.html
<!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="itemName">상품명</label>
<input type="text" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
</div>
<div>
<label for="price">가격</label>
<input type="text" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
</div>
<div>
<label for="quantity">수량</label>
<input type="text" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
</div>
</form>
</div>
</body>
</html>
th:object="${item}" 은 <form>에서 사용할 객체를 지정합니다.
th:filed="*{itemName}"에서 *{...}은 선택 변수 식으로 지정한 객체에 접근합니다. ( 즉, ${item.itemName}과 같다. )
또한 th:filed는 id, name, value 속성을 다음과 같이 모두 자동으로 만들어 줍니다.
- id : th:field 에서 지정한 변수 이름과 같다. id="itemName"
- name : th:field 에서 지정한 변수 이름과 같다. name="itemName"
- value : th:field 에서 지정한 변수의 값을 사용. value=""
수정 폼
Controller에 아래 코드 추가
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "form/editForm";
}
editForm.html
<!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" th:field="*{id}" class="form-control" readonly>
</div>
<div>
<label for="itemName">상품명</label>
<input type="text" th:field="*{itemName}" class="form-control">
</div>
<div>
<label for="price">가격</label>
<input type="text" th:field="*{price}" class="form-control">
</div>
<div>
<label for="quantity">수량</label>
<input type="text" th:field="*{quantity}" class="form-control">
</div>
</form>
</div>
</body>
</html>
수정 폼을 보면 등록폼을 통해 등록된 객체의 value 값을 읽어오는 것을 볼 수 있습니다.
단일 체크 박스 추가
위에서 작성한 addForm과 editForm html에 아래 코드를 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" th:field="*{open}" class="form-check-input">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
th:field를 사용하지 않는다면 체크박스 체크를 하지 않았을 시 값이 Null로 넘어오는 것을 방지하기 위해
히든 필드를 추가해야하지만,
th:filed를 사용하면 체크 박스의 히든 필드와 관련된 부분도 함께 해결해줍니다.
멀티 체크 박스 추가
체크 박스를 멀티로 사용해, 하나 이상을 체크할 수 있도록 할 수 있습니다.
Controller에 아래 코드 추가
@ModelAttribute("regions")
public Map<String, String> regions() {
Map<String, String> regions = new LinkedHashMap<>();
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
}
우리는 지금 등록 폼, 수정 폼 모두 이 체크 박스를 표시해야합니다.
하지만 중복된 코드를 복붙해서 해당 메서드마다 추가하는 것은 상당히 비효율적입니다.
그래서 이 코드에서 @ModelAttribute이 특별하게 사용되었는데
해당 컨트롤러를 호출할 때 regions 에서 반환한 값이 해당 컨트롤러에 있는 어떤 메서드가 호출되던지간에 먼저 이 메서드가 호출되어 모델 객체가 생성되고 자동으로 모델( Model )에 담기게 됩니다.
위에서 작성한 addForm과 editForm html에 아래 코드를 추가
<!-- 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>
- ${region.key}, ${region.value}
타임리프에서 map에 대한 key, value가 예약어로써 Map타입의 데이터 처리를 지원해주기 때문에 ${region.key}, ${region.value}로 map의 key,value값에 접근할 수 있습니다.
- th:for="${#ids.prev('regions')}"
멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 지만, 이렇게 반복해서 HTML 태그를 생성할 때, 생성된 HTML 태그 속성에서 name 은 같아도 되지만, id 는 모두 달라야 합니다.
따라서 타임리프는 체크박스를 each 루프 안에서 반복해서 만들 때 임의로 1 , 2 , 3 숫자를 뒤에 붙여줍니다.
- th:filed와 th:value를 함께 사용한 이유 ?
앞에서 th:filed를 지정하면 HTML 태그의 id , name , value 속성을 자동으로 지정해준다고 했습니다.
하지만 멀티 체크박스가 사용되는 경우에는 th:value를 따로 지정해주어야 합니다.
그 이유는 단일 값이 아닌 여러 값이 있기 때문에 th:field="*{regions}"의 값과 th:value="${region.key}"의 값이 일치하는지 확인합니다.
그래서 일치하면 체크박스를 체크처리 해주게 됩니다.
라디오 버튼 추가
라디오 버튼은 여러 선택지 중에 하나만 선택할 때 사용할 수 있습니다.
ItemType Enum
public enum ItemType {
BOOK("도서"),
FOOD("음식"),
ETC("기타");
private final String description;
ItemType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
Controller에 아래 코드 추가
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() { // enum은 values를 반환하면 value 값들을 배열로 넘겨준다.
return ItemType.values();
}
여기서도 @ModelAttribute를 특별하게 사용했는데, enum을 values로 반환해 enum의 value 값들을 배열로 Model 넘겨줬습니다.
위에서 작성한 addForm과 editForm html에 아래 코드를 추가
<!-- 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"></label>
</div>
</div>
- ${type.name()}
.name은 enum의 name을 그대로 가져올 수 있습니다. (여기서는 BOOK, FOOD, ETC)
- th:for="${#ids.prev('itemType')}"
멀티 체크박스 처럼 여러 선택지가 있기 때문에 필수로 들어가야합니다.
셀렉트 박스 추가
여러 목록 리스트 중에 하나를 선택하는 셀렉트 박스 입니다.
Controller에 아래 코드 추가
@ModelAttribute("deliveryCodes")
public Map<String, String> deliveryCodes() {
Map<String, String> deliveryCodes = new LinkedHashMap<>();
deliveryCodes.put("FAST", "빠른 배송");
deliveryCodes.put("NORMAL", "일반 배송");
deliveryCodes.put("SLOW", "느린 배송");
return deliveryCodes;
}
위에서 작성한 addForm과 editForm html에 아래 코드를 추가
<!-- SELECT -->
<div>
<div>배송 방식</div>
<select th:field="*{deliveryCode}" class="form-select">
<option value="">==배송 방식 선택==</option>
<option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.key}"
th:text="${deliveryCode.value}"></option>
</select>
</div>
- <option value="">==배송 방식 선택==</option>
아무 것도 선택하지 않을 때 나오는 기본 문구 입니다.
- <option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.key}" th:text="${deliveryCode.value}"></option>
FAST, NORMAL, SLOW 여러 옵션이 있기 때문에 each 반복문을 통해, deliveryCode.key와 일치하는 deliveryCode.value를 추가합니다.
'◼ Thymeleaf' 카테고리의 다른 글
[Thymeleaf] 타임리프 header, footer 레이아웃 적용하기 (간단 버전) (0) | 2023.05.30 |
---|---|
[Thymeleaf] 메시지 적용하기 (0) | 2023.04.04 |
[Thymeleaf] 템플릿 레이아웃 적용하기 (0) | 2023.04.03 |
Thymeleaf(타임리프)란 ? 타임리프의 기본 기능알아보기 (0) | 2023.04.02 |