Spring boot에 관련해서 조금 더 공부를 하다가 Thymeleaf에서 Form을 작성할 때
훨씬 수월하고 깔끔하게 작성할 수 있는 방식에 대해서 알게 되었는데 그냥 그렇구나 하고 넘어갈 뻔하다가
블로그에 조그마한 것이라도 정리하는 습관을 다시 들이기 위해 올리기로 했다.
상황 설명
쇼핑몰 서비스를 제공한다고 가정했을 때, 상품을 등록하는 과정에서 체크박스가 사용되는 경우가 많을 텐데
이것을 Spring boot와 Thymeleaf를 통합하여 간단하게 구현할 수 있는 방법을 알아보자
상품
@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;
}
}
우선 위와 같이 상품 Class를 준비해야 Thymeleaf와 원활한 Form을 만들 수 있다.
✅ 단일 체크박스
HTML에서 단일 체크박스를 사용할 때 체크를 한다면 상관이 없지만 체크를 하지 않으면 체크박스의 값이 null로 넘어가는 상황이 연출되는데 Thymeleaf는 아래와 같은 방법으로 해결할 수 있다.
⏹️ controller
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
먼저 Controller에서 model을 통해 HTML에 빈 Item 객체를 넘겨준다.
⏹️ HTML
<!-- form에 th:object 태그를 활용해 전달 받은 객체를 활용한다. -->
<form action="item.html" th:action th:object="${item}" method="post">
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<!-- th:field를 사용해 item 객체의 자원을 사용할 수 있다. -->
<!-- *{open} == ${item.open} (여기서 item은 th:object를 통해 받은 인자) -->
<input type="checkbox" id="open" th:field="*{open}" class="form-check-input">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
</form>
위 코드의 주석을 보면 th:object와 th:field를 통해 객체를 전달받고 해당 객체의 자원을 사용할 수 있다.
이러면 체크박스를 선택하면 value로 true, 선택하지 않으면 null이 아닌 value: false를 전달하게 된다.
✅ 멀티 체크박스
멀티 체크박스의 경우에는 여러 항목이 있을 때 체크 박스 만들다 보면 Id, name 등을 지정할 때 귀찮기도 하고
잘못하면 실수해서 name이 동일하다는 둥 오류가 발생할 수 있다.
이런 경우에도 Thymeleaf와 함께라면 문제가 없다.
⏹️ 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;
}
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
@ModelAttribute를 이용하면 특정 Controller에 모든 Mapping에 대해서 원하는 데이터를 넘기도록 할 수 있다.
위처럼 지역 정보를 원하는 만큼 작성해서 넘겨주어 HTML에서 전달받는다.
⏹️ HTML
<form action="item.html" th:action th:object="${item}" method="post">
<!-- multi checkbox -->
<div>
<div>등록 지역</div>
<div th:each="region : ${regions}" class="form-check form-check-inline">
<!-- th:field에 적용된 regions는 위 th:each의 regions가 아닌 th:object의 regions -->
<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>
</form>
위에서 설명했던 것과 마찬가지로 th:object와 th:field를 이용하고
반복적으로 만들어야 하기 때문에 th:each를 사용하며 label에 th:for을 이용하면 자동으로 id가 생성된다.
⏹️ 실행결과
<!-- 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>
✅ 라디오 버튼
라디오 버튼도 멀티 체크박스와 로직은 동일하기 때문에 Controller는 생략
⏹️ HTML
<form action="item.html" th:action th:object="${item}" method="post">
<!-- 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>
</form>
input type이 radio인 것을 제외하면 멀티 체크박스와 다른 점이 없다.
⏹️ 실행결과
<!-- radio button -->
<div>
<div>상품 종류</div>
<div class="form-check form-check-inline">
<input type="radio" value="BOOK" class="form-check-input" id="itemType1"
name="itemType">
<label for="itemType1" class="form-check-label">도서</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" value="FOOD" class="form-check-input" id="itemType2"
name="itemType" checked="checked">
<label for="itemType2" class="form-check-label">식품</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" value="ETC" class="form-check-input" id="itemType3"
name="itemType">
<label for="itemType3" class="form-check-label">기타</label>
</div>
</div>
실행 결과를 확인하면 선택된 항목에 checked 태그가 붙은 것을 확인할 수 있다.
✅ 셀렉트 박스
⏹️ HTML
<form action="item.html" th:action th:object="${item}" method="post">
<!-- 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>
</form>
⏹️ 실행결과
<!-- SELECT -->
<div>
<div>배송 방식</div>
<select class="form-select" id="deliveryCode" name="deliveryCode">
<option value="">==배송 방식 선택==</option>
<option value="FAST" selected="selected">빠른 배송</option>
<option value="NORMAL">일반 배송</option>
<option value="SLOW">느린 배송</option>
</select>
</div>
셀렉트 박스도 실행되면 선택 된 항목에 자동으로 selected 태그가 붙는 것을 확인할 수 있다.
✅ 마무리
Spring boot와 Thymeleaf를 함께 사용하는 것이 add Form을 작성할 때는 그렇게 와닿지 않을 수 있지만
상세조회 페이지나 수정 페이지를 만들다 보면 얼마나 편한지 모른다.
Controller에서 전달받은 데이터를 직접 조건문을 통해 나누다 보면 1, 2개 일 경우는 상관없지만 개수가 많아지면
진짜 난리가 난다...;;
하지만 이런 식으로 통합해 주면 Thymeleaf가 알아서 선택해 주고 name 적어주니 얼마나 편한지 모르겠다.
미리 알았더라면 이전에 일하던 프로젝트를 더 쉽게 할 수 있었을 텐데 역시 배움은 끝이 없는 것 같다.
'Java > Spring Boot' 카테고리의 다른 글
HttpSessionListener를 이용한 중복 로그인 방지 (0) | 2024.04.30 |
---|---|
Bean Validation (1) | 2024.02.26 |
Ajax를 통해 파일과 Json 업로드 후 Controller로 받기 (0) | 2023.05.12 |
Spring boot Interceptor Ajax 체크 (Redirect 이슈) (0) | 2023.04.05 |
Spring boot RestAPI 파일 다운로드 (0) | 2023.02.10 |