Spring과 Thymeleaf CheckBox

gwjeon·2021년 11월 15일
1


학습참조
인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)


스프링과 타임리프는 일반적인 HTML 코드의 form 방식에 대한 문제점을 개선하고자 다음과 같은 방식을 제공한다.
앞서, 이 포스팅은 다음과 같은 Item 객체와 Controller를 사용한다고 정의한다.

⭐ Item

public class Item {
	private Boolean open;
}

⭐ Controller


@GetMapping("...")
public String ...(@ModelAttribute Item item) {
	model.addAttribute("item", new Item());
    return "...";
}

@PostMapping("...")
public String ...(@ModelAttribute Item item) {
	log.info("Item.open={}", open);
    return "redirect:....";
}

✅ 일반적인 HTML 코드로 checkBox를 form에 담아 전송했을때

⭐ HTML

<form action method="post">
<input type="checkbox" id="open" name="open">
</form>

위와 같은 상황일때 checkbox가 checked 상태로 전송 되었을때는 Item.open=truelog가 출력이 될 것이다. 실제 전송 데이터는 ...?open=on과 같이 전송 되겠지만, 스프링 타입 컨버터가 Item 객체의 open 타입이 Boolean이면 true로 변환해준다. 일반적으로 위와 같은 상황일때는 문제 될 것이 없다.

하지만 일반적인 HTML 코드는 checked 상태가 아닐 경우에는 open 필드 자체가 서버로 전송되지 않는다. 만약 check를 하지 않고 폼 전송을 하였을 경우에는 Item.open=null log가 출력될 것이다.

✔ hidden 사용

스프링에서는 이와 같은 문제점을 개선하기 위하여 다음과 같이 약간의 트릭을 사용하여 체크 해제를 인식할 수 있다.

⭐ HTML

<form action method="post">
<input type="checkbox" id="open" name="open">
<input type="hidden" name="_open" value="on"> <!-- 히든 필드 추가 -->
</form>

위와 같이 input hidden 타입을 추가하고 기존 checkBox의 name에 언더스코어( _ )를 추가한 태그를 생성한후 전송한다면 check가 되어 있지 않은 상태에서도 false를 받을 수 있다.

스프링에서는 위와 같은 경우를 다음과 같이 해석한다.

checked 상태인 경우
  1. ...?open=on&_open=on와 같은 parameter로 전송되는데 open이 전송되었으면 _open은 무시하고 타입 컨버터가 true로 변환하여 Item 객체에 저장한다.
checked 상태가 아닌 경우
  1. ...?_open=on와 같은 parameter로 전송되는데 open이 parameter에 없고 _open만 있으니 타입 컨버터가 false로 변환하여 Item 객체에 저장한다.

✅ 타임리프를 사용 했을때 마법의 th:object, th:field

앞서 알아본 상황을 타임리프를 적용할시 더욱 효과적으로 개선할 수 있다.

⭐ HTML

<form action method="post" th:object="${item}"> <!-- (1) -->
<input type="checkbox" th:field="*{open}"> <!-- (2) -->
<!-- <input type="checkbox" id="open" name="open"> -->
<!-- <input type="hidden" name="_open" value="on"> --> <!-- 히든 필드 추가 -->
</form>

위와 같이 2가지 문법을 추가한다.

  • (1): th:object를 사용하여 커맨드 객체를 추가한다. 커맨드 객체란 스프링과 타임리프의 통합기능을 구현한 객체이며 form 태그에서 사용될 data들의 객체이다.
  • (2): checkbox 태그에 th:field를 사용한다. *{open}이라고 입력할 경우 커맨드 객체의 ${item.open}를 입력한 것과 같은 의미이며 축약하여 *{open}라고 입력했다.

다음과 같이 작성한 다음 타임리프를 통해 랜더링후 HTML 코드를 확인해보면 다음과 같은 결과를 확인할 수 있다.

<!--<input type="checkbox" id="open" name="open">-->
<input type="checkbox" id="open1" name="open" value="true"><input type="hidden" name="_open" value="on"/>
<!--<input type="hidden" name="_open" value="on">--> <!-- 히든 필드 추가 -->

잘 보면 hidden 타입 태그가 추가된 것을 볼 수 있다. 타임리프가 랜더링하면서 스스로 hidden 타입 태그를 추가하여 랜더링한다. 앞서 hidden 타입 태그를 추가한 경우와 같은 결과를 보여준다. 또한 id와 name까지 자동으로 생성 해준다. th:field를 사용함으로써 많은 부분을 편리하게 축약할 수 있다.

✔ checked 속성

또한 일반적인 HTML checkbox 태그는 다음과 같이 값에 상관 없이 checked라는 속성을 가지고 있을 경우 무조건 checked 상태가 된다.

<input type="checkbox" checked="checked">
<input type="checkbox" checked>

위 두가지 라인 모두 check 상태가 된다. 때문에 개발자가 if문을 통해 어떠한 값에 의하여 checked 속성을 넣을지, 뺄지에 대한 로직을 추가로 넣어줘야 한다. 개발자로써는 굉장히 번거로운 일이다.

하지만 이러한 경우도 타임리프에서 지원하는 th:field를 사용한다면 true와 false를 이용한 check 상태를 쉽게 관리할 수 있다. 아래는 form 태그에 속하는 input이 아닌 경우라고 가정하고 th:object를 가지는 커맨드 객체가 없으니 *{...}문법이 아닌 일반적인 ${...}문법을 사용했다.

<input type="checkbox" th:field="${item.open}">

위와 같이 코드를 구성한다면 타임리프를 통해 랜더링된후 확인할 수 있는 HTML 코드는 다음과 같다.

<input type="checkbox" id="open1" name="open" value="true" checked="checked">

th:field를 이용했기 때문에 id와 name, value가 자동적으로 추가됐고, ${item.open}의 값이 true이냐 false냐에 따라서 checked를 넣고 빼고를 처리 해준다.

profile
ansuzh

0개의 댓글