th:object="${item}" : <form> 에서 사용할 객체를 지정한다. 선택 변수 식( {...} )을 적용할 수 있다.
th:field="{itemName}" →*{itemName} 는 선택 변수 식을 사용했는데, ${item.itemName} 과 같다. 앞서 th:object 로 item 을 선택했기 때문에 선택 변수식을 적용할 수 있다.
th:field 는 id , name , value 속성을 모두 자동으로 만들어준다. → id : th:field 에서 지정한 변수 이름과 같다. id="itemName" → name : th:field 에서 지정한 변수 이름과 같다. name="itemName" → value : th:field 에서 지정한 변수의 값을 사용한다. value=""
@ModelAttribute("regions")
public Map<String, String> regions() {
Map<String, String> regions = new LinkedHashMap<>(); regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
}
@ModelAttribute 를 컨트롤러 클래스에서 별도의 메서드에 붙이면 해당 컨트롤러를 요청할 때 regions 에서 반환한 값이 자동으로 모델에 담기게 된다.
타임리프
<!-- multi checkbox -->
<div>
<div>등록 지역</div>
<div th:each="region : ${regions}" class="form-check form-check-inline"> <!-- ModelAttribute에 담긴 region -->
<input type="checkbox" th:field="${item.regions}" th:value="${region.key}" class="form-check-input" disabled> <!-- region.key: 서울 -> 부산 -> 제주 -->
<label th:for="${#ids.prev('regions')}" th:text="${region.value}" class="form-check-label">서울</label>
</div>
</div>
이 과정에서 중요한 것은 th:field를 사용하여 Model의 특정 필드와 체크박스를 연결하고, th:value를 사용하여 각 체크박스의 값을 지정하는 것이다.
th:each를 사용한 반복: regions 컬렉션에 대한 반복을 통해 각 지역에 대한 체크박스를 동적으로 생성한다. region 변수는 현재 반복 중인 지역을 나타낸다.
th:field의 사용: th:field="*{regions}"는 폼의 백엔드 모델에 있는 regions 필드와 체크박스 그룹을 바인딩한다. 이를 통해 체크박스의 선택 상태가 해당 모델 필드에 자동으로 반영된다.
th:value를 통한 값 지정: 각 체크박스에 th:value="${region.key}"를 사용하여 값을 지정한다. 이 값은 체크박스가 선택될 때 모델의 regions 필드에 저장될 식별 값이다.
th:text와 th:label을 사용한 레이블 설정: th:text를 사용하여 체크박스 옆에 표시될 지역 이름을 설정한다. th:label은 체크박스와 연결된 레이블을 지정하는데, th:for="${#ids.prev('regions')}"는 생성된 체크박스의 id를 레이블의 for 속성과 연결하여, 레이블 클릭 시 해당 체크박스가 선택되도록 한다.
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 값을 사용할 수 있도록 한다.
웹 브라우저에서 체크를 하나도 하지 않았을 때, 클라이언트가 서버에 아무런 데 이터를 보내지 않는 것을 방지한다. _regions 조차 보내지 않으면 결과는 null 이 된다. _regions 가 체크박스 숫자만큼 생성될 필요는 없지만, 타임리프가 생성되는 옵션 수 만큼 생성한다.
라디오 버튼
DeliveryCode
@Data
@AllArgsConstructor
public class DeliveryCode {
private String code;
private String displayName;
}
컨트롤러
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
ItemType.values() 를 사용하면 해당 ENUM의 모든 정보를 배열로 반환한다. 예) [BOOK, FOOD, ETC]
th:each를 사용한 반복: itemTypes 컬렉션에 대한 반복을 통해 각 상품 종류에 대한 라디오 버튼을 동적으로 생성한다. type 변수는 현재 반복 중인 상품 종류를 나타낸다.
th:field의 사용: th:field="${item.itemType}"는 폼의 백엔드 모델에 있는 itemType 필드와 라디오 버튼 그룹을 바인딩한다. 이를 통해 선택된 라디오 버튼의 값이 해당 모델 필드에 자동으로 반영된다.
th:value를 통한 값 지정: 각 라디오 버튼에 th:value="${type.name()}"를 사용하여 값을 지정한다. 이 값은 라디오 버튼이 선택될 때 모델의 itemType 필드에 저장될 값이다.
th:text와 th:label을 사용한 레이블 설정: th:text를 사용하여 라디오 버튼 옆에 표시될 상품 종류의 설명을 설정한다. th:label은 라디오 버튼과 연결된 레이블을 지정하는데, th:for="${#ids.prev('itemType')}"는 생성된 라디오 버튼의 id를 레이블의 for 속성과 연결하여, 레이블 클릭 시 해당 라디오 버튼이 선택되도록 한다.
disabled 속성의 사용: 라디오 버튼에 disabled 속성이 추가되어 있다. 이는 사용자가 해당 라디오 버튼을 선택하거나 변경할 수 없음을 의미한다. 특정 조건하에만 라디오 버튼을 활성화하려면, 서버 측 또는 클라이언트 측 스크립트를 사용하여 이 속성을 동적으로 제어할 수 있다.
실행 결과, 폼 전송
itemType=FOOD //음식 선택, 선택하지 않으면 아무 값도 넘어가지 않는다.
로그 추가
log.info("item.itemType={}", item.getItemType());
실행 로그
item.itemType=FOOD: 값이 있을 때
item.itemType=null: 값이 없을 때
체크 박스는 수정시 체크를 해제하면 아무 값도 넘어가지 않기 때문에, 별도의 히든 필드로 이런 문제를 해결했다.
라디오 버튼은 이미 선택이 되어 있다면, 수정시에도 항상 하나를 선택하도록 되어 있으므로 체크 박스와 달리 별도의 히든 필드를 사용할 필요가 없다.
th:field를 사용한 바인딩: th:field="${item.deliveryCode}"는 <select> 요소를 폼의 백엔드 모델에 있는 deliveryCode 필드와 바인딩한다. 이를 통해 사용자가 선택한 옵션의 값이 해당 모델 필드에 자동으로 반영된다.
첫 번째 option 요소: 첫 번째 option 요소는 사용자에게 기본 안내 메시지를 제공한다. value=""는 실제 선택 가능한 옵션이 아님을 나타낸다.
th:each를 사용한 옵션의 동적 생성: th:each="deliveryCode : ${deliveryCodes}"를 사용하여 deliveryCodes 컬렉션의 각 항목에 대해 <option> 요소를 동적으로 생성한다. deliveryCode 변수는 현재 반복 중인 배송 코드 객체를 나타낸다.
th:value와 th:text를 통한 옵션 설정: th:value="${deliveryCode.code}"를 사용하여 각 <option>의 값을 설정한다. 이 값은 사용자가 해당 옵션을 선택했을 때 deliveryCode 필드에 저장될 식별 값이다. th:text="${deliveryCode.displayName}"를 사용하여 드롭다운 메뉴에 표시될 배송 방식의 이름을 설정한다.
disabled 속성: <select> 요소에 disabled 속성이 추가되어 있다는 것은 사용자가 드롭다운 메뉴에서 선택을 변경할 수 없음을 의미한다. 특정 조건에서만 이 드롭다운을 활성화하려면, 서버 측 또는 클라이언트 측 스크립트를 사용하여 이 속성을 동적으로 제어할 수 있다.
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
타임리프(Thymeleaf)에서 템플릿 레이아웃은 웹 애플리케이션의 다양한 페이지에서 공통적으로 사용되는 레이아웃 구조를 재사용할 수 있도록 도와준다.
이를 통해 개발자는 중복 코드를 줄이고, 일관된 레이아웃을 유지할 수 있다.
예를 들어서 <head> 에 공통으로 사용하는 css , javascript 같은 정보들이 있는데, 이러한 공통 정보들을 한 곳 에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같이 사용하면 된다.
컨트롤러
@GetMapping("/layout")
public String layout() {
return "template/layout/layoutMain";
}
base.html
<html xmlns:th="http://www.thymeleaf.org">
<!-- common_header 를 호출해서 이 페이지의 title 태그와 link 태그를 대체함 -->
<!-- title 과 links 는 단순 파라미터 이름 -->
<head th:fragment="common_header(title,links)">
<title th:replace="${title}">레이아웃 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
<!-- 추가 -->
<th:block th:replace="${links}" />
</head>
layoutExtend.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!-- base.html로 common_header 라는 이름으로 타이틀과 링크 태그를 파라미터로 넘김 -->
<!-- 타이틀 태그와 링크 태그를 파라미터로 넘길 수 있음 -->
<head th:replace="template/layout/base :: common_header(~{::title},~{::link})">
<title>메인 타이틀</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
<body> 메인 컨텐츠 </body>
</html>
common_header(~{::title},~{::link}) 이 부분이 핵심이다.
::title 은 현재 페이지의 title 태그들을 전달한다.
::link 는 현재 페이지의 link 태그들을 전달한다.
결과
<!DOCTYPE html>
<html>
<!-- base.html로 common_header 라는 이름으로 타이틀과 링크 태그를 파라미터로 넘김 -->
<!-- 타이틀 태그와 링크 태그를 파라미터로 넘길 수 있음 -->
<head>
<title>메인 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" type="text/css" media="all" href="/css/awesomeapp.css">
<link rel="shortcut icon" href="/images/favicon.ico">
<script type="text/javascript" src="/sh/scripts/codebase.js"></script>
<!-- 추가 -->
<link rel="stylesheet" href="/css/bootstrap.min.css"><link rel="stylesheet" href="/themes/smoothness/jquery-ui.css">
</head>
<body> 메인 컨텐츠 </body>
</html>
메인 타이틀이 전달한 부분으로 교체되었다.
공통 부분은 그대로 유지되고, 추가 부분에 전달한 <link> 들이 포함된 것을 확인할 수 있다.
<head> 정도에만 적용하는게 아니라 <html> 전체에 적용할 수도 있다.
컨트롤러
@GetMapping("/layoutExtend")
public String layoutExtends() {
return "template/layoutExtend/layoutExtendMain";
}
layoutFile.html
<!DOCTYPE html>
<!-- layout 을 호출해서 이 페이지의 title 태그와 div 태그를 대체함 -->
<!-- title->title, div->section 으로 대체 -->
<!-- title 과 content 는 단순 파라미터 이름 -->
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="${title}">레이아웃 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<div th:replace="${content}">
<p>레이아웃 컨텐츠</p>
</div>
<footer> 레이아웃 푸터 </footer>
</body>
</html>
layoutExtendMain.html
<!DOCTYPE html>
<!-- layoutFile.html로 layout 라는 이름으로 타이틀과 섹션 태그를 파라미터로 넘김 -->
<!-- 타이틀 태그와 섹션 태그를 파라미터로 넘길 수 있음 -->
<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title},~{::section})}" xmlns:th="http://www.thymeleaf.org">
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
</body>
</html>
결과
<!DOCTYPE html>
<!-- layoutFile.html로 layout 라는 이름으로 타이틀과 섹션 태그를 파라미터로 넘김 -->
<!-- 타이틀 태그와 섹션 태그를 파라미터로 넘길 수 있음 -->
<html>
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
<footer> 레이아웃 푸터 </footer>
</body>
</html>
템플릿 조각을 참조할 때 사용하는 표현식은 템플릿명::조각명 형식을 따른다. 템플릿명은 조각이 정의된 파일의 이름이며, 조각명은 해당 파일 내에서 th:fragment로 정의된 조각의 이름이다.
th:fragment 가 있는 태그는 다른곳에 포함되는 코드 조각으로 이해하면 된다.
파라미터 전달하기
템플릿 조각에 파라미터를 전달하여 동적인 내용을 생성할 수도 있다. 이를 위해 조각을 정의할 때 파라미터를 명시하고, 조각을 사용할 때 파라미터 값을 전달한다.
<!-- header.html -->
<div th:fragment="headerFragment (title)">
<header>
<h1 th:text="${title}">기본 타이틀</h1>
</header>
</div>
<!-- index.html에서 조각 사용하며 파라미터 전달 -->
<div th:replace="header.html::headerFragment (${pageTitle})"></div>
예시
컨트롤러
@GetMapping("/fragment")
public String template() {
return "template/fragment/fragmentMain";
}
footer/html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy"> <!-- 이렇게 설정하면 함수 쓰듯이 copy 라는 이름으로 호출할 수 있다. (템플릿 조각) -->
푸터 자리 입니다.
</footer>
<footer th:fragment="copyParam (param1, param2)"> <!-- copyParam 라는 이름으로 파라미터를 사용할 수 있게 해준다.(템플릿 조각) -->
<p>파라미터 자리 입니다.</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
</body>
</html>
fragmentMain.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>부분 포함</h1>
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div> <!-- div 태그 안에 copy 를 넣음 -->
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div> <!-- div 태그를 copy 가 대체함 -->
<h2>부분 포함 단순 표현식</h2>
<div th:replace="template/fragment/footer :: copy"></div> <!-- copy 가 단순하다면 ~{} 생략 가능 -->
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div> <!-- copyParam 템플릿 조각 으로 div 태그 대체 -->
</body>
</html>
결과
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>부분 포함</h1>
<h2>부분 포함 insert</h2>
<div><footer> <!-- 이렇게 설정하면 함수 쓰듯이 copy 라는 이름으로 호출할 수 있다. (템플릿 조각) -->
푸터 자리 입니다.
</footer></div> <!-- div 태그 안에 copy 를 넣음 -->
<h2>부분 포함 replace</h2>
<footer> <!-- 이렇게 설정하면 함수 쓰듯이 copy 라는 이름으로 호출할 수 있다. (템플릿 조각) -->
푸터 자리 입니다.
</footer> <!-- div 태그를 copy 가 대체함 -->
<h2>부분 포함 단순 표현식</h2>
<footer> <!-- 이렇게 설정하면 함수 쓰듯이 copy 라는 이름으로 호출할 수 있다. (템플릿 조각) -->
푸터 자리 입니다.
</footer> <!-- copy 가 단순하다면 ~{} 생략 가능 -->
<h1>파라미터 사용</h1>
<footer> <!-- copyParam 라는 이름으로 파라미터를 사용할 수 있게 해준다.(템플릿 조각) -->
<p>파라미터 자리 입니다.</p>
<p>데이터1</p>
<p>데이터2</p>
</footer> <!-- copyParam 템플릿 조각 으로 div 태그 대체 -->
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
</ul>
</body>
</html>
결과
Escape
뷰 템플릿으로 HTML 화면을 생성할 때는 출력하는 데이터에 < 과 > 같은 특수 문자가 있는 것을 주의해서 사용해야 한다.
"Hello <b>Spring!</b>"
이렇게 작성한 코드는 아래와 같이 변환된다.
Hello <b>Spring!</b>
HTML 엔티티
웹브라우저는 < 를HTML테그의 시작으로 인식한다. 따라서 < 를 태그의 시작이 아니라 문자로 표현할 수 있는 방법이 필요한데, 이것을 HTML 엔티티라 한다. 그리고 이렇게 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다. 그리고 타임리프가 제공하는 th:text , [[...]] 는 기본적으로 이스케이프 (escape)를 제공한다.
예: <div th:if="${condition}">조건이 참일 때 보여질 내용</div>
th:unless
th:unless는 지정된 조건이 거짓(False)일 때 태그를 렌더링한다. th:if의 반대 역할을 한다.
예: <div th:unless="${condition}">조건이 거짓일 때 보여질 내용</div>
th:switch와 th:case
th:switch와 함께 사용되는 th:case는 다중 조건을 처리할 때 사용된다.
switch 문과 유사하게 작동한다.
Elvis 연산자
타임리프에서 Elvis 연산자는 널 값을 안전하게 처리하는데 사용된다. 이 연산자는 표현식이 널(null)인 경우 대체 값을 제공하는 방법이다. 타임리프의 표현식에서 ?: 기호를 사용하여 Elvis 연산자를 구현한다. 첫 번째 피연산자가 널이 아닌 경우 해당 값을 반환하고, 널인 경우 두 번째 피연산자의 값을 반환한다.
예를 들어, 사용자의 이름을 표시하되, 이름이 없는 경우 기본값으로 "익명"을 표시하고자 할 때 타임리프에서는 다음과 같이 Elvis 연산자를 사용할 수 있다.
<span th:text="${user.name} ?: '익명'"></span>
No-Operation
No-Operation, 줄여서 NOP라고도 불리는 것은, 실행될 때 아무런 동작도 하지 않는 연산을 말한다. 프로그래밍에서 NOP은 주로 코드의 흐름을 변경하지 않으면서 자리를 채우기 위해 사용된다. 예를 들어, 조건문에서 특정 조건 하에 아무런 동작도 수행하지 않으려 할 때 유용하다.
타임리프에서는 특별한 동작을 하지 않는 표현식으로 _ (언더스코어)를 사용한다. 이는 타임리프의 No-Operation이다. 타임리프 템플릿 내에서 어떤 조건이 만족하지 않을 때 아무런 동작도 하지 않기를 원할 경우, 이 No-Operation 표현식을 사용할 수 있다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>예시</h1>
<span th:text="${data}">html data</span>
<h1>1. 표준 HTML 주석</h1> <!-- 소스 보기에서 보임 -->
<!-- <span th:text="${data}">html data</span> -->
<h1>2. 타임리프 파서 주석</h1> <!-- 소스 보기에서 안보임 -->
<!--/* [[${data}]] */-->
<!-- 위는 한줄 아래는 여러 라인 -->
<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->
<h1>3. 타임리프 프로토타입 주석</h1> <!-- 서버 사이드 렌더링시 보임, 단순 html 파일을 열었을 때는 안보임 -->
<!--/*/ <span th:text="${data}">html data</span> /*/-->
</body>
</html>
결과
블록
<th:block> 태그는 타임리프(Thymeleaf) 템플릿 엔진에서 사용하는 특별한 태그로, 실제 HTML 문서에 추가되지 않는 가상의 태그이다.
이 태그는 주로 그룹화된 표현식을 처리하거나, 템플릿 내에서 조건부 렌더링, 반복 등의 로직을 적용할 때 유용하게 사용된다.
<th:block>은 렌더링 결과에 포함되지 않기 때문에, HTML 문서의 구조에 영향을 주지 않으면서 타임리프의 다양한 기능을 활용할 수 있다는 장점이 있다.
예를 들어, 여러 태그에 걸쳐 같은 조건을 적용하고 싶은 경우나 반복문을 사용하여 여러 요소를 생성해야 할 때 <th:block>을 사용하여 이러한 로직을 간결하게 처리할 수 있다.
컨트롤러
@GetMapping("/block")
public String block(Model model) {
addUsers(model);
return "basic/block";
}
타임리프
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- <th:block> 은 HTML 태그가 아닌 타임리프의 유일한 자체 태그 -->
<th:block th:each="user : ${users}">
<div>
사용자 이름1 <span th:text="${user.username}"></span>
사용자 나이1 <span th:text="${user.age}"></span>
</div>
<div>
요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
</div>
</th:block>
</body>
<!-- 타임리프의 특성상 HTML 태그안에 속성으로 기능을 정의해서 사용하는데, 위 예처럼 이렇게 사용하기 애매한 경우에 사용하면 된다. <th:block> 은 렌더링시 제거된다. -->
</html>
결과
자바스크립트 인라인
타임리프(Thymeleaf)는 자바스크립트 내에서 서버 사이드 변수를 사용할 수 있도록 자바스크립트 인라인 기능을 제공한다.
이를 통해 HTML 템플릿 내부에서 자바스크립트 코드에 타임리프 변수와 표현식을 쉽게 삽입하고 처리할 수 있다. 자바스크립트 인라인을 사용하면, 서버에서 생성된 데이터를 자바스크립트 변수로 바로 할당하여 클라이언트 사이드에서 활용할 수 있다.
사용 방법
자바스크립트 인라인은 th:inline="javascript" 속성을 사용하여 활성화할 수 있다. 이 속성을 <script> 태그에 추가하면, 태그 내부에서 타임리프 표현식을 자바스크립트 코드와 함께 사용할 수 있다.
`<script th:inline="javascript">`
텍스트 렌더링
var username = [[${user.username}]];
인라인 사용 전: var username = userA;
인라인 사용 후: var username = "userA";
인라인 사용 전 렌더링 결과를 보면 userA 라는 변수 이름이 그대로 남아있다. 타임리프 입장에서는 정확하게 렌더링 한 것이지만 아마 개발자가 기대한 것은 다음과 같은 "userA"라는 문자일 것이다.
결과적으로 userA 가 변수명으로 사용되어서 자바스크립트 오류가 발생한다. 다음으로 나오는 숫자 age의 경우에는 " 가 필요 없기 때문에 정상 렌더링 된다.
인라인 사용 후 렌더링 결과를 보면 문자 타입인 경우 " 를 포함해준다. 추가로 자바스크립트에서 문제가 될 수 있는 문자가 포함되어 있으면 이스케이프 처리도 해준다. 예) " → ₩"
자바스크립트 내추럴 템플릿
타임리프는 HTML 파일을 직접 열어도 동작하는 내추럴 템플릿 기능을 제공한다. 자바스크립트 인라인 기능을 사용하면 주석을 활용해서 이 기능을 사용할 수 있다.
var username2 = /*[[${user.username}]]*/ "test username";
인라인 사용 전: var username2 = /*userA*/ "test username";
인라인 사용 후: var username2 = "userA";
인라인 사용 전 결과를 보면 정말 순수하게 그대로 해석을 해버렸다. 따라서 내추럴 템플릿 기능이 동작하지 않고, 심지어 렌더링 내용이 주석처리 되어 버린다.
인라인 사용 후 결과를 보면 주석 부분이 제거되고, 기대한 "userA"가 정확하게 적용된다.
객체
타임리프의 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동으로 변환해준다.
var user = [[${user}]];
인라인 사용 전: var user = BasicController.User(username=userA, age=10);
인라인 사용 후: var user = {"username":"userA","age":10};
인라인 사용 전은 객체의 toString()이 호출된 값이다.
인라인 사용 후는 객체를 JSON으로 변환해준다.
컨트롤러
@GetMapping("/javascript")
public String javascript(Model model) {
model.addAttribute("user", new User("userA", 10));
addUsers(model);
return "basic/javascript";
}
타임리프
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 자바스크립트 인라인 사용 전 -->
<script>
var username = [[${user.username}]]; <!-- 텍스트를 "로 감싸지 않아서 오류 발생 -->
var age = [[${user.age}]];
//자바스크립트 내추럴 템플릿
var username2 = /*[[${user.username}]]*/ "test username";
//객체
var user = [[${user}]]; <!-- toString()이 호출됨 -->
</script>
<!-- 자바스크립트 인라인 사용 후 -->
<script th:inline="javascript"> <!-- 자바스크립트 문법을 알아서 처리해줌 -->
var username = [[${user.username}]];
var age = [[${user.age}]];
//자바스크립트 내추럴 템플릿
var username2 = /*[[${user.username}]]*/ "test username";
//객체
var user = [[${user}]]; <!-- 인라인 사용 후는 객체를 JSON으로 변환 -->
</script>
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript"> <!-- 객체를 JSON으로 변환 후 반복 -->
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
</body>
</html>
결과
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 자바스크립트 인라인 사용 전 -->
<script>
var username = userA; <!-- 텍스트를 "로 감싸지 않아서 오류 발생 -->
var age = 10;
//자바스크립트 내추럴 템플릿
var username2 = /*userA*/ "test username";
//객체
var user = BasicController.User(username=userA, age=10); <!-- toString()이 호출됨 -->
</script>
<!-- 자바스크립트 인라인 사용 후 -->
<script> <!-- 자바스크립트 문법을 알아서 처리해줌 -->
var username = "userA";
var age = 10;
//자바스크립트 내추럴 템플릿
var username2 = "userA";
//객체
var user = {"username":"userA","age":10}; <!-- 인라인 사용 후는 객체를 JSON으로 변환 -->
</script>
<script> <!-- 객체를 JSON으로 변환 후 반복 -->
var user1 = {"username":"userA","age":10};
var user2 = {"username":"userB","age":20};
var user3 = {"username":"userC","age":30};
</script>
</body>
</html>