[Spring Boot][5] 2. 타임리프 - 스프링 통합과 폼
🏷 프로젝트 생성
스프링 부트 스타터 사이트로 이동해서 프로젝트 생성
✔️ 프로젝트 선택
- Project: Gradle
- Project Language: Java
- Spring Boot: 2.5.x
✔️ Project Metadata
- Group: hello
- Artifact: form-start
- Name: form-start
- Package name: hello.form
- Packaging: Jar
- Java: 11
✔️ Dependencies
- Spring Web
- Lombok
- Thymeleaf
🏷 타임리프 스프링 통합
📌 타임리프는 크게 2가지 메뉴얼을 제공한다
1️⃣ 기본 메뉴얼
2️⃣ 스프링 통합 메뉴얼
타임리프는 스프링 없이도 동작하지만, 스프링과 통합을 위한 다양한 기능을 편리하게 제공한다!
타임리프 관련 설정을 변경하고 싶다면 ⬇️아래의 링크⬇️를 참고해서 application.properties
에 추가하면 된다.
🏷 입력 폼 처리
지금부터 타임리프가 제공하는 입력 폼 기능을 적용해서 기존 프로젝트의 폼 코드를 타임리프가 지원하는 기능을 사용해서 효율적으로 개선해보자❗️
1️⃣ 등록 폼
✔️ FormItemController
변경
th:object
를 적용하려면 먼저 해당 오브젝트 정보를 넘겨주어야 한다.- 등록 폼이기 때문에 데이터가 비어있는 빈 오브젝트를 만들어서 뷰에 전달하자.
✔️ form/addForm.html
변경
<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>
<div>
<label for="price">가격</label>
<input type="text" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
</div>
<div>
<label for="quantity">수량</label>
<input type="text" id="quantity" th:field="*{quantity}" 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=""
📌 참고
해당 예제에서id
속성을 제거해도th:field
가 자동으로 만들어준다.
2️⃣ 수정 폼
✔️ form/editForm.html
변경
<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>
<div>
<label for="itemName">상품명</label>
<input type="text" id="itemName" class="form-control" th:field="*{itemName}" >
</div>
<div>
<label for="price">가격</label>
<input type="text" id="price" class="form-control" th:field="*{price}">
</div>
<div>
- 수정 폼은 앞서 설명한 내용과 같다.
- 수정 폼의 경우
id
,name
,value
를 모두 신경써야 했는데, 많은 부분이th:field
덕분에 자동으로 처리되는 것을 확인할 수 있다👍🏻
🏷 요구사항 추가
타임리프를 사용해서 폼에서 체크박스, 라디오 버튼, 셀렉트 박스를 편리하게 사용하는 방법을 학습해보자!
기존 상품 서비스에 다음 요구사항이 추가되었다 😊
- 판매 여부
- 판매 오픈 여부
- 체크 박스로 선택할 수 있다.
- 등록 지역
- 서울, 부산, 제주
- 체크 박스로 다중 선택할 수 있다.
- 상품 종류
- 도서, 식품, 기타
- 라디오 버튼으로 하나만 선택할 수 있다.
- 배송 방식
- 빠른 배송
- 일반 배송
- 느린 배송
- 셀렉트 박스로 하나만 선택할 수 있다.
✔️ ItemType - 상품 종류
✔️ 배송 방식 - DeliveryCode
✔️ Item - 상품
⬆️ 3개의 파일을 생성한 후 작업을 시작해보자!
🏷 체크박스 - 단일1
✔️ 단순 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 checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다.
- 수정의 경우에는 상황에 따라서 이 방식이 문제가 될 수 있다.
사용자가 의도적으로 체크되어 있던 값을 체크를 해제해도 저장시 아무 값도 넘어가지 않기 때문에,
서버 구현에 따라서 값이 오지 않은 것으로 판단해서 값을 변경하지 않을 수도 있다. - 이런 문제를 해결하기 위해서 스프링 MVC는 약간의 트릭을 사용하는데, 히든 필드를 하나 만들어서, open처럼 기존 체크 박스 이름 앞에 언더스코어()를 붙여서 전송하면 체크를 해제했다고 인식할 수 있다❗️
- 히든 필드는 항상 전송되기 때문에 체크를 해제한 경우 여기에서
open
은 전송되지 않고,_open
만 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다!!
체크 해제를 인식하기 위한 히든 필드
<input type="hidden" name="_open" value="on"/>
✔️ 기존 코드에 히든 필드 추가
<!-- 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>
✔️ 실행 로그
- 체크 박스 체크 : open=on&_open=on
- 체크 박스 미체크 :
_open=on
🏷 체크박스 - 단일2
🤔 : 개발할 때 마다 이렇게 히든 필드를 추가해야 하는 거야? 귀찮은데,,
😉 : 타임리프가 제공하는 폼 기능을 사용하면 이런 부분을 자동으로 처리할 수 있단다^^
✔️ 타임리프 - 체크박스 코드 추가
<!-- 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>
⬆️ 타임리프 체크박스 HTML 생성 결과
<input type="hidden" name="_open" value="on"/>
- 이처럼 타임리프를 사용하면 체크 박스의 히든 필드와 관련된 부분도 함께 해결해준다!!
위의 내용을 상품 상세 와 상품 수정 에도 똑같이 적용하자 👍🏻
🏷 체크박스 - 멀티
체크 박스를 멀티로 사용해서, 하나 이상을 체크할 수 있도록 해보자! 예) 등록 지역
✔️ FormItemController
- 추가
@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(...)
을 사용해서 체크 박스를 구성하는 데이터를 반복해서 넣어주어야 한다.
✔️ addForm.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>
th:for="${#ids.prev('regions')}"
- 멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다.
- 그런데 문제는 이렇게 반복해서 HTML 태그를 생성할 때, 생성된 HTML 태그 속성에서 name 은 같아도 되지만, id 는 모두 달라야 한다❗️
- 따라서 타임리프는 체크박스를 each루프 안에서 반복해서 만들 때 임의로 1,2,3 숫자를 뒤에 붙여준다!
✔️ each로 체크박스가 반복 생성된 결과 - id 뒤에 숫자가 추가
<input type="checkbox" value="SEOUL" class="form-check-input" id="regions1"
name="regions">
<input type="checkbox" value="BUSAN" class="form-check-input" id="regions2"
name="regions">
<input type="checkbox" value="JEJU" class="form-check-input" id="regions3"
name="regions">
- HTML의
id
가 타임리프에 의해 동적으로 만들어지기 때문에<label for="id 값">
으로 label 의 대상이 되는id
값을 임의로 지정하는 것은 곤란하다🤔 - 타임리프는
ids.prev(...)
,ids.next(...)
을 제공해서 동적으로 생성되는 id 값을 사용할 수 있도록 한다❗️
💡 타임리프에서 name은 겹쳐도 되지만, id는 겹쳐서는 안 된다!!
⬆️ 상품 등록 폼에서 서울, 부산을 선택하고 확인한 로그 결과👍🏻
만약 지역선택을 하지 않는다면 어떻게 될까😐❓
_regions=on&_regions=on&_regions=on
➡️ 앞서 배웠듯, 웹 브라우저에서 체크를 하나도 하지 않았을 때, 클라이언트가 서버에 아무런 데이터를 보내지 않는 것을 방지한다!
✔️ item.html
에도 멀티 체크박스 부분 추가
- (주의🤚🏻)
item.html
에는th:object
를 사용하지 않았기 때문에th:field
부분에${item.regions}
으로 적어주어야 한다.
✔️ editForm.html
에도 멀티 체크박스 부분 추가
🏷 라디오 버튼
라디오 버튼은 여러 선택지 중에 하나를 선택할 때 사용할 수 있다! 라디오 버튼을 ❗️자바 ENUM을 활용해서❗️ 개발해보자🙂 예) 상품 종류
(체크박스는 여러개 선택 가능)
✔️ FormItemController
- 추가
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
itemTypes
를 등록 폼, 조회, 수정 폼에서 모두 사용하므로@ModelAttribute
의 특별한 사용법을 적용하자.ItemType.values()
를 사용하면 해당 ENUM의 모든 정보를 배열로 반환한다.
예) [BOOK, FOOD, ETC]
✔️ addForm.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>
</di
💡 라디오 버튼은 이미 선택이 되어 있다면, 수정시에도 항상 하나를 선택하도록 되어 있으므로 체크 박스와 달리 별도의 히든 필드를 사용할 필요가 없다!
✔️ item.html
에도 라디오 버튼 부분 추가
- (주의🤚🏻)
item.html
에는th:object
를 사용하지 않았기 때문에th:field
부분에${item.itemType}
으로 적어주어야 한다.
✔️ editForm.html
에도 라디오 버튼 부분 추가
💡 타임리프에서 ENUM 직접 사용하기
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
➡️ 스프링EL 문법을 사용하여 모델에 굳이 담지 않고도 직접 사용이 가능하지만, 이 방법을 추천하지는 않는다!
🏷 셀렉트 박스
셀렉트 박스는 여러 선택지 중에 하나를 선택할 때(❗️하나만 선택해야 함❗️) 사용할 수 있다! 예) 배송 방식
✔️ FormItemController
- 추가
@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;
}
DeliveryCode
라는 자바 객체를 사용DeliveryCode
를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 여기서도@ModelAttribute
사용!
✔️ addForm.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>
✔️ item.html
에도 셀렉트 박스 부분 추가
- (주의🤚🏻)
item.html
에는th:object
를 사용하지 않았기 때문에th:field
부분에${item.deliveryCode}
으로 적어주어야 한다.
✔️ editForm.html
에도 셀렉트 박스 부분 추가
⬆️ 총 실행 결과 구우우우웃 👍🏻
Author And Source
이 문제에 관하여([Spring Boot][5] 2. 타임리프 - 스프링 통합과 폼), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sorzzzzy/Spring-Boot5-2.-타임리프-스프링-통합과-폼저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)