Text - text, utext
•
타임리프는 기본적으로 HTML 태그의 속성에 기능을 정의해서 동작한다.
•
HTML의 콘텐츠에 데이터를 출력할 때는 다음과 같이 th:text를 사용하면 된다.
•
HTML 태그의 속성이 아니라 HTML 콘텐츠 영역 안에서 직접 데이터를 출력하고 싶으면 [[${data}]]를 사용한다.
Escape
•
HTML 엔티티
◦
웹브라우저는 <를 HTML태그의 시작으로 인식한다.
◦
따라서 <를 태그의 시작이 아니라 문자로 표현할 수 있는 방법이 필요한데, 이것을 HTML 엔티티라 한다. 그리고 이렇게 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다. 그리고 타임리프가 제공하는 th:text ,[[...]] 는 기본적으로 이스케이스(escape)를 제공한다.
•
<→ <
•
>→>
Unescape
•
타임리프에서는 아래와 같이 text unescape 처리가 가능하다.
•
th:text → th:utext
•
[[...]] → [(...)]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>text vs utext</h1>
<ul>
<li>th:text = <span th:text="${data}"></span></li>
<li>th:utext = <span th:utext="${data}"></span></li>
</ul>
<h1><span th:inline="none">[[...]] vs [(...)]</span></h1>
<ul>
<li><span th:inline="none">[[...]] = </span>[[${data}]]</li>
<li><span th:inline="none">[(...)] = </span>[(${data})]</li>
</ul>
</body>
</html>
HTML
복사
•
th:inline="none"
◦
타임리프는 [[...]] 를 해석하기 때문에, 화면에 [[...]] 글자를 보여줄 수 없다. 이 테그 안에서는 타임리프가 해석하지 말라는 옵션이다.
변수 - SpringEL
•
변수 표현식 : ${...}
Object
•
user.username : user의 username을 프로퍼티 접근 → user.getUsername()
•
user['username'] : 위와 같음 → user.getUsername()
•
user.getUsername() : user의 getUsername() 을 직접 호출
List
•
users[0].username : List에서 첫 번째 회원을 찾고 username 프로퍼티 접근 → list.get(0).getUsername();
•
users[0]['username'] : 위와 같음
•
users[0].getUsername() : List에서 첫 번째 회원을 찾고 메소드 직접 호출
Map
•
userMap['userA'].username : Map에서 userA를 찾고, username 프로퍼티 접근 → map.get("userA").getUsername()
•
userMap['userA']['username'] : 위와 같음
•
userMap['userA'].getUsername() : Map에서 userA를 찾고 메서드 직접 호출
기본 객체들
•
${#request}
•
${#response}
•
${#session}
•
${#servletContext}
◦
${#locale}
그런데 #request 는 HttpServletRequest 객체가 그대로 제공되기 때문에 데이터를 조회하려면
request.getParameter("data") 처럼 불편하게 접근해야 한다.
이런 점을 해결하기 위해 편의 객체도 제공한다.
•
HTTP 요청 파라미터 접근: param
◦
예) ${param.paramData}
•
HTTP 세션 접근: session
◦
예) ${session.sessionData}
•
스프링 빈 접근: @
◦
예) ${@helloBean.hello('Spring!')}
반복
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>기본 테이블</h1>
<table border="1">
<tr>
<th>username</th>
<th>age</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
</tr>
</table>
<h1>반복 상태 유지</h1>
<table border="1">
<tr>
<th>count</th>
<th>username</th>
<th>age</th>
<th>etc</th>
</tr>
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}">username</td>
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
<td>
index = <span th:text="${userStat.index}"></span>
count = <span th:text="${userStat.count}"></span>
size = <span th:text="${userStat.size}"></span>
even? = <span th:text="${userStat.even}"></span>
odd? = <span th:text="${userStat.odd}"></span>
first? = <span th:text="${userStat.first}"></span>
last? = <span th:text="${userStat.last}"></span>
current = <span th:text="${userStat.current}"></span>
</td>
</tr>
</table>
</body>
</html>
HTML
복사
<tr th:each="user : ${users}">
→ th:each 는 List 뿐만 아니라 배열, java.util.Iterable , java.util.Enumeration 을 구현한 모든
객체를 반복에 사용 가능
→ Map 도 사용할 수 있는데 이 경우 변수에 담기는 값은 Map.Entry
반복 상태 유지
<tr th:each="user, userStat : ${users}">
•
반복의 두번째 파라미터를 설정해서 반복 상태 확인 가능
◦
두번째 파라미터는 생략 가능한데, 생략하면 지정한 변수명( user ) + Stat 가 됩니다.
•
여기서는 user + Stat = userStat 이므로 생략 가능
반복 상태 유지 기능
•
index : 0부터 시작하는 값
•
count : 1부터 시작하는 값
•
size : 전체 사이즈
•
even , odd : 홀수, 짝수 여부( boolean )
•
first , last :처음, 마지막 여부( boolean )
•
current : 현재 객체
Spring Boot + Thymeleaf 
입력 폼 처리
•
th:object : 커맨드 객체를 지정한다.
•
*{...} : 선택 변수 식이라고 한다. th:object에서 선택한 객체에 접근한다.
◦
${object명.field명과 동일
•
th:field : HTML 태그의 id, name, value 속성을 자동으로 처리해준다.
렌더링 전
<input type="text" th:field="*{itemName}" />
HTML
복사
렌더링 후
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />
HTML
복사
•
th:object를 적용하려면 먼저 해당 오브젝트 정보를 폼에 넘겨주어야한다.
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
Java
복사
CheckBox
•
CheckBox Item.open field
◦
check시 : on 으로 서버 전송
◦
checkbox를 선택하지 않을 때
주의! HTML에서 체크 박스를 선택하지 않고 폼을 전송하면 open이라는 필드 자체가 서버로 전송되지 않는다.
•
HTML의 checkbox는 선택이 안되면 클라이어느에서 서버로 값 자체를 보내지 않는다. 수저으이 경우에는 상황에 따라서 이 방식이 문제가 될 수 있다.
◦
사용자가 체크되어 있던 값을 체크를 해제해도 저장 시 아무 것도 넘어가지 않기 때문에, 서버 구현에 따라서 값이 오지 않은 것으로 판단해서 값을 변경하지 않을 수도 있다.
•
이런 문제를 해결하기 위해서, Spring MVC는 약간의 트릭을 사용하는데, 히든 필드를 하나 만들어서 _open처럼 기존 체크 박스 이름 앞에 언더스코어를 붙여서 전송하면 체크를 해제했다고 인식할 수 있다. 히든 필드는 항상 전송된다. 따라서 체크를 해제한 경우 여기서 open은 전송되지 않고, _open만 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
•
체크 해제를 인식하기 위한 히든 필드
<input type="hidden" name="_open" value="on"/> <!-- 히든 필드 추가 -->
HTML
복사
◦
실행 로그
item.open=true // 체크 박스를 선택하는 경우
item.open=false // 체크 박스를 선택하지 않는 경우
HTML
복사
◦
체크박스 체크 : open=on&_open=on
▪
체크박스를 체크하면 스프링 MVC가 open에 값이 있는 것을 확인하고 사용한다. 이때 _open은 무시한다.
◦
체크박스 미체크 : _open=on
▪
체크박스를 체크하지 않으면 스프링 MVC가 _open만 있는 것을 확인하고, open의 값이 체크되지 않았다고 인식한다. 이 경우, 서버에서 Boolean타입을 찍어보면 결과가 null이 아니라 false인 것을 확인할 수 있다.
CheckBox - Thymeleaf
타임리프를 통한 CheckBox처리
•
렌더링 전
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" th:field="*{open}" class="form-check-input">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
HTML
복사
•
렌더링 후
◦
타임리프의 th:field를 사용하면 checkbox의 hidden 필드까지 추가해서 렌더링한다!
•
타임리프의 체크 확인
◦
checked="checked"
체크 박스에서 판매 여부를 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인할 수 있다.
◦
이런 부분을 개발자가 직접 처리하려면 상당히 번거롭다. 타임리프의 th:field를 사용하면, 값이 true
인 경우 체크를 자동으로 처리해준다.
@ModelAttribute의 특별한 사용법
등록 폼, 상세화면, 수정 폼에서 모두 서울, 부산, 제주라는 체크 박스를 반복해서 보여주어야 한다. 이렇게
하려면 각각의 컨트롤러에서 model.addAttribute(...) 을 사용해서 체크 박스를 구성하는 데이터를
반복해서 넣어주어야 한다.
@ModelAttribute 는 이렇게 컨트롤러에 있는 별도의 메서드에 적용할 수 있다.
이렇게하면 해당 컨트롤러를 요청할 때 regions 에서 반환한 값이 자동으로 모델( model )에 담기게 된다.
물론 이렇게 사용하지 않고, 각각의 컨트롤러 메서드에서 모델에 직접 데이터를 담아서 처리해도 된다.
Multi Checkbox
th:for=”${#ids.prev(’regions’)}”
•
멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다. 그런데 문제는 이렇게 반복해서 HTML 태그를
생성할 때, 생성된 HTML 태그 속성에서 name 은 같아도 되지만, id 는 모두 달라야 한다. 따라서
타임리프는 체크박스를 each루프 안에서 반복해서 만들 때 임의로 1,2,3 숫자를 뒤에 붙여준다.
each로 체크박스가 반복 생성된 결과 - id 뒤에 숫자가 추가
<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
복사
•
HTML의 id 가 타임리프에 의해 동적으로 만들어지기 때문에 <label for="id 값"> 으로 label 의
대상이 되는 id 값을 임의로 지정하는 것은 곤란하다. 타임리프는 ids.prev(...) , ids.next(...) 을
제공해서 동적으로 생성되는 id 값을 사용할 수 있도록 한다.
RadioButton
<!-- radio button -->
<div>
<div>상품 종류</div>
<div th:each="type : ${itemTypes}" class="form-check form-check-inline">
<input type="radio" th:field="${item.itemType}" th:value="${type.name()}" class="form-check-input" disabled>
<label th:for="${#ids.prev('itemType')}" th:text="${type.description}" class="form-check-label">
BOOK
</label>
</div>
</div>
HTML
복사
•
아무것도 선택하지 않을 경우, 서버에 null로 전송. _itemType같은 필드는 만들어지지 않는다. → 수정 시, RadioButton 해제가 불가능하기 때문
thymeleaf에서 enum 사용하기
1.
Model에 enum을 담아서 전달
•
Enum클래스.values() → 배열로 전달
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
Java
복사
2.
타임리프에서 ENUM 직접 접근
•
SpringEL 문법으로 타임리프에서 직접 접근이 가능하다. (Model에 배열을 넣어주지 않아도 된다.)
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
HTML
복사
◦
ENUM에 values() 를 호출하면 해당 ENUM의 모든 정보가 배열로 반환된다.
◦
그런데 이렇게 사용하면 ENUM의 패키지 위치가 변경되거나 할때 자바 컴파일러가 타임리프까지 컴파일 오류를 잡을 수 없으므로 추천하지는 않는다.
Select Box
<!-- SELECT -->
<div>
<div>배송 방식</div>
<select th:field="${item.deliveryCode}" class="form-select" disabled>
<option value="">==배송 방식 선택==</option>
<option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
th:text="${deliveryCode.displayName}">FAST</option>
</select>
</div>
<hr class="my-4">
HTML
복사