인프런 커뮤니티 질문&답변

성장하자님의 프로필 이미지

작성한 질문수

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

체크 박스 - 멀티

org.thymeleaf.spring5.processor.SpringInputCheckboxFieldTagProcessor 에러 질문

21.08.10 16:02 작성

·

635

2

안녕하세요. 강사님이 해주신 강의를 듣고 있는 수강생입니다.

코드를 따라가면서 @ModelAttribute를 복습하면서 컨트롤러에있는 메소드들이 실행될때마다 @ModelAttribute가 붙은 메소드로 부터 반환된 모델들이 생성된다는 것은 이해했습니다. 그래서  items.html 에서 모든 아이템에 대해서 등록지역을 출력하고 싶어 코드를 다른 html에 있는 코드를 items.html에 아이템을 반복하면서 조회하는 부분에 넣어주면 되겠거니 했지만 제목에 기재한 오류가 발생했습니다. 검색을 통해 해결을 하려했지만 그렇지 못해서 질문에 올렸습니다. 아래에 코드를 첨부해드리오니 확인 부탁드립니다. !! 감사합니다!! 에러가 발생하는 지점은

<input type="checkbox" th:field="${item.regions}" th:value="${region.key}" class="form-check-input" disabled> 이부분입니다.

<tr th:each="item : ${items}">
<td><a href="item.html" th:href="@{/form/items/{itemId} (itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{|/form/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
<td>
<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>
</td>
</tr>

답변 5

0

xxxxxseed님의 프로필 이미지

2024. 01. 27. 19:52

시간이 많이 지났지만...혹시 다른분들에게 도움 될까해서 올립니다.

image이렇게 하니

image이런식으로 나옵니다!

 

0

성장하자님의 프로필 이미지
성장하자
질문자

2021. 08. 11. 10:35

안녕하세요. 우선 질문에 대해 정성스런 답변 정말 감사합니다.!! 현재 답변해주신 내용은 실행하고 이해했습니다. 그런데 궁금한 점이 생겼습니다.

처음 답변에서 저장된 item의 regions(도메인에 List로 선언)을 반복문을 통해 꺼내면서 모델로 등록된 regions와 비교하면서 출력을 한 것으로 보입니다. 이 의도와 코드는 이해가 됬습니다. 그런데 items.html가 아닌 item.html, editForm.html을 보면 제가 처음 질문을 업로드한 코드와 같이 모델 'regions'를 for문을 통해 별도의 반복문(th:each)없이 item의 regions를 확인 한다는 점 에서 의문이 생겼습니다. 다른 곳에서는 답변과 같이 item.regions을 확인 하는데  th:each가 필요없는는데 items.html에서는 왜 필요한 것 일까요?  

질문 읽어주셔서 감사합니다.

OMG님의 프로필 이미지

2021. 08. 11. 20:36

제가 답변을 드리려고 확인한 내용으로는 다음과 같습니다.

@GetMapping
public String items(Model model) {

List<Item> items = itemRepository.findAll();

model.addAttribute("items", items);
model.addAttribute("item", new Item());
return "form/items";
}

위의 코드에서 new Items() 대신 items.get(0)으로 바꿀 경우

itemA와 B 모두의 BUSAN, JEJU(itemA)지역들이 출력되었습니다. 그렇게 되면 성장하자님이 원하는 동작이 아니기 때문에 new Items()로 바꾼 것이고 비어있는 체크박스(지역3개)가 출력이 된 것입니다.

items는 item의 List이고, item의 region은 String[] 이라는 차이가 있습니다.

위와 같은 상황에서 model에 item을 item1, item2 와 같이 담아 화면에 넘기고 싶었는데 화면단에서 해당 모델을 받아서 처리하기가 쉽지 않아 선택한 값들만이라도 출력하도록 코드를 구현하게 되었습니다.

이 부분을 성장하자님께서 개선하여 총 3개의 지역들이 모두 출력되면서 체크한 값들은 체크가 되도록  개선하여 결과를 공유해주시면 감사하겠습니다.

문제를 개선하기 위해 제가 생각했던 아이디어는 바로 위에서 말씀드렸고, 추가적으로 더 알게 된 것은 SpringInputCheckboxFieldTagProcessor.class가 checkbox 필드 태그 관련된 처리를 한다는 것이고 인텔리제이에서 shift 2번을 연속 클릭하면 뜨는 모달창에서 class를 선택하여 클래스 이름을 검색할 수 있으니 클래스를 분석하시면 추가적인 정보를 얻을 수 있지 않나 예상합니다.

-

말씀드린것과 같이 성장하자님이 해당 궁금한 사항에 대해 깊게 파고 들고자하시면 시도해보시고 최종적으로 원하는 결과를 만들어내시면 게시판에 공유해주세요.

감사합니다.

성장하자님의 프로필 이미지
성장하자
질문자

2021. 08. 12. 12:01

계속 시도해보다가 새로 알게된 것은 중첩된 th:each 즉 region을 조회하는 반복문에서 item 객체에 바인딩이 실패하는 것으로보아 중첩이 되었을때 바깥에 있는 반복문에 전달된 객체는 안에서 인식이 안되는 것 같습니다. 답변해 주신대로 중첩된 반복문을 쓰기 위해선 타임리프가 제공하는 block을 사용하여 item의 객체가 인식되도록 하는 것이고 이 상태에서 추가하지 않은 배송지역의 체크박스를 추가하려다보니 if else의 논리를 사용할 수 밖에 없었고, 결과는 각 지역이 모두 2번씩 등장하거나 값이 잘못 바인딩 되는 것으로 보입니다. sel에 해당하는 반복문을 순회하지 않고 기존 코드에서 region을 조회하는 부분에만 block을 추가해도 처음에 발생했던 오류가 계속 발생하네요 ㅠㅠㅠ... 며칠 동안 계속 고민했지만 원하는 결과를 얻을 수 없을 것 같습니다.. 선택한 지역만 출력하는 것으로 만족해야 할 것 같네요. 도와주셔서 감사합니다.

OMG님의 프로필 이미지

2021. 08. 13. 08:23

네 저도 찾아본 바로는 해당 내용까지가 한계인거 같더라구요.

말씀하신대로 if/else를 타야되는데 타임리프의 if랑 else의 조건을 같이 해야된다는 점에서도 문제가 발생하였던 것으로 기억합니다.

약간 우회해서 개선하는 방법으로는 select 박스 혹은 체크박스를 바깥에 목록으로 한번 출력해주는 정도는 가능할 것 같네요.

성장하자님께서 기존에 궁금하셨던 사항에 대해 개선하고자 하시는 의지가 남아있다면 새로 질문 글을 작성하여 서포터즈 분이나 영한님에게 도움을 구하셔도 좋아보입니다.

지금까지 알게된 내용들과 문제점 등을 잘 정리하여 질문을 올려주세요.

이 글을 링크로 남기셔도 좋습니다.

성장하자님의 프로필 이미지
성장하자
질문자

2021. 08. 13. 09:45

질문을 할 수 있는 다른 게시판이 또 있을까요?

OMG님의 프로필 이미지

2021. 08. 13. 12:11

현재 게시판 말고는 없는걸로 알고있습니다.

새 글을 작성해주세요

0

OMG님의 프로필 이미지

2021. 08. 11. 01:34

checked 없이 checkbox 목록만 띄워주는 방법은 

<tr th:each="item : ${items}">
<td><a href="item.html" th:href="@{/form/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{|/form/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
<td>
<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">
<label th:for="${#ids.prev('regions')}"
th:text="${region.value}" class="form-check-label">서울</label>
</div>
</td>
</tr>


ItemController.class
@GetMapping
public String items(Model model) {

List<Item> items = itemRepository.findAll();

model.addAttribute("items", items);
model.addAttribute("item", new Item());
return "form/items";
}

model.addAttribute("item", new Item()); 추가해주시면 됩니다.

0

OMG님의 프로필 이미지

2021. 08. 11. 01:30

안녕하세요. 성장하자님, 공식 서포터즈 OMG입니다.

질문글을 처음 보고 예상했던 방식이랑 달라서 잘못된 답변을 드렸네요.

잘못된 정보를 드려 죄송합니다.

 

질문을 다시 정독하고, 추가로 올려주신 글을 보고 시도해보았는데 완벽히 동작 시키기는 쉽지 않네요.

우선 제가 해결한 것까지만이라도 참고하시고 추가적으로 궁금한 사항은 새로 질문글을 올려주시면 감사하겠습니다.

items.html

<tr th:each="item : ${items}">
<td><a href="item.html" th:href="@{/form/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{|/form/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
<td>
<th:block th:each="region: ${regions}">
<th:block th:each="sel:${item.regions}">
<input type="checkbox" th:if="${sel.equals(region.key)}" th:text="${sel}" th:checked="checked"></input>
</th:block>
</th:block>
</td>
</tr>

TestDataInit.class

Item.class 생성자 추가

결과

감사합니다. 

OMG님의 프로필 이미지

2021. 08. 11. 01:36

BUSAN JEJU SEOUL 영어로 출력되는 것은

<input type="checkbox" th:if="${sel.equals(region.key)}" th:text="${region.value}" th:checked="checked">

로 바꾸시면 한글로 출력됩니다.

0

성장하자님의 프로필 이미지
성장하자
질문자

2021. 08. 10. 21:06

안녕하세요. 답변을 확인하고 강의내용을 다시 복습해보았습니다. th:field의  특성은 인자로 입력된 객체에 대해 id, name, value 속성을 자동으로 만들어 주는 것이라고 이해했습니다. 그리고 예를들어, th:field=${itemName}"가 id="itemName" name"itemName", value="itemA" 를 만들어주고 해당 상위 컴포넌트에 object 가 선언되면 *{itemName}으로 간소화 할 수 있습니다.  그리고 질문에서 언급드렸던 것처럼 멀티 체크박스편을 수강하면서  @ModelAttribute에 붙은 메소드는 컨트롤러에있는 메소드들이 실행될때마다 해당 어노테이션이 붙은 메소드로 부터 반환된 모델들이 생성된다는 것으로 이해했습니다.

그러다 item list를 조회하는 페이지인 item.html에서도 '등록 지역'에 대한 모델들이 생성이 될 것이니 해당 데이터를 나타내고자 하였습니다. 그래서 아이템을 <th:each="item" "${items}"> 태그 반복문을 통해 list로써 저장되어있는 모델 items에서 item 객체를 하나씩 받고 이 객체를 통해 regions을 받아오려고 시도를 했습니다. 해당 객체는 th:object로써 선언된 것이 아니기 때문에 th:field="*{regions}"가 아닌 th:field="${item.regions}"로 선언하였고 div 태그에 작성된 th:each="region :${regions}"를 통해 @ModelAttribute에 의해 생성된 모델로 부터 <key,value>를 region으로 받아와 th:field="${item.regions}"의 값과 매칭을해서 체크하거나 체크를 안하도록 표시하는 로직을 이해했습니다. 그리고 <lable>태그는 동적으로 생성된 컴포넌트에 대해 아이디를 부여하기 위한 로직으로 이해 했습니다. 원래 해당 예제 코드는 item.quantity 즉 아이템의 수량까지만 나타내고자 했는데 제가 그 밑에 추가를 하면서 오류가 발생했습니다. 제가 이해한 내용이 어디가 부족했는지 잘 모르겠습니다.....

<tr th:each="item : ${items}">
<td><a href="item.html" th:href="@{/form/items/{itemId} (itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{|/form/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
<td>
<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>
</td>
</tr>
OMG님의 프로필 이미지

2021. 08. 10. 21:34

제가 답변 드렸었는데요,  올리신 코드를 자세히 보니 생각한 것과 달라서 지웠는데 보셨나보네요.

저도 확인 중에 있으니 저나 다른 분께서 답변 드리도록 하겠습니다.