참고
더보기
Spring Boot & Thymeleaf 시리즈는 김영한 님의 "스프링 MVC 2편 - 백엔드 웹 개발 활용 기술" 강의를 정리한 글입니다. 글에 첨부된 사진은 해당 강의의 강의 자료에서 캡쳐한 것입니다. 제 Github에만 올려뒀다가, 정보 공유와 강의 홍보(?)를 위해 블로그에도 업로드합니다.
마크다운 형식으로 작성한 글을 블로그에 다시 올리는 거라 가독성이 많이 떨어집니다. 조금더 편하게 보시려면 아래의 Github repository에서 보시면 됩니다.
타임리프 - 체크 박스 다중 선택
체크 박스를 여러개 사용해서, 다중 선택을 할 수 있게 해보자.
요구사항 일부
- 등록 지역
- 서울, 부산, 제주
- 체크 박스로 다중 선택할 수 있다.
@ModelAttribute의 특별한 사용법
요구사항을 구현하기 위해서는 상품 등록 화면, 상품 상세 화면, 상품 수정 화면 모두에서 등록 지역인 서울, 부산, 제주 체크 박스를 보여줘야 한다. 이 등록 지역에 대한 정보는 컨트롤러에서 뷰로 넘겨줄 것이다.
FormItemController.java 내용 추가
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
Map<String, String> regions = new LinkedHashMap<>(); //key 순서 고정
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
model.addAttribute("regions", regions);
return "form/addForm";
}
Map<String, String>
에 등록 지역에 대한 정보를 넘겨줬다.- 문제는 이 코드를 상품 상세 정보에 대한 컨트롤러 메소드, 상품 수정에 대한 컨트롤러 메소드에도 추가해 줘야 한다는 것이다.
- 이렇게 되면 중복이 발생한다.
- 중복을 해결하기 위해 따로 메소드로 빼는 방법도 있지만, Spring이 제공하는
@ModelAttribute
를 사용하는 방법도 존재한다.
FormItemController.java 내용 추가
public class FormItemController {
@ModelAttribute("regions")
public Map<String,String> regions() {
Map<String, String> regions = new LinkedHashMap<>(); //key 순서 고정
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
}
}
- 컨트롤러 클래스 안에있는 특정 메소드에
@ModelAttribute
가 붙어 있으면, 해당 컨트롤러 클래스의 모든@RequestMapping
이 붙은 메소드가 호출될 때마다 그 메소드 호출 전에@ModelAttribute
가 붙은 메소드가 먼저 호출된다. - 이때
@ModelAttribute
가 붙은 메소드 실행 결과의 반환값은 자동으로Model
에 저장이된다. 이 때 key는@ModelAttribute
어노테이션의value
를 따른다.
참고
여기서는 등록 지역이 필요 없는 컨트롤러 메소드들도 많이 있으므로, 굳이@ModelAttribute
말고 그냥 메소드로 따로 빼는게 더 낫지 않았을까 싶다.
혹은 등록 지역이 동적으로 변하지 않는다면, 미리 static 변수로 초기화 해두고 넘겨주기만 하는 방식을 사용하는 것이 더 효율적일 것이다.
체크박스 적용 - 상품 등록
form/addForm.html
<!-- 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>
<div th:each="region : ${regions}">
Model
에서Map<String, String> regions
를 꺼내온다.Map
의 모든Entry
를th:each
로 반복한다.
<input type="checkbox" th:field="*{regions}" th:value="${region.key}" class="form-check-input">
th:object="${item}"
에 의해*{regions}
는${item.regions}
이다.th:field=*{regions}
에 의해id
,name
등의 속성이 렌더링 될 것이다.- 이 때
value
는th:value="${region.key}"
에 의해 렌더링 된다.region
은 반복에 사용되는 변수임에 유의.
타임리프 렌더링 결과
<!-- multi checkbox -->
<div>
<div>등록 지역</div>
<div class="form-check form-check-inline">
<input type="checkbox" value="SEOUL" class="form-check-input" id="regions1" name="regions"><input type="hidden" name="_regions" value="on"/>
<label for="regions1"
class="form-check-label">서울</label>
</div>
<div class="form-check form-check-inline">
<input type="checkbox" value="BUSAN" class="form-check-input" id="regions2" name="regions"><input type="hidden" name="_regions" value="on"/>
<label for="regions2"
class="form-check-label">부산</label>
</div>
<div class="form-check form-check-inline">
<input type="checkbox" value="JEJU" class="form-check-input" id="regions3" name="regions"><input type="hidden" name="_regions" value="on"/>
<label for="regions3"
class="form-check-label">제주</label>
</div>
</div>
th:each
에 의해 체크 박스가 여러 개 생성되었다.th:field
에 의해id
,name
속성이 렌더링 되고, 히든 필드가 생겼다.th:value
에 의해value
속성이 렌더링 되었다.
멀티 체크 박스에서의 id 속성
th:field=*{regions}
는 타임리프에 의해regions
라는 이름을 가지고id
,name
,value
등의 속성으로 렌더링 된다.- 문제는 반복해서 HTML 태그를 생성할 때,
name
속성은 같아도 되지만,id
속성은 모두 달라야한다. - 따라서 타임리프는
th:each
루프 안에서th:field
를 사용하여 체크박스를 반복해서 만들 때 임의로id
속성값에 넘버링을 한다.id="regions1"
,id="regions2"
이런 식으로 만들어진다.
체크 박스와 <label>
, 그리고 #ids
<label th:for="${#ids.prev('regions')}"
<label>
의for
속성값으로 특정 체크 박스의id
속성값을 넣어주면, 위 이미지처럼<label>
에 해당하는 부분(빨간 네모)을 클릭해도 체크 박스에 체크 할 수 있다.- 문제는 우리 예제에서 체크 박스의
id
가th:each
와th:field
에 의해 동적으로 생성된다는 것이다. - 이런 경우를 위해서 타임리프는
#ids
유틸리티 객체를 제공한다. for="${#ids.prev('regions')}"
이렇게 하면, 타임리프에서th:field=*{regions}
를 렌더링하면서 사용한id
의 넘버링 값을 가지고for
속성값을 렌더링한다.prev()
가 의미하는 것은 이전에 쓰였던 값을 가져온다는 것이다. 즉 바로 직전에 체크박스에서id
를 렌더링 할 때 쓴 그 값을 사용한다는 것이다.<label>
에 대응하는 체크박스가 바로 앞에 있으므로 이렇게 사용하는 것이다.
실행 결과
서울, 부산 선택
HTTP 바디: regions=SEOUL&_regions=on®ions=BUSAN&_regions=on&_regions=on
로그: item.regions=[SEOUL, BUSAN]
선택 X
HTTP 바디: _regions=on&_regions=on&_regions=on
로그: item.regions=[]
item.regions
가null
이 아닌 빈 배열임에 주의하자.- 히든 필드에 의해 체크를 하나도 하지 않아도
_regions=on
이 보내지기 때문에,null
이 아닌 빈 배열이 들어간다. 아무것도 보내지 않으면null
이 들어갈 것이다. - 사실
_regions
가 체크박스 숫자만큼 보내질 이유는 없다. 뭐라도 보내지기만 하면null
대신 비어있는 무언가를 알아서 채워넣을 수 있을테니 말이다. 그런데 그냥 타임리프가 체크 박스 수만큼 생성해서 보내는 거라서 별 신경 안써도 된다고 한다.
체크박스 적용 - 상품 상세, 상품 수정
상품 상세
form/item.html
<!-- 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>
- 상품 등록에서 작성한 내용을 그대로 가져온 뒤 몇 가지 수정만 했다.
disabled
속성을 추가했다.- 상품 상세에서는
th:object="${item}"
속성을 사용하지 않으므로*{regions}
를${item.regions}
로 고쳤다.
체크 여부 확인
- 멀티 체크 박스에서 등록 지역을 선택해서 저장하면, 조회시에
checked="checked"
속성이 추가된 것을 확인할 수 있다. - 타임리프는
th:field
에 지정한 값과th:value
의 값을 비교해서checked
속성을 자동으로 처리한다.
상품 수정
form/editForm.html
<!-- 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>
- 등록과 아예 동일하다.
Item
객체에 값이 존재하는 경우가 있다는 점만 다르다.
'Spring > Spring Boot & Thymeleaf' 카테고리의 다른 글
[Spring Boot & Thymeleaf] 23. 셀렉트 박스 (0) | 2022.10.24 |
---|---|
[Spring Boot & Thymeleaf] 22. 라디오 버튼 (0) | 2022.10.24 |
[Spring Boot & Thymeleaf] 20. 체크 박스 단일 선택 (0) | 2022.10.24 |
[Spring Boot & Thymeleaf] 19. 체크 박스, 라디오 버튼, 셀렉트 박스 (1) (0) | 2022.10.24 |
[Spring Boot & Thymeleaf] 18. 타임리프 입력 폼 (0) | 2022.10.24 |
댓글