티스토리 뷰
섹션2. 타임리프 기본기능(10개 강의, 1시간 39분)
1. 프로젝트 설정
- 이전강의(MVC 1편)에서 했던 프로젝트를 사용
2. 타임리프 스프링 통합
- 스프링과 통합을 위한 다양한 기능을 편리하게 제공.
스프링을 위해 개발된게 아닌가 싶을정도로 스프링 사용시 편하다
3. 입력 폼 처리
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
<!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" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">-->
<!--id label때문에 남겨둠-->
<input type="text" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
</div>
<div>
<label for="price">가격</label>
<!-- <input type="text" id="price" name="price" class="form-control" placeholder="가격을 입력하세요">-->
<input type="text" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
</div>
<div>
<label for="quantity">수량</label>
<!--<input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">-->
<input type="text" 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='@{/form/items}'|"
type="button">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>
- th:object="${item}" : 폼에서 사용할 컨트롤러에서 넘겨받은 객체를 지정
- th:field="*{itemName}"
- *{itemName} 요것은 ${item.itemName} 와 같다. 앞서 th:object 로 item 을 선택했기 때문에 선택 변수 식 적용가능
- 표시한거처럼 th:field 는 id , name , value 속성을 모두 자동으로 만들어줘서 편함
- edit 페이지도 수정
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "form/editForm";
}
<!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" name="id" class="form-control" value="1" th:value="${item.id}" readonly>-->
<input type="text" id="id" class="form-control" th:field="*{id}" readonly>
</div>
<div>
<label for="itemName">상품명</label>
<!--<input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}">-->
<input type="text" id="itemName" th:field="*{itemName}" class="form-control" >
</div>
<div>
<label for="price">가격</label>
<!--<input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}">-->
<input type="text" id="price" th:field="*{price}" class="form-control" >
</div>
<div>
<label for="quantity">수량</label>
<!--<input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}">-->
<input type="text" id="quantity" th:field="*{quantity}" class="form-control" >
</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='@{/form/items/{itemId}(itemId=${item.id})}'|"
type="button">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>
4. 요구사항 추가
- 판매 여부 : [판매 오픈 여부] 를 체크 박스로 단일 선택
- 등록 지역 : [서울, 부산, 제주] 를 체크 박스로 다중 선택
- 상품 종류 : [도서, 식품, 기타]를 라디오 버튼으로 단일 선택
- 배송 방식 : [빠른배송, 일반배송, 느린배송] 을 셀렉트 박스로 하나만 선택
5. 체크박스 단일 1.
<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>
- 판매여부(boolean)를 체크박스로 추가
- 문제점: 선택하면 true로 잘 넘어오지만 미선택시 null로 넘어온다
- HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 안보냄
- 수정하는 경우에는 상황에 따라서 이 방식이 문제가 될 수 있다(사용자가 의도적으로 체크되어 있던 값을 체크를 해제해도 저장시 아무 값도 넘어가지 않기 때문) 서버 구현에 따라서 값이 오지 않은 것으로 판단해서 값을 변경하지 않을 수도 있음
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input">
<!--hidden type 추가-->
<input type="hidden" name="_open" value="on">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
- 해당 문제를 해결하기 위한 스프링 MVC의 트릭
- 히든 필드를 하나 만들어서, _open 처럼 기존 체크 박스 이름 앞에 언더스코어( _ )를 붙여서 전송하면 체크를 해제했다고 인식할 수 있음
- 히든 필드는 항상 전송된다. 따라서 체크를 해제한 경우 여기에서 open 은 전송되지 않고, _open 만 전송되는데, 이 경우 스프링 MVC는 체크 를 해제했다고 판단
미선택시 false로 넘어옴
6. 체크박스 단일 2
- 타임리프 사용
<div>판매 여부</div>
<div>
<div class="form-check">
<!--<input type="checkbox" id="open" name="open" class="form-check-input">-->
<!--hidden type 추가-->
<!--<input type="hidden" name="_open" value="on">-->
<input type="checkbox" id="open" th:field="*{open}" class="form-check-input" >
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
타임리프가 제공하는 폼 기능을 사용하면 자동으로 처리할 수 있다.
- HTML 생성 결과를 보면 히든 필드 부분이 자동으로 생성됨 (체크 박스의 히든 필드와 관련된 부분도 함께 해결)
- 로그에도 false로 잘 넘어감
상품 상세, 수정 폼도 함께 수정
- 판매 여부를 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인
- 타임리프의 th:field 를 사용하면, 값이 true 인 경우 체크를 자동으로 처리해줘서 아주 간편하다
7. 체크박스 멀티
@ModelAttribute("regions")
public Map<String,String> regions(){
//공통으로 사용하는 부분, 어떤 컨트롤러를 호출해도 무조건 담김
Map<String, String> regions = new LinkedHashMap<>(); //순서보장위함
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
/// 지정네임 리턴값
// model.addAttribute("regions", regions);
}
- @ModelAttribute의 특별한 사용법
등록 폼, 상세화면, 수정 폼에서 모두 서울, 부산, 제주라는 체크 박스를 반복해서 보여주어야 한다.
이렇게 하려면 각각 의 컨트롤러에서 model.addAttribute(...) 을 사용해서 체크 박스를 구성하는 데이터를 반복해서 넣어주어야 한다.
@ModelAttribute는 이렇게 컨트롤러에 있는 별도의 메서드에 적용할 수 있다.
이렇게 하면 해당 컨트롤러를 요청할 때 regions 에서 반환한 값이 자동으로 모델( model )에 담기게 된다.
<!-- 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>
- label for= "id값" 에 지정된 id 가 checkbox 에서 동적으로 생성된 regions1 , regions2 , regions3 에 맞추어 순서대로 입력된 것을 확인가능
서울, 부산을 선택했을때
아무것도 선택 안했을때의 로그
상품 상세, 수정 폼도 함께 수정
8. 라디오 버튼
package hello.itemservice.domain.item;
public enum ItemType {
BOOK("도서"), FOOD("식품"), ETC("기타");
private final String description;
ItemType(String description) {
this.description = description;
}
public String description(){
return description;
}
}
- enum 사용
@ModelAttribute("itemTypes")
public ItemType[] itemTypes(){
return ItemType.values();
}
- itemTypes 를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 @ModelAttribute 사용
- ItemType.values() : 해당 ENUM의 모든 정보를 배열로 반환한다. ex) [BOOK, FOOD, ETC]
<div>
<div>상품 종류</div>
<div th:each="type : ${itemTypes}" class="form-check form-check-inline">
<input type="radio" th:field="${item.itemType}" th:value="${type.name()}" class="form-check-input" disabled>
<label th:for="${#ids.prev('itemType')}" th:text="${type.description}" class="form-check-label">
BOOK
</label>
</div>
</div>
- 선택한 식품( BOOK)에 checked="checked" 가 적용된 것 확인
9. 셀렉트 박스
@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;
}
DeliveryCode 라는 자바 객체를 사용하는 방법으로 진행하겠다. DeliveryCode 를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 @ModelAttribute 의 특별한 사용법을 적용하자. 참고: @ModelAttribute 가 있는 deliveryCodes() 메서드는 컨트롤러가 호출 될 때 마다 사용되므로 deliveryCodes 객체도 계속 생성된다. 이런 부분은 미리 생성해두고 재사용하는 것이 더 효율적이다.
10. 정리
'Thymeleaf' 카테고리의 다른 글
[Thymeleaf] 기본기능 (0) | 2024.01.24 |
---|
- Total
- Today
- Yesterday