본문 바로가기
Spring/Spring Boot & Thymeleaf

[Spring Boot & Thymeleaf] 18. 타임리프 입력 폼

by Kloong 2022. 10. 24.

참고

더보기

Spring Boot & Thymeleaf 시리즈는 김영한 님의 "스프링 MVC 2편 - 백엔드 웹 개발 활용 기술" 강의를 정리한 글입니다. 글에 첨부된 사진은 해당 강의의 강의 자료에서 캡쳐한 것입니다. 제 Github에만 올려뒀다가, 정보 공유와 강의 홍보(?)를 위해 블로그에도 업로드합니다.

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

마크다운 형식으로 작성한 글을 블로그에 다시 올리는 거라 가독성이 많이 떨어집니다. 조금더 편하게 보시려면 아래의 Github repository에서 보시면 됩니다.

 

GitHub - Kloong1/TIL: Today I Learned.

Today I Learned. Contribute to Kloong1/TIL development by creating an account on GitHub.

github.com

타임리프 - 입력 폼 처리

기본 HTML Form 대신 타임리프가 제공하는 입력 폼 기능을 적용해서 기존 프로젝트를 개선해보자.

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

적용 결과 미리 보기

타임리프 입력 폼 적용 전 HTML Form
<form action="item.html" th:action method="post">
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" name="price" class="form-control" placeholder="가격을 입력하세요">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">
        </div>
    <!-- 중략 -->
</form>
  • th:action 외에는 타임리프가 적용된 부분이 없다.
  • id="..." name="..." 이 중복되어서 나타난다.
타임리프 입력 폼 적용 후 HTML Form
<form action="item.html" th:action th:object="${item}" method="post">
        <div>
            <label for="itemName">상품명</label>
            <input type="text" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
        </div>
    <!-- 중략 -->
</form>
  • 타임리프가 지원하는 th:objectth:field 가 적용되었다.
  • 중복되는 속성인 id="..." name="..." 가 없어졌다.

타임리프 입력 폼 적용 방법

th:object 로 Model에서 오브젝트를 꺼내 커맨드 객체로 지정할 수 있다. 따라서 컨트롤러에서 해당 오브젝트를 넘겨줘야 한다. 상품 등록 폼에 타임리프를 적용하려고 하는 상황이기 때문에, 데이터가 없는 빈 상품 오브젝트를 만들어서 뷰에 넘겨줘야 한다.

FormItemController.java 변경
@GetMapping("/add")
public String addForm(Model model) {
    model.addAttribute("item", new Item());
    return "form/addForm";
}
  • item 이라는 이름을 가진 빈 Item 객체를 넘겨준다.
form/addForm.html 의 HTML Form 에 타임리프 적용
<form action="item.html" th:action th:object="${item}" method="post">
        <div>
            <label for="itemName">상품명</label>
            <input type="text" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
        </div>
    <!-- 중략 -->
</form>
  • th:object="${item}"
    • <form> 태그 내부에서 사용할 커맨드 객체를 지정한다.
    • 선택 변수 식 *{...} 을 사용하면 해당 객체에 편리하게 접근할 수 있다.
  • th:field="*{itemName}"
    • 선택 변수 식 *{...} 을 사용하여 th:object 에서 지정한 객체에 접근했다.
    • *{itemName}th:object=${item} 에 의해 ${item.itemName} 과 같은 의미를 가진다.
  • th:fieldid , name , value 속성을 모두 자동으로 만들어준다.
    • id : th:field 에서 지정한 변수 이름과 같다. id="itemName" 으로 렌더링된다.
    • name : th:field 에서 지정한 변수 이름과 같다. name="itemName" 으로 렌더링된다.
    • value : th:field 에서 지정한 변수의 값을 사용한다. 컨트롤러에서 빈 객체를 넘겨줬으므로 value="" 으로 렌더링된다.

상품 수정 폼에 타임리프 적용

상품 등록 폼에서는 뷰에 빈 Item 객체를 넘겨줬다. 하지만 상품 수정 폼에서는 데이터가 존재하는 Item 객체를 뷰에 넘겨줘야 한다. 이 때 th:field 에 의해 넘겨받은 객체의 값을 가지고 value 속성을 렌더링하므로 수정 폼을 더 편하게 만들 수 있다.

FormItemController.java 그대로 유지
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
    Item item = itemRepository.findById(itemId);
    model.addAttribute("item", item);
    return "form/editForm";
}
  • 이미 저장되어있는 Item 객체를 뷰에 넘겨준다.
form/editForm.html 타임리프 적용 전
<form action="item.html" th:action 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>
        </div>
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}">
        </div>
    <!-- 중략 -->
</form>
  • 등록 폼과 마찬가지로 id="..." name="..." 가 중복되어 나타난다.
  • 넘겨받은 Item 객체의 기존 값을 화면에 표시하기 위해 value=${item.xxx} 속성을 사용한다. 문제는 이 속성도 idname 처럼 중복되어 나타난다.
form/editForm.html 타임리프 적용 후
 <form action="item.html" th:action th:object="${item}" method="post">
        <div>
            <label for="id">상품 ID</label>
            <input type="text" class="form-control" th:field="*{id}" readonly>
        </div>
        <div>
            <label for="itemName">상품명</label>
            <input type="text" class="form-control" th:field="*{itemName}">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" class="form-control" th:field="*{price}">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" class="form-control" th:field="*{quantity}">
        </div>
  • th:field 에 의해 id="..." name="..." value="..." 의 중복이 전부 제거되었다.
  • 개발자 입장에서 HTML Form을 만드는 일이 매우 편리해졌다.
  • 심지어 멤버 변수 이름으로 접근하기 때문에 오타가 나면 IDE의 도움으로 즉각 확인이 가능하다.
    • IDE가 아니더라도 오타가 나면 렌더링에 실패해서 사용자에게 에러 페이지를 반환하기 때문에, 로그를 따로 확인해 볼 필요 없이 실행 시켜보기만 해도 에러가 났다는 사실을 바로 확인할 수 있다.

정리

  • th:object , th:field 덕분에 폼을 개발할 때 약간의 편리함을 얻었다.
  • 사실 이것의 진짜 위력은 뒤에 설명할 검증(Validation)에서 나타난다.
  • 이후 검증 부분에서 폼 처리와 관련된 부분을 더 깊이있게 알아보자.

댓글