[스프링] 스프링 통합과 폼

gyeol·2023년 11월 18일

스프링

목록 보기
28/50
post-thumbnail

김영한 님의 '스프링 MVC 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 속성 자동 처리

렌더링 전
<input type="text" th:field="*{itemName}"/>

렌더링 후
<input type="text" id="itemName", name= "itemName" th:field="*{itemName}"/>

우리가 만들어주지 않아도 id와 name이 자동생성된 것을 확인할 수 있다.
id 속성을 지워도 th:field가 자동으로 만들어 줌을 확인할 수 있다.

체크 박스

HTML에서 체크박스를 선택하지 않고 폼을 전송하면 필드 자체가 서버로 전성되지 않는다.
HTTP 요청 메시지를 서버에서 보고 싶으면 다음 설정을 추가하면 된다.

//application.properties 파일에 추가

logging.level.org.apache.coyote.http11=debug

HTML 체크 박스는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는다. 수정의 경우 상황에 따라 이 방식이 문제가 될 수 있다. 사용자가 의도적으로 체크되어 있던 값을 해제해도 저장 후 아무 값도 넘어가지 않기에 서버 구현에 따라서 값이 오지 않은 것으로 판단해 값을 변경하지 않을 수도 있다.
이런 문제를 해결하기 위해 스프링 MVC는 약간의 트릭을 사용한다. 히든 필드를 하나 만들어 _open 처럼 기존 체크 박스 이름 앞에 언더스코어를 붙여 전송하면 체크를 해제했다고 인식할 수 있다. 히든 필드는 항상 전송된다. 따라서 체크를 해제한 경우 open은 전송되지 않고, _open만 전송되는데 이 경우 스프링 MVC는 해제했다고 판단한다.

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

이를 인식하기위한 히든 필드이다. 이때 log를 찍어보면 체크박스를 선택하지 않았을 때 나오던 null값이 false로 변경됐음을 알 수 있다.

하지만 개발마다 히든 필드를 추가하는 것은 상당히 번거롭다. 타임리프가 제공하는 폼 기능을 사용하면 이런 부분을 자동으로 처리할 수 있다.

//수정 전
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on"/>

//수정 후
<input type="checkbox" id="open" th:field="*{open} class="form-check-input">

기존 체크박스 코드를 제거하고 타임리프가 제공하는 체크 박스 코드로 변경했다.
타임리프를 사용하면 체크 박스의 히든 필드와 관련된 부분도 함께 해결해준다. 체크박스에서 체크를 선택하면 타임리프의 th:field가 값이 true인 경우 체크를 자동으로 처리해주기에 편리하다.

⚠️ th:field를 사용할 때 th:object를 잘 확인하고 사용하자.

멀티 체크박스

체크박스의 값을 반복해서 보여줘야할 때 값을 항상 각각 컨트롤러에서 model.addAttribute(...)를 사용해 데이터를 반복해서 넣어주어야 한다. @ModelAttribute를 사용하면 컨트롤러에 있는 별도의 메서드에 적용할 수 있다. 이렇게 하면 해당 컨트롤러를 요청할 때 반환 값이 자동으로 모델에 담기게 된다.

예를 들어 부산, 서울, 제주라는 값을 체크박스로 보여진다고 해보자.

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

이 값들이 각 html 파일에 넘겨져서 보여줘야 된다.

<!-- 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')}" :
멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다. 그런데 문제는 이렇게 반복해서 HTML 태그를 생성할 때 생성된 HTML 태그 속성에서 name은 같아도 되지만 id는 모두 달라야한다. 따라서 체크박스를 each루프 안에서 반복해 만들때 임의로 숫자 1, 2, 3을 붙여준다.

결과:
<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">

HTML의 id가 타임리프에 의해 동적으로 만들어지기 때문에 <label for="id 값">으로 label의 대상이 되는 id값을 임의로 지정하는 것은 곤란하다. 타임리프는 ids.prev(...), ids.next(...)을 제공해 동적으로 생성되는 id값을 사용할 수 있도록 한다.

라디오 버튼

라디오 버튼은 여러 선택지 중에서 하나를 선택할 때 사용할 수 있다.
라디오 버튼도 체크박스와 똑같이 th:field를 사용한다.

체크 박스의 경우 수정 시 체크를 해제하면 아무 값도 넘어가지 않기 때문에 히든 필드로 이런 문제를 해결했다. 라디오 버튼은 이미 선택이 되어 있다만, 수정 시에도 항상 하나를 선택하도록 되어 있으므로 체크 박스와 달리 별도의 히든 필드를 사용할 필요가 없다.

셀렉트 박스

셀렉트 박스는 여러 선택지 중 하나를 선택할 수 있다.

profile
공부 기록 공간 '◡'

0개의 댓글