타임리프의 기초에 대해서 이전 포스트에서 다뤄봤다.
특징으로
- 서버 사이드 HTML 렌더링 (SSR) : 백엔드 서버에서 HTML을 동적으로 다룰때 렌더링하는 용도
- 네츄럴 템플릿 : 순수 HTML을 유지할 수 있고 뷰 템플릿을 거치면 동적으로도 변경된 결과 가능.
- 스프링 통합 지원 등이 있다.
텍스트 - text, utext
<span th:text="${data}">
- HTML 콘텐츠에 데이터를 출력할 때 위와같이 th:text를 사용하면 된다.
HTML 태그 속성이 아니라 HTML 콘텐츠 영역안에서 직접 데이터를 출력하고 싶으면 [[...]]를 사용 ([[${data}]])

Escape
HTML 문서는 <, > 같은 특수문자를 기반으로 정의된다. 따라서 뷰 템플릿으로 HTML 화면을 생성할 땐 특수문자를 조심해야한다.
model.addAttribute("data", "Hello <b>Spring!</b>");
개발자의 의도는 Spring을 강조하기 위해 <b>태그를 사용했는데, 화면엔 <b> </b>태그가 그대로 출력된다.
그 이유는 웹 브라우저는 '<'를 태그의 시작으로 인식한다. 따라서 '<'를 태그의 시작이 아닌 문자로 표현할 방법이 필요한데, 이 방법이 HTML 엔티티이다. HTML 엔티티가 <를 문자로 출력해주기 때문에
- th:text = Hello <b>Spring!</b>
이런식으로 출력이 되는 것이다. 의도대로 Spring을 강조하고 싶다면 untext를 사용하면 된다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<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:utext, [(..)]를 사용하면 의도대로 Spring이 강조되게 출력할 수 있다.

개발하다보면 escape를 사용하지 않아서 HTML이 깨질때가 있다. escape를 기본으로 쓰고 필요시에만 unescape를 사용해라.
변수 - SpringEL
타임리프에선 변수를 사용할 때 변수 표현식을 사용한다.
변수 표현식: ${...}
그리고 이 변수 표현식에는 스프링 EL이라는 스프링이 제공하는 표현식을 사용할 수 있다.
@GetMapping("/variable")
public String variable(Model model) {
User userA = new User("userA", 10);
User userB = new User("userB", 20);
List<User> list = new ArrayList<>();
list.add(userA);
list.add(userB);
Map<String, User> map = new HashMap<>();
map.put("userA", userA);
map.put("userB", userB);
model.addAttribute("user", userA);
model.addAttribute("users", list);
model.addAttribute("userMap", map);
return "basic/variable";
}
@Data
static class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>SpringEL 표현식</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>${users[0].username} = <span th:text="${users[0].username}"></span></li>
<li>${users[0]['username']} = <span th:text="${users[0]['username']}"></span></li>
<li>${users[0].getUsername()} = <span th:text="${users[0].getUsername()}"></span></li>
</ul>
<ul>Map
<li>${userMap['userA'].username} = <span th:text="${userMap['userA'].username}"></span></li>
<li>${userMap['userA']['username']} = <span th:text="${userMap['userA']
['username']}"></span></li>
<li>${userMap['userA'].getUsername()} = <span th:text="${userMap['userA'].getUsername()}"></span></li>
</ul>
</body>
</html>
SpringEL의 다양한 표현식 사용법
Object
- user.username: user의 username을 프로퍼티로 접근 (user.getUsername())
- user['username']: 위와 같음
- user.getUsername(): user의 getUsername()을 직접 호출
List
- user[0].username: List에서 첫번째 회원을 찾고 username 프로퍼티 접근 (list.get(0).getUsername())
- user[0]['username']: 위와 같음
- user[0].getUsername(): List에서 첫번째 회원을 찾고 메서드 직접 호출
Map
- userMap['userA'].username: Map에서 key인 userA를 찾고, username 프로퍼티로 접근 (map.get("userA").getUsername())
- userMap['userA']['username]: 위와 같음
- userMap['userA'].getUsername(): Map에서 userA를 찾고 메서드 직접 호출
지역 변수 선언
th:with를 사용하면 지역 변수를 선언해서 사용할 수 있다. 당연히 선언한 태그 안에서만 사용할 수 있음.
<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
users[0]이 first로 치환되어 사용되는 구조이다.
기본 객체들
타임리프는 기본 객체들을 제공한다.
- ${#request, response, session, servletContext}들은 스프링 부트 3.0부터 지원하지 않는다.
- ${#locale}
스프링 부트 3.0이상 버전은 model에 객체를 추가해서 사용해야 한다.
@GetMapping("/basic-objects")
public String basicObjects(Model model,
HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
session.setAttribute("sessionData", "Hello Session");
model.addAttribute("request", request);
model.addAttribute("response", response);
model.addAttribute("servletContext", request.getServletContext());
return "basic/basic-objects";
}
@Component("helloBean")
static class HelloBean {
public String hello(String data) {
return "Hello " + data;
}
}
스프링 부트 3.0이상버전이니 model에 직접 추가한 코드이다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<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>
${request}는 HttpServletRequest 객체가 그대로 제공되기 때문에 regeust.getParameter("data")처럼 불편하게 접근해야한다.

이런 점을 편리하게 해결하기 위해 편의 객체도 제공한다.
- http 요청 파라미터 접근 : param
- http 세션 접근 : session
- 스프링 빈 접근 : @
(예시코드는 위 코드 참조)

유틸리티 객체와 날짜
@GetMapping("/date")
public String date(Model model) {
model.addAttribute("localDateTime", LocalDateTime.now());
return "basic/date";
}
<!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>
별거없다. 필요할때 찾아서 쓰면 됨
URL 링크
타임리프에서 URL을 생성할 땐 @{...} 문법을 사용하면 된다.
@GetMapping("link")
public String Link(Model model) {
model.addAttribute("param1", "data1");
model.addAttribute("param2", "data2");
return "basic/link";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>URL 링크</h1>
<ul>
<li><a th:href="@{/hello}">basic url</a></li>
<li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query
param</a></li>
<li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li>
<li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path
variable + query parameter</a></li>
</ul>
</body>
</html>
단순한 URL
- @{/hello} -> hello
쿼리 파라미터
- @{/hello(param1=${param1}, param2=${param2})} -> /hello?param1=data1¶m2=data2
- ()에 있는 부분은 쿼리 파라미터로 처리된다.
경로 변수
- @{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})} -> /hello/data1/data2
- URL 경로상에 변수가 있으면 () 부분은 경로변수로 처리된다.
경로 변수 + 쿼리 파라미터
- @{/hello/{param1}(param1=${param1}, param2=${param2})} -> /hello/data1?param2=data2
- 경로 변수와 쿼리파라미터를 함께 사용할 수 있다. (param2부터는 쿼리파라미터로 표현됨)
리터럴
리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.
String a = "Hello" < Hello는 문자 리터럴
int a = 10 & 20 < 10, 20은 숫자 리터럴
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>리터럴</h1>
<ul>
<li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li>
<li>'hello world!' = <span th:text="'hello world!'"></span></li>
<li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>
<li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
</ul>
</body>
</html>
타임리프에서 문자 리터럴은 항상 ' 로 감싸야 한다. 하지만 항상 감싸는것은 귀찮으니 공백 없이 쭉 이어진다면 하나의 의미있는 코튼으로 인지해서 다음과 같이 작은 따옴표를 생략할 수 있다.
A-Z, a-z, 0-9, [], ., -, _
오류나는 코드
<span th:text="hello world!"></span>
hello 띄고 world!가 있으므로 하나의 의미있는 토큰으로 인식되지 않음
"'hello world!'"로 감싸줘야 인식 가능.
연산
자바의 연산과 크게 다르지 않다. 하지만 HTML 엔티티만 조심하면 된다.
<li>1 > 10 = <span th:text="1 > 10"></span></li>
<li>1 gt 10 = <span th:text="1 gt 10"></span></li>
<li>1 >= 10 = <span th:text="1 >= 10"></span></li>
<li>1 ge 10 = <span th:text="1 ge 10"></span></li>
<li>1 == 10 = <span th:text="1 == 10"></span></li>
<li>1 != 10 = <span th:text="1 != 10"></span></li>
<, >를 사용할 수 없기떄문에 gt, ge를 사용하면 된다.
elvis 연산자는 ?:로 사용한다 (알고있으니 패스)
No-Operation : ?:를 만족하지 않으면 HTML 기본 내용을 출력한다.
<li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li>
속성 값 설정
타임리프는 주로 HTML 태그에 th:* 속성을 지정하는 방식으로 동작한다. 속성을 적용하면 기존 속성을 대체한다. 없다면 새로 만든다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>속성 설정</h1>
<input type="text" name="mock" th:name="userA" />
<h1>속성 추가</h1>
- 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/>
<h1>checked 처리</h1>
- checked o <input type="checkbox" name="active" th:checked="true" /><br/>
- checked x <input type="checkbox" name="active" th:checked="false" /><br/>
- checked=false <input type="checkbox" name="active" checked="false" /><br/>
</body>
</html>
th:가 있으면 기존 속성을 대체해 mock대신 userA가 들어오게 된다.
mvc1편에서 다뤘던것 처럼 뷰 템플릿을 통할 때 이런 방식이 적용된다.
속성 추가
- th:attrappend: 속성 값의 뒤에 값을 추가한다.
- th:attrprepend: 속성 값의 앞에 값을 추가한다.
- th:classappend: class 속성에 자연스럽게 추가한다. (편의 기능)
checked 처리
위 코드 checked처리 부분에, th를 쓰지않고 checked속성을 쓰면, true, false 여부와 상관없이 checked 처리가 된다.
타임리프의 th:checked는 값이 false인 경우 checked 속성 자체를 제거한다.
그래서 나중에 컨트롤러에서 데이터를 받아올 때 th:checked="${isChecked}" 이런식으로 활용할 수 있게 된다.
조건부 평가
타임리프의 조건식은 if, unless(if 반대) 두가지가 있다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>if, unless</h1>
<table border="1">
<tr>
<th>count</th>
<th>username</th>
<th>age</th>
</tr>
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}">1</td>
<td th:text="${user.username}">username</td>
<td>
<span th:text="${user.age}">0</span>
<span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
<span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
</td>
</tr>
</table>
<h1>switch</h1>
<table border="1">
<tr>
<th>count</th>
<th>username</th>
<th>age</th>
</tr>
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}">1</td>
<td th:text="${user.username}">username</td>
<td th:switch="${user.age}">
<span th:case="10">10살</span>
<span th:case="20">20살</span>
<span th:case="*">기타</span>
</td>
</tr>
</table>
</body>
</html>
if, unless는 해당 조건이 맞지 않으면 태그 자체를 렌더링 하지 않는다. (span부분 자체가 렌더링 되지 않음)
switch도 사용할 수 있다.
주석
<!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>
<!--/*/
/*/-->
<span th:text="${data}">html data</span>
</body>
</html>
1. HTML 표준 주석
- 타임리프가 렌더링 하지 않고 그대로 남겨둠
2. 타임리프 파서 주석
- 타임리프의 진짜 주석이다. 렌더링에서 주석 부분을 제거
3. 프로토타입 주석
- HTML을 웹 브라우저에서 열었을 경우엔 타임리프가 렌더링하지 않음. 타임리프 렌더링을 거치면 정상적으로 렌더링되어 출력됨
블록
<th:block>은 HTML 태그가 아닌 타임리프의 유일한 자체 태그이다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
block으로 된 부분들이 같이 출력되는걸 확인할 수 있다.
위 처럼 어쩔수없이 사용하거나 사용하기 애매한 경우에 사용된다. 주로 쓰는 방법은 아니다.
자바스크립트 인라인
타임리프는 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.
<script th:inline="javascript">
@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}]];
</script>
<!-- 자바스크립트 인라인 사용 후 -->
<script th:inline="javascript">
var username = [[${user.username}]];
var age = [[${user.age}]];
//자바스크립트 내추럴 템플릿
var username2 = /*[[${user.username}]]*/ "test username";
//객체
var user = [[${user}]];
</script>
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
</body>
</html>

인라인을 사용하지 않은 소스와 사용한 소스를 비교해보자
텍스트 렌더링 기능
- var username = UserA는 문자열이기 때문에 UserA로 사용되면 오류가 발생한다. age의 경우는 숫자라 ""가 필요 없지만 usernamse은 ""가 필요하다. 이러한 부분을 인라인이 처리해준다.
추가로 문제가 될 수 있는 문자가 포함되어 있으면 escape처리도 해준다.
자바스크립트 내추럴 템플릿
- 인라인 사용 전 결과를 보면 그대로 해석을 해버렸기 때문에 내추럴 템플릿 기능이 동작하지 않고, 렌더링 내용이 주석처리 되어버렸다.
인라인을 사용 한 후엔 주석부분이 제거되고 의도대로 "UserA"가 정확히 적용 된다.
객체
- 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동으로 변환해준다.
인라인 사용 전은 toString이 호출된 값이고, 사용 후엔 JSON으로 변환된 객체를 보여준다.
자바스크립트 인라인 each
- each를 지원한다. 위의 코드처럼 사용할 수 있다.
템플릿 조각
웹 페이지를 개발할 때는 공통 영역이 많이 있다. 상단 영역이나 하단, 좌측 카테고리 등 여러 페이지에서 함께 사용하는 영역들이 있다. 이런 부분을 코드로 복사해서 사용하면 변경시 여러 페이지를 다 변경해야하기 때문에 비효율적이다. 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다.
<!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>
<!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:fragment가 있는 태그는 다른곳에 포함되는 코드 조각으로 이해하면 된다.
(그래서 fragmentMain.html에서 푸터를 호출할 수 있었음)
부분 포함 insert
- th:insert를 사용하면 현재 태그 (div) 내부에 추가한다.
부분 포함 replace
- th:replace를 사용하면 현재 태그 (div)를 대체한다.
부분 포함 단순 표현식
- ~{...}를 사용하는것이 원칙이지만 코드가 단순하면 생략할 수 있다.
파라미터 사용
- 파라미터를 전달해서 동적으로 조각을 렌더링 할 수도 있다.

템플릿 레이아웃1
이전엔 일부 코드 조각을 가지고 와서 사용했다면, 이번에는 개념을 더 확장해서 코드 조각을 레이아웃에 넘겨서 사용하는 방법에 대해 알아보자
<head>에 공통으로 사용하는 css, javascript 같은 정보들이 있는데, 이런 공통 정보들을 한곳에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같은 방법으로 사용하면 된다.
<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>
레이아웃의 베이스가 되는 코드
<!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>
레이아웃에 추가적인 요소를 적용시킨 코드

레이아웃 타이틀을 -> 메인 타이틀로 변경하고, 추가로 링크 태그들을 전달한것을 확인할 수 있다.
common_header(~{::title},~{::link})이 부분이 핵심이다.
::title은 현재 페이지의 title 태그들을 전달
::link는 현재 페이지의 link 태그들을 전달한다.
템플릿 레이아웃2
템플릿 레이아웃을 확장시켜보자
앞서 이야기 한 개념들을 <head>가 아닌 <html> 전체에도 적용시킬 수 있다.
<!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>
layoutFile.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>
<html> 에 th:replace로 html 자체를 layoutFile.html로 변경했다. layoutFile.html엔 th:fragment 속성이 정의되어 있다.
이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용을 전달해서 부분부분 변경할 수 있다.
규모가 작다면 fragment로 조각조각을 넣어서 구현할 수 있지만, 규모가 크거나 할 경우에 모든 웹 페이지를 하나하나 변경할 수 없는 노릇이니 레이아웃을 사용하는 것이다.
'Spring' 카테고리의 다른 글
| 메세지, 국제화 (1) | 2025.09.07 |
|---|---|
| 타임리프 - 스프링 통합과 폼 (1) | 2025.09.06 |
| 상품 도메인 개발 - 뷰 템플릿, 타임리프 (1) | 2025.08.28 |
| 스프링 MVC - 기본 기능 (1) | 2025.08.25 |
| Spring MVC - 구조 이해 (0) | 2025.06.19 |
