Thymeleaf 통합 (2) 체크 박스 - 단일(1)
체크 박스 - 단일1
단순 HTML 체크 박스
resources/templates/form/addForm.html 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<!-- 또 다른 <div> 요소로, 이 부분은 아래에 있는 체크박스를 묶는 역할. -->
<div class="form-check">
<!-- <div class="form-check">은 폼(form) 요소를 스타일링하고 구성하기 위한 CSS 클래스를 가진 <div> 요소. -->
<input type="checkbox" id="open" name="open" class="form-check-input">
<!-- <input> 요소는 사용자로부터 정보를 입력받는데 사용됩니다.
이 경우, type="checkbox" 속성을 가지고 있어 "체크박스".
id 속성은 고유한 식별자로 사용되며,
name 속성은 서버로 데이터를 전송할 때 사용되는 필드의 이름.
class 속성은 스타일링을 위한 CSS 클래스를 지정. -->
<label for="open" class="form-check-label">판매 오픈</label>
<!-- <label> 요소는 사용자 인터페이스의 라벨.
for 속성은 라벨과 연결된 입력 요소를 지정.
이 경우, "판매 오픈" 라벨은 id가 "open"인 체크박스와 연결되어
체크박스를 클릭할 때 라벨을 클릭한 것과 같은 효과.
class 속성은 라벨을 스타일링하기 위한 CSS 클래스를 지정. -->
</div>
</div>
resources/templates/form/addForm.html 전체 코드
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<!-- 부트스트랩 CSS 파일을 불러옵니다. -->
<link th:href="@{/css/bootstrap.min.css}" href="../css/bootstrap.min.css" rel="stylesheet">
<style>
/* 스타일 정의: .container 클래스에 최대 너비를 설정합니다. */
.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" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
</div>
<div>
<!-- 가격 입력 필드와 레이블 -->
<label for="price">가격</label>
<input type="text" id="price" name="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
</div>
<div>
<!-- 수량 입력 필드와 레이블 -->
<label for="quantity">수량</label>
<input type="text" id="quantity" name="quantity" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
</div>
<hr class="my-4">
<!-- 추가! -->
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input"> <!-- item의 open 필드 -->
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
<div class="row">
<div class="col">
<!-- 상품 등록 버튼 -->
<button class="w-100 btn btn-primary btn-lg" type="submit">상품 등록</button>
</div>
<div class="col">
<!-- 취소 버튼: 버튼 클릭 시 'items.html' 페이지로 이동합니다. -->
<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>
상품이 등록되는 곳에 다음과 같이 로그를 남겨서 값이 잘 넘어오는지 확인
FormItemController 추가
@Slf4j 클래스 레벨에 추가
@GetMapping("/add")
public String addForm(@ModelAttribute Item item, Model model) { // 추가
// 로깅 추가
log.info("item.open={}", item.getOpen());
model.addAttribute("item", new Item());
return "form/addForm";
}
실행하면
F12를 확인해보면
체크박스에 체크했더니 로그 찍어보니까 true로 나옴.
반대로
이렇게 넘기면
일단 저장은 되었고
뒤이어서 null 이 찍힌다.
open : on이 없음.
값 자체가 넘어가지 않음을 확인할 수 있음.
HTTP 요청 메시지 로깅
HTTP 요청 메시지를 서버에서 보고 싶으면 다음 설정을 추가하면 된다.
application.properties
logging.level.org.apache.coyote.http11=debug
이거 틀고 체크 하고 등록을 다시 진행해보면
반대로 체크 없이 등록을 진행하면
HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다.
수정의 경우에는 상황에 따라서 이 방식이 문제가 될 수 있다.
사용자가 의도적으로 체크되어 있던 값을 체크를 해제해도 저장 시 아무 값도 넘어가지 않기 때문에,
서버 구현에 따라서 값이 오지 않은 것으로 판단해서 값을 변경하지 않을 수도 있다.
이런 문제를 해결하기 위해서 스프링 MVC는 약간의 트릭을 사용하는데, 히든 필드를 하나 만들어서,
_open 처럼 기존 체크 박스 이름 앞에 언더스코어( _ )를 붙여서 전송하면 체크를 해제했다고 인식할 수 있다.
히든 필드는 항상 전송된다. 따라서 체크를 해제한 경우 여기에서 open 은 전송되지 않고, _open 만 전송되는데,
이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
addForm.html 에 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input"> <!-- item의 open 필드 -->
<!-- 히든 필드 추가-->
<input type="hidden" name="_open" value="on"/>
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
실행 다시 하면
로그는
문제없고,
한편 체크 안 하고 등록해보면
로그는
히든 필드는 무조건 전송됨을 확인할 수가 있음.
여기서 주목할 부분은 이거다.
null 이 아니라 false 가 들어가 있다는 점.
체크박스는 미 체크 시 아예 값을 안 넘기는 태생적 한계가 있다.
그래서 트릭처럼 하나 만들어서 사용하는 것.
실행 로그
체크 박스 체크
체크 박스 미체크
개발자는 true, false 를 보고 판단한다면 엄청나게 편리
그런데 할 때마다 내가 직접 히든 필드를 넣어줘야 한다.
이 마저도 타임리프가 해결하는 기능이 있다...!