1. 기존 HTML 속성의 대체
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 1. 기존 속성의 대체 -->
<button th:id="${ idName }" th:value="${ btnName }"></button>
</body>
</html>
package com.scala.personal.mycomm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("")
public String index(Model model) {
model.addAttribute("idName", "my-id");
model.addAttribute("btnName", "my-button");
return "index";
}
}
- HTML 태그에 기본적으로 존재하는 속성들은 앞에 th: 를 붙이는 것으로 thymeleaf 문법을 사용할 수 있게 된다.
- button 태그에 th:id, th:value 를 사용하여 id, value 속성을 정의하고 Controller에서 변수 값을 추가해보자
- 그러고나서 서버를 구동하고 다시 접속해보면 button 태그의 id와 value가 작성한 thymeleaf 문법대로 들어가있는 것을 확인할 수 있다. 이처럼 th:<속성이름> 을 사용하면 thymeleaf 문법을 사용하여 기존 html의 속성값도 동적으로 설정하는 것이 가능하다.
2. 반복문(순회)
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 2. 반복문 -->
<div th:each="num: ${ numList }">
<p th:text="${ num }"></p>
</div>
</body>
</html>
package com.scala.personal.mycomm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class HomeController {
@GetMapping("")
public String index(Model model) {
model.addAttribute("numList", List.of(1, 6, 2, 7, 3, 9, 0, 13));
return "index";
}
}
- thymeleaf 에서는 th:each 속성을 통해 순회 가능한 객체의 요소에 순차적으로 접근하는 기능을 제공한다.
- th:each="<변수 이름>: ${ <순회할 객체 이름> }" 의 형태로 th:each 속성을 지정해주면 속성이 지정된 태그 내부에서는 변수 이름을 통해서 그 값에 접근할 수 있다. 위 코드에서는 Controller가 등록한 numList 의 각 요소를
p 태그의 text로 사용하고있다.
- 그 상태로 서버를 구동하고 접속해보면 위처럼 리스트에 넣어준 정수값들이 순차적으로 출력된 것을 볼 수 있다.
3. th:if / th:unless
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 3. if문 -->
<div th:if="${ score == 100 }">
만점입니다!
</div>
<div th:unless="${ score == 100 }">
만점이아닙니다.
</div>
</body>
</html>
package com.scala.personal.mycomm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("")
public String index(Model model) {
model.addAttribute("score", 100);
return "index";
}
}
- th:if 와 th:unless 속성을 사용하면 조건식을 만족할 경우나 만족하지 않을 경우에만 해당 태그를 나타내도록 할 수 있다.
- th:if=${ <조건문> }, th:unless=${ <조건문> } 의 형태로 조건문을 설정한 뒤 Controller에서 scroe를 100으로 설정해보자.
- 기대한대로 조건문이 잘 동작하는 것을 확인할 수 있다.
- 주의할 점은 일반적인 프로그래밍 언어의 if/else 관계와는 달리 th:unless 에도 조건문을 써줘야한다는 점이다.
- 즉, th:unless 는 엄밀히 말하면 else 보다는 if not ~ 에 가까운 속성이다.
4. th:switch / th:case
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 4. switch 문 -->
<div th:each="input: ${ inputs }">
<div th:switch="${ input }">
<p th:case="1">입력된 숫자는 1입니다.</p>
<p th:case="2">입력된 숫자는 2입니다.</p>
<p th:case="3">입력된 숫자는 3입니다.</p>
<p th:case="*">입력된 숫자는 1, 2, 3 중 어느것도 아닙니다.</p>
</div>
</div>
</body>
</html>
package com.scala.personal.mycomm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class HomeController {
@GetMapping("")
public String index(Model model) {
model.addAttribute("inputs", List.of(3, 2, 6, 1, 9));
return "index";
}
}
- th:switch / th:case 속성을 사용하면 switch 문을 사용할 수 있다.
- th:switch="${ <조건 변수 이름> }" 의 형태로 조건값을 지정하고 태그 내부에서 th:case="<케이스값>" 의 형태로 케이스를 지정해주는 것으로 케이스값이 매칭되는 태그만이 보여지도록 할 수 있다.
- case 값으로 "*" 을 줄 경우 default 케이스의 역할을 한다.
- 서버를 구동하고 접속해보면 정상적으로 switch문이 동작하는 것을 확인할 수 있다.
5. th:block
- 크롬 개발자도구에서 위에서 사용했던 반복문과 조건문을 사용한 태그들이 실제 html로 어떻게 변환되고있는지 살펴보면 한가지 사실을 확인할 수 있다. 바로 속성이 적용된 태그 내부가 아닌 속성이 적용된 태그 자체가 반복되거나 조건에 따라 표시되고 있다는 것이다.
- 물론 div를 쓰지 않고 내부의 태그들마다 th:each나 th:if 등을 사용하는 방법도 있지만 내부의 태그가 많아지면 굉장히 번거로운 작업인데다 switch문의 경우 조건값을 넣어줄 바깥 태그의 존재가 필수적이다. 그렇다면 지금의 방식을 그대로 사용하면서 내부의 내용만을 반복하거나 조건에 따라 표시하고싶다면 어떻게 해야할까?
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 2. 반복문 -->
<th:block th:each="num: ${ numList }">
<p th:text="${ num }"></p>
</th:block>
<!-- 3. if문 -->
<th:block th:if="${ score == 100 }">
만점입니다!
</th:block>
<th:block th:unless="${ score == 100 }">
만점이아닙니다.
</th:block>
<!-- 4. switch 문 -->
<th:block th:each="input: ${ inputs }">
<th:block th:switch="${ input }">
<p th:case="1">입력된 숫자는 1입니다.</p>
<p th:case="2">입력된 숫자는 2입니다.</p>
<p th:case="3">입력된 숫자는 3입니다.</p>
<p th:case="*">입력된 숫자는 1, 2, 3 중 어느것도 아닙니다.</p>
</th:block>
</th:block>
</body>
</html>
package com.scala.personal.mycomm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class HomeController {
@GetMapping("")
public String index(Model model) {
model.addAttribute("numList", List.of(1, 6, 2, 7, 3, 9, 0, 13));
model.addAttribute("score", 100);
model.addAttribute("inputs", List.of(3, 2, 6, 1, 9));
return "index";
}
}
- thymeleaf는 th:block이라는 태그를 지원한다.(thymeleaf가 지원하는 유일한 태그이다)
- th:block은 thymeleaf 문법상으론 일반적인 태그처럼 동작하지만 html 생성시 자기 자신은 존재하지 않는 태그로 취급되기 때문에 th:block 태그에 th:each 나 th:if, th:switch 등을 사용하면 블록 내부의 내용만을 반복하거나 조건에 따라 표시할 수 있다.
- 위와 같이 지금까지의 모든 예제에서 바깥의 div 태그를 th:block으로 대체해보자.
- 깔끔하게 내부의 내용만이 반복되거나 조건에 따라 표시된 것을 볼 수 있다.
6. th:object
- 태그에 th:object 속성을 사용하여 해당 태그 내에서 사용할 객체를 지정할 수 있다.
- 이 기능은 주로 데이터를 객체단위로 표시하거나 form 태그의 전송값을 객체에 매핑시키기 위해 사용된다.
package com.scala.personal.mycomm;
import org.springframework.stereotype.Component;
public class FormData {
String attr1;
int attr2;
String attr3;
public String getAttr1() {
return attr1;
}
public int getAttr2() {
return attr2;
}
public String getAttr3() {
return attr3;
}
public void setAttr1(String attr1) {
this.attr1 = attr1;
}
public void setAttr2(int attr2) {
this.attr2 = attr2;
}
public void setAttr3(String attr3) {
this.attr3 = attr3;
}
}
- 먼저 FormData 클래스를 정의한다. FormData 클래스는 attr1, attr2, attr3의 3개의 필드를 가진다.
- 필드값을 가져오거나 설정하기 위해 getter, setter를 만들어줘야한다.
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 5. object -->
<form method="post" action="" th:object="${formData}">
<input type="text" th:field="*{attr1}">
<input type="number" th:field="*{attr2}">
<select th:field="*{attr3}">
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<input type="submit" value="제출">
</form>
</body>
</html>
- index.html에는 th:object를 사용한 form 태그를 작성하자. 주의할 점은 input 값마다 th:field 속성을 사용하여 formData의 각 속성과 매핑시켜줘야한다는 점이다. 여기서 앞서 알아봤던 thymeleaf의 표현식중 하나인 선택 변수 표현식을 사용한다.
- 다른 thymeleaf 속성들이 변수 표현식 내부에서 변수 이름의 앞뒤로 공백이 들어가도 변수를 인식하는데 문제가 없었던 것과 달리 th:object나 th:field에서 사용되는 변수 표현식이나 선택 변수 표현식에서는 객체명, 필드명 앞뒤로 공백이 들어가선 안된다는 점에도 주의하자.
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
<!-- 5. object -->
<table th:object="${formData}">
<tr>
<th>attr1</th>
<th>attr2</th>
<th>attr3</th>
</tr>
<tr>
<td th:text="*{attr1}"></td>
<td th:text="*{attr2}"></td>
<td th:text="*{attr3}"></td>
</tr>
</table>
</body>
</html>
- form_result.html에서는 마찬가지로 th:object를 사용하여 formData를 사용할 객체로 지정하고 각 속성값을 테이블 형태로 표시한다. 여기서는 객체의 속성을 참조할 뿐이기 때문에 th:field 는 사용할 필요가 없다.
package com.scala.personal.mycomm.controller;
import com.scala.personal.mycomm.FormData;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class HomeController {
@GetMapping("")
public String index(@ModelAttribute FormData formData, Model model) {
return "index";
}
@PostMapping("")
public String formResult(@ModelAttribute FormData formData, Model model) {
return "form_result";
}
}
- Controller에서는 formData 객체를 model에 추가해준다. model.addAttribute("formData", new FormData()); 와 같이 직접 객체를 생성하여 추가하는 방법도 있지만 위와 같이 메소드의 매개변수에 formData를 추가하고 @ModelAttribute 어노테이션을 붙여주면 Spring이 자동으로 의존성을 주입하고 model에 추가까지 해준다.
- @ModelAttribute(<속성이름>) 의 형태로 속성의 이름도 지정할 수 있다. 지정하지 않을 경우 매개변수 이름을 속성 이름으로 사용한다.
- 물론 form의 post타입 요청에 매핑될 메소드도 정의해주고 이 메소드에서도 formData를 model에 추가해줘야한다.
- 서버를 구동하고 접속해보면 attr1, attr2, attr3를 입력하는 form을 볼 수 있으며 이를 입력하고 제출버튼을 클릭하면 제출한 값이 정상적으로 테이블 형태로 표시되는 것을 볼 수 있다.
- 구글링을 통해 알아본 @ModelAttribute 어노테이션에 대한 정보글에는 대부분 @ModelAttribute를 사용할 대상 클래스는 Bean 으로 등록되어있어야 한다고 되어있었는데, 코드를 보면 알다시피 FormData는 Bean으로 등록되어있는 상태가 아니다. @ModelAttribute가 내부적으로 인스턴스를 Bean으로 등록하여 사용하는것인지 애초에 Bean일 필요가 없는 것인지는 아직 모르겠다.
7. th:inline
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 6. inline -->
<p th:text="${ name }">My name is </p>
</body>
</html>
package com.scala.personal.mycomm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("")
public String index(Model model) {
model.addAttribute("name", "David");
return "index";
}
}
- th:text 나 th:utext 등을 사용하여 서버로부터 전달받은 값을 표시할 수 는 있지만 이 방식은 태그 내부에 작성한 값을 무시해버린다. 위의 코드의 경우에도 p태그 내부의 My name is 는 사라지고 David만이 출력된다.
- 물론 이전에 배운 Literal Substitution 문법을 사용해서 th:text="|My name is ${ name }|" 와 같이 사용할 수는 있지만 보기에 썩 좋지는 않다. thymeleaf로 참조한 변수의 값을 HTML상의 텍스트와 함께 사용할 방법은 없을까?
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 6. inline -->
<p th:inline="text">My name is [[${ name }]]</p>
</body>
</html>
- 이런 경우에 사용 가능한 것이 th:inline 속성이다. th:inline 속성값을 "text"로 지정하면 HTML상의 텍스트와 함께 thymeleaf 변수값을 사용할 수 있다. [[${<변수이름>}]] 의 형태로 사용 가능하다.
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>My Community</title>
</head>
<body>
This is Index Page via Controller
<!-- 6. inline -->
<script th:inline="javascript">
let myName = [[${ name }]];
console.log(myName);
</script>
</body>
</html>
- th:inline은 script 타입의 사용 또한 가능하다. th:inline 속성값을 "javascript"로 하면 script 태그 내에서 js 코드를 작성할 때도 thymeleaf 변수 값을 사용할 수 있다. 코드를 위와 같이 작성하고 서버를 구동하여 접속해보면 변수 name이 정상적으로 javascript에서 사용되고 있는 것을 볼 수 있다.
- 이 기능은 dart 라는 구글이 javascript의 대체재로 개발한 스크립트언어에도 사용 가능하지만(th:inline="dart") 여기서 굳이 예시를 들지는 않기로 한다.
이번 포스팅으로 thymeleaf를 사용하여 템플릿을 작성할 준비는 거의 끝났다. 다음 포스팅에서는 마지막으로 thymeleaf가 지원하는 fragment 기능에 대해서 알아보기로 하자.
'프레임워크 > Spring' 카테고리의 다른 글
#13 모델 설계 1 (0) | 2022.05.13 |
---|---|
#12 Thymeleaf 3 (0) | 2022.05.10 |
#10 Thymeleaf 1 (0) | 2022.05.06 |
#9 MVC 구조 갖추기 - Controller/View 구현 1 (0) | 2022.05.05 |
#8 Spring Boot 프로젝트 (0) | 2022.05.04 |