참고
Spring Boot & Thymeleaf 시리즈는 김영한 님의 "스프링 MVC 2편 - 백엔드 웹 개발 활용 기술" 강의를 정리한 글입니다. 글에 첨부된 사진은 해당 강의의 강의 자료에서 캡쳐한 것입니다. 제 Github에만 올려뒀다가, 정보 공유와 강의 홍보(?)를 위해 블로그에도 업로드합니다.
마크다운 형식으로 작성한 글을 블로그에 다시 올리는 거라 가독성이 많이 떨어집니다. 조금더 편하게 보시려면 아래의 Github repository에서 보시면 됩니다.
타임리프 - 체크 박스 단일 선택
단순 HTML 체크 박스 - 타임리프 미적용
타임리프를 적용하지 않은 단순 HTML 체크 박스를 알아보자.
resources/templates/form/addForm.html 내용 추가
<hr class="my-4">
<!-- single checkbox -->
<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>
FormItemController.java 내용 추가
//생략
@Slf4j
public class FormItemController {
@PostMapping("/add")
public String addItem(@ModelAttribute Item item,
RedirectAttributes redirectAttributes) {
log.info("item.open={}", item.getOpen());
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/form/items/{itemId}";
}
//생략...
}
@Slf4j
어노테이션을 추가했다.log.info("item.open={}", item.getOpen());
open
이라는 파라미터 값이 잘 들어와서item
객체에 저장되어있는지 출력해본다.
application.properties
logging.level.org.apache.coyote.http11=debug
- 모든 HTTP 요청 메시지를 로그로 출력한다.
- 폼의 입력 값이 잘 넘어오는지 확인할 수 있다.
실행 결과 로그
FormItemController : item.open=true //체크 박스에 체크한 경우
FormItemController : item.open=null //체크 박스에 체크 안 한 경우
- 체크 박스에 체크하면 HTML Form에서
open=on
이라는 값을 넘긴다. - Spring은
on
이라는 값을 boolean 타입인true
값으로 변환해준다.- Spring의 타입 컨버터가 이 기능을 수행한다. 자세한 내용은 뒤에서 설명한다.
체크 여부에 따른 HTTP 요청 메시지 바디
itemName=asd&price=123&quantity=1&open=on] //체크 박스에 체크한 경우
itemName=asd&price=123&quantity=1] //체크 박스에 체크 안 한 경우
- 체크하지 않은 경우 아예
open
파라미터가 존재하지 않는다!
주의! - 순수 HTML 체크 박스에서 체크를 하지 않는 경우
- 순수 HTML Form에서 체크 박스에 체크를 하지 않고 입력 값을 전송하면, 아예 해당 파라미터가 넘어가지 않는다!
- 즉 여기서는
open
이라는 파라미터 자체가 서버로 전송되지 않는다! - 그래서
item.open=null
이 출력되는 것이다. - 상황에 따라서 이 방식이 문제가 될 수 있다.
- 처음부터 체크가 되어있는 체크박스가 있다고 하자.
- 사용자가 의도적으로 체크를 해제해도, 폼 입력값 전송 시 해당 체크박스에 대한 값이 아예 넘어가지 않는다.
- 따라서 서버는 값이 넘어오지 않는 경우를 감지하고, 체크 해제에 대한 올바른 동작을 추가로 처리해주어야 한다. 그렇지 않으면 값이 변경되지 않을 것이다.
- 우리가 원하는 것은
open=off
라던가open=false
같이 체크를 하지 않았다는 사실을 명확히 알 수 있는 어떤 값이 넘어오는 것이다.- 그렇게 되면 다른 로직을 추가하지 않고 단순히 값을 덮어 씌우는 것으로 해결이 가능해진다.
해결 방법 - Spring MVC의 히든 필드
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on" />
- 이런 문제를 해결하기 위해서 스프링 MVC는 히든 필드를 사용하는 방법을 제공한다.
name=_open
처럼 원하는 체크 박스의name
속성 값 앞에 언더스코어( _ )를 붙인 히든 필드를 만든다.- 히든 필드는 화면에 보이진 않지만, 값이 항상 전송된다.
- 따라서 체크 박스의 체크를 해제한 경우
open=on
은 전송되지 않고,_open=on
만 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
체크 여부에 따른 HTTP 요청 메시지 바디
itemName=asd&price=123&quantity=1&open=on&_open=on] //체크 박스에 체크한 경우
itemName=asd&price=123&quantity=1&_open=on] //체크 박스에 체크 안 한 경우
open=on&_open=on
- 체크 박스에 체크하면 스프링 MVC가
open
파라미터 값이 존재하는 것을 확인하고 사용한다. - 이때
_open
은 무시한다.
- 체크 박스에 체크하면 스프링 MVC가
_open=on
- 체크 박스를 체크하지 않으면 스프링 MVC가
_open
파라미터만 있는 것을 확인한다. 이 때 스프링 MVC는 체크 박스가 체크되지 않았다고 인식한다. - 이 경우 서버에서 해당 값을 찍어보면
null
이 아니라false
가 정상적으로 출력되는 것을 확인할 수 있다.
- 체크 박스를 체크하지 않으면 스프링 MVC가
체크 박스 - 타임리프 적용
개발할 때 마다 이렇게 히든 필드를 추가하는 것은 상당히 번거롭다. 타임리프가 제공하는 폼 기능을 사용하면 히든 필드를 알아서 만들어준다.
form/addForm.html 내용 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<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="${item}"
속성이 있기 때문에*{...}*
으로 해당 오브젝트의 멤버 변수에 접근이 가능하다.- 물론
${item.open}
으로 접근해도 상관 없다.
- 물론
th:field="*{open}*"
속성을 적용했다.
타임리프 렌더링 결과
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" class="form-check-input" name="open" value="true"><input type="hidden" name="_open" value="on"/>
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
th:field="*{open}"
에 의해 다음과 같은 내용이 추가되었다.name="open" value="true"
이 추가되었다. 체크박스에 체크를 한 뒤 내용을 전송하면open=true
형태로 전송된다.<input type="hidden" name="_open" value="on"/>
- 히든 필드가 추가되어있다.
실행 로그
FormItemController : item.open=true //체크 박스를 선택하는 경우
FormItemController : item.open=false //체크 박스를 선택하지 않는 경우
- 아까와 달라진 것은 타임리프가 히든 필드를 알아서 추가해주는 것 정도밖에 없기 때문에 결과도 동일하게 잘 나온다.
상품 상세에 적용
상품 상세 화면에서 해당 상품의 판매 여부가 체크 박스 형태로 나타나도록 기능을 추가해보자.
form/item.html 내용 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" th:field="${item.open}" class="form-check-input" disabled>
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
- 마찬가지로
th:field
속성을 사용했다. - 여기서는
<form>
태그에th:object="${item}"
속성이 없으므로*{...}
를 사용할 수 없다. disabled
속성으로 체크 박스의 체크 여부를 변경할 수 없게 해야 한다.
타임리프 렌더링 결과
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" class="form-check-input" disabled name="open" value="true" checked="checked">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
- 상품 등록 시 판매 여부 체크 박스에 체크를 해서 등록하면, 상품 상세 화면에서
checked="checked"
속성이 추가되는 것을 확인 할 수 있다.- 체크 박스는
checked
속성의 값과 관계 없이 해당 속성이 존재하기만 하면 체크 표시가 된다. - 따라서 타임리프의 도움 없이 이런 부분을 처리하려면 매우 번거롭다.
th:field
를 사용하면 변수의 값이true
인 경우checked
속성을 만들고, 없으면 만들지 않는다.
- 체크 박스는
name="open" value="true"
도 추가되어있는 것을 확인할 수 있다.disabled
옵션에 의해 타임리프가 히든 필드를 추가하지 않은 것으로 보인다.
참고
앞에서 배운th:checked="${item.open}"
으로 체크 표시를 할 수도 있다. 단 이때는name
과value
속성은 따로 추가해줘야 한다.
그런데 상품 상세에서는disabled
속성에 의해 체크 여부를 변경할 수 없으므로name
속성과value
속성이 따로 필요 없긴 하다.
상품 수정에 적용
form/editForm.html 내용 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" th:field="*{open}" class="form-check-input">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
- 상품 등록에서 적용한 코드와 완전히 동일하다.
타임리프 렌더링 결과
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" class="form-check-input" name="open" value="true" checked="checked"><input type="hidden" name="_open" value="on"/>
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
item.getOpen()
의 값이true
인 경우checked="checked"
속성이 추가되는 것 말고는 상품 등록의 경우와 완전히 동일하다.name="open" value="true"
속성이 추가되어있다.- 히든 필드가 추가되어있다.
주의: ItemRepository의 update() 코드 추가 필요
판매 여부 수정 내용이 실제로 적용되기 위해서는 open
멤버 변수의 값이 실제로 바뀌어야한다. 하지만 기존의 ItemRepository
의 update()
메소드에는 값을 변경하는 코드가 없었다. 따라서 다음과 같이 코드를 변경해야 한다.
ItemRepository 의 update()
public void update(Long itemId, Item updateParam) {
Item findItem = findById(itemId);
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
findItem.setOpen(updateParam.getOpen());
findItem.setItemType(updateParam.getItemType());
findItem.setDeliveryCode(updateParam.getDeliveryCode());
findItem.setRegions(updateParam.getRegions());
}
open
외에도 추가된 멤버 변수들이 있으므로 코드를 미리 추가해줬다.
참고: 체크박스에서
th:field
의id
속성 추가
체크 박스에서th:field
를 사용했을 때,id
속성이 없으면 타임리프가 알아서id
속성을 만들어 준다. 이 때 체크 박스는 다중 선택이 가능하므로 만약th:field=*{open}
이렇게 하면id="open1"
이런식으로 숫자를 추가해서id
속성을 만든다.
이 내용에 대해서는 체크박스 다중 선택 챕터에서 자세히 알아보자.
'Spring > Spring Boot & Thymeleaf' 카테고리의 다른 글
[Spring Boot & Thymeleaf] 22. 라디오 버튼 (0) | 2022.10.24 |
---|---|
[Spring Boot & Thymeleaf] 21. 체크 박스 다중 선택 (0) | 2022.10.24 |
[Spring Boot & Thymeleaf] 19. 체크 박스, 라디오 버튼, 셀렉트 박스 (1) (0) | 2022.10.24 |
[Spring Boot & Thymeleaf] 18. 타임리프 입력 폼 (0) | 2022.10.24 |
[Spring Boot & Thymeleaf] 17. 스프링 통합과 HTML Form (0) | 2022.10.24 |
댓글