DevLog ๐ถ
[Spring] Thymeleaf ๊ธฐ๋ณธ ๋ฌธ๋ฒ ์ ๋ฆฌ ๋ณธ๋ฌธ
๊น์ํ ๋์ '์คํ๋ง MVC 2ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํ์ฉ ๊ธฐ์ '์ ๋ณด๊ณ ์ ๋ฆฌํ ๊ธ์ ๋๋ค ๐
| ํ์๋ฆฌํ ๋ฌธ๋ฒ ์์๋ณด๊ธฐ
- SSR์์ ๋ง์ด ์ฌ์ฉํ๋ ํ์๋ฆฌํ์ ๊ธฐ๋ณธ ๋ฌธ๋ฒ์ ๋ํด์ ์์๋ณด์.
- ํ๋ก์ ํธ dependency์๋ ๋ค์์ ์ถ๊ฐํด์ฃผ์๋ค.
: Spring Web, Lombok, Thymeleaf
- ํ์๋ฆฌํ์ ๊ฒฝ์ฐ '๋ค์ธ๋ด ํ ํ๋ฆฟ'์ด๋ผ๋ ํน์ง์ด ์๋ค.
: HTML์ ๊ทธ๋๋ก ์ ์งํ๊ธฐ ๋๋ฌธ์ ์น ๋ธ๋ผ์ฐ์ ์์ ํ์ผ์ ์ด์ด๋ ๋ด์ฉ ํ์ธ์ด ๋๋ฉฐ, ๋ทฐ ํ ํ๋ฆฟ์ ๊ฑฐ์น๋ฉด ๋์ ์ผ๋ก ๊ฒฐ๊ณผ๊ฐ ๋ณํ๋๋ค.
: ์์ํ HTML์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด์ ๋ทฐ ํ ํ๋ฆฟ๊น์ง ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ด ํฐ ์ฅ์ !
โ ๊ธฐ๋ณธ ์ ์ธ
<html xmlns:th="http://www.thymeleaf.org">
โ ํ ์คํธ ์ถ๋ ฅ - text, utext
<a th:text="${data}">
: HTML content์ ๋ฐ์ดํฐ ์ถ๋ ฅ์ ์ฌ์ฉ, ํ๊ทธ ์์ฑ์ ๊ธฐ๋ฅ ์ ์.
[[${data}]]
: HTML ์ฝํ ์ธ ์์ญ ์์์ ์ง์ ๋ฐ์ดํฐ๋ฅผ ์ถ๋ ฅํ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๊ธฐ
[ThymeleafController.java]
@Controller
@RequestMapping("/thymeleaf")
public class ThymeleafController {
@GetMapping("/text-basic")
public String textBasic(Model model) {
model.addAttribute("data", "hello!");
return "thymeleaf/text-basic";
}
}
[text-basic.html] - resources/templates/thymeleaf ๋ฐ์ ๋์๋ค.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Data in Content</h1>
<ul>
<li>th:text ์ฌ์ฉํ๊ธฐ <span th:text="${data}"></span></li>
<li>์ปจํ
์ธ ์์์ ์ง์ ์ถ๋ ฅํ๊ธฐ = [[${data}]]</li>
</ul>
</body>
</html>
- ๋ ๊ฒฝ์ฐ ๋ชจ๋ ์ ๋์จ๋ค.
- ์ค์ฉ์ฑ์ผ๋ก ๋ดค์ ๋๋ [[]]์ ๋ ๋ง์ด ์ฌ์ฉํ์ง ์์๊น ์ถ์๋ฐ... ์ด๊ฑฐ๋ ์ทจํฅ ์ฐจ์ด์ผ ๊ฒ ๊ฐ๋ค.
โ Escape ์ฒ๋ฆฌํ๊ธฐ
- html์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๊ทธ์์ <>์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, < ์์ฒด๋ฅผ ๋ฌธ์๋ก ํํํ๊ธฐ ์ํด์ ํน์ํ ๋ฐฉ๋ฒ์ด ํ์ํ๋ค.
- < ์ด๋ฌํ ํน์๋ฌธ์๋ฅผ html ์ํฐํฐ๋ก ๋ณ๊ฒฝํ๋ ๊ฒ์ escape๋ผ๊ณ ํ๋ฉฐ, th:text, [[...]]์ ๊ธฐ๋ณธ์ ์ผ๋ก escape๋ฅผ ์ ๊ณตํ๋ค.
< : <
> : >
& : &
" : "
' : &apos
๐ฉ HTML ์ํฐํฐ๋?
- HTML์์ ์ฌ์ฉํ๋ ์์ฝ๋ ๋ฌธ์๋ 'HTML ์์ฝ์ด'๋ผ๊ณ ํ๋ฉฐ, ์ด๋ฅผ HTML ์ฝ๋์์ ์ฌ์ฉํ๋ฉด ์น ๋ธ๋ผ์ฐ์ ๋ ๋ค๋ฅธ ์๋ฏธ๋ก ํด์ํ๋ค.
- HTML ์์ฝ์ด๋ฅผ ๊ธฐ์กด์ ์ฌ์ฉํ๋ ์๋ฏธ ๊ทธ๋๋ก ์ฌ์ฉํ๊ธฐ ์ํด, ๋ณ๋๋ก ๋ง๋ ๋ฌธ์์ ์ ์ํฐํฐ๋ผ๊ณ ํ๋ค.
- ๋ณดํต ์ํฐํฐ๋ &entity_name, &#entity_number ํํ๋ก ๋ํ๋ธ๋ค.
- ์ํฐํฐ์ ์ด๋ฆ์ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ๋ค.
- ์ด์ค์ผ์ดํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ง ์์ผ๋ ค๋ฉด th:utext, [(...)]์ ์ฌ์ฉํ๋ฉด ๋๋ค.
@GetMapping("/text-unescaped")
public String textUnescaped(Model model) {
model.addAttribute("data", "<b>hello!</b>");
return "thymeleaf/text-unescaped";
}
[text-unescaped.html]
<!DOCTYPE html>
<html lang="en">
<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>
- th:inline ์ต์ ์ ์ฃผ์ด์ [[...]]์ด๋ [(...)]์ ํด์ํ์ง ๋ชปํ๋๋ก ํ์๋ค.
- ๋ค์์ฒ๋ผ ์ด์ค์ผ์ดํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ง ๋ชปํ๋๋ก ๋ง๋ค ์ ์๋ค.
โ ๋ณ์ ํํ์ - ${...}
@GetMapping("/variable")
public String variable(Model model) {
User user1 = new User("hi", 22);
User user2 = new User("hihi", 23);
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
Map<String, User> map = new HashMap<>();
map.put("user1", user1);
map.put("user2", user2);
model.addAttribute("user", user1);
model.addAttribute("userList", users);
model.addAttribute("userMap", map);
return "thymeleaf/variable";
}
@Getter
static class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
[variable.html]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Spring EL ํํ์</h1>
<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>${userList[0].username} = <span th:text="${userList[0].username}"></span></li>
<li>${userList[0]['username']} = <span th:text="${userList[0]['username']}"></span></li>
<li>${userList[0].getUsername()} = <span th:text="${userList[0].getUsername()}"></span></li>
</ul>
<ul>Map
<li>${userMap['userA'].username} = <span th:text="${userMap['user1'].username}"></span></li>
<li>${userMap['userA']['username']} = <span th:text="${userMap['user1']['username']}"></span></li>
<li>${userMap['userA'].getUsername()} = <span th:text="${userMap['user1'].getUsername()}"></span></li>
</ul>
</body>
</html>
- ๊ฐ์ฒด ์ ๊ทผ
user.username
user['username']
user.getUsername()
- List ์ ๊ทผ
userList[0].username
userList[0]['username']
userList[0].getUsername()
- Map ์ ๊ทผ
userMap['user1'].username
userMap['user1]['username']
userMap['user1'].getUsername()
- ์ ๊ทผํ๋ ค๋ฉด getter๊ฐ ํ์์ ์ผ๋ก ์ด๋ ค์์ด์ผ ํ๋ค.
โ ์ง์ญ ๋ณ์ ์ ์ธ - th:with
<h1>์ง์ญ ๋ณ์ - (th:with)</h1>
<div th:with="first=${userList[0]}">
<p>์ฒ์ ์ฌ๋์ ์ด๋ฆ์ <span th:text="${first.username}"></span></p>
</div>
- th:with๋ฅผ ์ฌ์ฉํ๋ฉด ์ง์ญ ๋ณ์๋ฅผ ์ ์ธํด์ ์ ๊ทผํ ์ ์๋ค. (์กฐ๊ธ ๋ ๊น๋ํ๊ฒ ์ค๋ณต๋๋ ๋ถ๋ถ ์ฒ๋ฆฌ ๊ฐ๋ฅ)
โ ๊ธฐ๋ณธ ๊ฐ์ฒด๋ค
@GetMapping("/basic-objects")
public String basicObjects(HttpSession session) {
session.setAttribute("sessionData", "Hello Session");
return "thymeleaf/basic-objects";
}
@Component("helloBean")
static class HelloBean {
public String hello(String data) {
return "Hello " + data;
}
}
[basic-objects.html]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
</body>
</html>
http://localhost:8080/thymeleaf/basic-objects?paramData=hello
โ ํธ์ ๊ฐ์ฒด
HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ ์ ๊ทผ: ${param.name}
HTTP ์ธ์ ์ ๊ทผ: ${session.name}
์คํ๋ง ๋น ์ ๊ทผ: ${@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 : ์์ด๋ ์ฒ๋ฆฌ ๊ด๋ จ ๊ธฐ๋ฅ ์ ๊ณต
- ๋์ค์ ํ์ํ ๋ ์ง์ ์ฐพ์๋ณด๋ ๊ฒ ์ข์ ๋ฏ!
โ ๋ ์ง ํ์ - temporals
[date.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>LocalDateTime</h1>
<ul>
<li>default = <span th:text="${localDateTime}"></span></li>
<li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime, 'yyyy-MM-dd HH:mm:ss')}"></span></li>
</ul>
<h1>LocalDateTime - Utils</h1>
<ul>
<li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
<li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
<li>${#temporals.monthName(localDateTime)} = <span th:text="${#temporals.monthName(localDateTime)}"></span></li>
<li>${#temporals.monthNameShort(localDateTime)} = <span th:text="${#temporals.monthNameShort(localDateTime)}"></span></li>
<li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeek(localDateTime)} = <span th:text="${#temporals.dayOfWeek(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeekName(localDateTime)} = <span th:text="${#temporals.dayOfWeekName(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeekNameShort(localDateTime)} = <span th:text="${#temporals.dayOfWeekNameShort(localDateTime)}"></span></li>
<li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
<li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
<li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
<li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>
</ul>
</body>
</html>
- ์ด๋ ๊ฒ ๋ ์ง ํ์๊ฐ ๊ฐ๋ฅํ๋ค :D
โ URL ์์ฑ
- ๋จ์ํ URL
: {@/hello}
-> /hello
- ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ
: @{/hello(param1=${param1}, param2={param2})}
์ด๋, model์ {"param1", "data1"} {"param2", "data2"} ๋ฃ์ ๊ฒฝ์ฐ
-> /hello?param1=data1¶m2=data2
- ๊ฒฝ๋ก ๋ณ์
: @{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
-> /hello/data1/data2
- ๊ฒฝ๋ก ๋ณ์ + ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ
: @{/hello/{param1}(param1=${param1}, param2=${param2})}
-> /hello/data1?param2=data2
โ ๋ฆฌํฐ๋ด
- ์์ค ์ฝ๋ ์์์ ๊ณ ์ ๋ ๊ฐ์ ๋งํ๋ ์ฉ์ด
๋ฌธ์ / ์ซ์ / boolean / null
- ๋ฌธ์๋ ํญ์ ''์ผ๋ก ๊ฐ์ธ์ผ ํ๋ค.
- ๊ทธ๋ฌ๋, ๊ณต๋ฐฑ ์์ด ์ญ ์ด์ด์ง๋ ๊ฒฝ์ฐ๋ผ๋ฉด ํ๋์ ํ ํฐ์ผ๋ก ์ธ์งํ๊ธฐ ๋๋ฌธ์ ๊ฐ์ธ์ง ์์๋ ๋๋ค.
- ๋น์ฐํ, ๊ณต๋ฐฑ์ด ์๋ ๊ฒฝ์ฐ๋ผ๋ฉด ''๋ก ๊ฐ์ธ์ผ ํ๋ค.
th:text="hello" (o)th:text="'hello world'" (o)
- ๋ฆฌํฐ๋ด ๋์ฒด ๋ฌธ๋ฒ.
: ''๋ก ๊ฐ์ธ์ง ์์๋ ํ ๋ฒ์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค!
<span th:text="|hello ${data}|">
- ๊ธฐ์กด์๋ th:text="'hello ' + ${data}"๋ก ์์ฑํ์ด์ผ ํ์ง๋ง, ||๋ก ํ ๋ฒ์ ๋์ฒดํ ์ ์๋ค.
โ ์ฐ์ฐ
- ๋น๊ต์ฐ์ฐ: HTML ์ํฐํฐ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ ๊ฒฝ์ฐ ์กฐ์ฌํ๊ธฐ
> (gt), < (lt), >= (ge), <= (le), ! (not), == (eq), != (neq, ne)
- ?์ ํตํ ๋คํญ ์ฐ์ฐ์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค
<li>(10 % 2 == 0) ? '์ง์':'ํ์' = <span th:text="(10 % 2 == 0)? '์ง์':'ํ์'"></span></li>
- ์ฐ์ฐ ์ํ ์ ํจ
<li>${nullData}? : _ = <span th:text="${nullData}?: _">๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</span></li>
: _์ ์ฌ์ฉํ๊ฒ ๋๋ฉด ํ์๋ฆฌํ์ ํ๊ทธ ํจ๊ณผ๊ฐ ๋ฌดํจ๋๋ค. ${}์ ๊ฒฐ๊ณผ๊ฐ ์๋ '๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค'๊ฐ ์ถ๋ ฅ๋๋ค.
โ ์์ฑ ๊ฐ ์ค์ - th:*
- th:*์ ํตํด ์์ฑ์ ์ง์ ํ๋ฉด, ํ์๋ฆฌํ๋ ๊ธฐ์กด ์์ฑ์ th:*๋ก ์ง์ ํ ์์ฑ์ผ๋ก ๋์ฒดํ๋ค.
- ์ด๋, ๊ธฐ์กด ์์ฑ์ด ์๋ค๋ฉด ์๋ก ๋ง๋ ๋ค.
<input type="text" name="mock" th:name="userA" />
-> <input type="text" name="userA" />
- th:attrappend : ์์ฑ ๊ฐ์ ๋ค์ ๊ฐ ์ถ๊ฐ
<input type="text" class="text" th:attrappend="class=' large'" />
-> <input class="text large">
- th:attrprepend : ์์ฑ ๊ฐ์ ์์ ๊ฐ ์ถ๊ฐ
<input type="text" class="text" th:attrprepend="class='large '" />
-> <input class="large text">
- th:classappend : class ์์ฑ์ ์์ฐ์ค๋ฝ๊ฒ ์ถ๊ฐ
<input type="text" class="text" th:classappend="large" />
-> <input class="text large">
- ๊ธฐ์กด์ HTML์์๋ checked๋ผ๋ ์ต์ ๋ง ์์ผ๋ฉด ์ฒดํฌ๊ฐ ๋๊ธฐ ๋๋ฌธ์, ์ด๋ฅผ ๋ฐฉ์งํด๋ณด์. (T/F ์ฒ๋ฆฌํ๊ธฐ)
- checked=false <input type="checkbox" name="active" checked="false" />
: false๋ผ๊ณ ํด๋ ๋ฐ๋ก ์ฒดํฌ ํ์๊ฐ ๋์ด๋ฒ๋ฆฌ๋ ๊ฒ.
- checked o <input type="checkbox" name="active" th:checked="true" />
- checked x <input type="checkbox" name="active" th:checked="false" />
: th:checked๊ฐ false๋ผ๋ฉด ์์ checked ์ต์
์์ฒด๋ฅผ ์ ๊ฑฐํด๋ฒ๋ฆฐ๋ค.
โ ๋ฐ๋ณต๋ฌธ - th:each
<tr th:each="user : ${users}">
- users์ ์๋ ๊ฐ์ ํ๋์ฉ user์ ๋ด์์ ํ๊ทธ๋ฅผ ๋ฐ๋ณต ์คํํด์ฃผ๊ธฐ.
- java.util.Iterable, java.util.Enmeration์ ๊ตฌํ๋ง ๋ชจ๋ ๊ฐ์ฒด์ ๋ํด์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
<tr th:each="user, userStat : ${users}">
- ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํด์ ๋ฐ๋ณต์ ์ํ๋ฅผ ํ์ธํ ์ ์๋ค. (์๋ต ์ ์ง์ ํ ๋ณ์๋ช + Stat ํํ)
- ๋ฐ๋ณต์ ์ํ ํ์ธ ์ ์ ์ฒด ์ฌ์ด์ฆ๋ ํ์ฌ ๊ฐ์ฒด ๋ฑ์ ์ป์ ์ ์๋ค.
"${userStat.index}" : 0๋ถํฐ ์์ํ๋ ์ผ์ข ์ ์ธ๋ฑ์ค ๊ฐ์ ๋ณด์ฌ์ค๋ค.
count : 1๋ถํฐ ์์ํ๋ ์ผ์ข ์ ์ธ๋ฑ์ค ๊ฐ
size : ์ ์ฒด ์ฌ์ด์ฆ
even : ์ง์ ์ฌ๋ถ <boolean>
odd : ํ์ ์ฌ๋ถ <boolean>
first : ์ฒ์์ธ์ง <boolean>
last: ๋ง์ง๋ง์ธ์ง <boolean>
current: ํ์ฌ ๊ฐ
โ ์กฐ๊ฑด๋ถ ํ๊ฐ
th:if : if๋ฌธ, ๋ง์กฑํ๋์ง ํ์ธ
th:unless : if๋ฌธ์ ๋ฐ๋, ๋ง์กฑํ์ง ์๋์ง ํ์ธ
- ๋ ๋ชจ๋ true์ผ ๊ฒฝ์ฐ ํ๊ทธ ์ถ๋ ฅ, false์ผ ๊ฒฝ์ฐ ํด๋น ํ๊ทธ๋ ์ถ๋ ฅ๋์ง ์๋๋ค.
th:switch, th:case : ์๋ฐ์์์ switch-case๋ฌธ ๊ทธ๋๋ก. th:case="*" -> ๋ง์กฑํ๋ ์กฐ๊ฑด์ด ์์ ๋ ์ฌ์ฉํ๋ ๋ํดํธ ๊ฐ.
โ ์ฃผ์
- ํ์ค HTML ์ฃผ์: <!-- -->
: ํ์๋ฆฌํ๊ฐ ๋ ๋๋งํ์ง ์๊ณ ๊ทธ๋๋ก ๋จ๊ฒจ๋๋ค.
- ํ์๋ฆฌํ ํ์ ์ฃผ์ : <!-- /* */-->
: ํ์๋ฆฌํ์ ์ฃผ์์ด๊ธฐ ๋๋ฌธ์, ๋ ๋๋ง์์ ์ฃผ์ ๋ถ๋ถ ์ ๊ฑฐ.
- ํ์๋ฆฌํ ํ๋กํ ํ์ ์ฃผ์ : <!--/*/ /*/-->
: HTML ์ฃผ์์ด๊ธฐ ๋๋ฌธ์ ์น ๋ธ๋ผ์ฐ์ ๋ ๋ ๋๋ง X, ํ์๋ฆฌํ ๋ ๋๋ง ์์๋ ์ ์ ๋ ๋๋ง.
: ์ฆ, HTML ํ์ผ์์๋ ์ฃผ์, ํ์๋ฆฌํ์์๋ ๋ณด์ด๊ฒ ํ๋ ๊ธฐ๋ฅ์ด๋ค.
โ ๋ธ๋ก - th:block
- ํ์๋ฆฌํ๋ ํ๊ทธ ์์ ์์ฑ์ผ๋ก ๊ธฐ๋ฅ์ ์ ์ํ๋ค ๋ณด๋๊น, ์ฌ๋ฌ ๊ฐ๋ฅผ ๋ฌถ์ด์ ์ฌ์ฉํ๊ณ ์ถ์ ๋๊ฐ ์๋ค.
- 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>
- ์ด๋ฐ ์์ผ๋ก 2๊ฐ์ div ํ๊ทธ๋ฅผ ๋ฌถ์ด์ ๋ฐ๋ณต๋ฌธ์ ๋๋ฆฌ๊ณ ์ถ๋ค๋ ์ง... ๊ทธ๋ด ๋ ์ฌ์ฉํ๋ค.
โ ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ - <script th:inline="javascript">
- ์๋ฐ์คํฌ๋ฆฝํธ์์ ํ์๋ฆฌํ๋ฅผ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
- ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ ์ฌ์ฉ X
<script>
var username = [[${user.username}]];
var age = [[${user.age}]];
var username2 = /*[[${user.username}]]*/ "test username";
var user = [[${user}]];
</script>
- ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ ์ฌ์ฉ O
<script th:inline="javascript">
var username = [[${user.username}]];
var age = [[${user.age}]];
var username2 = /*[[${user.username}]]*/ "test username"; // ์๋ฐ์คํฌ๋ฆฝํธ์์๋ ๋ค์ธ๋ด ํ
ํ๋ฆฟ ๊ฐ๋ฅ
var user = [[${user}]];
</script>
- ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ์ ์ฌ์ฉํ์ง ์์ ๊ฒฝ์ฐ
var username = [[${user.username}]];
์ฌ๊ธฐ์ var username = userA๋ผ๊ณ ์ธ์์ด ๋๋ค. ์ฆ, userA๋ผ๋ ๋ณ์ ์์ฒด๊ฐ ๋ค์ด๊ฐ๋ ํํ๊ฐ ๋์ด๋ฒ๋ฆฐ๋ค.
-> ์ด๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด var username = "[[${user.username}]]"; ์ด๋ ๊ฒ ์จ์ผ ํ๋ค.
๋ฐ๋ฉด, ์ธ๋ผ์ธ์ ์ฌ์ฉํ๋ฉด var username = "userA"๋ผ๊ณ ๋์ค๊ธฐ ๋๋ฌธ์ ๋ฌธ์์ด์ด ์ ๋ค์ด๊ฐ๋ ๊ฑธ๋ก ๋์จ๋ค.
- ๋ํ, ๋ด์ถ๋ด ํ ํ๋ฆฟ ๊ธฐ๋ฅ์ ์ ๊ณต ์ ๋ฌด์๋ ์ฐจ์ด๊ฐ ์๋ค.
var username2 = /*[[${user.username}]]*/ "test username";
์ธ๋ผ์ธ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด var username2 = /*userA*/ "test username"; ์ด๋ ๊ฒ ๋์ค์ง๋ง,
์ธ๋ผ์ธ์ ์ฌ์ฉํ๋ฉด var username2 = "userA"๋ผ๊ณ ๋์จ๋ค.
: ์ธ๋ผ์ธ x์ผ ๋๋ ์ ๋ง ๊ทธ๋๋ก ํด์ํด๋ฒ๋ฆฌ์ง๋ง, ์ธ๋ผ์ธ์ ์ฌ์ฉํ๋ฉด ๋ ๋๋ง ์ ์ฃผ์ ๋ถ๋ถ์ ์ ๊ฑฐํ๊ณ ๊ฐ์ด ์ ๋ ฅ๋๋ค.
- ๋ง์ง๋ง์ผ๋ก, var user = [[${user}]]; ์ด ๋ถ๋ถ์ด๋ค.
์ธ๋ผ์ธ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด var user = BasicController.User(username=userA, age=10);๋ก ๋์ค์ง๋ง,
์ธ๋ผ์ธ์ ์ฌ์ฉํ๋ฉด var user = {"username":"userA","age":10};๋ก ๋์จ๋ค.
: ์ธ๋ผ์ธ x์ผ ๋๋ ๊ฐ์ฒด์ toString์ด, ์ธ๋ผ์ธ ์ฌ์ฉ ์์๋ JSON์ผ๋ก ๋ณํํ ๊ฐ์ด ๋ค์ด๊ฐ๊ฒ ๋๋ ๊ฒ์ด๋ค.
- ๋ํ, ์ธ๋ผ์ธ ์ฌ์ฉ ์ ๋ฌธ์ ํ์ ์ด๋ฉด ์๋์ผ๋ก "์ ์ถ๊ฐํด์ฃผ๊ณ , "๋ '๊ฐ์ ๋ฌธ์๋ ์๋์ผ๋ก ์ด์ค์ผ์ดํ ์ฒ๋ฆฌ๋ ํด์ค๋ค!
- ๋ํ, ์๋ฐ์คํฌ๋ฆฝํธ ์ธ๋ผ์ธ์ each๋ฅผ ํตํด ๋ฐ๋ณต๋ฌธ๋ ์ง์ํ๋ค.
<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>
| ํ์๋ฆฌํ - ํ ํ๋ฆฟ ์กฐ๊ฐ / ๋ ์ด์์
- ์ฌ๋ฌ ํ์ด์ง์์ ํจ๊ป ์ฌ์ฉํ๋ ์์ญ๋ค์ ๋ํด ํ ํ๋ฆฟ ์กฐ๊ฐ ๋ฐ ๋ ์ด์์์ผ๋ก ๊ด๋ฆฌํ์.
@GetMapping("/fragment")
public String template() {
return "template/fragment/fragmentMain";
}
[footer.html] - resources/templates/template/fragment/
<!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>
- th: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</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<h2>๋ถ๋ถ ํฌํจ replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>
<h2>๋ถ๋ถ ํฌํจ ๋จ์ ํํ์</h2>
<div th:replace="template/fragment/footer :: copy"></div>
<h1>ํ๋ผ๋ฏธํฐ ์ฌ์ฉ</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('๋ฐ์ดํฐ1', '๋ฐ์ดํฐ2')}"></div>
</body>
</html>
- ์กฐ๊ฐ์ ๋ํ ์ฝ์ ์ ๋ค์๊ณผ ๊ฐ์ด th:insert, th:replace๋ฅผ ์ด์ฉํ ์ ์๋ค.
- th:insert๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ฌ ํ๊ทธ์ ๋ด๋ถ์ ์ฝ์ ํ๊ฒ ๋๋ค.
- th:replace๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ฌ ํ๊ทธ๋ฅผ ๋์ฒดํด๋ฒ๋ฆฐ๋ค.
- ~{fragment์ ๊ฒฝ๋ก :: ์ด๋ฆ}์ผ๋ก ์ ๊ทผํ๋ค.
- ์ด๋, ์ฝ๋๊ฐ ๋จ์ํ๋ฉด ~{}์ ์๋ตํ ์๋ ์๋ค. (๊ทธ๋๋ ์ฌ๋งํ๋ฉด ์จ์ฃผ๋ ๊ฒ์ด ์ข๋ค)
- ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๋ฌํ ๋๋ ()์ ํตํด ์ ๋ฌํ๋ค.
- ์ด๋ฒ์๋, ์ฝ๋ ์กฐ๊ฐ์ ๋ ์ด์์์ผ๋ก ๋๊ฒจ๋ณด์.
@GetMapping("/layout")
public String layout() {
return "template/layout/layoutMain";
}
[base.html] - resources/templates/template/layout
<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>
- head์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ๋ถ๋ถ์ ๋ํด ๋ฌถ์ด๋์๋ค.
- th:fragment = "common_header (title, links)"
- th:replace๋ก ๋ฐ์์จ title, links ์ฌ์ฉ.
- th:replace="${title}"
[layoutMain.html]
<!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>
<head th:replace="template/layout/base :: common_header(~{::title},~{::link})">
- ๊ธฐ๋ณธ์ ์ผ๋ก th:replace๋ฅผ ์ ์ฉํ์ฌ, base.html์ ์๋ head๋ฅผ ์ฌ์ฉํ๊ฒ ๋ค๋ ๊ฒ.
- ์ด๋, ๋จ์ํ base ๊ทธ ์์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋, ์ปค์คํ ์ ํ๊ธฐ ์ํด ํ์ฌ ํ์ด์ง์ ๊ฐ์ ์ ๋ฌํด์ฃผ๋ ๊ฒ!
- ::title์ ํ์ฌ ํ์ด์ง์ title ํ๊ทธ๋ค์ ์ ๋ฌํ๋ค. (ํ๊ทธ ์์ฒด๊ฐ ์ ๋ฌ!)
- ::link๋ ํ์ฌ ํ์ด์ง์ link ํ๊ทธ๋ค์ ์ ๋ฌํ๋ค.
- ์กฐ๊ธ ๋ ์์ฉํด๋ณด์.
@GetMapping("/layoutExtend")
public String layoutExtends() {
return "template/layoutExtend/layoutExtendMain";
}
[layoutFile.html] - resources/templates/template/layoutExtend
<!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>
- th:fragment = "layout (title, content)"
- th:replace๋ก ๋ฐ์์จ title๊ณผ content ์ฌ์ฉ
- th:replace="${title}"
[layoutExtendMain.html]
<!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>
- ์๋ฆฌ๋ ๋์ผํ๋ค.
th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title}, ~{::section})}
- ์ผ๋ถ๊ฐ ์๋, ํต์ผ๋ก ๊ต์ฒด๋ฅผ ์์ผ๋ฒ๋ฆฌ์๋ ๊ฒ.
- ํ์ฌ ํ์ด์ง์ title, section ํ๊ทธ ์ฌ์ฉ
- ๋ค์ ํฌ์คํ ์์๋ ์ด์ ํฌ์คํ ์์ ์งํํ์๋ '์ํ ๋ฑ๋กํผ'์ ์กฐ๊ธ ๋ ๋ฐ์ ์์ผ๋๊ฐ๋ณด์.