Thymeleaf
๊ธฐ๋ณธ ๊ธฐ๋ฅ
์ฌ์ฉ ์ ์ธ
Copy <html xmlns:th="http://www.thymeleaf.org"></html>
์์ฑ ๋ณ๊ฒฝ
Copy th:href="@{/css/bootstrap.min.css}"
URL ๋งํฌ ํํ์
Copy 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}|}"
์์ฑ ๋ณ๊ฒฝ
Copy th:onclick="|location.href='@{/basic/items/add}'|"
๋ฐ๋ณต ์ถ๋ ฅ
Copy <tr th:each="item : ${items}"></tr>
๋ณ์ ํํ์
Copy <td th:text="${item.price}">10000</td>
์์ฑ ๋ณ๊ฒฝ
Copy <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
Copy ใ
๊ฐ๋จํ ํํ
- ๋ณ์ ํํ์: ${...}
- ์ ํ ๋ณ์ ํํ์: \*{...}
- ๋ฉ์์ง ํํ์: #{...}
- ๋งํฌ 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 ์ํฐํฐ : <
๋ฌธ์๋ฅผ ํ๊ทธ์ ์์์ด ์๋ ๋ฌธ์๋ก ํํํ๋ ๋ฐฉ๋ฒ
Copy <ul>
<li>th:text ์ฌ์ฉ = <span th:text="${data}"></span></li>
<li>์ปจํ
์ธ ์์์ ์ง์ ์ถ๋ ฅํ๊ธฐ = [[${data}]]</li>
</ul>
Unescape
Copy <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.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
Copy <div th:if="${errors?.containsKey('globalError')}"></div>
errors?.
์ errors ๊ฐ null ์ผ๋ NullPointerException ๋์ , null ์ ๋ฐํํ๋ ๋ฌธ๋ฒ ์ฐธ๊ณ
์ง์ญ๋ณ์
Copy <div th:with="first=${users[0]}">
<p>first member name : <span th:text="${first.username}"></span></p>
</div>
๊ธฐ๋ณธ ๊ฐ์ฒด
Thymeleaf ๊ธฐ๋ณธ ๊ฐ์ฒด
ํธ์ ๊ฐ์ฒด
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
Copy <a th:href="@{/hello}"></a>
query parameter
/hello?param1=data1¶m2=data2
Copy <a th:href="@{/hello(param1=${param1}, param2=${param2})}"></a>
path variable
/hello/data1/data2
Copy <a
th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}"
></a>
query parameter + path variable
/hello/data1?param2=data2
Copy <a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}"></a>
Reference
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#link-urls
๋ฆฌํฐ๋ด
๋ฌธ์: 'hello' (๋ฌธ์ ๋ฆฌํฐ๋ด์ ํญ์ ์์๋ฐ์ดํ๋ก ๊ฐ์ธ์ผ ํจ)
Copy <span th:text="'hello'"></span>
์ฐ์ฐ
์ฐ์ ์ฐ์ฐ
Copy 10 + 2 = <span th:text="10 + 2"></span>
๋น๊ต ์ฐ์ฐ
Copy <!--
> (gt)
< (lt)
>= (ge)
<= (le)
! (not)
== (eq)
!= (neq, ne)`
-->
1 >= 10 = <span th:text="1 >= 10"></span>
์กฐ๊ฑด์
Copy (10 % 2 == 0) ? = <span th:text="(10 % 2 == 0)?'์ง์':'ํ์'"></span>
Elvis ์ฐ์ฐ์
Copy <!-- ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ ์ค์ ๋ฌธ์์ด ์ถ๋ ฅ -->
${data} = <span th:text="${data}?: '๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.'"></span>
No-Operation
Copy <!-- ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ tag ๋ฐ์ดํฐ ๊ทธ๋๋ก ์ถ๋ ฅ (Thymeleaf ๊ฐ ์คํ๋์ง ์๋ ๊ฒ ์ฒ๋ผ ๋์) -->
${data} = <span th:text="${data}?: _">๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</span>
์์ฑ ๊ฐ ์ค์
์์ฑ ์ค์
Copy <input type="text" name="mock" th:name="userA" />
์์ฑ ์ถ๊ฐ
Copy <input type="text" class="text" th:classappend="large" /><br />
checked ์ฒ๋ฆฌ
Copy <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 />
๋ฐ๋ณต
๋ฐ๋ณต
Copy <tr th:each="user : ${users}">
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
</tr>
์ํ ์ ์ง
Copy <!-- ์๋ต ์ userStat ๋ก ์ฌ์ฉ -->
<tr th:each="user, state : ${users}"></tr>
even , odd : ํ/์ง์ ์ฌ๋ถ
first , last :์ฒ์/๋ง์ง๋ง ์ฌ๋ถ
์กฐ๊ฑด๋ถ ํ๊ฐ
if, unless
Copy <span th:text="'์ด๋ฅธ'" th:if="${user.age gt 20}"></span>
<span th:text="'์ด๋ฅธ'" th:unless="${user.age le 20}"></span>
switch
Copy <td th:switch="${user.age}">
<span th:case="10">10์ด</span>
<span th:case="20">20์ด</span>
<span th:case="*">๊ธฐํ</span>
</td>
์ฃผ์
ํ์ค HTML ์ฃผ์
ํ์๋ฆฌํ๊ฐ ๋ ๋๋งํ์ง ์๊ณ ์ ์ง
Copy <!-- <span th:text="${data}"></span> -->
ํ์๋ฆฌํ ํ์ ์ฃผ์
๋ ๋๋ง์์ ์ฃผ์ ๋ถ๋ถ์ ์ ๊ฑฐ (ํ์๋ฆฌํ ์ฃผ์)
Copy <!--/* [[${data}]] */-->
<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->
ํ์๋ฆฌํ ํ๋กํ ํ์
์ฃผ์
ํ์๋ฆฌํ ๋ ๋๋ง์ ๊ฑฐ์ณ์ผ๋ง ์ด ๋ถ๋ถ์ด ์ ์ ๋ ๋๋ง (HTML ์์๋ง ์ฃผ์ ์ฒ๋ฆฌ)
Copy <!--/*/
<span th:text="${data}">html data</span>
/*/-->
๋ธ๋ก
th:each ๋ก ํด๊ฒฐ์ด ์ด๋ ค์ธ ๋ ์ฌ์ฉ
Copy <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
Copy <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
Copy <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
Copy <!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
Copy <!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
๋ ์ด์์์ด๋ผ๋ ํฐ ํ
Copy <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 ๋ค์ ์ ๋ฌ
Copy <!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 ๋ง ๋ณ๊ฒฝ
Copy <!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 ๋ ์ ๋ฌ
Copy <!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
์
๋ ฅ ํผ ์ฒ๋ฆฌ
Copy <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 ์์ฑ์ ์๋์ผ๋ก ์์ฑ
๋ ๋๋ง ์ /ํ
Copy <input type="text" th:field="*{itemName}" />
Copy <input type="text" id="itemName" name="itemName" th:value="*{itemName}" />
์ฒดํฌ ๋ฐ์ค
๋จ์ผ
Register
Copy <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
Copy <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์ ํญ์ ๋ด๊ธฐ๊ฒ ๋จ
Copy @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
Copy <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
Copy <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
Copy <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
์ ๊ฐ์ ๋น๊ตํด์ ์ฒดํฌ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
๋ผ๋์ค ๋ฒํผ
Copy public enum ItemType {
BOOK("๋์"), FOOD("์ํ"), ETC("๊ธฐํ"); // NAME(description)
private final String description;
ItemType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
Copy <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
Copy <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
๊ณต์ ์ฌ์ดํธ
๊ณต์ ๋ฉ๋ด์ผ - ๊ธฐ๋ณธ ๊ธฐ๋ฅ
๊ณต์ ๋ฉ๋ด์ผ - ์คํ๋ง ํตํฉ