4 분 소요

 인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술편을 학습하고 정리한 내용 입니다.

이전 시간에 만든 추가 요구 사항을 구현해 보자.

체크 박스 - 단일 1

단순 HTML 체크 박스

resources/templates/form/addForm.html 추가

1
2
3
4
5
6
7
8
9
10
<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>

또 등록 컨트롤러에서 로그를 남겨보자.

1
2
3
4
5
@PostMapping("/add")  
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {  
    log.info("item.open={}", item.getOpen());
    ...
}

잘 넘어온다.

체크 박스를 체크하면 HTML Form에서 open=on이라는 값이 넘어간다.

스프링은 on이라는 문자를 true타입으로 변환해준다.

그런데 체크 박스를 체크하지 않고 넘긴다면?

open이라는 필드 자체가 안넘어간다.

이를 방지 하기 위해서 스프링에서 다음과 같이 이용한다.

1
2
3
4
5
6
7
8
9
<!-- single checkbox -->  
<div>판매 여부</div>  
<div>  
    <div class="form-check">  
        <input type="checkbox" id="open" name="open" class="form-check-input">  
        <input type="hidden" name="_open" value="on" > <!--히든 필드 추가-->  
        <label for="open" class="form-check-label">판매 오픈</label>  
    </div>  
</div>

이렇게_opne=on 이 넘어간다.

로그를 보면 false 로 스프링이 변환 시킨 걸 알 수 있다.

HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다. 수정의 경우에는 상황에 따라서 이 방식이 문제가 될 수 있다. 사용자가 의도적으로 체크 되어 있던 값을 체크를 해제해도 저장 시 아무 값도 넘어 가지 않기 때문에, 서버 구현에 따라서 값이 오지 않는 것으로 판단해서 값을 변경하지 않을 수도 있다.


이런 문제를 해결하기 위해서 스프링 MVC는 약간의 트릭을 사용하는데, 히든 필드를 하나 만들어서 _opne처럼 기존 체크 박스 이름 앞에 언더스코어(_)를 붙여서 전송하면 체크를 해제했다고 인식할 수 있다.

히든 필드는 항상 전송된다. 따라서 체크를 해제한 경우 여기에서 open은 전송되지 않고, _open만 전송 되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.

체크 박스 - 단일 2

타임리프

개발할 때마다 히든 필드를 추가하는 일은 귀찮은 일이다.

타임리프의 기능을 이용해서 간단히 사용해 보자.

타임리프 - 체크 박스 코드 추가

1
2
3
4
5
6
7
8
9
10
<hr class="my-4">  
  
<!-- 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>

기존에 있던 히든 필드를 제거하고, 이전에 배운 th:field를 사용해서*{open} 을 가져왔다.

(form 태그 에서 th:object="${item}"로 객체 바인딩 해놨음. 아니였으면 ${itme.opne})

렌더링 된 소스를 보면 히든 필드를 알아서 추가해 줬다.

상품 상세에 판매 오픈여부를 추가해 보자.

resources/templates/form/item.html

1
2
3
4
5
6
7
8
9
<hr class="my-4">  
<!-- 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>

*{open} 하니깐 바로 오류 표시 해준다. 여긴 th:object로 바인딩 하지 않았으니까..

잘 된다. 체크를 안하고 또 등록 해 보자.

체크가 안되있다!

소스보면 checked 옵션이 없다!

이제 마지막으로 수정 코드에 추가해 보자. (걍 등록 복붙하면 된다.)

이렇게 추가했다.

자 이렇게 필드가 추가 됐고, 체크를 해서 저장을 하면 ?

반영되지 않았다.

이럴땐 로그나 디버그로 값이 넘어오는지 확인하고, 넘어온다면, 이제 업데이트 관련 로직을 살펴봐야 한다.

open관련 항목이 없다. 추가하자.(나중을 위해 다른 추가 항목도 추가해 주겠다.)

1
2
3
4
5
6
7
8
9
10
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.setRegions(updateParam.getRegions());  
    findItem.setItemType(updateParam.getItemType());  
    findItem.setDeliveryCode(updateParam.getDeliveryCode());  
}

다음과 같이 추가했고,

다시 해보면

잘 반영 된다.

체크 박스 - 멀티

체크 박스를 멀티로 사용해서, 하나 이상을 체크할 수 있도록 해보자.

  • 등록 지역
    • 서울, 부산, 제주
    • 체크 박스를 다중으로 선택할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/add")  
public String addForm(Model model) {  
    model.addAttribute("item", new Item());  
    Map<String, String> regions = new LinkedHashMap<>();  
    regions.put("SEOUL", "서울");  
    regions.put("BUSAN", "부산");  
    regions.put("JEJU", "제주");  
  
    model.addAttribute("regions", regions);  
  
    return "form/addForm";  
}

추가 폼에 이렇게 지역을 추가해줬다. 순서가 중요해서 LinkedHashMap을 사용했다.

자 그런데 이렇게 작성하면 문제가 뭘까?

상세, 수정 등등 계속 저 regions 맵을 만들고 모델에 넣어줘야 한다는 것이다.

스프링에선 @ModelAttribute 라는 걸 지원한다.

FormItemController 내에 메서드를 만들자

1
2
3
4
5
6
7
8
@ModelAttribute("regions")  
public Map<String, String> regions() {  
    Map<String, String> regions = new LinkedHashMap<>();  
    regions.put("SEOUL", "서울");  
    regions.put("BUSAN", "부산");  
    regions.put("JEJU", "제주");  
    return  regions;  
}

이렇게 해 놓으면 모든 FormItemController 내에 메서드에 model로 자동으로 들어가게 된다.

물론 성능이 중요하다면, 이런 고정 값은 따로 빼서 저장 하는 것이 더 낳을 것 같지만..

그럼 이제 addForm 컨트롤러에 중복되는 코드를 삭제 시킬 수 있다. (물론 메서드 마다 하나하나 추가 해도 상관 없다.)

이제 resources/templates/form/addForm.html 에 추가하자.

1
2
3
4
5
6
7
8
9
10
<!-- 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:each="region : ${regions}"로 for 문 돌리고

` th:field=”*{regions}” 얘는 Item.regions 이고 th:value=”${region.key}”` 얘는 model에 담긴 region 이다.

1
2
<label th:for="${#ids.prev('regions')}"  
               th:text="${region.value}" class="form-check-label">서울</label>

얘는 이름을 클릭해도 체크 되도록 레이블 달아 준건데, 중요한건 레이블의 for과 checkbox에 id값이 같아야 한다.

타임리프는 th:for="${#ids.prev('regions')}" 이렇게 이름을 만들 수 있다.

타임리프는 체크박스를 each루프 안에서 반복해서 만들 때 임의로 1, 2, 3 숫자를 뒤에 붙여준다.

이런 식으로 잘 만들어 졌다.

등록한 걸 로그로 찍어 보면

이렇게 배열로 나온다. 만약 체크를 아예 안했다면, 빈 배열로 넘어간다.

이건 타임리프가 _regions를 알아서 생성해서 넘기기 때문.

상품 상세 (item.html)에도 추가

1
2
3
4
5
6
7
8
9
10
<!-- multi checkbox -->  
<div>  
    <div>등록 지역</div>  
    <div th:each="region : ${regions}" class="form-check form-check-inline">  
        <input type="checkbox" th:field="${item.regions}" th:value="${region.key}"  
               class="form-check-input" disabled>  
        <label th:for="${#ids.prev('regions')}"  
               th:text="${region.value}" class="form-check-label">서울</label>  
    </div>  
</div>

여긴 th:object를 사용하지 않았기 때문에 th:field="${item.regions}"를 사용했다.

자 잘된다. 이걸 어떻게 체크하는지는 타임리프가 알아서 다 해준다.

타임리프는 th:field에 지정한 값과 th:value의 값을 비교해서 체크를 자동으로 처리해준다.

상품 수정 (editForm.html)에도 추가

등록 꺼 복붙 하면된다.

1
2
3
4
5
6
7
8
9
<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>

잘 된다.

댓글남기기