타임리프(Thymeleaf) - 기본기능6

SeungTaek·2021년 11월 27일
2

타임리프(Thymeleaf)

목록 보기
6/6
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!

📌 입력 폼 처리

th:object, *{...}, th:field 3개를 함께 사용하면 <form>을 편리하게 작성할 수 있다.

  • th:object: 커맨드 객체를 지정한다.
    • 단, 해당 오브젝트 정보를 model에 담아서 넘겨주어야 한다.
    • 등록 폼이기 때문에 데이터가 비어있는 빈 오브젝트를 만들어서 뷰에 전달한다.
  • *{...}: th:object에서 선택한 객체에 접근한다.
    • ${객체.필드}와 같다.
    • th:object=${객체명}+*{필드명} 을 사용하던지, ${객체.필드}를 사용하던지 선택하면 된다.
      • 하위 태그에 객체 접근 할 일이 많다면 전자가 편리하겠쥬?
  • th:field: HTML 태그의 id, name, value 속성을 자동으로 만들어준다.
    • input에 적용하는 필드이다.
    • id와 name은 th:field에서 지정한 변수 이름과 같게 만들어진다.
      • <label>등과 함께 사용시 id가 존재하지 않으면 ide측에서 오류로 간주한다. (타임리프는 렌더링시 만들어주기 때문). 따라서 필요할 시에는 id를 직접 적어야한다.
    • value는 th:field에서 지정한 변수의 값(model에 담긴 값)을 사용한다.
@GetMapping("/add")
public String addForm(Model model) {
  model.addAttribute("item", new Item()); //빈 오브젝트를 뷰에 넘겨준다.
  return "form/addForm";
}
<form action="item.html" th:object="${item}" method="post">
	<label for="itemName">상품명</label>
	<input type="text" id="itemName" th:field="*{itemName}" placeholder="이름을 입력하세요">
	
	<label for="price">가격</label>
	<input type="text" id="price" th:field="*{price}" placeholder="가격을 입력하세요">
</form>

위 코드를 렌더링하면 다음과 같이 변한다.

<form action="item.html" method="post">
	<label for="itemName">상품명</label>
	<input type="text" id="itemName" name="itemName" value="" placeholder="이름을 입력하세요">
	
	<label for="price">가격</label>
	<input type="text" id="price" name="price" value="" placeholder="가격을 입력하세요">
</form>






📌 체크 박스-싱글

<form action="/thymeleaf/result" method="post">
  <input type="checkbox" id="open" name="open">
  <label for="open">판매 오픈</label>
  <button>submit</button>
</form>
  • 위와 같은 form에서 전송하면 어떻게 되는가?

    1. 체크되어 있는 상태: controller에서 스프링 타입 컨버터를 이용해 정상적으로 값을 받는다.
      • String으로 받으면 open=on, boolean으로 받으면 open=true
    2. 체크되어 있지 않는 상태: open이라는 필드 자체가 서버로 전송되지 않는다.
      • 서버 입장에서는 open 필드를 체크하지 않은건지, 진짜로 값이 넘어오지 않은건지 판단할 수 없다.
  • 이런 문제를 해결하기 위해 스프링은 히든 필드를 만들어서 체크되지 않으면 open=false을 받는다.

  • 히든 필드는 다음과 같은 규칙에 따라 만든다.

    • type="hidden"
    • name="_"+기존 체크 박스 이름

[수정 코드]

<form action="/thymeleaf/result" method="post">
  <input type="checkbox" id="open" name="open">
  <input type="hidden" name="_open" value="on"/>
  <label for="open">판매 오픈</label>
  <button>submit</button>
</form>
  • 스프링 mvc가 하는 로직은 다음과 같다.

    1. open과 _open이 동시에 값이 들어옴 -> open이 진짜 체크되어 있는 상태구나 -> open=true, _open은 무시
    2. _open만 값이 들어옴 -> open은 체크되어 있지 않구나 -> open=false
  • 주의 해야 할 점

    • 본인은 이거때문에 은근 삽질을 했는데...
    • 스프링 mvc가 변환해주기 때문에 @RequestParam 처럼 값을 받으면 이 처리를 못해준다.
    • @ModelAttribute 등을 사용해서 스프링 mvc가 변환을 해주게하자.
  • 히든 필드 생성을 타임리프가 해준다.

    • 타임리프가 제공하는 폼 기능(th:field)를 사용하면 렌더링시 자동으로 히든 필드를 만들어준다.
    • 렌더링된 html 소스를 보면 확인이 가능하다.

[타임리프 적용]

<form action="/thymeleaf" method="post">
  <input type="checkbox" id="open" th:field="${shop.open}">
  <label for="open" class="form-check-label">판매 오픈</label>
  <button>submit</button>
</form>






📌 체크박스-멀티

  • 가정: regions를 map으로 만들어 model에 담은 후 체크 박스를 원소만큼 만들려고 한다.
<form action="/thymeleaf" method="post" th:object="${shop}">
  <div th:each="region : ${regions}">
    <input type="checkbox" th:field="*{regions}" th:value="${region.key}">
    <label th:for="${#ids.prev('regions')}" th:text="${region.value}">서울</label>
  </div>
</form>
  • th:each를 사용해서 객체의 원소만큼 태그를 생성 할 수 있다.
  • 문제는 <label>이다. input 태그와 연결을 해주어야 하는데, 동적으로 input 태그가 생성된다면 id 속성 값을 어떻게 해야할까?
  • 타임리프는 이 문제 해결을 위해 ids.prev(...)을 제공한다. 렌더링시 관련된 input의 id에 맞추어서 자동 생성해준다. (숫자를 뒤에 붙여준다.)
  • 따라서 렌더링된 html 코드는 다음과 같다.
<form action="/thymeleaf" method="post">
  <div>
    <input type="checkbox" value="SEOUL" id="regions1" name="regions">
    <input type="hidden" name="_regions" value="on"/>
    <label for="regions1">서울</label>
  </div>
  <div>
    <input type="checkbox" value="BUSAN" id="regions2" name="regions">
    <input type="hidden" name="_regions" value="on"/>
    <label for="regions2">부산</label>
  </div>
  <div>
    <input type="checkbox" value="JEJU" id="regions3" name="regions">
    <input type="hidden" name="_regions" value="on"/>
    <label for="regions3">제주</label>
  </div>
</form>
  • 주의할 점
    • <input><label>의 위치 관계에 따라 원하는데로 렌더링이 되지 않을 수 있다.
    • 따라서 출력 결과를 가지고 ids.prev(...) 또는 ids.next(...)을 적절하게 사용해야 한다.






📌 셀렉트 박스

  • 보통 리스트로 전달하게 되는데, value와 사용자에게 보여지는 text가 다르다면 객체로 따로 만들어서 model에 담는다.

  • 예를 들어 다음과 같이 배송 속도 데이터인 DeliveryCode 객체가 있다고 하자.

    • 서버에서 처리할 데이터 값인 code와 사용자에게 보여질 displayName으로 이루어져 있다.
@Data
public class DeliveryCode {
    String code;
    String displayName;

    public DeliveryCode(String code, String displayName) {
        this.code = code;
        this.displayName = displayName;
    }
}
  • 컨트롤러에서 model에 이 객체 리스트를 담는다고 해보자.
@GetMapping("/thymeleaf")
public String thymeleaf(Model model){
  List<DeliveryCode> deliveryCodes = new ArrayList<>();
  deliveryCodes.add(new DeliveryCode("FAST", "빠른 배송"));
  deliveryCodes.add(new DeliveryCode("NORMAL", "일반 배송"));
  deliveryCodes.add(new DeliveryCode("SLOW", "느린 배송"));
  model.addAttribute("deliveryCodes", deliveryCodes);
  return "thymeleaf";
}
  • 그럼 타임리프에서는 다음과 같이 사용할 수 있다.
<select field="deliveryCodes">
  <option value="">==배송 방식 선택==</option>
  <option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
          th:text="${deliveryCode.displayName}">fast
  </option>
</select>
  • 실제 렌더링된 결과는 다음과 같다.
<select field="deliveryCodes">
  <option value="">==배송 방식 선택==</option>
  <option value="FAST">빠른 배송</option>
  <option value="NORMAL">일반 배송</option>
  <option value="SLOW">느린 배송</option>
</select>






📌 Cf) 스프링이 제공하는 @ModelAttribute 의 편리한 사용법

  • 여러 폼에서 똑같은 데이터를 model에 담아야 한다고 하자.
    • 코드 중복이 생긴다.
  • 스프링은 중복되는 model 데이터를 별도의 메서드에 적용할 수 있다.

[사용 전] regions를 model에 담는 코드가 중복된다.

@GetMapping("/thymeleaf")
public String thymeleaf(Model model){
  Map<String, String> regions = new LinkedHashMap<>();
  regions.put("SEOUL", "서울");
  regions.put("BUSAN", "부산");
  model.addAttribute("regions", regions);
  return "thymeleaf";
}

@GetMapping("/thymeleaf/edit")
public String thymeleafEdit(Model model){
  Map<String, String> regions = new LinkedHashMap<>();
  regions.put("SEOUL", "서울");
  regions.put("BUSAN", "부산");
  model.addAttribute("regions", regions);
  return "thymeleaf/edit";
}

[사용 후]

@ModelAttribute("regions")
public Map<String, String> regions() {
  Map<String, String> regions = new LinkedHashMap<>();
  regions.put("SEOUL", "서울");
  regions.put("BUSAN", "부산");
  return regions;
}

@GetMapping("/thymeleaf")
public String thymeleaf(Model model){
  return "thymeleaf";
}

@GetMapping("/thymeleaf/edit")
public String thymeleafEdit(Model model){
  return "thymeleaf/edit";
}
  • 별도의 메서드로 분리를 한다.
  • 메서드에 어노테이션이 붙는데, @ModelAttribute(사용할 이름) 이다.
  • 모델에 담을 데이터를 리턴하면 된다.

인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요.

profile
I Think So!

0개의 댓글