Thymeleaf#1 ๊ธฐ๋ณธ๊ธฐ๋ฅ
by JiwonDev๊ณต์ ๋ ํผ๋ฐ์ค์ ์น์ ํ๊ฒ ์ ํ์์ง๋ง, ์ค๋ช ์ ์ฝ๋ ๊ฒ ๋ณด๋จ ์์ ๋ฅผ ๋ณด๋๊ฒ ํจ์ฌ ์ดํด๊ฐ ์ฝ๋ค. ํด๋น ๊ธ์ ์ญ ์ฝ์ด๋ณด๋ฉฐ ๊ธฐ๋ณธ๊ธฐ๋ฅผ ์ตํ๊ณ ์ถ๊ฐ๋ก ํ์ํ ๋ ๊ณต์ ๋ ํผ๋ฐ์ค๋ฅผ ์ฐธ๊ณ ํด์ ์ฌ์ฉํ๋๋ก ํ์.
# ๊ณต์ ๋ ํผ๋ฐ์ค
HTML์ ํํ๋ฅผ ์ ์งํ๋ฉด์ ์ฌ์ฉํ ์ ์๋ ํ์๋ฆฌํ๋์ฌ์ฉํ๊ธฐ ์ฝ๋ค. ํ๋ คํ ํด๋ผ์ด์ธํธ๊ฐ ํ์ํ๋ค๋ฉด React, Vue๊ฐ์ ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋๊ฒ ๋ง์ง๋ง, ๊ฐ๋จํ ํ์ด์ง๋ UX, UI๊ฐ ํฌ๊ฒ ์ค์ํ์ง์๋ ๊ด๋ฆฌ์์ฉ HTML ๊ฐ์ ๊ฒฝ์ฐ์๋ ์ฌ์ ํ ์ธ๋งํ๋ค. ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ผ๊ณ ํ๋๋ผ๋, View ํ ํ๋ฆฟ ์์ง ํ๋์ ๋๋ ์์๋ฌ์ผํ๋ค. ํ๋๋ง ๋ฐฐ์ฐ๋ฉด ๋ค ๋น์ท๋น์ทํ๊ธด ํ์ง๋ง Thymeleaf ๊ฐ ์คํ๋ง์์ ๊ณต์์ ์ผ๋ก ๋ฐ์ด์ฃผ๊ณ ์์ด์ ๊ฐ์ด ์ฐ๊ธฐ ํธ๋ฆฌํ๋ค.
- ๊ณต์ ์ฌ์ดํธ: https://www.thymeleaf.org/
- ๊ณต์ ๋ฉ๋ด์ผ - ๊ธฐ๋ณธ ๊ธฐ๋ฅ: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
- ๊ณต์ ๋ฉ๋ด์ผ - ์คํ๋ง ํตํฉ: https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
Tutorial: Thymeleaf + Spring
Preface This tutorial explains how Thymeleaf can be integrated with the Spring Framework, especially (but not only) Spring MVC. Note that Thymeleaf has integrations for both versions 3.x and 4.x of the Spring Framework, provided by two separate libraries c
www.thymeleaf.org
* ์ฐธ๊ณ ๋ก xhtml, thymeleaf ์ฒ๋ผ HTML์ ์ปค์คํ ํ๊ทธ๋ฅผ ์ถ๊ฐ๋ก ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, <html>์์ฑ์ผ๋ก xmlns (xml namespace)๋ฅผ ์ง์ ํด์ฃผ์ด์ผ ๋ค๋ฅธ ํ ํ๋ฆฟ๊ณผ ์ถฉ๋์ ๋ฐฉ์งํ๊ณ ํด๋น HTML์ ํ์๋ฆฌํ๊ฐ ์ฌ์ฉ๋์์์ ๋ช ์์ ์ผ๋ก ํ์ํ ์ ์๋ค.
<!DOCTYPE html>
<!-- ์ผ๋ฐ ์ต์
(xmlns) ๋์ ํ์๋ฆฌํ ์ต์
(xmlns:th) ์ฌ์ฉํ๋ฉด, ํ์๋ฆฌํ ๋ ๋๋ง ์ดํ ํด๋น ์์ฑ์ ์ญ์ ํ๋ค. -->
<html xmlns:th="http://www.thymeleaf.org">
...
# Thymeleaf ์์ ์ฌ์ฉํ๋ ํํ์
ํ์๋ฆฌํ๋ ๋ณดํต HTML ํ๊ทธ์ ์์ฑ์ ์ด์ฉํ์ฌ ์๋ฒ์์ ๋ ๋๋งํ ๋ด์ฉ์ ํ์ํ๋ค. ๊ทธ๋์ HTML ํ์ผ์ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์๋ค. ํ๊ทธ์ ์์ฑ์ด ์๋๋ผ ๋ด์ฉ์ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด [[...]] ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
- ๋ชจ๋ธ ๊ฐ์ฒด์ ์๋ ๊ฐ data = "Hello World" ์ผ ๋
<h1>์ปจํ
์ธ ์ ๋ฐ์ดํฐ ์ถ๋ ฅํ๊ธฐ</h1>
<ul>
<li>th:text ์ฌ์ฉ <span th:text="${data}"> ๋์ฒด ํ
์คํธ </span></li>
<li>์ปจํ
์ธ ์์์ ์ง์ ์ถ๋ ฅํ๊ธฐ = [[${data}]]</li>
</ul>
์ฐธ๊ณ ๋ก ์์ ์์ ์์ ์ฌ์ฉ๋ [[ ... ]] ๋ thymeleaf ๋ณ์์์ ๋ํ๋ด๋ ๊ธฐ๋ฅ๋ ์์ง๋ง, < > ์ ๊ฐ์ HTML ์์ฝ๋ฌธ์๋ฅผ ๋ฌธ์์ด(<)๋ก ๋ง๋ค์ด์ฃผ๋ ์ญํ ๋ ํ๋ค. ๋ง์ฝ ์๋์ ์ผ๋ก ๋ฌธ์์ด์ด ์๋ HTML ํ๊ทธ๋ก ์ธ์๋๊ฒ ํ๊ณ ์ถ๋ค๋ฉด th:utext ๋๋ [(...)] ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
๊ทธ ์ธ ์ ๊ณตํ๋ ํํ์์ ์๋์ ๊ฐ์๋ฐ, ๊ตณ์ด ์ธ์ฐ์ง ์์๋ ์ฌ์ฉํ๋ค๋ณด๋ฉด ์์ฐ์ค๋ฝ๊ฒ ์๊ฒ๋๋ค.
//• ๊ฐ๋จํ ํํ:
โฆ ๋ณ์ ํํ์: ${...}
โฆ ์ ํ ๋ณ์ ํํ์: *{...}
โฆ ๋ฉ์์ง(์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ) ํํ์: #{...}
โฆ ๋งํฌ 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: _
# ์ฃผ์ (Comment)
ํ์๋ฆฌํ ์ฃผ์์ ์ฌ์ฉํ๋ฉด ๋ ๋๋ง์ HTML ์์ฒด์์ ์ฃผ์์ ์ญ์ ํด๋ฒ๋ฆฐ๋ค.
- ํ๋กํ ํ์ ์ ์ฃผ์์ ์ํ ๊ธฐ๋ฅ์ ์๋๊ณ , ํ์๋ฆฌํ ๋ ๋๋ง ํ์๋ง ํด๋น ํ๊ทธ๋ฅผ ๋ณด์ด๋๋ก ๋ง๋ค์ด์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค.
<!-- 1. HTML ์ฃผ์ -->
<h1> 2. ํ์๋ฆฌํ Parser์ฉ ์ฃผ์. ์์ ์ฝ์ง์๊ณ ๋ ๋๋ง์ ์ญ์ ํด๋ฒ๋ฆผ </h1>
<!--/* Thymeleaf ์ฃผ์ */-->
<!--/*-->
<span th:text="${data}">์ฌ๋ฌ์ค Thymeleaf ์ฃผ์</span>
<!--*/-->
<h1>3. ํ์๋ฆฌํ ํ๋กํ ํ์
์ฃผ์. ๊ฑฐ์ ์ฌ์ฉํ ์ผ ์์</h1>
<!--/*/
<span th:text="${data}">html data</span>
/*/-->
# ๋ชจ๋ธ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
์๋์ ๊ฐ์ Controller์ Domain์ด ์๋ค๊ณ ๊ฐ์ ํ์.
@Data // = @Get, @Set, @toStr, @Equals&Hashcode, @ReqArgs
static class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
@GetMapping("/variable")
public String variable(Model model) {
User userA = new User("UserA", 10);
User userB = new User("UserB", 20);
List<User> list = Arrays.asList(userA, userB);
Map<String, User> map = Map.of("userA", userA, "userB", userB); // Java9
model.addAttribute("user", userA);
model.addAttribute("userList", list);
model.addAttribute("userMap", map);
return "basic/variable";
}
์ด๋ ๊ฒ model์ ๋ด์ ๊ฐ์ฒด, ๊ฐ์ ์๋์ ๊ฐ์ด HTML ํ๊ทธ์ ์์ฑ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค. ์ฐธ๊ณ ๋ก thํ๊ทธ๋ฅผ ์ฌ์ฉํ์ ๋ ์์ ๋ด์ฉ์ ์๋ฒ์์ ๋ ๋๋ง ๋ ๋ ์์ด์ง๋ค.
<span th:text="${userMap['userA'].username}"> ๋์ฒด ํ
์คํธ(๋ ๋๋ง์ ์ญ์ ) </span>
<ul> Object
<li>${user.username} = <span th:text="${user.username}"></span></li>
<li>${user['username']} = <span th:text="${user['username']}"></span></li>
<li>${user.getUsername()} = <span th:text="${user.getUsername()}"></span></li>
</ul>
<ul> List
<li> ๊ฐ: <span th:text="${userList[0].username}"></span></li>
<li> ๊ฐ: <span th:text="${userList[0]['username']}"></span></li>
<li> ๊ฐ: <span th:text="${userList[0].getUsername()}"></span></li>
</ul>
<ul> Map
<li> ๊ฐ: <span th:text="${userMap['userA'].username}"></span></li>
<li> ๊ฐ: <span th:text="${userMap['userA']['username']}"></span></li>
<li> ๊ฐ: <span th:text="${userMap['userA'].getUsername()}"></span></li>
</ul>
# ์น ๊ธฐ๋ณธ ๊ฐ์ฒด (req, resp, session, servletcontext, locale)
๋ชจ๋ธ ์ฌ์ฉ๋ฒ๊ณผ ๊ฐ๋ค. ์ฌ์ฉํ ๊ฐ์ฒด์ ${#name} ์ผ๋ก ํ์ํด์ฃผ๋ฉด ๋๋ค.
๋ค๋ง request๋ ์์ฃผ ์ฌ์ฉํ๊ฒ ๋๋๋ฐ, ๋งค๋ฒ HttpServletRequest ๊ฐ์ฒด๋ฅผ ์ฃผ๊ธฐ์ ์ฌ์ฉํ๊ธฐ ๋ถํธํ๋ค.
๊ทธ๋์ ํ์๋ฆฌํ์์๋ ์์ฒญ ํ๋ผ๋ฉํ ์ฉ๋๋ก param์ด๋ผ๋ ํธ์์ฉ ๊ฐ์ฒด๋ฅผ ์ถ๊ฐ๋ก ์ ๊ณตํ๋ค. param.name ์ผ๋ก ์ฌ์ฉ๊ฐ๋ฅ
<h1>๊ธฐ๋ณธ ๊ฐ์ฒด (Expression Basic Objects)</h1>
<ul>
<li>request = <span th:text="${#request}"></span></li>
<li>response = <span th:text="${#response}"></span></li>
<li>session = <span th:text="${#session}"></span></li>
<li>servletContext = <span th:text="${#servletContext}"></span></li>
<li>locale = <span th:text="${#locale}"></span></li>
</ul>
<h1>ํธ์ ๊ฐ์ฒด</h1>
<ul>
<li>Request Parameter = <span th:text="${param.paramData}"></span></li>
<li>session = <span th:text="${session.sessionData}"></span></li>
<li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
</ul>
์์ ์๋ ๋์์์ง๋ง, ${@springBean}์ผ๋ก ์คํ๋ง ๋น ๊ฐ์ฒด์ ์ ๊ทผํ ์๋ ์๋ค.
// <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
@Component("helloBean")
static class HelloBean {
public String hello(String data) {
return "Hello " + data;
}
}
# ์ ํธ๋ฆฌํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ์ฒด
Tutorial: Using Thymeleaf ํ์ํ ๋ ์ฐพ์๋ณด๋ฉด ๋๋ค. ์ฌ์ฉ๋ฒ์ ์น ๊ธฐ๋ณธ๊ฐ์ฒด์ ๋์ผํ๋ค.
- Java8์ ์ถ๊ฐ๋ ๋ ์ง ๊ฐ์ฒด๋ ${#temporals} ๋ก ์ฌ์ฉํ ์ ์๋ค. ๋ ํผ๋ฐ์ค์ ์์ ํ์์ด์ ์ถ๊ฐ.
//<span th:text="${#temporals.format(localDateTime, 'yyyy-MM-dd HH:mm:ss')}"></span> @GetMapping("/date") public String date(Model model) { model.addAttribute("localDateTime", LocalDateTime.now()); return "basic/date"; }โ
# URL ๋งํฌ
th:href ์ @{/link} ๋ฅผ ์ด์ฉํ๋ฉด ๋๋ค. ( name = ${value} ) ๋ฅผ ์ด์ฉํ์ฌ ๊ฒฝ๋ก ๋ณ์๋ฅผ ์ง์ ํด ์ค ์ ์๋ค. ๋จ ์ ์ด๋๊ณ ์ฌ์ฉํ์ง ์์ ๋ณ์๋ ์๋์ผ๋ก ํ๋ผ๋ฉํ ?name=value ๋ก ์ถ๊ฐ๋๊ฒ ๋๋ค.
- ์ฐธ๊ณ ๋ก @{} ์ฐ์ง์๊ณ , HTML ํ๊ทธ๋ง๋ฅ ๋ฐ๋ก th:href="/" ๋ฐ์๋ ๋๋ค.
- ๋งํฌ์ @{} ์ฌ์ฉํ๊ฒ๋๋ฉด ์๋๊ฒฝ๋ก๋ฅผ ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์๋ค. ์๋ธ๋ฆฟ ์ปจํ ์คํธ(ํฐ์บฃ ์ธ์คํด์ค) ๊ธฐ์ค์ผ๋ก ์ฐพ์์ฃผ๊ธฐ ๋๋ฌธ์, ํ๋ก์ ํธ ๋ฃจํธ ๊ฒฝ๋ก๊ฐ ๋ฌ๋ผ์ก๋๋ผ๋ url ๋ณ๊ฒฝ์์ด ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค.
<ul>
<li><a th:href="@{/hello}">basic url</a></li>
<!-- /hello?param1=data1¶m2=data2 -->
<li><a th:href="@{/hello(param1=${data1}, param2=${data2})}"> query param</a></li>
<!-- /hello/data1/data2 -->
<li><a th:href="@{/hello/{param1}/{param2} (param1=${data1}, param2=${data2})}">path variable</a></li>
<!-- /hello/data1?param2=data2 -->
<li><a th:href="@{/hello/{param1} (param1=${data1}, param2=${data2})}">path var + query param</a></li>
</ul>
# ๋ฆฌํฐ๋๊ณผ ์ฐ์ฐ์
10.5, true, false, null ๊ทธ๋ฅ ์ฌ์ฉํ๋ฉด ๋๊ธดํ์ง๋ง, ๋ช๊ฐ์ง ์ฃผ์์ฌํญ๋ง ์์๋ณด์.
- ๋ฌธ์ ๋ฆฌํฐ๋์ ๊ณต๋ฐฑ์ ๋ฃ๊ณ ์ถ๋ค๋ฉด ์์ ๋ฐ์ํ('~')๋ก ๊ฐ์ธ๋ฉด ๋๋ค. ๊ทธ๋ฅ ๊ณต๋ฐฑ์ ๋ฃ์ผ๋ฉด ์ค๋ฅ๊ฐ ๋๋ค.
'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span>
'hello world!' = <span th:text="'hello world!'"></span>
- ๋ฌธ์์ด์ ์ฝ๊ฒ ์์ฑํ๋ [๋ฆฌํฐ๋ ๋์ฒด ๋ฌธ๋ฒ]์ด ์กด์ฌํ๋ค. | ~ | ๋ฅผ ์ฌ์ฉํด์ ๋ฐ๋ก ์ฐ๋ฉด ๋๋ค. (Js, Kotlin๊ณผ ๋น์ท)
'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span>
๋ฆฌํฐ๋ด ๋์ฒด |hello ${data}| = <span th:text="|hello ${data}|"></span>
- ์ฐ์ ์ฐ์ฐ, ์กฐ๊ฑด์์ Java์ ์๋ฒฝํ๊ฒ ๋์ผํ๋ค. ๊ฒฐ๊ตญ Java๋ก ๋์๊ฐ๋ ๊ฑฐ๋๊น
10 + 2 = <span th:text="10 + 2"></span>
10 % 2 == 0 = <span th:text="10 % 2 == 0"></span>
(10 % 2 == 0)? '์ง์':'ํ์' = <span th:text="(10 % 2 == 0)? '์ง์':'ํ์'"></span>
- ๋จ, ๋น๊ต์ฐ์ฐ์ ๊ฒฝ์ฐ HTML ํ๊ทธ์ ๊ฒน์น๋ ๋ฌธ์ < > ๋ ์ ์ฉ ๊ธฐํธ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
1 > 10 = <span th:text="1 > 10"></span> <!-- ์์ HTML์์๋ > ์ด๋ฐ์์ผ๋ก ์ฌ์ฉ๋๋ค -->
1 gt 10 = <span th:text="1 gt 10"></span>
1 >= 10 = <span th:text="1 >= 10"></span>
1 ge 10 = <span th:text="1 ge 10"></span>
1 == 10 = <span th:text="1 == 10"></span>
# Elvis ์ฐ์ฐ์ ( null ์ฒ๋ฆฌ, ${data}?: )
- ์กฐ๊ฑด์์ ์ถ์ฝ๋ฒ์ ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. ๋ฐ์ดํฐ๊ฐ ์์ ๋ ๋ฐํํ ๋์ฒด ๊ฐ์ ์ง์ ํ ์ ์๋ค.
${data}? = <span th:text="${data}?: '๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.'"></span></li>
${nullData}? = <span th:text="${nullData}?: '๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.'"></span></li>
- ํ์ด์ฌ ์ฒ๋ผ _ ๋ฅผ ์ฌ์ฉํ ์ ์๋ค. _ ๊ฐ ์ ํ๋ ๊ฒฝ์ฐ ํด๋น ํ๊ทธ๋ ๋ ๋๋ง ๋์ง ์๊ณ HTML ๊ทธ๋๋ก ์ถ๋ ฅ๋๋ค.
${data}? = <span th:text="${data}?: _">๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</span>
${nullData}? = <span th:text="${nullData}?: _">๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</span>
# ํ๊ทธ ์์ฑ ๊ฐ ์ค์
<tag th:์์ฑ="~"> ์ผ๋ก ์ ์ผ๋ฉด, ํ์๋ฆฌํ ๋ ๋๋ง ์ <tag ์์ฑ="~"> ์ผ๋ก ๋ฐ๊ฟ์ค๋ค.
- ๊ทธ๋ฅ ์์ฑ์ ์ฌ์ฉํด๋ ๋๋ค. ๋จ th:์์ฑ์ ์ด์ฉํ๋ฉด ์๋์ ํ๊ทธ๋ค์ ์ด์ฉํด ์์ฑ์ ๋ฐ๊ฟ ์ ์๋ค.
th:attrappend : ์์ฑ ๊ฐ์ ๋ค์ ๊ฐ์ ์ถ๊ฐํ๋ค.
th:attrprepend : ์์ฑ ๊ฐ์ ์์ ๊ฐ์ ์ถ๊ฐํ๋ค.
th:classappend : class ์์ฑ์ผ๋ก ํด๋น ๊ฐ์ ์ถ๊ฐํ๋ค.
- th:attrappend = <input type="text" class="text" th:attrappend="class='large'" /><br/>
- th:attrprepend = <input type="text" class="text" th:attrprepend="class='large'" /><br/>
- th:classappend = <input type="text" class="text" th:classappend="large" /><br/>
- ๋๋ ์๋์ ๊ฐ์ด ์ฒดํฌ๋ฐ์ค ์ฒ๋ฆฌ๋ฑ์ ํ ๋ th:checked๋ฅผ ์ฌ์ฉํ๋ฉด boolean ๋ก์ง์ ๋ง๋ค๊ธฐ ํธํ๋ค.
- checked o <input type="checkbox" name="active" th:checked="true" /><br/>
- checked x <input type="checkbox" name="active" th:checked="false" /><br/>
<!-- ๊ธฐ์กด์ HTML์ checked ์์ฑ์ด ์์ผ๋ฉด ๊ทธ๋ฅ ์ฒดํฌํด๋ฒ๋ฆผ checked=boolean ์ต์
์์ -->
- checked=false <input type="checkbox" name="active" checked="false" /><br/>
# ๋ฐ๋ณต๋ฌธ (th:each)
์๋์ ๊ฐ์ users ๊ฐ ์ฃผ์ด์ก์ ๋ th:each="user : ${users}"๋ก ์ํํ ์ ์๋ค. ์ด๋ ๊ฒ ์์ฑํ๋ฉด user๋ฅผ ์ฌ์ฉํ ํ๊ทธ๊ฐ ๊ฐ ๋ฐฐ์ด์ ๊ฐ์๋งํผ ๋ฐ๋ณต๋๋ค.
- Map์ ๊ฒฝ์ฐ ์ธ์๋ก Entry(key, value)๊ฐ ๋ด๊ธฐ๊ฒ ๋๋ค.
@GetMapping("/each")
public String each(Model model) {
addUsers(model);
return "basic/each";
}
private void addUsers(Model model) {
List<User> list = new ArrayList<>();
list.add(new User("userA", 10));
list.add(new User("userB", 20));
list.add(new User("userC", 30));
model.addAttribute("users", list);
}
<table border="1">
<tr th:each="user : ${users}">
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
</tr>
</table>
th:each๋ฅผ ์ฌ์ฉํ ๋, 2๋ฒ์งธ ์ธ์(ex. userStat)๋ฅผ ์ฃผ์ด์ ๋ฐ๋ณต ์ธ๋ฑ์ค, ์ ์ฒดํฌ๊ธฐ, ํธ์์ฉ ์กฐ๊ฑด์ฐ์ฐ์๋ฑ์ ์ฌ์ฉํ ์ ์๋ค.
<tr th:each="user, userStat : ${users}"> <!-- ์ด๋ฆ์ ๋ณ๊ฒฝํด๋ ๋๋ค. -->
index 0๋ถํฐ ์์ = <span th:text="${userStat.index}"></span>
count 1๋ถํฐ ์์ = <span th:text="${userStat.count}"></span>
size ๋ฐฐ์ด ํฌ๊ธฐ = <span th:text="${userStat.size}"></span>
even? ์ง์์ฌ๋ถ boolean = <span th:text="${userStat.even}"></span>
odd? ํ์์ฌ๋ถ boolean = <span th:text="${userStat.odd}"></span>
first? ์ฒ์์ฌ๋ถ boolean = <span th:text="${userStat.first}"></span>
last? = ๋ง์ง๋ง์ฌ๋ถ boolean = <span th:text="${userStat.last}"></span>
current = ํ์ฌ ๊ฐ์ฒด = <span th:text="${userStat.current}"></span>
# ์กฐ๊ฑด๋ฌธ ( th:if , th:unless , th:switch )
์กฐ๊ฑด์ ๋ง์กฑํ์ง ์์์ ๋, ํด๋น ํ๊ทธ๋ฅผ ์์ ๋ฒ๋ฆด ์ ์๋ค. ์ฐธ๊ณ ๋ก unless๋ if์ ๋ถ์ ๋ฌธ์ด๋ค.
- switch์์ default case๋ th:case="*" ๋ก ์ง์ ํ ์ ์๋ค.
<span th:text="${user.age}">0</span>
<span th:text="'20์ด'" th:if="${user.age == 20}"></span>
<span th:text="'20์ด์ด์'" th:if="${user.age ge 20}"></span>
<span th:text="'20์ด๋ฏธ๋ง'" th:unless="${user.age ge 20}"></span>
<td th:switch="${user.age}">
<span th:case="10">10์ด</span>
<span th:case="20">20์ด</span>
<span th:case="*">๊ธฐํ</span>
</td>
# ๋ธ๋ก <th:block>
ํ์๋ฆฌํ๋ HTML ๋ฌธ๋ฒ์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ค. ์ ์ผํ๊ฒ <th:block>๋ง ํ์๋ฆฌํ ์ ์ฉ ํ๊ทธ๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ด์ฐจํผ ๋ ๋๋ง๋๋ฉด ์ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์ด๋ค.
- React์ <Fragment>์ ๋น์ทํ๋ค. ํน์ ํ๊ทธ๋ก ๊ฐ์ธ์ง ์๊ณ ๊ทธ๋๋ก ์ฌ์ฉํ๊ธฐ ์ํด์ ์ด๋ค.
<!-- th:block์ ๋ ๋๋ง์ ์ฌ๋ผ์ง๋ค. ์์ ์๋ ๋ด์ฉ๋ง ๋จ๊ฒ๋๋ค. -->
<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>
# ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ script th:inline="javascript"
ํ์๋ฆฌํ์์ ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ <script>๋ฅผ ์ฌ์ฉํ๊ณ ์ํ๋ฉด, ๋ฐ๋์ <script th:inline="javascript"> ์ต์ ์ ์ฃผ์ด์ผ ํ๋ค. ์ด๋ ๊ธฐ์กด ํ๊ทธ๋ค์ th:tag๋ก ๋์ฒดํด์ ์ฌ์ฉํ๋ ์ด์ ์ ๋น์ทํ๋ค.
<!-- ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ ์ฌ์ฉ ์ -->
<script>
var username = [[${user.username}]]; // ๊ทธ๋ฅ ํ์๋ฆฌํ ๋ฌธ๋ฒ์ ์ถ๊ฐํ ๊ฒฝ์ฐ
var age = [[${user.age}]];
//์ฃผ์์ ์ด์ฉํด ์์ ์๋ฐ์คํฌ๋ฆฝํธ๋ง ์ฌ์ฉํ ์๋ ์๋ค. user.username ์ด ๋์ ๋ค์ด๊ฐ.
var username2 = /*[[${user.username}]]*/ "test username";
//๊ฐ์ฒด
var user = [[${user}]];
</script>
- ํด๋น ์คํฌ๋ฆฝํธ๋ฅผ HTML๋ก ๋ ๋๋งํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ๊ทธ ์ด์ ๋ ๊ฒฐ๊ณผ HTML์ ๋ณด๋ฉด ์ ์ ์๋ค.
- ์ฝ๋์์์ "" ๋ฅผ ์ถ๊ฐํ๋ฉด ๋ ๊น? ๋ ๋๋ง ๋ ์ง์ง "๋ฌธ์์ด"์ธ์ง ํ์๋ฆฌํ ๋ณ์์ธ์ง ์ด๋ป๊ฒ ๊ตฌ๋ถํ ์ ์์๊น?
// ์ด๊ฒ ํ์๋ฆฌํ ๋ณ์์ธ์ง, ๊ทธ๋ฅ "[[..]]" ๋ฌธ์์ด์ธ์ง ์ด๋ป๊ฒ ์ ์ ์์๊น?
var username = "[[${user.username}]]";
์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์, ํ์๋ฆฌํ์๋ <script th:inline = "javascript"> ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
<!-- th:inline = "javascript ์ฌ์ฉ์ ๋ ๋๋ง ๊ฒฐ๊ณผHTML -->
<script>
var username = "userA"; // ๋ฌธ์์ด์ ๋ฐ์ํ๋ก ๊ฐ์ธ์ค๋ค
var age = 10; // ์ซ์๋ ๊ทธ๋๋ก ์ฒ๋ฆฌ
var username2 = "userA"; // ์๋ฐ์คํฌ๋ฆฝํธ ํ์๋ฆฌํ ์ฒ๋ฆฌ๋ ๊น๋ํ๊ฒ ํด์ค.
var user = {"username":"userA","age":10}; // ๊ฐ์ฒด๋ ์ฝ๋๋ฅผ JSON{..} ๋ฌธ๋ฒ์ผ๋ก ๋ฐ๊ฟ์ค๋ค
</script>
# ์๋ฐ์คํฌ๋ฆฝํธ ์์์ ํ์๋ฆฌํ์ th:each ์ฌ์ฉ
[# th:each= "..." ] ~Javascript Code~ [/] ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
<script> <!-- ๋ ๋๋ง ๊ฒฐ๊ณผ -->
var user1 = {"username":"userA","age":10};
var user2 = {"username":"userB","age":20};
var user3 = {"username":"userC","age":30};
</script>
# ํ ํ๋ฆฟ ์กฐ๊ฐ (th:fragment)
๋จผ์ ์ฌ์ฌ์ฉํ ํ ํ๋ฆฟ์ ๋ง๋ค์ด๋์. ๋ณดํต fragment ํด๋๋ฅผ ๋ง๋ค์ด ๋ชจ์๋๊ณ ๋ ํ๋ค.
- ํ๊ทธ์ th:fragment ="name" ์ ์ด์ฉํ์ฌ ํ ํ๋ฆฟ ์ด๋ฆ๊ณผ ์ ๋ ฅ๋ฐ์ ํ๋ผ๋ฉํ๋ฅผ ์ง์ ํด์ค ์ ์๋ค.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<!-- footer.html 1๋ฒ ํ
ํ๋ฆฟ -->
<footer th:fragment="copy">
ํธํฐ ์๋ฆฌ ์
๋๋ค.
</footer>
<!-- footer.html 2๋ฒ ํ
ํ๋ฆฟ -->
<footer th:fragment="copyParam (param1, param2)">
<p>ํ๋ผ๋ฏธํฐ ์๋ฆฌ ์
๋๋ค.</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
</body>
</html>
์ด๋ ๊ฒ ๋ง๋ ํ ํ๋ฆฟ์ ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค. (ํ๋ก์ ํธ resource/templates๋ฅผ ๋ฒ ์ด์ค ๊ฒฝ๋ก๋ก ํ์ํ๋ค.)
- insert๋ ํด๋น ํ๊ทธ ์ ๋ด์ฉ์ ๋ง๋ค๊ณ , replace๋ ํด๋น ํ๊ทธ๋ฅผ ์ญ์ ํ ํ ๋ง๋ ๋ค.
<h1>๋ถ๋ถ ํฌํจ insert</h1>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<!-- ํ๋ก์ ํธ๊ฒฝ๋ก/resource/templates/template/fragment/footer.html ์ ์๋ copy ํ๊ทธ -->
<h1>๋ถ๋ถ ํฌํจ replace</h1>
<div th:replace="~{template/fragment/footer::copy}"></div>
<h1>ํ๋ผ๋ฏธํฐ ์ฌ์ฉ</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('๋ฐ์ดํฐ1', '๋ฐ์ดํฐ2')}"></div>
๋ค๋ง ~{...} ๋ฅผ ์๋ตํด๋ ๊ฒฝ๋ก ์ธ์์ด ๊ฐ๋ฅํ ๊ฒฝ์ฐ, ์๋ตํด๋ ๋๊ฐ์ด ๋์ํ๋ค.
<!-- ~{}๋ ์๋ต๊ฐ๋ฅ -->
<div th:replace="template/fragment/footer :: copy"></div>
<div th:replace="template/fragment/footer :: copyParam ('๋ฐ์ดํฐ1', '๋ฐ์ดํฐ2')"></div>
# ์ฌ์ฌ์ฉ๊ฐ๋ฅํ ๋ ์ด์์ ๋ง๋ค๊ธฐ
์๋์ ๊ฐ์ด th:replace๋ฅผ ์ง์ ํด์ฃผ๋ฉด, ํด๋น ํ๊ทธ ์์ ์๋ ๋ด์ฉ์ด ์ญ์ ๋๊ณ ์ง์ ํ ํ ํ๋ฆฟ์ผ๋ก ๊ต์ฒด๋๋ค.
์ด๋, ํ ํ๋ฆฟ์์ [ํน์ ํ๊ทธ ์์ ๊ธฐ์กด ๋ด์ฉ] ์ ์ฌ์ฌ์ฉํ ์ ์๋ค.
- ํ๋ผ๋ฉํ์ ~{::TagName} ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ด์ฉ์ ์ ํ์๋ ๋ชจ๋ ํด๋น ํ๊ทธ(TagName)๋ฅผ ํ๋ผ๋ฉํ๋ก ๋๊ธด๋ค.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!-- head ํ๊ทธ์ ๋ด์ฉ์ ์ญ์ ๋๊ณ , 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>
- th:replace๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ด์ฐจํผ ๊ต์ฒด๋ ๊ฑฐ๋ผ ๊ธฐ์กด ํ๊ทธ๋ฅผ ๋ฌด์์ ์ฌ์ฉํ๋ ์๊ด์๋ค. (๋์ถฉ th:block์ผ๋ก ํด๋๋จ)
ํ์ง๋ง ํ๊ทธ๋ฅผ ์๋ฏธ์๊ฒ ์ฌ์ฉํ๋๊ฒ, ํ ํ๋ฆฟ์ ๋ง๋ค๋๋ ํธํ๊ณ HTML์ ๊ฐ๋ ์ฑ๋ ์ฌ๋ผ๊ฐ๋ค.
์ฐธ๊ณ ๋ก ~{::tag} ๋ก ๋ฐ์๋๋ฐ, ๊ธฐ์กด์ฝ๋์ ํด๋นํ๊ทธ๊ฐ 3๊ฐ๋ผ๋ฉด, th:replace์์๋ ๋ฐ๋ณต๋ฌธ์ฒ๋ผ 3๊ฐ ์ฐ๋ฌ์ ๋ง๋ค์ด์ง๋ค.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!-- common_header ํ
ํ๋ฆฟ ์ ์, template/layout/base.html-->
<head th:fragment="common_header(myTitle, myLinks)">
<!-- ์ถ๊ฐ, ํ๋ผ๋ฉํ ์
๋ ฅ์ผ๋ก ๋ฐ์ ์๋งํผ title ํ๊ทธ๋ฅผ ๋ง๋ฌ. -->
<title th:replace="${myTitle}">๋ ์ด์์ ํ์ดํ</title>
<!-- ๊ณตํต, ๋ชจ๋ ๊ณณ์ ๊ณตํต ๋ ์ด์์์ ์ ์ฉ -->
<link media="all" rel="stylesheet" th:href="@{/css/awesomeapp.css}" type="text/css">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script th:src="@{/sh/scripts/codebase.js}" type="text/javascript"></script>
<!-- ์ถ๊ฐ, ํ๋ผ๋ฉํ ์
๋ ฅ์ผ๋ก ๋ฐ์ ์ ๋งํผ link ํ๊ทธ๋ฅผ ๊ทธ๋๋ก ์ ๋ฌํจ. -->
<th:block th:replace="${myLinks}"/>
</head>
</html>
# ์ ์ฒด HTML์ ๊ณตํต ๋ ์ด์์ ์ ์ฉํ๊ธฐ (Extends)
<html> ํ๊ทธ์ ํ ํ๋ฆฟ์ ์ ์ฉํ๋ฉด, ํ์ด์ง์ ๊ณตํต์ ์ธ ๋ ์ด์์์ ์ ์ฉํ๊ณ ํ์ํ ๋ ๊ตฌ์กฐ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ค.
<!DOCTYPE html>
<html th:replace="~{template/layoutExtend/layoutFile :: myLayout(~{::title}, ~{::section})}"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>๋ฉ์ธ ํ์ด์ง ํ์ดํ</title>
</head>
<body>
<section>
<p>๋ฉ์ธ ํ์ด์ง ์ปจํ
์ธ </p>
<div>๋ฉ์ธ ํ์ด์ง ํฌํจ ๋ด์ฉ</div>
</section>
</body>
</html>
- ์ ์ฒด HTML์ด ์๋ ํ
ํ๋ฆฟ์ ๊ฑฐ์ณ์ ๋ ๋๋ง ๋๋ค. ๊ด๋ฆฌํ๋ ํ์ด์ง๊ฐ ๋ง์ ๊ฒฝ์ฐ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
์ด ๋ th:replace๋ฅผ ์ฌ์ฉํ ํ๊ทธ๋, ์ด์ฐจํผ ๋ด์ฉ์ด ์ญ์ ๋๋ฏ๋ก ์ฃผ์์ผ๋ก ์ฌ์ฉํ๋ฉด ์ ์ฉํ๋ค.
<!DOCTYPE html>
<!-- template/layoutExtend/layoutFile.html ์์ ์๋ layout(title, content) ํ
ํ๋ฆฟ -->
<html th:fragment="myLayout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="${title}">~ ์ ์ฉ๋ ํ์ดํ th:replace๋ผ ์ด ํ
์คํธ๋ ์ญ์ ๋จ~</title>
</head>
<body>
<h1>๋ ์ด์์ H1</h1>
<div th:replace="${content}">
<p>~ ์ ์ฉ๋ ์ปจํ
์ธ , th:replace๋ผ ์ด ํ
์คํธ๋ ์ญ์ ๋จ ~</p>
</div>
<footer>
๋ ์ด์์ ํธํฐ
</footer>
</body>
</html>
# ์ ํ๋ณ์ *{...} ์ th:object
์ ํ๋ณ์ *{...} ์ th:object ๋ฅผ ์ด์ฉํด์ ๊ฐ์ฒด์์ ๊ฐ์ ๊บผ๋ผ ๋ ์ค๋ณต๋๋ ์ฝ๋๋ฅผ ์ค์ผ ์ ์๋ค.
myClass.session.user ์ด๋ฐ ์ฝ๋๋ฅผ th:object= ${myClass.session}๋ก ์ ์ธํ๊ณ , ์ดํ *{user} ๋ก ํธํ๊ฒ ์ฌ์ฉํ๋ ๊ธฐ๋ฅ.
- ๋น์ฐํ๊ฑฐ์ง๋ง, th:object๋ฅผ ์ ์ ํ๊ทธ ๋ด๋ถ(ํ์ ํ๊ทธ)์์๋ง ์ฌ์ฉํ ์ ์๋ค.
<!-- ๋ ๋๋ง ์ , ์ ํ ๋ณ์ *{...} ์ฌ์ฉ -->
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
<!-- ๋ ๋๋ง ์ดํ -->
<div>
<p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>
# ๋ฉ์์ง ๊ธฐ๋ฅ #{...}
์์์ ์น ๊ธฐ๋ณธ ๊ฐ์ฒด๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ${#...} ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค๊ณ ํ๋ค.
๊ทธ๋ฅ ์ธ๋ถ ํ์ผ์ ๊ฐ์ ์ฝ์ด์ค๊ณ ์ถ์๊ฒฝ์ฐ #{...}์ ์ฌ์ฉํ๋ฉด ๋๋ค. thymeleaf engine ๊ฒฝ๋ก์ ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ ธ์๋ ํ๋กํผํฐ ํ์ผ(message properties)์ด ์กด์ฌํ๋ ๊ฒฝ์ฐ ์ธ๋ถ์์ ํด๋น ๊ฐ์ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ด๋ค.
์ด๋ ๋์ค์ ๊ตญ์ ํ & ๋ฉ์์ง ๊ธฐ๋ฅ์ ๋ฐฐ์ฐ๊ฒ๋๋ฉด ์ ์ด๋ฐ๊ฑธ ๋ฐ๋ก ์ ๊ณตํ๋์ง ์ ์ ์๊ฒ๋๋ค.
// message properties ํ์ผ
home.welcome = ์๋
ํ์ธ์? ๋ฐ๊ฐ์ต๋๋ค
home.param = ์ด๊ฑด {0} ํ๋ผ๋ฉํ {1}.
<p th:text="#{home.welcome}">์ธ์ฌ๋ง</p>
<p th:text="#{home.param(${name}, ${end})}">์ธ์ฌ๋ง(๊ณ ๊ฐ๋ช
)</p>
- ์คํ๋ง์์ ๋ฐ์ Model ์์ [ name= "๊ฐ์์ง", end= "์ ๋๋ค" ] ์ด๋ ๊ฒ ์ ์ฅ๋์ด์๋ค๋ฉด ์๋์ ๊ฐ์ด ๋ ๋๋ง ๋๋ค.
<p> ์๋
ํ์ธ์? ๋ฐ๊ฐ์ต๋๋ค </p>
<p> ์ด๊ฑด ๊ฐ์์ง ํ๋ผ๋ฉํ ์
๋๋ค. </p>
'๐ฑ Spring Framework > Spring MVC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring ๋ฉ์์ง, ๊ตญ์ ํ ๊ธฐ๋ฅ (0) | 2021.08.28 |
---|---|
Thymeleaf#2 Spring๊ณผ HTML Form ์ฒ๋ฆฌ (0) | 2021.08.28 |
#2 HTTP API ์์ฒญ ๋งคํ, ํค๋ ์กฐํ (0) | 2021.08.11 |
#1 HTTP ์์ฒญ ๋งคํ๊ณผ ๊ธฐ๋ณธ ๋ก๊น (0) | 2021.08.11 |
# HTTP ๋ฐ์ดํฐ ์ ์ก/์๋ต (0) | 2021.08.11 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev