본문 바로가기

Advance I/Spring MVC

22.08.25

체크 박스 - 멀티 (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 속성을 추가해준다.

 

'Advance I > Spring MVC' 카테고리의 다른 글

22.08.30  (0) 2022.08.30
22.08.26  (0) 2022.08.26
22.08.24  (0) 2022.08.24
22.08.22  (0) 2022.08.22
22.08.20  (0) 2022.08.20