타임리프 - 스프링 통합 폼
타임리프는 스프링 없이도 동작하지만, 스프링과 통합을 위한 다양한 기능을 편리하게 제공한다.
이번에는 편리한 폼 관리를 위한 추가 속성인 th:object와 th:field를 사용해서 기존에 작성했던 상품 관리 폼을 다채롭게 변경해본다.
[상품 등록 폼 - 기본]
[상품 등록 폼 - 수정]
[단일 체크박스]인 판매 여부, [다중 체크박스]인 등록 지역, [라디오 체크] 방식의 상품 종류, [리스트 형식]의 배송방식 부분을 추가한다.
1. 입력 폼 처리 - th:object & th:field
타임리프가 제공하는 입력 폼 기능을 적용해서 기존 프로젝트의 폼 코드를 효율적으로 개선한다.
추가적인 속성은 다음과 같다.
· th:object
커맨드 객체를 지정한다.
· *{...}
선택 변수 식
th:object에서 선택한 객체에 접근한다.
· th: fieeld
HTML 태그의 id, name, value 속성을 자동으로 처리해준다.
[th:object 사용]
<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>
object로 지정한 변수(${item})은 해당 태그 내에서 커맨드 객체가 되고 *{...}방식으로 해당 변수의 프로퍼티에 접근할 수 있다.
[th:field 사용 - 렌더링 전]
<input type="text" th:field="*{itemName}" class="form-control"
placeholder="이름을 입력하세요">
[th:field 사용 - 렌더링 후]
<input type="text" id="itemName" class="form-control" placeholder="이름을 입력하세요"
name="itemName" value="">
field 값의 변수명으로 id와 name이 생성되고 변숫값으로 value가 생성된다(예제에서 item은 빈 객체).
2. 데이터 추가
다음과 같은 옵션을 폼에 새롭게 추가한다.
[수정된 폼 예시]
여러 옵션들을 처리하기 위해 데이터를 담을 객체를 생성한다.
[ItemType - 상품 종류]
public enum ItemType {
BOOK("도서"), FODD("음식"), ETC("기타");
private final String description;
ItemType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
상품 종류는 ENUM을 사용한다.
설명을 위해 description 필드를 추가했다.
[DeliveryCode - 배송 방식]
@Data
@AllArgsConstructor
public class DeliveryCode {
private String code;
private String displayName;
}
배송 방식은 DeliveryCode 라는 클래스를 사용한다.
code 는 [FAST] 같이 시스템에 전달하는 값이고, displayName 은 [빠른 배송] 같은 고객에게 보여주는 값이다.
[Item - 상품]
@Data
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
private Boolean open; //판매 여부
private List<String> regions; //등록 지역
private ItemType itemType; //상품 종류
private String deliveryCode; //배송 방식
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
여러 선택 옵션에 따른 데이터를 저장하기 위해 다양한 형태의 데이터를 추가하였다.
3. 단일 체크 박스
다음과 같이 단일 체크 박스를 추가한다.
[단순 HTML 체크 박스]
<!-- 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>
위의 예제처럼 체크 박스를 만들 경우 체크 박스를 체크하면 HTML Form에서 open=on 이라는 값이 넘어간다.
스프링은 on이라는 문자를 true로 바꿔 item.open의 값은 true가 된다.
반면 체크박스를 체크하지 않으면 open이라는 필드 자체가 서버로 전송되지 않기 때문에 item.open은 null인 상태가 된다.
이러한 방식은 수정 시에 문제가 될 수 있다.
사용자가 체크되어있던 체크 박스의 체크를 해제 해고 저장 시 아무 값도 넘어가지 않기 때문에 서버 구현에 따라 값이 오지 않은 것으로 판단해서 값을 변경하지 않을 수 있기 때문이다.
이런 문제를 해결하기 위해 히든 필드를 하나 만들고, _open같은 전송 시 체크를 해제했다고 인식할 수 있는 속성을 추가하는 방법이 있다.
히든 필드는 항상 전송되기 때문에 체크를 해제한 경우 open은 전송되지 않지만 _open은 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
[단순 HTML 체크 박스 - 히든 필드 추가]
<!-- 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>
위의 예제처럼 히든 필드를 추가하면 체크 박스가 체크되지 않아도 item.open의 값은 null이 아니라 false가 된다.
[HTML 체크 박스 - th:field 추가]
<!-- 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를 사용하면 id, name, value를 자동으로 생성하는 것에 더해서 체크 박스 태그의 경우 히든 필드를 추가해준다.
[HTML 체크 박스 - th:field 추가, 페이지 소스]
<!-- 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>
4. 멀티 체크 박스
체크 박스를 멀티로 사용해서, 하나 이상을 체크할 수 있도록 한다.
등록 지역이라는 필드를 만들고 서울, 부산, 제주를 체크 박스로 다중 선택할 수 있게 한다.
[지역 데이터 추가 - @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;
}
컨트롤러에 지역 데이터 객체를 반환하는 메서드를 추가한다.
위의 예제처럼 메서드에 @ModelAttribute를 추가하면 해당 컨트롤러를 요청할 때마다 메서드에서 반환한 값이 자동으로 모델에 담기게 된다.
[멀티 체크 박스 추가 - 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>
모델에 담긴 regions에 반복문을 적용시켰고, value에는 서버에 전송될 key값을 넘겨준다("SEOUL", "BUSAN" 등).
th:field에 반복문이 적용될 경우 id를 자동으로 반복 생성하게 되는데, 이 경우 타임리는 기존의 id에 임의로 1,2,3 ...과 같은 숫자를 뒤에 붙여준다.
id가 타임리프에 의해 동적으로 만들어지기 때문에 단순히 <label for="id 값>을 사용할 수 없다.
label에 적용되는 id 값도 th:field에 의해 생성되는 id값에 맞춰 동적으로 바뀌여야 한다.
타임리프는 ids.prev{...}, ids.next{...}를 제공해서 동적으로 생성되는 id값을 사용할 수 있도록 한다.
※ ids.prev{...} : 이전 시퀀스를 반환
ids.next{...} : 다음 시퀀스를 반환
[멀티 체크 박스 추가 - 페이지 소스]
<!-- 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"
</div>
</div>
5. 라디오 버튼
라디오 버튼은 여러 선택지 중에 하나를 선택할 때 사용할 수 있다.
상품 종류 필드를 만들고 도서, 식품, 기타 중 하나만 선택할 수 있도록 한다.
[데이터 추가 - 상품 종류]
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
value 메서드를 사용하면 해당 ENUM의 모든 정보를 배열로 반환한다.
[라디오 버튼 추가 - HTML]
<!-- 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>
체크 박스와 같이 반복문과 th:field, ids.prev를 사용해서 라디오 버튼을 추가한다.
6. 셀렉트 박스
셀렉트 박스는 여러 선택지 중에 하나를 선택할 때 사용할 수 있다.
배송 방식을 필드를 만들고, 빠른 배송, 일반 배송, 느린 배송 중 하나만 선택하게 한다.
[데이터 추가 - 배송 방식]
@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;
}
[셀렉트 박스 - HTML]
<!-- 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>
</div>
앞에서 한 것과 같이 반복문과 th:field를 사용해 셀렉트 박스 부분을 추가한다.
이런 식으로 상품 상세와 수정 부분에도 지금까지의 방식을 적용하면 상품 추가와 같은 폼으로 만들 수 있다.
'Spring > 스프링 MVC 활용' 카테고리의 다른 글
#6 로그인 처리 - 쿠키, 세션 (0) | 2021.07.30 |
---|---|
#5 Bean Validation (0) | 2021.07.28 |
#4 검증 - Validation (0) | 2021.07.28 |
#3 메시지, 국제화 (0) | 2021.07.27 |
#1 타임리프 - 기본 기능 (0) | 2021.07.25 |
댓글