티스토리 뷰

 

섹션2. 타임리프 기본기능(10개 강의, 1시간 39분)

 

1. 프로젝트 설정
- 이전강의(MVC 1편)에서 했던 프로젝트를 사용


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

스프링을 위해 개발된게 아닌가 싶을정도로 스프링 사용시 편하다 

 

 


3. 입력 폼 처리

   @GetMapping("/add")
    public String addForm(Model model) {
        model.addAttribute("item", new Item());
        return "form/addForm";
    }
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        }
    </style>
</head>
<body>

<div class="container">

    <div class="py-5 text-center">
        <h2>상품 등록 폼</h2>
    </div>

    <form action="item.html" th:action th:object="${item}" method="post">
        <div>
            <label for="itemName">상품명</label>
<!--            <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">-->
                <!--id label때문에 남겨둠-->
            <input type="text" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
        </div>
        <div>
            <label for="price">가격</label>
<!--            <input type="text" id="price" name="price" class="form-control" placeholder="가격을 입력하세요">-->
            <input type="text" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
        </div>
        <div>
            <label for="quantity">수량</label>
            <!--<input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">-->
            <input type="text" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
        </div>

        <hr class="my-4">

        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">상품 등록</button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='items.html'"
                        th:onclick="|location.href='@{/form/items}'|"
                        type="button">취소</button>
            </div>
        </div>

    </form>

</div> <!-- /container -->
</body>
</html>

 

- th:object="${item}" : 폼에서 사용할 컨트롤러에서 넘겨받은 객체를 지정

- th:field="*{itemName}"

- *{itemName} 요것은 ${item.itemName} 와 같다. 앞서 th:object 로 item 을 선택했기 때문에 선택 변수 식 적용가능

 

 

 

- 표시한거처럼 th:field 는 id , name , value 속성을 모두 자동으로 만들어줘서 편함

 

 

- edit 페이지도 수정

    @GetMapping("/{itemId}/edit")
    public String editForm(@PathVariable Long itemId, Model model) {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "form/editForm";
    }
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        }
    </style>
</head>
<body>

<div class="container">

    <div class="py-5 text-center">
        <h2>상품 수정 폼</h2>
    </div>

    <form action="item.html" th:action th:object="${item}" method="post">
        <div>
            <label for="id">상품 ID</label>
            <!--<input type="text" id="id" name="id" class="form-control" value="1" th:value="${item.id}" readonly>-->
            <input type="text" id="id" class="form-control" th:field="*{id}"  readonly>
        </div>
        <div>
            <label for="itemName">상품명</label>
            <!--<input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}">-->
            <input type="text" id="itemName" th:field="*{itemName}" class="form-control" >
        </div>
        <div>
            <label for="price">가격</label>
            <!--<input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}">-->
            <input type="text" id="price" th:field="*{price}" class="form-control" >
        </div>
        <div>
            <label for="quantity">수량</label>
            <!--<input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}">-->
            <input type="text" id="quantity" th:field="*{quantity}" class="form-control" >
        </div>

        <hr class="my-4">

        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">저장</button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='item.html'"
                        th:onclick="|location.href='@{/form/items/{itemId}(itemId=${item.id})}'|"
                        type="button">취소</button>
            </div>
        </div>

    </form>

</div> <!-- /container -->
</body>
</html>

 

 

 

4. 요구사항 추가

- 판매 여부 : [판매 오픈 여부] 를 체크 박스로 단일 선택

- 등록 지역 : [서울, 부산, 제주] 를 체크 박스로 다중 선택

- 상품 종류 : [도서, 식품, 기타]를 라디오 버튼으로 단일 선택

- 배송 방식 : [빠른배송, 일반배송, 느린배송] 을 셀렉트 박스로 하나만 선택

 

 


5. 체크박스 단일 1.

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

 

 

- 판매여부(boolean)를 체크박스로 추가

- 문제점: 선택하면 true로 잘 넘어오지만 미선택시 null로 넘어온다

  • HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 안보냄
  • 수정하는 경우에는 상황에 따라서 이 방식이 문제가 될 수 있다(사용자가 의도적으로 체크되어 있던 값을 체크를 해제해도 저장시 아무 값도 넘어가지 않기 때문) 서버 구현에 따라서 값이 오지 않은 것으로 판단해서 값을 변경하지 않을 수도 있음

 

 

  <div>판매 여부</div>
    <div>
        <div class="form-check">
            <input type="checkbox" id="open" name="open" class="form-check-input">
            <!--hidden type 추가-->
            <input type="hidden"  name="_open" value="on">
            <label for="open" class="form-check-label">판매 오픈</label>
        </div>
    </div>

 

- 해당 문제를 해결하기 위한 스프링 MVC의 트릭

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

미선택시 false로 넘어옴

 

 

 

6. 체크박스 단일 2

- 타임리프 사용

<div>판매 여부</div>
    <div>
        <div class="form-check">
            <!--<input type="checkbox" id="open" name="open" class="form-check-input">-->
            <!--hidden type 추가-->
            <!--<input type="hidden"  name="_open" value="on">-->
            <input type="checkbox" id="open" th:field="*{open}" class="form-check-input" >
            <label for="open" class="form-check-label">판매 오픈</label>
        </div>
    </div>

 

타임리프가 제공하는 폼 기능을 사용하면 자동으로 처리할 수 있다.

 

 

- HTML 생성 결과를 보면 히든 필드 부분이 자동으로 생성됨 (체크 박스의 히든 필드와 관련된 부분도 함께 해결)

 

- 로그에도 false로 잘 넘어감

 

 

상품 상세, 수정 폼도 함께 수정

 

 

 

- 판매 여부를 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인

- 타임리프의 th:field 를 사용하면, 값이 true 인 경우 체크를 자동으로 처리해줘서 아주 간편하다

 

 

 

 


7. 체크박스 멀티

    @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("regions", 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>

 

 

- label for= "id값" 에 지정된 id 가 checkbox 에서 동적으로 생성된 regions1 , regions2 , regions3 에 맞추어 순서대로 입력된 것을 확인가능

 

 

서울, 부산을 선택했을때

아무것도 선택 안했을때의 로그

 

 

상품 상세, 수정 폼도 함께 수정

 

 


8. 라디오 버튼

package hello.itemservice.domain.item;

public enum ItemType {
    BOOK("도서"), FOOD("식품"), ETC("기타");

    private final String description;

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

    public String description(){
        return description;
    }
}

- enum 사용

 

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

- itemTypes 를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 @ModelAttribute 사용

- ItemType.values() : 해당 ENUM의 모든 정보를 배열로 반환한다. ex) [BOOK, FOOD, ETC]

 

<div>
    <div>상품 종류</div>
    <div th:each="type : ${itemTypes}" class="form-check form-check-inline">
        <input type="radio" th:field="${item.itemType}" th:value="${type.name()}" class="form-check-input" disabled>
        <label th:for="${#ids.prev('itemType')}" th:text="${type.description}" class="form-check-label">
            BOOK
        </label>
    </div>
</div>

 

 

- 선택한 식품( BOOK)에 checked="checked" 가 적용된 것 확인

 

 


9. 셀렉트 박스

 @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 의 특별한 사용법을 적용하자. 참고: @ModelAttribute 가 있는 deliveryCodes() 메서드는 컨트롤러가 호출 될 때 마다 사용되므로 deliveryCodes 객체도 계속 생성된다. 이런 부분은 미리 생성해두고 재사용하는 것이 더 효율적이다.

 

 

 

 


10. 정리

'Thymeleaf' 카테고리의 다른 글

[Thymeleaf] 기본기능  (0) 2024.01.24
댓글
최근에 올라온 글
최근에 달린 댓글
링크
Total
Today
Yesterday