개발놀이터

타임리프심화1 본문

Spring/Spring

타임리프심화1

마늘냄새폴폴 2021. 10. 16. 01:02

본 포스트는 김영한님의 인프런강의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 보고 정리한 포스트입니다. 자세한 내용은 강의를 참고해주세요

 

*입력 폼 처리
지금부터 타임리프가 제공하는 입력 폼 기능을 적용해서 기존 프로젝트의 폼 코드를 타임리프가 지우너하는 기능을 사용해서 효율적으로 개선해보자

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

랜더링 전
<input type="text" th:field="*{itemName}"/>

랜더링 후
<input type="text" id="itemName" name="itemName" value=${item.itemName}/>

th:object를 적용하려면 먼저 해당 오브젝트 정보를 넘겨줘야한다.
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}

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



*{itemName} = ${item.itemName} 

*{}(선택변수식)은 form태그 안에서 th:object가 선언되어있을 때에 한에서 사용할 수 있다.

 
*th:object를 활용한 체크박스(단일)
랜더링 전
       

		<div>
            <div class="form-check">
                <input type="checkbox" id="open" name="open" th:field="*{open}" class="form-check-input">
                <label for="open" class="form-check-label">판매 오픈</label>
            </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>



th:field을 사용하면 id, name, value, hidden태그까지 싹 다 만들어준다. 근데 hidden태그는 왜 만들어진거지?

html에서 checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다. 체크가 됐을 때는 불린이라면 true로 반환을 해주는데 체크가 안되면 서버로 값 자체를 보내지 않기 때문에 null이 찍힌다. 이는 수정을 할때 굉장히 불편해지는데 값이 null이면 false로 바꾸고 어쩌고 하는 로직이 들어가야 하기 때문이다. 그냥 체크가 안되면 false로 해주면 안되나? 그래서 생긴것이 바로 hidden태그이다.

스프링 mvc에서는 약간의 트릭을 사용하는데 히든 필드를 하나 만들어서 _open처럼 기존 체크박스 이름 앞에 언더스코어를 붙여서 전송하면 체크를 해제했다고 인식할 수 있다. 어떻게 이를 인식하냐

1. _open=on&open=on 이렇게 둘다 전송되면 체크됐다고 판단 값을 true로 변환
2. _open=on 이렇게 하나만 전송되면 체크해제됐다고 판단 값을 false로 변환

또 타임리프에서 th:field을 사용하면 체크박스와 라디오버튼 같은 경우 th:field에 담긴 값과 th:value의 값을 비교해 자동으로 체크를 해준다. 


*th:object를 활용한 체크박스(멀티)

cf) @ModelAttribute의 특별한 사용법
model에 addAttribute가 중복사용될때 사용하는 방법이다. 여러 메소드에서 model.addAttribute가 반복된다면 이 방법을 사용하면 좋다. 

@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에 객체가 담긴다. 즉 model.addAttribute("regions", 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>



이렇게 사용하면 한가지 걸리는 점이 있다. th:field를 사용하여 *{regions}를 선언하는건 좋은데 th:each로 반복하게되면 name이나 value는 그렇다쳐도 id가 중복될 것 같다. 이런 경우에 어떻게할까? 타임리프에선 이런 문제를 해결하기 위해 정한 regions에 숫자를 하나씩 붙여가며 반복한다. 결론적으로 id는 regions1, regions2, regions3이렇게 되기 때문에 걱정할 필요 없다.

#ids.prev는 뭐지?
페이지를 동적으로 만들다보면 th:field처럼 랜더링 하기 전에 결정되지 않은 값이 생기기 마련이다. 위의 예제에선 id, name, value등등이 결정되지 않았다. checkbox나 radio버튼은 라벨과 묶어서 사용하는 것이 일반적인데 라벨에 있는 for속성을 checkbox의 id와 연결해줘야 하는데 id가 결정되지 않은 상태이다. 이럴 때 사용하라고 있는것이 바로 #ids.prev이다. 위의 regions에 숫자를 붙여 생기는 id를 받을 수 있게 만들어주는 코드이다. 


*th:object를 활용한 라디오버튼

public enum ItemType {

    BOOK("도서"), FOOD("음식"), ETC("기타");

    private final String description;

    ItemType(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}



   

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


ItemType.values()를 사용하면 해당 ENUM의 모든 정보를 배열로 반환한다. 

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


위의 체크박스 예제와 똑같다. 다른 점은 ENUM타입을 사용했다는 점이다. 


*th:object를 활용한 셀렉트 박스(콤보박스)
셀렉트 박스는 여러 선택지 중에 하나를 선택할 때 사용할 수 있다. 

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



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



위의 예제와 다 비슷비슷하다. 

'Spring > Spring' 카테고리의 다른 글

스프링 Validation  (0) 2021.10.23
스프링 메시지 / 국제화  (0) 2021.10.18
타임리프기본2  (0) 2021.10.15
타임리프기본1  (0) 2021.10.13
스프링 dirty checking과 merge  (0) 2021.08.31