입력 폼 처리
• th:object : 커맨드 객체를 지정한다. form 태그 안에서만 적용
• *{...} : 선택 변수 식이라고 한다. th:object 에서 선택한 객체에 접근한다.
• th:field
HTML 태그의 id , name , value 속성을 자동으로 처리해준다.
렌더링 전
<input type="text" th:field="*{itemName}" />
렌더링 후
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />
등록 폼
th:object 를 적용하려면 먼저 해당 오브젝트 정보를 넘겨주어야 한다. 등록 폼이기 때문에 데이터가
비어있는 빈 오브젝트를 만들어서 뷰에 전달하자.
FormItemController 변경
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="itemName">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
</div>
• th:object="${item}" : <form> 에서 사용할 객체를 지정한다. 선택 변수 식( *{...} )을 적용할 수 있다.
• th:field="*{itemName}"
*{itemName} 는 선택 변수 식을 사용했는데, ${item.itemName} 과 같다. 앞서 th:object 로 item 을 선택 했기 때에 선택 변수 식을 적용할 수 있다.
• th:field 는 id , name , value 속성을 모두 자동으로 만들어준다.
• id : th:field 에서 지정한 변수 이름과 같다. id="itemName"
• name : th:field 에서 지정한 변수 이름과 같다. name="itemName"
• value : th:field 에서 지정한 변수의 값을 사용한다. value=""
렌더링 전
<div>
<label>상품명</label>
<input type="text" id="itemName" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
</div>
렌더링 후
<input type="text" id="itemName" class="form-control" placeholder="이름을 입력하세요" name="itemName" value="">
체크 박스 - 단일1
주의 - 체크 박스를 선택하지 않을 때
HTML에서 체크 박스를 선택하지 않고 폼을 전송하면 open 이라는 필드 자체가 서버로 전송되지 않는다.
서버에서 Boolean 타입을 찍어보면 결과가 null 인 것을 확인할 수 있다.
log.info("item.open={}", item.getOpen());
HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다. 수정의 경우에는
상황에 따라서 이 방식이 문제가 될 수 있다. 사용자가 의도적으로 체크되어 있던 값을 체크를 해제해도
저장시 아무 값도 넘어가지 않기 때문에, 서버 구현에 따라서 값이 오지 않은 것으로 판단해서 값을 변경하지
않을 수도 있다.
이런 문제를 해결하기 위해서 스프링 MVC는 약간의 트릭을 사용하는데, 히든 필드를 하나 만들어서,
_open 처럼 기존 체크 박스 이름 앞에 언더스코어( _ )를 붙여서 전송하면 체크를 해제했다고 인식할 수
있다. 히든 필드는 항상 전송된다. 따라서 체크를 해제한 경우 여기에서 open 은 전송되지 않고, _open 만
전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
체크 해제를 인식하기 위한 히든 필드
<input type="hidden" name="_open" value="on"/>
체크 박스 - 단일2
타임리프
개발할 때 마다 이렇게 히든 필드를 추가하는 것은 상당히 번거롭다. 타임리프가 제공하는 폼 기능을
사용하면 이런 부분을 자동으로 처리할 수 있다.
타임리프 - 체크 박스 코드 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" th:field="*{open}" class="form-checkinput">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
체크 박스의 기존 코드를 제거하고 타임리프가 제공하는 체크 박스 코드로 변경하자
타임리프 체크 박스 HTML 생성 결과
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" class="form-check-input" name="open"
value="true">
<input type="hidden" name="_open" value="on"/>
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
<input type="hidden" name="_open" value="on"/>
타임리프를 사용하면 체크 박스의 히든 필드와 관련된 부분도 함께 해결해준다. HTML 생성 결과를 보면
히든 필드 부분이 자동으로 생성되어 있다.
상품 상세에도 적용하자
item.html
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" th:field="${item.open}" class="formcheck-
input" disabled>
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
주의: item.html 에는 th:object 를 사용하지 않았기 때문에 th:field 부분에 ${item.open} 으로
적어주어야 한다.
disabled 를 사용해서 상품 상세에서는 체크 박스가 선택되지 않도록 했다
HTML 생성 결과
<hr class="my-4">
<!-- single checkbox -->
<div class="form-check">
<input type="checkbox" id="open" class="form-check-input" disabled
name="open" value="true"
checked="checked">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
타임리프의 체크 확인
checked="checked"
체크 박스에서 판매 여부를 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인할 수 있다.
이런 부분을 개발자가 직접 처리하려면 상당히 번거롭다. 타임리프의 th:field 를 사용하면, 값이 true
인 경우 체크를 자동으로 처리해준다.
상품 수정에도 적용하자.
editForm.html
<hr class="my-4">
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" th:field="*{open}" class="form-checkinput">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
상품 수정도 th:object , th:field 를 모두 적용해야 한다.
실행해보면 체크 박스를 수정해도 반영되지 않는다. 실제 반영되도록 다음 코드를 수정하자
ItemRepository - update() 코드를 다음과 같이 수정하자
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());
}
체크 박스 - 멀티
@ModelAttribute( )가 있는 경우에 FormItemController가 호출되는 경우 자동으로
@ModelAttibute어노테이션 아래에 있는 함수의 리턴값이 Model에 가 담기게 된다.
@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("regions", regions) ---> model.addAttibute("이름", value)
}
addForm.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">
<label th:for="${#ids.prev('regions')}"
th:text="${region.value}" class="form-check-label">서울</label>
</div>
</div>
• th:for="${#ids.prev('regions')}"
멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다. 그런데 문제는 이렇게 반복해서 HTML 태그를 생성할 때, 생성된 HTML 태그 속성에서 name 은 같아도 되지만, id 는 모두 달라야 한다. 따라서 타임리프는 체크박스를 each 루프 안에서 반복해서 만들 때 임의로 1 , 2 , 3 숫자를 뒤에 붙여준다.
HTML의 id 가 타임리프에 의해 동적으로 만들어지기 때문에 <label for="id 값"> 으로 label 의 대상이 되는 id 값을 임의로 지정하는 것은 곤란하다. 타임리프는 ids.prev(...) , ids.next(...) 을 제공해서 동적으로 생성되는 id 값을 사용할 수 있도록 한다.
라디오 버튼
itemTypes 를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 @ModelAttribute 의 특별한 사용법을 적용하자.
ItemType.values() 를 사용하면 해당 ENUM의 모든 정보를 배열로 반환한다. 예) [BOOK, FOOD, ETC]
체크 박스는 수정시 체크를 해제하면 아무 값도 넘어가지 않기 때문에, 별도의 히든 필드로 이런 문제를
해결했다.
라디오 버튼은 이미 선택이 되어 있다면, 수정시에도 항상 하나를 선택하도록 되어 있으므로 체크
박스와 달리 별도의 히든 필드를 사용할 필요가 없다.
셀렉트 박스
DeliveryCode 라는 자바 객체를 사용하는 방법으로 진행하겠다.
DeliveryCode 를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 @ModelAttribute 의 특별한 사용법을
적용하자.
'스프링 > 스프링 MVC 패턴 2편' 카테고리의 다른 글
5. 검증2 - Bean Validation (0) | 2022.12.01 |
---|---|
4. 검증1 - Validation (0) | 2022.11.30 |
3. 메시지, 국제화 (0) | 2022.11.29 |
1. 타임리프 - 기본 기능 (0) | 2022.10.28 |