01.Thymeleaf
Thymeleaf
๊ธฐ๋ณธ ๊ธฐ๋ฅ
์ฌ์ฉ ์ ์ธ
<html xmlns:th="http://www.thymeleaf.org"></html>
์์ฑ ๋ณ๊ฒฝ
th:href="@{/css/bootstrap.min.css}"
URL ๋งํฌ ํํ์
th:href="@{/css/bootstrap.min.css}" <!-- --> th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}" th:href="@{|/basic/items/${item.id}|}"
์์ฑ ๋ณ๊ฒฝ
th:onclick="|location.href='@{/basic/items/add}'|"
๋ฐ๋ณต ์ถ๋ ฅ
<tr th:each="item : ${items}"></tr>
๋ณ์ ํํ์
<td th:text="${item.price}">10000</td>
์์ฑ ๋ณ๊ฒฝ
<input type="text" id="price" name="price" value="10000" th:value="${item.price}" />
๊ธฐ๋ณธ ํํ์
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#standard-expression-syntax
ใ
๊ฐ๋จํ ํํ
- ๋ณ์ ํํ์: ${...}
- ์ ํ ๋ณ์ ํํ์: \*{...}
- ๋ฉ์์ง ํํ์: #{...}
- ๋งํฌ URL ํํ์: @{...}
- ์กฐ๊ฐ ํํ์: ~{...}
ใ
๋ฆฌํฐ๋ด
- ํ
์คํธ: 'one text', 'Another one!',โฆ
- ์ซ์: 0, 34, 3.0, 12.3,โฆ
- ๋ถ๋ฆฐ: true, false
- ๋: null
- ๋ฆฌํฐ๋ด ํ ํฐ: one, sometext, main,โฆ
ใ
๋ฌธ์ ์ฐ์ฐ
- ๋ฌธ์ ํฉ์น๊ธฐ: +
- ๋ฆฌํฐ๋ด ๋์ฒด: |The name is ${name}|
ใ
์ฐ์ ์ฐ์ฐ
- Binary operators: +, -, \*, /, %
- Minus sign (unary operator): -
ใ
๋ถ๋ฆฐ ์ฐ์ฐ
- Binary operators: and, or
- Boolean negation (unary operator): !, not
ใ
๋น๊ต์ ๋๋ฑ
- ๋น๊ต: >, <, >=, <= (gt, lt, ge, le)
- ๋๋ฑ ์ฐ์ฐ: ==, != (eq, ne)
ใ
์กฐ๊ฑด ์ฐ์ฐ
- If-then: (if) ? (then)
- If-then-else: (if) ? (then) : (else)
- Default: (value) ?: (defaultvalue)
ใ
ํน๋ณํ ํ ํฐ
- No-Operation: \_
ํ
์คํธ
ํ ์คํธ ์ถ๋ ฅ
๊ธฐ๋ณธ์ ์ผ๋ก escape ๋ฅผ ์ ๊ณต
escape : HTML ์์ ์ฌ์ฉํ๋ ํน์ ๋ฌธ์๋ฅผ HTML ์ํฐํฐ๋ก ๋ณ๊ฒฝํ๋ ๊ฒ
HTML ์ํฐํฐ :
<
๋ฌธ์๋ฅผ ํ๊ทธ์ ์์์ด ์๋ ๋ฌธ์๋ก ํํํ๋ ๋ฐฉ๋ฒ
<ul> <li>th:text ์ฌ์ฉ = <span th:text="${data}"></span></li> <li>์ปจํ ์ธ ์์์ ์ง์ ์ถ๋ ฅํ๊ธฐ = [[${data}]]</li> </ul>
Unescape
th:text
-->th:utext
[[...]]
-->[(...)]
<ul> <li>th:utext = <span th:utext="${data}"></span></li> <li><span th:inline="none">[(...)] = </span>[(${data})]</li> </ul>
SpringEL ํํ์
Object
${user.username}
= userA${user['username']}
= userA${user.getUsername()}
= userA
List
${users[0].username}
= userA${users[0]['username']}
= userA${users[0].getUsername()}
= userA
Map
${userMap['userA'].username}
= userA${userMap['userA']['username']}
= userA${userMap['userA'].getUsername()}
= userA
Safe Navigation Operator
<div th:if="${errors?.containsKey('globalError')}"></div>
errors
?.
์ errors ๊ฐ null ์ผ๋ NullPointerException ๋์ , null ์ ๋ฐํํ๋ ๋ฌธ๋ฒ ์ฐธ๊ณ
์ง์ญ๋ณ์
<div th:with="first=${users[0]}">
<p>first member name : <span th:text="${first.username}"></span></p>
</div>
๊ธฐ๋ณธ ๊ฐ์ฒด
Thymeleaf ๊ธฐ๋ณธ ๊ฐ์ฒด
${#request}
${#response}
${#session}
${#servletContext}
${#locale}
ํธ์ ๊ฐ์ฒด
HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ ์ ๊ทผ:
${param.paramData}
HTTP ์ธ์ ์ ๊ทผ:
${session.sessionData}
์คํ๋ง ๋น ์ ๊ทผ:
${@helloBean.hello('Spring!')}
์ ํธ๋ฆฌํฐ ๊ฐ์ฒด์ ๋ ์ง
#message : ๋ฉ์์ง, ๊ตญ์ ํ ์ฒ๋ฆฌ
#uris : URI ์ด์ค์ผ์ดํ ์ง์
#dates : java.util.Date ์์ ์ง์
#calendars : java.util.Calendar ์์ ์ง์
#temporals : ์๋ฐ8 ๋ ์ง ์์ ์ง์
#numbers : ์ซ์ ์์ ์ง์
#strings : ๋ฌธ์ ๊ด๋ จ ํธ์ ๊ธฐ๋ฅ
#objects : ๊ฐ์ฒด ๊ด๋ จ ๊ธฐ๋ฅ ์ ๊ณต
#bools : boolean ๊ด๋ จ ๊ธฐ๋ฅ ์ ๊ณต
#arrays : ๋ฐฐ์ด ๊ด๋ จ ๊ธฐ๋ฅ ์ ๊ณต
#lists , #sets , #maps : ์ปฌ๋ ์ ๊ด๋ จ ๊ธฐ๋ฅ ์ ๊ณต
#ids : ์์ด๋ ์ฒ๋ฆฌ ๊ด๋ จ ๊ธฐ๋ฅ ์ ๊ณต, ๋ค์์ ์ค๋ช
Reference
URL ๋งํฌ
๋จ์ URL
/hello
<a th:href="@{/hello}"></a>
query parameter
/hello?param1=data1¶m2=data2
<a th:href="@{/hello(param1=${param1}, param2=${param2})}"></a>
path variable
/hello/data1/data2
<a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}" ></a>
query parameter + path variable
/hello/data1?param2=data2
<a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}"></a>
Reference
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#link-urls
๋ฆฌํฐ๋ด
๋ฌธ์: 'hello' (๋ฌธ์ ๋ฆฌํฐ๋ด์ ํญ์ ์์๋ฐ์ดํ๋ก ๊ฐ์ธ์ผ ํจ)
<span th:text="'hello'"></span>
์ซ์: 10
๋ถ๋ฆฐ: true , false
null: null
์ฐ์ฐ
์ฐ์ ์ฐ์ฐ
10 + 2 = <span th:text="10 + 2"></span>
๋น๊ต ์ฐ์ฐ
<!-- > (gt) < (lt) >= (ge) <= (le) ! (not) == (eq) != (neq, ne)` --> 1 >= 10 = <span th:text="1 >= 10"></span>
์กฐ๊ฑด์
(10 % 2 == 0) ? = <span th:text="(10 % 2 == 0)?'์ง์':'ํ์'"></span>
Elvis ์ฐ์ฐ์
<!-- ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ ์ค์ ๋ฌธ์์ด ์ถ๋ ฅ --> ${data} = <span th:text="${data}?: '๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.'"></span>
No-Operation
<!-- ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ tag ๋ฐ์ดํฐ ๊ทธ๋๋ก ์ถ๋ ฅ (Thymeleaf ๊ฐ ์คํ๋์ง ์๋ ๊ฒ ์ฒ๋ผ ๋์) --> ${data} = <span th:text="${data}?: _">๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</span>
์์ฑ ๊ฐ ์ค์
์์ฑ ์ค์
<input type="text" name="mock" th:name="userA" />
์์ฑ ์ถ๊ฐ
<input type="text" class="text" th:classappend="large" /><br />
checked ์ฒ๋ฆฌ
<input type="checkbox" name="active" th:checked="true" /><br /> <input type="checkbox" name="active" th:checked="false" /><br /> <input type="checkbox" name="active" th:checked="${isChecked}" /><br />
๋ฐ๋ณต
๋ฐ๋ณต
<tr th:each="user : ${users}"> <td th:text="${user.username}">username</td> <td th:text="${user.age}">0</td> </tr>
์ํ ์ ์ง
<!-- ์๋ต ์ userStat ๋ก ์ฌ์ฉ --> <tr th:each="user, state : ${users}"></tr>
index : 0๋ถํฐ ์์
count : 1๋ถํฐ ์์
size : ์ ์ฒด ์ฌ์ด์ฆ
even , odd : ํ/์ง์ ์ฌ๋ถ
first , last :์ฒ์/๋ง์ง๋ง ์ฌ๋ถ
current : ํ์ฌ ๊ฐ์ฒด
์กฐ๊ฑด๋ถ ํ๊ฐ
if, unless
<span th:text="'์ด๋ฅธ'" th:if="${user.age gt 20}"></span> <span th:text="'์ด๋ฅธ'" th:unless="${user.age le 20}"></span>
switch
<td th:switch="${user.age}"> <span th:case="10">10์ด</span> <span th:case="20">20์ด</span> <span th:case="*">๊ธฐํ</span> </td>
์ฃผ์
ํ์ค HTML ์ฃผ์
ํ์๋ฆฌํ๊ฐ ๋ ๋๋งํ์ง ์๊ณ ์ ์ง
<!-- <span th:text="${data}"></span> -->
ํ์๋ฆฌํ ํ์ ์ฃผ์
๋ ๋๋ง์์ ์ฃผ์ ๋ถ๋ถ์ ์ ๊ฑฐ (ํ์๋ฆฌํ ์ฃผ์)
<!--/* [[${data}]] */--> <!--/*--> <span th:text="${data}">html data</span> <!--*/-->
ํ์๋ฆฌํ ํ๋กํ ํ์ ์ฃผ์
ํ์๋ฆฌํ ๋ ๋๋ง์ ๊ฑฐ์ณ์ผ๋ง ์ด ๋ถ๋ถ์ด ์ ์ ๋ ๋๋ง (HTML ์์๋ง ์ฃผ์ ์ฒ๋ฆฌ)
<!--/*/ <span th:text="${data}">html data</span> /*/-->
๋ธ๋ก
th:each ๋ก ํด๊ฒฐ์ด ์ด๋ ค์ธ ๋ ์ฌ์ฉ
<th:block th:each="user : ${users}">
<div>
name: <span th:text="${user.username}"></span> age:
<span th:text="${user.age}"></span>
</div>
<div>์์ฝ <span th:text="${user.username} + ' / ' + ${user.age}"></span></div>
</th:block>
JavaScript Inline
javascript inline
<script th:inline="javascript"> var username = [[${user.username}]]; var age = [[${user.age}]]; //์๋ฐ์คํฌ๋ฆฝํธ ๋ด์ถ๋ด ํ ํ๋ฆฟ var username2 = /*[[${user.username}]]*/ "test username"; //๊ฐ์ฒด var user = [[${user}]]; </script> <!-- var username = "userA"; var age = 10; var username2 = "userA"; var user = {"username":"userA","age":10}; -->
each
<script th:inline="javascript"> [# th:each="user, stat : ${users}"] var user[[${stat.count}]] = [[${user}]]; [/] </script> <!-- var user1 = {"username":"userA","age":10}; var user2 = {"username":"userB","age":20}; var user3 = {"username":"userC","age":30}; -->
ํ
ํ๋ฆฟ ์กฐ๊ฐ
/resources/templates/template/fragment/footer.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <footer th:fragment="copy">ํธํฐ ์๋ฆฌ ์ ๋๋ค.</footer> <footer th:fragment="copyParam (param1, param2)"> <p>ํ๋ผ๋ฏธํฐ ์๋ฆฌ ์ ๋๋ค.</p> <p th:text="${param1}"></p> <p th:text="${param2}"></p> </footer> </body> </html>
/resources/templates/template/fragment/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 (div tag ์์ ์ฝ์ )</h2> <div th:insert="~{template/fragment/footer :: copy}"></div> <h2>๋ถ๋ถ ํฌํจ replace (div tag ๋์ฒด)</h2> <div th:replace="~{template/fragment/footer :: copy}"></div> <h1>ํ๋ผ๋ฏธํฐ ์ฌ์ฉ</h1> <div th:replace="~{template/fragment/footer :: copyParam ('๋ฐ์ดํฐ1', '๋ฐ์ดํฐ2')}" ></div> </body> </html>
ํ
ํ๋ฆฟ ๋ ์ด์์
๋์ ์ธ ๋ ์ด์์
/resources/templates/template/layout/base.html
๋ ์ด์์์ด๋ผ๋ ํฐ ํ
<html xmlns:th="http://www.thymeleaf.org"> <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> </html>
/resources/templates/template/layout/layoutMain.html
ํ ์์ ํ์ํ ์ฝ๋ ์กฐ๊ฐ๋ค์ ์ ๋ฌ
::title ์ ํ์ฌ ํ์ด์ง์ title tag ๋ค์ ์ ๋ฌ
::link ์ ํ์ฌ ํ์ด์ง์ link tag ๋ค์ ์ ๋ฌ
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <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>
๋ฉ์ธ ๋ ์ด์์
/resources/templates/template/layoutExtend/layoutFile.html
๊ธฐ๋ณธ ๋ ์ด์์(header, footer) ํ์ ์ ์งํ๊ณ title, content ๋ง ๋ณ๊ฒฝ
<!DOCTYPE html> <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>
/resources/templates/template/layoutExtend/layoutExtendMain.html
๊ธฐ๋ณธ ๋ ์ด์์ ํ๋ก ๊ต์ฒดํ๋๋ฐ ํ๋๋ฐ title, content ๋ ์ ๋ฌ
<!DOCTYPE html> <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>
Form
์
๋ ฅ ํผ ์ฒ๋ฆฌ
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="itemName">์ํ๋ช
</label>
<input
type="text"
id="itemName"
th:field="*{itemName}"
class="formcontrol"
placeholder="์ด๋ฆ์ ์
๋ ฅํ์ธ์"
/>
</div>
</form>
th:object
: ์ปค๋งจ๋ ๊ฐ์ฒด๋ฅผ ์ง์ \*{...}
: ์ ํ ๋ณ์ ์ (th:object ์์ ์ ํํ ๊ฐ์ฒด์ ์ ๊ทผ)th:field
: HTML ํ๊ทธ์ id , name , value ์์ฑ์ ์๋์ผ๋ก ์์ฑ๋ ๋๋ง ์ /ํ
<input type="text" th:field="*{itemName}" />
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />
์ฒดํฌ ๋ฐ์ค
๋จ์ผ
Register
<div>
<div class="form-check">
<input
type="checkbox"
id="open"
th:field="*{open}"
class="form-checkinput"
/>
<label for="open" class="form-check-label">ํ๋งค ์คํ</label>
</div>
</div>
ํ์๋ฆฌํ๊ฐ ์๋์ผ๋ก
<input type="hidden" name="_open" value="on"/>
์์ฑ์ฒดํฌ ๋ฐ์ค๋ฅผ ์ฒดํฌํ ๊ฒฝ์ฐ on ์ ์ ๋ฌํ์ง๋ง, ์ฒดํฌํ์ง ์์ ๊ฒฝ์ฐ ๊ฐ ์์ฒด๋ฅผ ์ ๋ฌํ์ง ์์ -> ์ด ๊ฒฝ์ฐ hidden type ์ _name input ํ๊ทธ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด, false ๋ก ๊ฐ์ ์ ๋ฌ (_open=on)
View
<div>
<div class="form-check">
<input
type="checkbox"
id="open"
th:field="${item.open}"
class="form-check-input"
disabled
/>
<label for="open" class="form-check-label">ํ๋งค ์คํ</label>
</div>
</div>
๋ฉํฐ
(์ฐธ๊ณ ) @ModelAttribute ๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋น Controller ํธ์ถ ์ regions() ์์ ๋ฐํํ ๊ฐ์ด ์๋์ผ๋ก Model์ ํญ์ ๋ด๊ธฐ๊ฒ ๋จ
@ModelAttribute("regions")
public Map<String, String> regions() {
Map<String, String> regions = new LinkedHashMap<>();
regions.put("SEOUL", "์์ธ");
regions.put("BUSAN", "๋ถ์ฐ");
regions.put("JEJU", "์ ์ฃผ");
return regions;
}
Register
<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>
ํ์๋ฆฌํ๋ each๋ก ์ฒดํฌ๋ฐ์ค ์์ฑ ์ ๋์ ์ผ๋ก id์ ์๋ฒ์ ๋งค๊ฒจ์ค๋ค.
#ids
๋ ๋์ ์ผ๋ก ์์ฑ๋ id๋ฅผ ์ธ์result
<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" />
View
<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>
ํ์๋ฆฌํ๋
th:field
์ ์ง์ ํ ๊ฐ๊ณผth:value
์ ๊ฐ์ ๋น๊ตํด์ ์ฒดํฌ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
๋ผ๋์ค ๋ฒํผ
public enum ItemType {
BOOK("๋์"), FOOD("์ํ"), ETC("๊ธฐํ"); // NAME(description)
private final String description;
ItemType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
<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"
>
BOOK
</label>
</div>
๋ผ๋์ค ๋ฒํผ์ ํญ์ ํ๋์ ๊ฐ์ด ์ ํ๋์ด์ผ ํ๋ฏ๋ก ํ๋ ๋ฒํผ์ ๋ฐ๋ก ์์ฑํ์ง ์์.
์
๋ ํธ ๋ฐ์ค
Register
<select th:field="*{deliveryCode}" class="form-select">
<option value="">==๋ฐฐ์ก ๋ฐฉ์ ์ ํ==</option>
<option
th:each="deliveryCode : ${deliveryCodes}"
th:value="${deliveryCode.code}"
th:text="${deliveryCode.displayName}"
>
FAST
</option>
</select>
Reference
Last updated