본문 바로가기
Spring/MVC 2편

타임리프 - 스프링 통합과 폼

by JHyun0302 2023. 8. 6.
728x90

설정 방법

타임리프 템플릿 엔진을 스프링 빈에 등록하고, 타임리프용 뷰 리졸버를 스프링 빈으로 등록하는 방법 


build.gradle

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

 

타임리프 관련 설정을 변경하고 싶으면 다음을 참고해서 application.properties 에 추가하면 된다.

 

 

 

 


입력 폼 처리

 

  • th:object : 커맨드 객체를 지정한다.
  • *{...} : 선택 변수 식이라고 한다. th:object 에서 선택한 객체에 접근한다.
  • th:field
         HTML 태그의 id , name , value 속성을 자동으로 처리해준다.

 

 

 

 

@GetMapping("/add")
public String addForm(Model model) {
	model.addAttribute("item", new Item());
	return "form/addForm";
}

Controller에서 빈 오브젝트 만들어서 뷰에 전달

 

 

 

<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>

...

 

 

렌더링 전

<input type="text" id="itemName" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">

렌더링 후

<input type="text" id="itemName" class="form-control" placeholder="이름을 입력하세요" name="itemName" value="">

 

 

정리 : th:object , th:field 덕분에 폼을 개발할 때 약간의 편리함을 얻었다.

 

 

 

 


 체크 박스 - 단일1

 

<hr class="my-4">

<!-- 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>

 

 

실행 로그

FormItemController : item.open=true //체크 박스를 선택하는 경우 
FormItemController : item.open=null //체크 박스를 선택하지 않는 경우

 

 

HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다. (null) → 문제!

 

해결법

  • 히든 필드를 하나 만들어서, _open처럼 기존 체크 박스 이름 앞에 언더스코어(_)를 붙여서 전송하면 체크를 해제했다고 인식 가능
  • 히든 필드는 항상 전송된다.
  • 체크를 해제한 경우 여기에서 open 은 전송되지 않고, _open 만 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단

 

 

 

◎참고: HTTP 요청 메시지를 서버 볼 때 설정

application.properties

 logging.level.org.apache.coyote.http11=debug

 

 

 


 체크 박스 - 단일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 생성 결과

<!-- 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>

 

 

 

 


 체크 박스  - 멀티

 

Controller

@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의 특별한 사용법

등록 폼, 상세화면, 수정 폼에서 모두 체크 박스를 반복해서 보여주어야 한다.

이렇게 하려면 각각의 컨트롤러에서 model.addAttribute(...) 을 사용해서 체크 박스를 구성하는 데이터를 반복해서 넣어주어야 한다.

@ModelAttribute: regions 에서 반환한 값이 자동으로 모델( model )에 담기게 된다.

 

 

<!-- 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 숫자를 뒤에 붙여준다.

 

 

 

<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">

 

<!-- 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" class="form-check-label">제주</label>
	</div>
</div>
<!-- -->

 

 

 

 

 


라디오 버튼

 

Controller

@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
  return ItemType.values();
}

 

 

<!-- 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>

 

 

결과: itemType=FOOD //음식 선택, 선택하지 않으면 아무 값도 넘어가지 않는다.

 

 

<!-- radio button -->
<div>
    <div>상품 종류</div>
    <div class="form-check form-check-inline">
        <input type="radio" value="BOOK" class="form-check-input" id="itemType1" name="itemType">
        <label for="itemType1" class="form-check-label">도서</label>
    </div>

    <div class="form-check form-check-inline">
        <input type="radio" value="FOOD" class="form-check-input" id="itemType2" name="itemType" checked="checked">
        <label for="itemType2" class="form-check-label">식품</label>
    </div>

    <div class="form-check form-check-inline">
        <input type="radio" value="ETC" class="form-check-input" id="itemType3" name="itemType">
        <label for="itemType3" class="form-check-label">기타</label>
    </div>
</div>

 

 

 

 

※ 타임리프에서 ENUM 직접 사용하기

@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
	return ItemType.values();
}

 

타임리프에서 ENUM 직접 접근

<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">

 

이렇게 사용하면 ENUM의 패키지 위치가 변경되거나 할때 자바 컴파일러가 타임리프까지 컴파일 오류를 잡을 수 없으므로 추천 X!

 

 

 

 

 

 


셀렉트 박스

 

Controller

@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;
}

 

 

 

<!-- SELECT -->
<div>
    <DIV>배송 방식</DIV>
    <select class="form-select" id="deliveryCode" name="deliveryCode">
    	<option value="">==배송 방식 선택==</option>
        <option value="FAST">빠른 배송</option>
        <option value="NORMAL">일반 배송</option>
        <option value="SLOW">느린 배송</option> 
    </select>
</div>

 

 

 

 

 

<!-- SELECT -->
<div>
	<div>배송 방식</div>
	<select th:field="*{deliveryCode}" class="form-select" disabled> 
    	<option value="">==배송 방식 선택==</option>
		<option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
 		th:text="${deliveryCode.displayName}">FAST</option>
	</select>
</div>

<hr class="my-4">

 

※ disabled 를 사용해서 상품 상세에서는 셀렉트 박스가 선택되지 않도록 했다.

 

 

 

 

 

<!-- SELECT -->
<div>
    <DIV>배송 방식</DIV>
    <select class="form-select" id="deliveryCode" name="deliveryCode"> 
        <option value="">==배송 방식 선택==</option>
        <option value="FAST" selected="selected">빠른 배송</option>
        <option value="NORMAL">일반 배송</option>
        <option value="SLOW">느린 배송</option>
    </select>
</div>

 

※ selected="selected"  // 빠른 배송을 선택한 예시인데, 선택된 샐랙트 박스가 유지되는 것

반응형

'Spring > MVC 2편' 카테고리의 다른 글

로그인 처리1 - 쿠키, 세션  (0) 2023.08.07
검증2 - Bean Validation  (0) 2023.08.07
검증1 - Validation  (0) 2023.08.06
메시지, 국제화  (0) 2023.08.06
타임리프 - 기본 기능  (0) 2023.08.05