스프링과 타임리프는 일반적인 HTML 코드의 form 방식에 대한 문제점을 개선하고자 다음과 같은 방식을 제공한다.
앞서, 이 포스팅은 다음과 같은 Item 객체와 Controller를 사용한다고 정의한다.
public class Item {
private Boolean open;
}
@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:....";
}
<form action method="post">
<input type="checkbox" id="open" name="open">
</form>
위와 같은 상황일때 checkbox가 checked 상태로 전송 되었을때는 Item.open=true
log가 출력이 될 것이다. 실제 전송 데이터는 ...?open=on
과 같이 전송 되겠지만, 스프링 타입 컨버터가 Item 객체의 open 타입이 Boolean이면 true로 변환해준다. 일반적으로 위와 같은 상황일때는 문제 될 것이 없다.
하지만 일반적인 HTML 코드는 checked 상태가 아닐 경우에는 open 필드 자체가 서버로 전송되지 않는다. 만약 check를 하지 않고 폼 전송을 하였을 경우에는 Item.open=null
log가 출력될 것이다.
스프링에서는 이와 같은 문제점을 개선하기 위하여 다음과 같이 약간의 트릭을 사용하여 체크 해제를 인식할 수 있다.
<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를 받을 수 있다.
스프링에서는 위와 같은 경우를 다음과 같이 해석한다.
...?open=on&_open=on
와 같은 parameter로 전송되는데 open이 전송되었으면 _open은 무시하고 타입 컨버터가 true
로 변환하여 Item 객체에 저장한다....?_open=on
와 같은 parameter로 전송되는데 open이 parameter에 없고 _open만 있으니 타입 컨버터가 false
로 변환하여 Item 객체에 저장한다.앞서 알아본 상황을 타임리프를 적용할시 더욱 효과적으로 개선할 수 있다.
<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가지 문법을 추가한다.
*{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를 사용함으로써 많은 부분을 편리하게 축약할 수 있다.
또한 일반적인 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를 넣고 빼고를 처리 해준다.