타임리프는 크게 2가지의 메뉴얼을 제공한다.
이전 포스트에서 다루었던 기본 메뉴얼, 그리고 스프링 통합 메뉴얼이다.
타임리프는 스프링 없어도 동작하지만, 스프링과 통합을 위해 다양한 기능을 편리하게 제공한다.
스프링 통합으로 추가되는 기능들
- 스프링의 SpringEL 문법 통합
- 스프링 빈 호출 지원
- 편리한 폼 관리를 위한 추가 속성
- th:object (기능 강화, 폼 커맨드 객체 선택)
- th:field, th:errors, th:errorclass
- 폼 컴포넌트 기능
- checkbox, radio button, List 등을 편리하게 사용할 수 있게 지원
- 스프링의 메시지, 국제화 기능의 편리한 통합
- 스프링의 검증, 오류 처리 통합
- 스프링의 변환 서비스 통합 (ConversionService)
스프링부트는 타임리프 템플릿 엔진을 스프링 빈에 등록하고, 타임리프용 뷰 리졸버를 스프링 빈으로 등록하는 과정을 자동화해준다.
1
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf
를 build.gradle에 추가해주면 된다.
입력 폼 처리
타임리프가 제공하는 입력 폼 기능을 제공해 기존 상품관리 프로젝트의 폼 코드를 개선해본다.
addForm의 코드를 수정해봤다.
th:object 를 적용하려면 먼저 오브젝트 정보를 넘겨주어야 한다. 등록 폼이기 때문에 비어있는 빈 오브젝트(new Item()) 을 전달했다.
- th:object : form 에서 사용할 객체를 지정한다.
- th:field : id, name, value 속성을 모두 자동으로 만들어준다.
- 위의 예시에서는 label for= 부분에서 IDE가 오류로 인식해 빨간줄이 떠서 id는 남겨두었는데 id 없애도 오류없이 잘 작동된다.
- id, name, value 모두 th:field 에서 지정한 변수의 이름이나 값을 사용한다.
- id : itemName
- name : itemName
- value : “” (빈 객체이기 때문)
수정 폼 처리
비교를 위해 quantity는 남겨두었다.
위에서 설명했듯 th:object, th:field 를 사용했다.
사용 전에는 quantity와 같이 id, name, value 모두 신경 썼어야 했다.
다 지우고 field만 사용해 모든 작업을 처리할 수 있다.
참고
지금도 살짝 편리한 것은 맞지만 사실 크게 의미가 없어보인다. 이 기능들의 진짜 위력은 검증(Validation)에서 나타난다.
체크박스, 라디오버튼, 셀렉트 박스
기존 상품 서비스에서 아래와 같은 요구사항이 추가되었다고 가정한다.
- 판매 여부
- 판매 오픈 여부
- 체크박스로 선택 가능
- 등록 지역
- 서울, 부산, 제주
- 체크 박스로 다중 선택 가능하다.
- 상품 종류
- 도서, 식품, 기타
- 라디오버튼으로 하나만 선택 가능하다.
- 배송 방식
- 빠른 배송
- 일반 배송
- 느린 배송
- 셀렉트박스로 하나만 선택 가능하다.
기능 추가를 위한 추가
Enum ItemType
DeliveryCode
code는 시스템에서 “FAST” 와 같이 사용할 것이고, displayName은 고객에게 보여줄 “빠른 배송” 같은 사용.
Item 클래스에 추가.
단일 체크박스 - 타임리프 이용 전
우선 Form 안에 순수 HTML 체크박스를 넣어본다.
input의 name이 open 이기 때문에 @ModelAttribute를 통해 값이 Item의 open에 들어가게 된다.
체크를 하고 로그를 찍어보면 true값이 잘 들어간다.
하지만 체크를 하지 않으면?
false가 아닌 null 값이 들어가게 된다. open이라는 필드 자체가 서버로 전송되지 않는 것이다.
설명
HTTP 요청 메시지를 보면 체크하지 않았을 때
itemName=itemA&price=10000&quantity=10 처럼 open의 이름조차 전송되지 않는 것을 볼 수 있다.
HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다. 따라서 수정의 경우에 상황에 따라 문제가 발생할 수 있다. 사용자가 체크를 의도적으로 해제해도 아무 값도 넘어가지 않아 서버 구현에 따라 아무것도 처리하지 않을 수 있기 때문이다.
이러한 문제를 해결하기 위해 스프링 MVC는 히든 필드를 하나 만든다.
_open과 같이 기존 체크 박스 이름 앞에 언더스코어를 붙여 전송하면 체크를 해제했다고 인식할 수 있다. 히든 필드는 항상 전송된다.
따라서 체크를 해제한 경우 마찬가지로 open은 전송되지 않고 _open만 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
위와 같이 히든 타입으로 _open을 보내면
체크를 했을 때 true, 하지 않았을 때 false를 반환하는 것을 볼 수 있다.
- 체크했을 때
- 스프링 MVC가 open에 값이 있는 것을 확인하고 사용한다.
- _open은 무시된다.
- 미체크
- 스프링 MVC가 _open만 있는 것을 확인하고 open의 값이 체크되지 않았다고 인식한다.
단일 체크박스 - 타임리프 이용 후
개발할때마다 위와 같이 히든 필드를 추가하려면 매우 번거롭다. 타임리프의 폼 기능을 이용하면 이러한 부분을 자동으로 처리할 수 있다.
단순히 th:field를 이용하면 된다.
렌더링한 부분을 보면 위의 순수 HTML 코드처럼 하나하나 다 해준다. 히든 필드를 생성하는 것 까지.
참고
input 태그 안에 disabled 넣으면 체크 박스 누르는 것을 비활성화 할 수 있다.
이 때 이전 체크 여부에 따라 체크가 true인지 false인지 알아서 되어있는 상태인데 이것도 타임리프의 th:field가 자동화해주는 부분이다.
실제는 checked 라는 속성이 설정되어있어야 한다.
수정폼에 적용
수정폼에 넣고 체크를 하거나 풀고 수정을 눌러도 수정이 되지 않는다.
아이템 리포지토리에서 update 함수에 파라미터를 받아와 객체자체를 수정해주는 코드를 추가하지 않았기 때문이다.
파라미터는 정상적으로 도착한다.
멀티 체크박스
우선 등록 지역기능은 등록폼, 수정폼, 상세페이지 등등에 다 들어가야 한다.
체크박스로 체크된 부분을 파라미터를 받아 오는 것은 타임리프에서 해주고 그 파라미터로 객체를 업데이트하는 것은 리포지토리에서 하는데 우선 저렇게 서울, 부산, 제주 자체를 보여주려면 하나하나 추가해줘야 하는 것이다.
이를 편하게 해주는 스프링의 기능이 있다.
컨트롤러에 이렇게 담아두면 해당 컨트롤러 부분이 호출될 때 regions에서 반환한 값이 자동으로 모델에 담기게 된다.
물론 하나하나 직접 입력해줘도 된다. 성능적인 부분도 고려해야한다. 동적으로 변하지 않는 것이라면 Map이 생성되고 하는 부분이 계속 호출되기 때문에 static으로 만들어놓고 불러쓰는 등의 방식을 고려해야 한다.
이제 멀티 체크박스 부분을 알아보자.
th:each로 region을 돌려 input을 3개를 만든다.
th:filed 의 regions는 item.regions로 item의 List<> reigions에 들어갈 값이다.
th:value의 region.key는 each로 map을 돌린 key 값들이다. 체크될 경우 저 key 값들이 item의 regions List에 들어가는 것이다.
핵심
label 태그의 th:for를 보자.
각각의 체크박스. 즉 서울의 체크박스가 서울 값을 넣어주고, 부산의 체크박스가 부산 값을 넣어주려면 각 체크박스가 그 부분을 잘 가리켜야 한다. 아이디를 통해 찾는다.
th:for에서 체크박스의 아이디를 찾는 것이다. 하지만 roop로 th:field를 돌려 id가 알아서 생기는데 이것에 어떻게 접근할까?
1
th:for="${#ids.prev('regions')}"
#ids를 이용해 가져온다.
each로 체크박스가 반복 생성된 결과를 보면 아래와 같다.
1
2
3
<input type="checkbox" value="SEOUL" class="form-check-input" id="region1 name="regions">
<input type="checkbox" value="BUSAN" class="form-check-input" id="region2 name="regions">
<input type="checkbox" value="JEJU" class="form-check-input" id="region3 name="regions">
따라서 #ids.prev(), #ids.next() 를 이용해 동적으로 생성되는 id값을 사용하면 된다.
라디오 버튼
여러 선택지 중 하나를 선택할 때 사용할 수 있다. 라디오 버튼을 자바 Enum을 활용해 개발해본다.
컨트롤러에 위 코드를 넣는다. ENUM의 모든 정보를 배열로 반환한다.
[BOOK, FOOD, ETC]
type만 radio이고 나머지는 위에서 설명한 것과 비슷하다.
field는 @ModelAttribute로 넘기기위한, 즉 item.itemType이고,
value는 넘길 값인 BOOK, FOOD, ETC 값이다.
참고
라디오 버튼은 한번 체크하면 체크 해제가 안되기 때문에 히든필드를 따로 보내지 않아도 된다.
셀렉트 박스
여러 선택지 중 하나를 선택해 사용할 수 있다. 이번에 자바 객체를 활용한다.
컨트롤러에 추가.
select 태그로 시작하고 option을 달아주는 구조로 된다.
field로 item.deliveryCode가 지정되었고,
option을 반복문으로 model의 deliveryCodes 값들을 가져온다.