일반적으로 폼에서 사용하기 좋은 thymeleaf를 별도로 정리해보려 합니다.
form은 데이터를 등록하거나 수정하는 뷰 페이지에서 주로 사용되며, 데이터를 post/get으로 넘겨주게 됩니다.
타임리프가 form에서 어떤 기능을 도울 수 있을지 정리해보겠습니다.
- input field의 id, name 자동 등록
<input> 안의 id와 name을 자동으로 등록할 수 있습니다.
컨트롤러에서 객체를 model에 넣어 줬을 때, 객체의 맴버변수를 id, name, value를 넣을 수 있습니다.
컨트롤러에서 Item 객체에 itemName 맴버변수의 값만 추가해서 model에 넣었습니다.
Html에서 th:field="*{itemName}" 만 추가한 것으로 id, name, value를 자동으로 렌더링 시켜줍니다.
Html에서 작업을 편하게 할 수 있습니다.
th:field="*{itemName}"
id : th:field 에서 지정한 변수 이름과 같다. id="itemName"
name : th:field 에서 지정한 변수 이름과 같다. name="itemName"
value : th:field 에서 지정한 변수의 값을 사용한다. value=""
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="상품명1">상품명</label>
<input type="text" class="form-control" placeholder="이름을 입력하세요" >
</div>
<div>
<label for="상품명2">상품명</label>
<input type="text" class="form-control" placeholder="이름을 입력하세요" th:field="*{itemName}">
</div>
</form>
그림처럼 item 객체의 맴버변수가 3개일 때 이를 다 적용할 수도 있습니다.
※ 참고로 label 은 주로 값을 입력받는 곳에 이름을 적는 태그이며
for 옵션을 통해 for 의 value와 id 이름이 같은것을 찾아 연결시키는 역할을 합니다.
label for 에 들어갈 값을 id값과 매칭시켜 미리 적어두던지 하는 것도 좋을 것 같습니다.
그리고 id 처럼 옵션을 수동으로 입력했다면, 수동으로 입력한 값이 우선순위를 가지게 됩니다.
다만, "" 처럼 빈값을 넣었다면 th:field의 값이 더 우선순위를 가지니 참고해야합니다.
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="상품명1">상품명</label>
<input type="text" class="form-control" placeholder="이름을 입력하세요" >
</div>
<div>
<label for="상품명2">상품명</label>
<input type="text" class="form-control" placeholder="이름을 입력하세요" th:field="*{itemName}">
</div>
<div>
<label for="상품명3">상품명</label>
<input type="text" id="" value="" class="form-control" placeholder="이름을 입력하세요" th:field="*{itemName}">
</div>
</form
- input type여러가지 지원
th:object="${item}" th:field="*{변수명}" 이부분을 핵심적으로 등록해서 사용합니다.
form 이라는 상위태그에 th:object 객체를 선언해주면, form 내부에서 th:field="*{변수명}" 으로 객체이름 없이 변수명 만으로 id, name, value 옵션을 자동으로 할당할 수 있습니다.
th:object 선언이 없다면, th:value="${객체.멤버변수명}" 으로 값을 태그에 매핑시킬 수도 있습니다.
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="상품명2">상품명</label>
<input type="text" class="form-control" placeholder="이름을 입력하세요" th:field="*{itemName}">
</div>
<!-- single checkbox 순수 체크박스, value가 체크시 true, 비체크시 null 로 서버에 온다. -->
<div>체크 확인</div>
<div>
<div class="form-check">
<input type="checkbox" th:field="*{open}" class="form-check-input" checked="checked">
<label class="form-check-label">체크 확인</label>
</div>
</div>
</form>
-- check box
체크박스로 단순히 HTML을 이용하다보면, request로 값이 서버로 도착할 때
객체의 맴버변수 Boolean 형식의 값에 매핑 되는 것을 보면, 체크시 true로, 비체크시 null로 들어오게 됩니다.
체크 박스를 체크하면 HTML Form에서 open=on 이라는 값이 넘어가며, 스프링은 on 이라는 문자를 true 타입으
로 변환해줍니다. (스프링 타입 컨버터가 이 기능을 수행한다고 합니다.)
그러나 체크 박스를 선택하지 않을 때
HTML에서 체크 박스를 선택하지 않고 폼을 전송하면 open 이라는 필드 자체가 서버로 전송되지 않습니다.
그래서 null로 값이 잡힙니다.
HTTP request body를 봐도 확인이 됩니다.
<!-- single checkbox 순수 체크박스, value가 체크시 true, 비체크시 null 로 서버에 온다. -->
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
비체크시 null로 들어오는 문제를 해결하기 위해 히든 필드를 하나 만들어 줘야 합니다.
위 소스기준으로 _open 처럼 기존 체크 박스 이름 앞에 언더스코어( _ )를 붙여서 전송하면
springMVC 에서 체크를 해제했다고 인식할 수 있습니다.
name="_변수이름" 으로 name 옵션을 설정하여 추가합니다.
<!-- single checkbox 순수 체크박스, value가 체크시 true, 비체크시 null 로 서버에 온다. -->
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on" />
<label for="open" class="form-check-label">체크 확인</label>
</div>
체크가 안됬을 경우, _open 만 서버로 요청값에 담겨 전달되며, spring MVC는 이를 'open'은 체크되지 않았다고 인식하게 됩니다.
로그를 찍어보면 다음처럼 체크되지 않은 경우 false로 값이 매핑된 것을 확인할 수 있습니다.
!! 다만 개발할때마다 매번 이럴때 hidden 필드를 추가하는건 비효율적입니다.
타임리프가 제공하는 폼 기능을 사용해서 해결할 수 있습니다.
th:field="*{변수명}" 객체에 해당하는 변수명을 th:field로 넣으면 자동으로 hidden 필드가 추가됩니다.
<div class="form-check">
<input type="checkbox" th:field="*{open}" class="form-check-input">
<label class="form-check-label">체크 확인</label>
</div>
다만 자동으로 th:field로 주입되면 value가 true로 지정되는것을 볼 수 있는데,
체크박스라서 value보다는 체크유무로 on이 전달되나 안되나 로 구분하면 됩니다.
체크유무는 checked가 들어가있나 안들어가있나로 구분하기 때문입니다.
중요한 부분은 체크박스에서 th:field="*{변수명}" 을 사용하게 하려면, th:object를 선언해주는 것이 핵심 입니다.
th:object="${item}" th:field="*{변수명}"
th:object를 쓰지 않는다면 주입된 객체와 맴버변수명을 직접 입력해서 사용하면 됩니다.
th:value="${item.id}"
-- 멀티 check box
체크박스를 여러개 써야 할 경우도 있습니다.
타임리프에서 지원해줍니다.
아래처럼 div를 여러개로 반복시키며 체크박스를 선택하도록 할 수 있습니다.
model 에 regions에 대한 map을 만들어서 regions 라는 키에 addAttribute 시켰습니다.
<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>
그림을 보면, regions라는 name으로 묶여 id, name이 자동으로 할당되었고, hidden input 도 자동으로 들어갔습니다.
비체크시 null로 들어오는 문제를 해결하기 위해 히든 필드를 만들어 주는 것인데, th:field 옵션으로 id, name, hidden 을 만들다보니 hidden 옵션도 여러개가 만들어 진 것을 확인할 수 있습니다.
하나만 있어도 충분하나 여러개 만들어져도 상관없습니다. 체크 하나도 없을시 null이 아닌 빈값으로 멤버변수에 저장되도록 설정하기 위한 것이기 때문입니다. 사진으로 예시를 보여드리면 다음과 같습니다.
만일 th:object로 등록한게 아니면 직접 멤버변수를 찾아서 이어줘도 됩니다
th:field="*{regions}" -> th:field="${item.regions}"
<div>
<div>멀티 체크박스 확인</div>
<!--/* org.thymeleaf.expression.Ids #ids 타임리프 지원기능으로 보입니다. */-->
<div th:each="region : ${regions}" class="form-check form-check-inline">
<input type="checkbox" th:field="${item.regions}" th:value="${region.key}" class="form-check-input" disabled>
<label th:for="${#ids.prev('regions')}" th:text="${region.value}" class="form-check-label"></label>
</div>
</div>
-- radio box
라디오 버튼에도 적용 가능합니다.
th:field="${객체명.멤버변수명}" 으로 값을 할당하면, id, name, value를 자동으로 매핑시켜 줍니다.
라디오 버튼은 id와 value가 중요하며, 넘길때 어떤 값을 넘겨야되는지가 확실해야 합니다.
그런데 선택하지 않을 경우 null이 반환되며, thymeleaf에서 자동으로 hidden _name 태그를 만들어 주지 않습니다.
다만 한번 선택하면, 취소가 안되는 radio 버튼 특성이 있다보니, hidden 필드를 자동으로 안만들어 준다고 합니다.
저장되는 상태가 true false상태가 아니다보니, null이 나을수도 있다고는 하나, 저장될 때 null이 저장되는걸 원지 않는 경우
내부 자바 로직에서 처리할 수 있도록 조치를 하는게 필요해보입니다.
반복을 위한 array나, List, map 을 불러와서 반복시킬 수 있습니다.
<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">
</label>
</div>
</div>
타임리프에서 Enum을 Model에담아서 전달하는 대신, 자바 객체에 직접 접근해서 사용하는 방법도 있다고합니다.
${T(hello.itemservice.domain.item.ItemType).values()} 스프링EL 문법으로 ENUM을 직접 사용
할 수 있다. ENUM에 values() 를 호출하면 해당 ENUM의 모든 정보가 배열로 반환된다고 합니다.
그런데 이렇게 사용하면 ENUM의 패키지 위치가 변경되거나 할때 자바 컴파일러가 타임리프까지 컴파일 오류를 잡을
수 없으므로 유지보수에 좋지 않은 방법 같습니다.
-- select box
라디오 버튼처럼 여러 선택지 중 하나를 선택할 때 사용합니다.
th:field="${객체명.멤버변수명}" 으로 값을 자동으로 할당하는게 가능한데, select 태그에 직접 할당해야 합니다.
each 를 통해서 반복적으로 여러 option을 설정할 수 있습니다.
다만 value와 text를 직접 할당해주는 부분이 중요합니다.
라디오 버튼처럼 자동으로 hidden 옵션이 생성되는것도 아니므로, 빈값설정을 위해 option을 더 넣어주던지,
내부자바로직에서 처리시키던지 하는 방법이 필요합니다.
<select th:field="*{deliveryCode}" class="form-select">
<option value="">==Select Box 선택==</option>
<option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
th:text="${deliveryCode.displayName}">FAST</option>
</select>
신기한 점은 <select> 태그에 th:field="${객체명.멤버변수명}" 를 넣어 줬을 경우
th:each="deliveryCode : ${deliveryCodes}" 이 코드에 따라서 모델에 넣어진 deliveryCodes가 출력 될텐데, 거기에 value에 맞는 옵션이 있다면 자동으로 selected를 설정해 준다는 것입니다.
selected="selected"
이 기능으로 수정할 값을 선택해서 수정하는 것도 가능합니다.
<select th:field="${item.deliveryCode}" class="form-select" disabled>
<option value="">==Select Box 선택==</option>
<option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
th:text="${deliveryCode.displayName}">FAST</option>
</select>
'IT기술 > spring' 카테고리의 다른 글
[Spring] 하이버네이트 Validator, Bean Validation 빈 벨리데이션이란? (0) | 2024.02.20 |
---|---|
[Spring] BindingResult 검증도구 (1) | 2024.02.20 |
[Thymeleaf] 스프링에서 웹페이지 만드는 방법1, 타임리프 문법 정리 (0) | 2024.02.17 |
[Spring] thymeleaf 디자인 간략 최소 정리 (0) | 2024.02.17 |
[Spring] MVC에서 메시지 컨버터로 응답 만들기 (0) | 2024.02.16 |