타임리프는 스프링 없이도 동작하지만, 스프링과 통합을 위한 다양한 기능을 편리하게 제공한다. 그리고 이런 부분은 스프링으로 백엔드를 개발하는 개발자 입장에서 타임리프를 선택하는 하나의 이유가 된다.
타임리프 기능인 th:object를 활용하려면 컨트롤러에서 모델을 뷰에 넘겨주어야 한다. th:object 변수에 모델 객체를 담으면, th:field=*{itemName} 를 사용하여 id,name,value 속성 을 자동으로 생성하고 실제 객체 필드에 주입한다. 결과적으로 addform에서 업데이트된 아이템 객체는 @ModelAttribute에서 post 메서드 관련 객체로 재사용된다.
수정 폼에서 객체에 대한 값을 보여주기 위해 th:object 설정과, th:value="${item.id}"와 같이 속성을 지정한다. 수정 폼의 경우 id, name, value를 모두 신경써야 했는데, 많은 부분이 th:field 덕분에 자동으로 처리되는 것을 확인할 수 있다.
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="id">상품 ID</label>
<input type="text" id="id" class="form-control" th:field="*{id}" readonly>
</div>
<input type="checkbox" id="open" name="open" class="form-check-input">
체크 박스를 체크하면 HTML Form에서 open=on이라는 값이 넘어간다. 스프링은 on이라는 문자를 true 타입으로 변환해준다. 하지만 체크를 하지 않으면 JAVA 객체의 open이란 boolean 필드에 할당 되지 않는다.(null) 이런 경우 값을 해제해도 null이 넘어가기 때문에 변경이 안된다.
해당 문제를 type="hidden" name="open"을 추가해 name 앞에 를 붙여 체크 해제시에도 값이 false로 넘어갈수 있게 한다.(open=on 일시 히든 태그는 무시한다.)
타임 리프를 사용하면 히든 필드 없이 아래와 같이 간단하게 사용가능하다.
<input type="checkbox" id="open" th:field="*{open}" class="form-check-input"/>
상품 상세에서 마지막에 disabled 처리하면 체크박스 결과를 보여주기만 할수 있다.
체크박스를 멀티로 하여 등록 지역(서울,부산,제주)를 선택하도록 해보자.
@ModelAttribute("regions")
public Map<String,String> regions(){
Map<String,String>regions=new LinkedHashMap<>();
regions.put("SEOUL","서울");
regions.put("BUSAN","부산");
regions.put("JEJU","제주");
return regions;
}
해당 메서드를 클래스 전역에 설정하면 모든 메서드의 모델에 서울,제주,부산이 담기게 된다.
템플릿 코드*
<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>
1.th:value로 지정한 key 값이 존재하는지 th:field 모델 객체에서 확인하고, 존재 여부에 따라 체크 박스 default 값을 설정한다. 폼을 제출할땐 체크 여부에 따라 스프링 Item 모델 인스턴스 item.regions에 th:value로 지정한 값이 모델 객체의 필드에 매핑된다.
2.regions 모델 인스턴스를 th:each 반복문으로 id를 동적으로 생성하고 th:field(th:object에서 설정한 item 객체 , 데이터와 모델이 연동됨)의 id를 label 태그의 th:for 속성에 #ids를 넣어주게 된다.(타임리프는 반복문에서 "1","2","3"과 같이 id 뒤에 구별자를 붙여준다.)
멀티 체크박스 결과 로그
라디오 버튼은 여러 선택지 중에 하나를 선택할 때 사용할 수 있다. 라디오 버튼을 ENUM 클래스를 활용해서 개발해보자.
컨트롤러
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
해당 ItemType 클래스는 ENUM(열거형)이고 ItemType.values()는 요소가 담긴 배열을 반환한다.(ex.[BOOK,FOOD,ETC])
템플릿 코드
<!-- 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:value="{type.name()}" enum.name() 메서드는 th:field에 해당하는 enum 값을 String으로 가져온다. th:text="${type.description}"는 프로퍼티를 접근하기 때문에 Getter 메서드가 필요하다.
HTML 원본 페이지
라디오 버튼에서 체크는 필수요소가 아니기 때문에 hidden 필드가 존재하지 않는다.
th:value="{type.name()}와 th:field="${item.itemType}" 값이 같다면 checked="checked" 값을 할당한다.(ex.음식만 체크했음 )
셀렉트 박스는 여러 선택지 중에 하나를 선택할 때 사용할 수 있다. 셀렉트 박스를 자바 객체를 활용해서 개발해보자.
컨트롤러
@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;
}
Note) 해당 컨트롤러는 메서드가 실행될때마다 객체를 생성해서 주입하는 메모리가 낭비된다. 따라서 @PostConstruct 주입하는 방식을 사용하면 좋다.
템플릿 코드
<!-- 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>
<hr class="my-4">
셀렉트 박스는 <select> 문법으로 시작한다.
HTML 원본 페이지
마찬가지로 <option>에서 th:field="*{deliveryCode}"와 th:value="${deliveryCode.code}"를 비교하여 값이 있는 경우 selected="selected"를 넣어준다.