본문 바로가기

SPRING

김영한 - 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술 2일차

타임리프 스프링 통합

 

타임리프는 스프링과 통합을 위한 다양한 기능을 편리하게 제공한다.

 

스프링 통합으로 추가되는 기능들

스프링의 SpringEL 문법 통합

${@myBean.doSomething()}처럼 스프링 빈 호출 지원

편리한 폼 관리를 위한 추가 속성

  · th:object 

  · th:field , th:errors , th:errorclass

폼 컴포넌트 기능

   · checkbox , radio , button , List 등을 편리하게 사용할 수 있는 기능 지원

스프링의 메시지, 국제화 기능의 편리한 통합

스프링의 검증, 오류 처리 통합

스프링의 변환 서비스 통합

 

입력 폼 처리

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";
}
<form action="item.html" th:action method="post" th:object="${item}">
    <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}" 는 ${item.itemName} 과 같다. th:object로 item을 선택했기 때문에 선택 변수식을 사용했다.

 

체크 박스 - 단일1

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

 

체크 박스 값이 잘 넘어 오는지 로그를 남겨서 확인해 보았다.

 

@PostMapping("/add")
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
    log.info("item.open={}", item.getOpen());
	...
}

 

체크박스를 체크하면 HTML Form에서 open=on이라는 값이 넘어간다. 스프링은 on이라는 문자를 true 타입으로 변환해준다. (스프링 타입 컨버터가 이 기능을 수행한다.)

 

타임리프 태그를 사용하지 않고 일반적인 HTML을 사용했을 경우 

 

<input type="checkbox" id="open" name="open" class="form-check-input">

 

체크를 하지 않으면 클라이언트에서 서버로 값 자체를 보내지 않는다.

그래서 로그에서는 NULL 값이 찍히게 된다.

 

이러한 문제를 해결하기 위해서는 스프링 MVC는 히든필드를 하나 만들어서 체크박스 이름 앞에 _ 를 붙여서 전송하면 스프링 MVC는 체크를 해제했다고 판단한다. 

 

<input type="hidden" name="_open" value="on"/>

 

타임리프를 사용하면 체크박스의 히든 필드도 자동으로 생성해준다.

 

 

등록 폼에서 체크박스에 체크를 하지 않았으면 서버에는 값이 false로 찍히는데 

HTML 페이지 소스를 보면 input 태그의 value 값이 True인 것을 볼 수있다.

 

 

 

이러한 이유를 찾아 보았는데 

 

체크가 되면 open:true를 서버에 전송하고

체크가 안되면 open값 자체가 전송이 안되니 value가 true여도 문제가 없기 떄문에

서버에 잇는 value 값을 무시하고 value="true"를 디폴트 값으로 렌더링하면서 서버상의 true,false값은 checked 태그로만 확인하기 때문에 input 태그의 value 값은 항상 true로 나오게 된다.

 

체크박스 - 멀티

 

체크박스 같은 경우 등록 폼, 상세화면, 수정 폼에 모두 체크 박스를 보여주어야한다.

이렇게 하려면 컨트롤러에서 반복적으로 체크박스를 구성하는 데이터를 넣어주어야 한다.

@ModelAttribute는 이렇게 컨트롤러에 있는 별도의 메소드에 적용할 수 있다.

이렇게 하면 regions에서 반환한 값이 자동으로 모델(model)에 담기게 된다.

 

@ModelAttribute("regions")
public Map<String ,String> regions() {

    Map<String, String> regions = new LinkedHashMap<>();
    regions.put("SEOUL", "서울");
    regions.put("BUSAN", "부산");
    regions.put("JEJU", "제주");

    return regions;
}
<!-- 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')}"

멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다.

하지만 반복해서 생성할때 name은 같아도 id는 모두 달라야한다.

타임리프는 체크박스를 each 루프 안에서 반복해서 만들 때 임의로 1,2,3 숫자를 붙여준다.

 

 

로그에 보면 선택한 값이 잘 들어오는 것을 볼수있다.

 

타임리프의 체크 확인

멀티 체크 박스에서 등록 지역을 선택해서 저장하면, 조회시에 checked 속성이 추가 된 것을 볼 수 있다.

타임리프는 th:field에 지정한 값과 th:value의 값을 비교해서 체크를 자동으로 처리해준다.

 

 

라디오 버튼

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();
}

 

itemsTypes를 등록 폼 , 조회 , 수정 폼에서 모두 사용하면 @ModelAttribute를 썼다.

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

 

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

도서를 선택했을때 서버에서 로그를 찍어보았다.

 

라디오 버튼은 이미 선택이 되어 있다면, 수정시에도 항상 하나를 선택하도록 되어 있으므로 체크박스와 달리

별도의 히든 필드를 사용할 필요가 없다.

 

셀렉트 박스

배송방식을 선택 할 수있는 셀렉트 박스를 만들어 보았다.

 

@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 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>
<hr class="my-4">

 

 

배송 방식을 선택하여 소스를 확인해 보았다.

 

selected="selected" 선택된 셀렉트 박스가 유지되는 것을 확인 했다.

 

출처

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2