728x90
타임리프 스프링 통합
https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
- 타임리프 메뉴얼
- 추가된 기능
- 스프링의 SpringEL 문법 통합
- ${@myBean.doSomethid()} 처럼 스프링 빈 호출 지원
- 편리한 폼 관리를 위한 추가 속성
- th:object (기능 강화, 폼 커맨드 객체 선택)
- 커맨드 객체란 별도의 Class를 만들어서 값을 binding 받을 객체를 의미한다.
- th:field , th:errors , th:errorclass
- th:object (기능 강화, 폼 커맨드 객체 선택)
- 폼 컴포넌트 기능
- checkbox, radio button, List 등을 편리하게 사용할 수 있는 기능을 지원한다.
- 스프링 메시지, 국제화 기능의 편리한 통합
- 스프링 검증, 오류 처리 통합
- 스프링 변환 서비스 통합 (ConversionService)
🔍 스프링 부트는 설정을 자동화로 해준다.
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
- build.gradle 에 한 줄을 넣어주면 된다.
- 그러면 타임리프와 관련된 설정용 스프링 빈을 자동으로 등록해준다.
💡 타임리프 설정 변경 및 추가
- 스프링 부트가 제공하는 타임리프 설정 페이지다.
- 타임리프 관련 설정은 application.properties 에 추가해 설정 및 변경이 가능하다.
입력 폼 처리
- 타임리프 속성을 사용하여 입력 폼을 효율적으로 쓸 수 있다.
🔍 사용 방법
- th:object
- 커맨드 객체를 지정한다.
- *{...}
- 선택 변수식이다.
- th:object 에서 선택한 객체에 접근한다.
- 참고) *{itemName} 은 ${item.itemName} 과 같다.
- th:field
- HTML 태그의 id , name , value 속성을 자동으로 속성을 모두 자동으로 만들어준다.
- id 는 th:field 에서 지정한 변수 이름과 같다.
- 예) id="itemName"
- name 은 th:field 에서 지정한 변수 이름과 같다.
- 예) name="itemName"
- value 는 th:field 에서 지정한 변수의 값을 사용한다.
- 예) value="${itemName}"
💡 th:object 사용 예
📌 컨트롤러
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
- th:object 를 사용하기 위해선 컨트롤러가 해당 오브젝트 정보를 뷰에게 넘겨주어야 한다.
📌 HTML
<form action="item.html" th:action th:object="${item}" method="post">
- 컨트롤러의 모델을 통해 데이터를 전달 받는다.
- th:object="${item}" 을 통해 커맨드 객체인 item 객체를 넘겨받았다. 이것을 프로퍼티로 사용하면 된다.
💡 th:field 사용 예
- 사용 예) 상위 태그에 itemName 프로퍼티를 갖는 object를 th:object 로 선언해준 상태다.
✔ 랜더링 전
<input type="text" th:field="*{itemName}"/>
✔ 랜더링 후
<input type="text" id="itemName", name="itemName", th:value="*{itemName}" />
- id는 th:field에서 지정한 변수 이름과 같다.
단일 체크 박스 1
🔍 HTML checkbox 주의
<input type="checkbox" id="open" name="open" class="form-check-input">
- HTML checkbox는 check가 되지 않는다면 클라이언트가 서버에게 값을 보내지 않는다.
- 즉, open 이라는 프로퍼티가 false 가 아니라 아예 전송을 하지 않았기 때문에 서버에서 null 값으로 존재한다.
- 만약에 HTML checkbox를 check 한다면 on 이라는 값이 서버로 전달되는데, 이 때 스프링 타입 컨버터에 의해서 true 로 변환해준다.
- 이러한 특징은 수정의 경우 문제가 될 수 있다. 사용자가 정보를 수정할 때 체크를 한 상태에서 체크를 해제했을 때 아무 값도 넘어가지 않기 때문에 서버는 값이 변경되었는지 안되어있는지 알 수가 없다.
🔍 히든 필드를 사용하여 해결
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on"/> <!-- 히든필드 추가 -->
- 스프링은 이런 문제를 해결하기 위해 히든 필드를 지원한다.
- 히든 필드를 작성할 때 name 속성명을 기존 name 속성명 앞에 _ 를 추가해줘야 한다.
- 이렇게 작성하면 체크박스를 체크하지 않고 서버로 값을 넘길 때 서버에서 null 이 아니라 명확하게 false 값을 받을 수 있다.
단일 체크 박스 2
- 타임리프 기능을 사용하여 히든 필드를 더 간편하게 사용할 수 있다.
- HTML checkbox에서 th:field 속성을 사용하면 id, name, value, hidden field, checked 속성까지 자동으로 설정해준다.
🔍 th:field를 사용하여 해결
✔ 히든 필드 사용
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on"/> <!-- 히든필드 추가 -->
✔ 히든필드 없이 th:field 사용 (권장)
<input type="checkbox" id="open" th:field="${item.open}" class="form-check-input">
- 만약에 th:field 를 사용할 때 th:object="${item}" 를 같이 사용한다면 ${item.open} 을 *{open} 으로 바꿀 수 있다.
멀티 체크 박스
🔍 @ModelAttribute 의 또 다른 사용법
@ModelAttribute("data")
public String data() {
return "testData";
}
- 요청 파라미터를 조회할 때 쓰이는 @ModelAttribute 애노테이션을 파라미터 레벨이 아닌 메서드 레벨에 적용시키면 @ModelAttribute 애노테이션의 name의 속성값이 Model 객체의 key로 값이고, 메서드의 반환 값이 Model 객체의 value 값으로 담기게 된다.
- 예) model("data", "testData");
- @ModelAttribute 애노테이션이 메서드 레벨에 적용되면 메서드 반환값이 해당 메서드를 가지고 있는 클래스인 컨트롤러의 모든 Model 객체에 담기게 된다.
- @ModelAttirbute 애노테이션이 메서드 레벨에 적용되면 컨트롤러가 호출될 때 마다 해당 메서드도 매번 같이 호출된다.
🔍 멀티 체크 박스 사용법
<!-- 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>
- 컨트롤러에게 모델을 통해 반복 가능한 요소인 regions 값을 받는다.
- th:each 를 통해 regions 값들이 반복하면서 한 개씩 순차적으로 regions 값에 들어가게 된다.
- th:field 속성을 사용하여 th:value에 들어가는 region.key값과 비교해 값이 포함되어 있으면 HTML checked 속성이 추가된다.
- th:for="${#ids.prev('regions')}"
- 멀티 체크 박스는 같은 이름의 여러 체크박스가 생성된다.
- name 속성명은 모두 같아도 되지만, id 명은 유일해야하기에 체크박스마다 달라야한다.
- 따라서 타임리프에서는 편의목적으로 ids라는 프로퍼티와 메서드를 제공한다. ids는 반복하는 요소의 인덱스로 1, 2, 3 처럼 숫자다.
- ids.prev(...) : 아이디 앞에 인수값을 붙여준다.
- 예) data1, data2, data3, .....
- ids.next(...) : 아이디 뒤에 인수값을 붙여준다.
- 예) 1data, 2data, 3data, .....
- 해당 메서드들을 통해 HTML id 값을 동적으로 만들 수 있다.
- ids.prev(...) : 아이디 앞에 인수값을 붙여준다.
🧷 정리
- 여러 개의 체크박스가 필요한 경우 th:each 를 이용해 요소를 반복 생성할 수 있다.
- th:field 와 th:value 를 같이 작성하면 타임리프가 자동으로 두 값을 비교해 checked 설정을 해준다.
- 반복 생성되는 태그의 아이디를 모두 다르게 생성해줘야하기 때문에 타임리프에서 ids라는 편의 프로퍼티와 관련 메서드를 제공한다.
라디오 버튼
- 라디오 버튼은 자바 Enum을 이용해서 만들 수 있다.
🔍 사용법
- th:each 속성을 이용해서 여러 라디오 버튼을 만든다.
- 예제 코드
<!-- radio button -->
<div th:object="${item}">
<div>상품 종류</div>
<div th:each="type : ${반복요소}" class="form-check form-check-inline">
<input type="radio" th:field="*{라디오요소}" th:value="${type.비교값}"
class="form-check-input">
<label th:for="${#ids.prev('itemType')}" th:text="${type.description}"
class="form-check-label">
BOOK
</label>
</div>
</div>
- 반복요소의 count 만큼 내부 태그가 반복되어 생성된다.
- th:field 에 들어간 th:object 에서 선언한 item의 요소는 th:value 의 값과 비교해 checked가 자동으로 표현된다.
- 멀티 체크박스와 동일하게 th:each 속성 내부에서 ids를 이용해 인덱스 접근 및 인수로 넘겨준 값과 문자열을 결합하여 고유한 아이디를 만들 수 있다.
더보기
<!-- radio button -->
<div th:object="${item}">
<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>
🔍 @ModelAttribute를 통해 Enum 직접 사용하기
📌 ItemType (enum)
package hello.itemservice.domain.item;
public enum ItemType {
BOOK("도서"), FOOD("음식"), ETC("기타");
private final String description;
ItemType(String description) {
this.description= description;
}
public String getDescription() {
return description;
}
}
📌 @ModelAttribute로 Enum 값 넣기
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
- @ModelAttribute 를 메서드 레벨에 적용하여 해당 컨트롤러의 모든 모델에 Enum 값을 넣어줬다.
- Enum 클래스는 values() 메서드를 사용하면 Enum의 모든 정보가 배열로 반환된다.
🔍 타임리프에서 Enum 직접 접근하기
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
- ${T(FQCN)}
- FQCN : Fully Qualified Class Name
- FQCN 자리에 해당 Enum 파일의 패키지명까지 다 적어줘야한다.
- ${T(hello.itemservice.domain.item.ItemType).values()} 처럼 스프링EL 문법으로 타임리프로 Enum을 직접 사용할 수 있다.
- Enum 패키지 경로가 변경될 때 컴파일러가 타임리프까지 컴파일 오류를 잡아줄 수 없으므로 권장하지 않는다.
🧷 정리
- 라디오버튼은 아무것도 선택하지 않을 경우 아무 값도 넘어가지 않아 Null이 된다.
- 체크박스와 다르게 별도의 히든필드가 자동으로 생성되지 않는다.
- 라디오버튼은 선택이 한 번 되면 항상 하나를 선택하도록 되어있어 히든필드가 필요하지 않다.
셀렉트 박스
🔍 사용법
- 반복요소가 있는 셀렉트박스 역시 th:each 로 요소를 반복한다.
- 수정페이지에서 기본으로 선택되어야 하는 값이 있다면 th:field 요소를 이용해 가능하다.
- select 태그 에 선언된 th:field 속성과 option 태그 에서 선언된 th:value 를 타임리프가 비교해 일치할 경우 자동으로 seleted 속성을 추가한다.
📌 Item 객체
public class Item {
private String deliveryCode;
...
}
📌 DeliveryCode (배송상태)
/**
* FAST : 빠른 배송
* NORMAL : 일반 배송
* SLOW : 느린 배송
*/
@Data
@AllArgsConstructor
public class DeliveryCode {
private String code;
private String displayName;
}
- 배송상태를 나타내는 경우 지금처럼 클래스로 만들어도 되고 Enum으로 만들어도 된다.
📌 컨트롤러
@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;
}
- @ModelAttribute가 메서드 레벨에 사용되어서 컨트롤러가 호출 될 때 마다 deliveryCodes() 메서드가 호출된다. 그래서 deliveryCodes 객체도 계속 생성된다.
- 예제라서 이렇게 단순히 했다. 실무에서는 deliveryCodes를 미리 static으로 생성하고 생성된 것을 재사용하는 것이 더 효율적이다.
📌 HTML
<!-- SELECT -->
<div>
<div>배송 방식</div>
<select th:field="${item.deliveryCode}" class="form-select">
<option value="">==배송 방식 선택==</option>
<option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
th:text="${deliveryCode.displayName}">FAST</option>
</select>
</div>
🔍 결과
👀 참고자료
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
https://catsbi.oopy.io/81398571-33b8-471f-8191-ca7415b86326
728x90
'[ Spring ] > SpringMVC 2편' 카테고리의 다른 글
[Spring] 로그인 처리1 - 쿠키, 세션 (0) | 2022.03.12 |
---|---|
[Spring] 검증2 - Bean Validation (0) | 2022.03.11 |
[Spring] 검증1 - Validation (0) | 2022.03.07 |
[Spring] 메시지, 국제화 (0) | 2022.03.06 |
[Spring] Thymeleaf 타임리프 - 기본 기능 (0) | 2022.03.02 |