제육's 휘발성 코딩
반응형

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

  • 타임리프는 ${@myBean.doSomething()}처럼 스프링 빈 호출 지원
  • th:object, th:field, th:errors, th:errorclass 등 폼 관리 속성 지원
  • HTTP 요청 메시지 로깅을 보고 싶다면 propertieslogging.level.org.apache.coyote.http11=debug를 설정

입력 폼 처리

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

        <hr class="my-4">

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

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

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

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

        <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 : 커맨드 객체를 지정한다. 폼 태그에 인라인으로 지정하면 th:field="${item.itemName}"th:field="*{itemName}"와 같이 사용할 수 있다.
  • *{...} : 선택 변수 식이라고 한다. th:object에서 선택한 객체에 접근한다.
  • th:field : input 속성 중 name, id, value를 자동으로 만들어 주고, 체크 박스의 경우 체크된 필드는 checked="checked" 속성을 추가해준다.

체크 박스

  • 체크 박스를 선택하는 경우 true, 선택하지 않는 경우 null을 반환한다. Http 메시지 바디를 보면 체크박스를 선택했을 경우 on선택하지 않는 경우 아예 해당 필드가 서버에 보내지 않는다. 스프링은 on이라는 문자열을 true로 변환해준다.
<input type="hidden" name="_open" value="on"/> <!-- 기존 name : open -->
  • 스프링 MVC는 null문제를 해결하기 위해 _기존 name명 으로 히든 필드를 만들어서 전송하면 체크를 해제한 경우 _기존 name명만 전송되기 때문에 이 경우는 스프링이 체크를 해제했다고 판단한다. 즉, 체크 박스를 선택한 경우 _open을 무시하고, _open이 서버에 넘어오면 open 필드의 값이 false로 받아와진다.
<form action="item.html" th:action th:object="${item}" method="post">
    <input type="checkbox" id="open" th:field="*{open}" class="form-check-input">
</form>
  • 타임리프에서는 th:object, th:field 설정 하나만으로 히든 필드를 처리해준다.

체크 박스 - 멀티

@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(name)를 사용하여 공통된 로직을 한번에 사용할 수 있다. 즉, 해당 클래스 안에 존재하는 모든 메서드는 호출될 때 마다 해당 객체를 생성한다. 메모리 누수를 감안할 필요가 있다.
<!-- 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>
  • controller에서 보낸 데이터를 th:each를 통해 표시한다. 단, id값은 중복될 수 없으므로 th:for="${#ids.prev('regions')}"를 사용하여 루프안에서 숫자를 붙여줘야 한다.
<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">
  • each를 통해 반복 생성된 결과는 다음과 같다.

라디오 버튼

public enum ItemType {

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

    private final String description;

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

    public String getDescription() {
        return description;
    }
}
  • 라디오 버튼은 여러 선택지 중에 하나를 선택할 때 사용할 수 있다. 라디오 버튼을 자바 ENUM을 활용해보자.
  • 체크 박스는 수정 시 체크를 해제하면 아무 값도 넘어가지 않기 때문에, 별도의 히든 필드 설정이 필요하지만, 라디오 버튼은 이미 선택되어 있다면, 수정시에도 항상 하나를 선택하도록 되어 있으므로 히든 필드를 사용할 필요가 없다.
  • ENUM을 타임리프에서 직접 접근할 수도 있다. <div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}"> 다만, 타임리프 컴파일 오류를 잡을 수 없으므로 추천하지 않는다.
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
  return ItemType.values();
}
  • itemTypes를 등록 폼, 조회, 수정 폼에서 모두 사용하므로 @ModelAttribute를 사용하자.
  • .values()를 사용하면 해당 ENUM의 모든 정보를 반환한다. 예) [BOOK, FOOD, ETC]

셀렉트 박스

/**
 * FAST: 빠른 배송
 * NORMAL: 일반 배송
 * SLOW: 느린 배송
 */
@Data
@AllArgsConstructor
public class DeliveryCode {

    private String code;
    private String displayName;

}
  • 셀렉트 박스를 사용하기 위한 DeliveryCode 클래스
@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;
}
  • 컨트롤러 단에서 List@ModelAttribute를 사용하여 공통 처리를 해준다.
<!-- SELECT -->
<div>
  <div>배송 방식</div>
  <select th:field="${item.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>
  • 타임리프를 사용하여 select, option 완성

본 포스팅은 인프런 - 김영한님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 참고하였습니다.

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^