체크 박스 - 멀티 (regions)
@ModelAttribute의 특별한 사용법
@ModelAttribute("regions")
public Map<String, String> regions(){
Map<String, String> regions = new LinkedHashMap<>();
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
}
등록 폼, 상세화면, 수정 폼에서 모두 서울, 부산, 제주라는 체크 박스를 반복해서 보여주어야 한다. 이렇게 하려면 각각의 컨트롤러에서 model.addAttribute(...) 을 사용해서 체크 박스를 구성하는 데이터를 반복해서 넣어주어야 한다.
즉, 같은 데이터가 여러 모델에 담겨야 한다.
@ModelAttribute 는 컨트롤러에 있는 메서드에 적용할 수 있다. 이렇게 하면 해당 컨트롤러를 요청할 때 이 메서드에서 반환한 값이 자동으로 모델( model )에 담기게 된다.
*동적으로 변경되지 않는 값이라면 static 영역에 미리 선언해두고 사용하는 것이 성능상 좋다. 위의 경우, 어찌 됐든 메서드가 호출되는 과정이 포함되기 때문. (큰 영향은 아님)
타임리프 체크박스 (멀티)
<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">
<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>
- th:field="*{regions}" == "${item.regions}" 입력한 값이 저장되는 필드
- th:value="${region.key}" <= th:each="region : ${regions}" 선택하면 입력(전달)되는 값
th:for="${#ids.prev('regions')}"
체크박스의 label(체크 항목)을 클릭해도 체크 처리가 가능하다. 즉, label은 체크 박스의 id를 알아야 한다. 체크 박스는 th:field를 사용하기 때문에 id를 자동 생성하고 있다. HTML 태그 속성에서 name 은 같아도 되지만, id 는 모두 달라야 한다. 따라서 타임리프는 체크박스를 each 루프 안에서 반복해서 만들 때 임의로 1 , 2 , 3 숫자를 뒤에 붙여준다.
HTML의 id 가 타임리프에 의해 동적으로 만들어지기 때문에 으로 label 의 대상이 되는 id 값을 임의로 지정하는 것은 곤란하다. 타임리프는 ids.prev(...) , ids.next(...) 을 제공해서 동적으로 생성되는 id 값을 사용할 수 있도록 한다.
<input type="checkbox" value="SEOUL" class="form-check-input" id="regions1" name="regions">
<input type="checkbox" value="BUSAN" class="form-check-input" id="regions2" name="regions">
<input type="checkbox" value="JEJU" class="form-check-input" id="regions3" name="regions">
이 역시 th:field를 사용하기 때문에 히든 필드(_regions) 가 생긴다. 웹 브라우저에서 체크를 하나도 하지 않았을 때, 클라이언트가 서버에 아무런 데이터를 보내지 않는 것을 방지한다. 참고로 _regions 조차 보내지 않으면 결과는 null 이 된다. _regions 가 체크박스 숫자만큼 생성될 필요는 없지만, 타임리프가 생성되는 옵션 수 만큼 생성해서 그런 것이니 무시하자.
타임리프의 체크 확인
checked="checked" 멀티 체크 박스에서 등록 지역을 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인할 수 있다. 타임리프는 th:field 에 지정한 값( 도메인 item.regions )과 th:value ( 모델 regions )의 값을 비교해서 체크를 자동으로 처리해준다.
라디오 버튼 (ItemType)
다중 선택 가능한 체크 박스와 다르게 라디오 버튼은 여러 선택지 중 하나를 선택하는 옵션
이번엔 ENUM을 활용한 방식으로 개발해보자.
@ModelAttribute("itemType")
public ItemType[] itemTypes(){
ItemType[] values = ItemType.values();
return values;
}
itemTypes 를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 @ModelAttribute 의 특별한 사용법을 적용했다.
ItemType.values() 를 사용하면 해당 ENUM의 모든 정보를 배열로 반환한다. 예) [BOOK, FOOD, ETC]
타임리프 라디오 버튼
<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>
생성된 HTML
<!-- 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>
선택한 선택지( FOOD )에 checked="checked" 가 적용된 것을 확인할 수 있다.
라디오 버튼은 히든 필드를 만들지 않는다. 무조건 1개만 선택할 수 있으며, 1번 선택하면 선택지를 비울 수 없기 때문이다.
타임리프에서 ENUM 직접 사용하기
모델에 ENUM을 담아서 전달하는 대신에 타임리프는 자바 객체에 직접 접근할 수 있다.
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}"
class="form-check form-check-inline">
스프링EL 문법으로 ENUM을 직접 사용할 수 있다. ENUM에 values() 를 호출하면 해당 ENUM의 모든 정보가 배열로 반환된다.
그런데 이렇게 사용하면 ENUM의 패키지 위치가 변경되거나 할때 자바 컴파일러가 타임리프까지 컴파일 오류를 잡을 수 없으므로 추천하지는 않는다.
셀렉트 박스(DeliveryCode)
셀렉트 박스는 여러 선택지 중에 하나를 선택하는 옵션
자바 객체를 활용해서 개발해보자.
@ModelAttribute("deliveryCodes")
public List<DeliveryCode> deliveryCodes(){
List<DeliveryCode> deliveryCodes = new ArrayList<>();
deliveryCodes.add(new DeliveryCode("FAST", "빠른 배송"));
deliveryCodes.add(new DeliveryCode("NORMAL", "일반 배송"));
deliveryCodes.add(new DeliveryCode("SLOW", "느린 배송"));
return deliveryCodes;
}
타임리프 셀렉트 박스
<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>
생성된 HTML
<!-- 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>
선택한 선택지( FAST )에 selected="selected" 가 적용된 것을 확인할 수 있다.
타임리프가 도메인의 필드 값 ( ${item.deliveryCode} : String ) 과 옵션의 속성 value 값 ( ${deliveryCode.code} : String } 이 같은 옵션을 찾아 selected 속성을 추가해준다.