선생님이 올려주신 파일 중 form-start를 내 폴더에 받아 form으로 이름을 바꿔 사용
우리가 mvc1 강의에서 했던 간단한 프로젝트와 내용 같음. (선생님이 몇 가지만 바꾸신?)
그리고 코드를 넣었더니 게시물이 너무 길어져서 핵심 내용만 넣도록 해보겠다. (코드는 강의자료나 내가 가진 코드를 참고하자~~!)
타임리프가 제공하는 두가지 메뉴얼
타임리프는 스프링 없이도 동작하지만 스프링과 통합을 위한 다양한 기능을 편리하게 제공.
📌 스프링 통합으로 추가되는 기능들
- SpringEL 문법 통합
${@myBean.doSomething()}
처럼 스프링 빈 호출 지원- 편리한 폼 관리를 위한 추가 속성
-th:object
(기능 강화, 폼 커멘드 객체 선택)
th:field
,th:errors
,th:errorclass
- 폼 컴포넌트 기능
- 스프링의 메시지, 국제화 기능의 편리한 통합(미국에서 들어오면 영어로)
- 스프링의 검증, 오류 처리 통합
- 스프링의 변환 서비스 통합 (ConversionService)
build.gradle에 implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
를 한 줄만 넣어주면 사용할 수 있다.
th:object
: 커멘트 객체를 지정
*{ }
: 선택 변수 식.th:object
에서 선택한 객체에 접근.
th:field
: HTML 태그의 id, name, value 속성을 자동으로 처리 (id도 생략할 수 있지만 냅둔거임)
add, edit 부분 컨트롤러, 뷰 코드를 변경해 보았다.
왜 이렇게 하느냐
수정 폼의 경우 id, name, value를 모두 신경쓰지 않아도 된다. th:field
덕분에 자동으로 처리되기 때문.
그리고 나중에 검증(Validation)에서 진짜 왜 쓰는지를 알 수 있을 것
📌 추가된 요구 사항
- 판매 여부
체크박스
로 선택- 등록 지역
서울, 부산, 제주
체크박스
로 선택- 상품 종류
도서, 식품, 기타
라디오 버튼
으로 하나만 선택- 배송 방식
빠른, 일반, 느린 배송
셀렉트 박스
로 하나만 선택
ENUM, 클래스, String 코드를 추가했고 일부러 다양한 상황을 준비했다.
각각의 상황에 어떻게 폼의 데이터를 받을 수 있는지 공부하자.
(체크 박스 체크를 안 하면 값이 아예 안 넘어감. open=false를 기대했지만 open=null이 됨.)
➡️ 그래서 히든필드 사용
체크박스를 체크하면 HTML Form에서 open=on
이라는 값이 넘어가는데 스프링은 on→true로 변환해준다.
그런데 문제는 체크박스를 체크하지 않았을 때 false라는 값을 넘겨받지 못하는 것.
이는 _open
이라는 ⭐️ 히든필드를 사용함으로써 해결할 수 있었다.
HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않는데 수정시 이것이 문제가 될 수 있다. 사용자가 의도적으로 체크되어 있던 값을 해제해도 저장시 아무 값이 넘어가지 않아 값이 변경되지 않을 수 있다.
히든 필드는 _open
처럼 기존 체크박스 이름 앞에 _
를 붙여 전송하면 체크를 해제한 경우에는 open이 전송되지 않고, _open만 전송되는데 이때 스프링 MVC는 체크를 해제했다고 판단할 수 있다.
체크박스 체크
open=on&_open=on
스프링 MVC가 앞에 있는 값을 사용하고_open
무시체크박스 미체크
_open=on
하나 밖에 없어서 open 값이 체크되지 않았다는 것을 인식함
이 경우 서버에서 boolean 타입을 찍어보면 null이 아니라 false인 것을 확인할 수 있음
개발할 때마다 히든 필드를 추가하기엔 모든걸 귀찮아하는 개발자들이 가만 놔둘리가 없다.
타임리프가 제공하는 폼 기능을 이용하자.
⭐️ th:field=""
⬆️ 상품 상세에서는 체크박스를 수정할 수 없도록 disable
📌 타임리프의 체크 확인 checked="checked"
조회시 checked 속성이 추가되기 때문에 예전에 배웠듯 checked라는 속성이 존재만해도 체크해버린다.
이런 부분을 개발자가 직접 처리하려면 번거로우니까 또 타임리프 기능을 이용하자.
th:field
를 사용하면 값이 true인 경우 체크를 자동으로 처리해준다.
ItemRepository.java 파일의 update()에 findItem 코드를 추가해 변경 사항을 반영할 수 있었다.
: 하나 이상 체크(다중 선택)
new LinkedHashMap<>();
: 그냥 해시맵을 쓰면 순서가 보장되지 않으므로 LinkedHashMap 사용
📌
@ModelAttribute
의 특별한 사용법
(어떤 메소드가 호출되든 모델에 다 담김)
등록 폼, 상세화면, 수정 폼에서 모두 서울, 부산, 제주라는 체크 박스를 반복해 보여줘야 한다.그러면 각각의 컨트롤러에서
model.addAttribute()
를 사용해 체크 박스를 구성하는 데이터를 반복해 넣어줘야 한다.
@ModelAttribute
는 이렇게 컨트롤러가 있는 별도의 메소드에 적용할 수 있다.해당 컨트롤러를 요청할 때 regions에서 반환한 값이 자동으로 모델에 담기게 된다.
(이렇게 말고 각각의 컨트롤러 메소드에서 모델에 직접 데이터를 담아 처리해도 됨)
th:for="${#ids.prev('regions')}"
: (이렇게 안 하면 다 같이 한 아이디를 쓰므로 안 됨.)
멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있지만 반복해 HTML 태그를 생성할 때 생성된 HTML 태그 속성에서 name은 같아도 되지만 id는 모두 달라야 한다.
타임리프는 체크박스를 each 루프 안에서 반복해 만들 때 임의로 이름 뒤 1, 2, 3... 숫자를 붙여준다.
HTML의 id가 타임리프에 의해 동적으로 만들어지기 때문에 <label for="id 값">
으로 label의 대상이 되는 id 값을 임의로 지정하는 것은 곤란하다. 타임리프는 ids.prev()
, ids.next()
을 제공해 동적으로 생성되는 id 값을 사용할 수 있도록 한다.
⬆️ 로그 출력하는 코드 삽입 후
: 하나만 선택 가능
ItemType.values()
: 해당 ENUM의 모든 정보를 배열로 반환
라디오 버튼은 한 번 선택되면 값을 뺄 수 없다. 그러므로 수정시에도 항상 하나를 선택하도록 되어 있어 체크 박스와 달리 별도의 히든 필드가 필요 없다.
📌 타임리프에서 ENUM 직접 사용하기
해왔던 것처럼 모델에 ENUM을 담아 전달하는 대신 타임리프는 자바 객체에 직접 접근할 수 있다.
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
스프링 EL 문법으로 ENUM을 직접 사용할 수 있다. ENUM에 values()를 호출하면 해당 ENUM의 모든 정보가 배열로 반환된다.하지만 !!
이렇게 사용하면 ENUM 패키지 위치가 변경될 때 자바 컴파일러가 타임리프까지 컴파일 오류를 잡을 수 없으므로 추천하지 않는다.