Thymeleaf (타임리프)
- 스프링 부트에서는 JSP를 이용해서 View 페이지를 개발하는 방식에서 벗어나 템플릿 기반의 화면 처리를 지원한다.
ex) FreeMarker, Mustache, Thymeleaf 를 이용해서 확장자가 html 인 페이지를 개발할 수 있다.
- 그 템플릿 기반 화면 처리 지원 중 Thymeleaf 는 기존의 JSP 에서 사용하는 JSTL과 유사한 기능(출력) 을 제공한다.
- 타임리프를 써서 View 페이지를 생성할땐 html 확장자로 저장해서 타임리프를 쓴다
타임리프 사용시 View 파일이 저장되는 위치
- 타임리프 사용시 VIew 가 저장될 위치가 정해져있으므로 application.propertes 에서 ViewResolver 를 설정하지 않음
- 타임리프 사용시 View 파일들이 저장될 위치는 resources/templates
+ templates 하위에 폴더를 만들어서 그 안에 저장해도 된다, 어쨌건 templates 폴더 안에 View 파일이 있어야함
- 이때, View 파일의 확장자는 html 으로 저장해야한다
Thymeleaf 프로젝트 생성 방법
- Spring Boot 프로젝트 생성시 Template Engines 카테고리의 Thymeleaf 를 체크해야한다
프로젝트 Thymeleaf : 실습 준비
파일들 살펴보기 : pom.xml
- pom.xml 부분
<!-- 타임리프(thymeleaf) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 타임리프 라이브러리가 추가되어있다
파일들 살펴보기 : application.properties
server.port=80
- 내장된 아파치 톰캣의 포트번호를 80 으로 설정하고 있다, 설정하지 않으면 기본 8080 으로 설정된다
타임리프 사용시 주의
1. JSP 출력시 application.properties 에서 prefix, suffix 를 설정하지만, 타임리프 사용 출력시 설정하지 않는다
2. 타임리프 사용시 View 파일은 반드시 resources/templates 폴더 안에 저장된다
- templates 하위에 폴더를 만들어서 그 안에 저장해도 된다, 어쨌건 templates 폴더 안에 View 파일이 있어야함
- webapp 안에 더이상 View 파일이 들어가지 않게된다
3. 이때, View 파일의 확장자는 html 으로 저장해야한다, 따라서 JSTL, EL 등 JSP 지원 태그 사용 불가, 타임리프 지원 태그를 배워야한다
+ 공통적으로 사용될 CSS, JS, 이미지는 resources/static 폴더 하위에 저장
파일들 살펴보기 : index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample1";
// location.href="sample2";
// location.href="sample3";
// location.href="sample4";
// location.href="sample5";
// location.href="sample6";
// location.href="sample7";
// location.href="listTest";
</script>
</body>
</html>
- 타임리프 사용시 JSP 파일인 index.jsp 는 사용 가능
- 이 파일은 JSP 파일로 만들어도 자동 실행되어서 괜찮지만, 다른 파일들은 View 파일로 만들면 실행되지 않음
파일들 살펴보기 : SampleController.java
+ DB 연동하는 예제가 아니므로 Service, DAO 는 없다
- SampleController.java
package com.example.demo.controller;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.model.Member;
@Controller
public class SampleController {
@RequestMapping("sample1")
public String sample1(Model model) {
// model.addAttribute("greeting", "Hello World");
model.addAttribute("greeting", "안녕 하세요");
return "sample1";
}
@RequestMapping("sample2")
public String sample2(Model model) {
Member member = new Member(1,"test","1234","홍길동", new Timestamp(System.currentTimeMillis()));
model.addAttribute("member", member);
return "sample2";
}
@RequestMapping("sample3")
public String sample3(Model model) {
List<Member> list = new ArrayList<Member>();
for(int i=0; i<10; i++) {
Member member = new Member(1,"test"+i,"1234","홍길동"+i, new Timestamp(System.currentTimeMillis()));
list.add(member);
}
model.addAttribute("list", list);
return "sample3";
}
@RequestMapping("sample4")
public String sample4(Model model) {
List<Member> list = new ArrayList<Member>();
for(int i=0; i<10; i++) {
Member member = new Member(i,"u000"+i %3, // 3으로 나눈 나머지 id
"p000"+i %3, // 3으로 나눈 나머지 pw
"홍길동"+i,
new Timestamp(System.currentTimeMillis()));
list.add(member);
}
model.addAttribute("list", list);
return "sample4";
}
@RequestMapping("sample5")
public String sample5(Model model) {
String result = "SUCCESS";
model.addAttribute("result", result);
return "sample5";
}
@RequestMapping("sample6")
public String sample6(Model model) {
model.addAttribute("now", new Date());
model.addAttribute("price", 123456789);
model.addAttribute("title", "This is a just sample");
model.addAttribute("options", Arrays.asList("AAA","BBB","CCC","DDD"));
return "sample6";
}
@RequestMapping("sample7")
public String sample7(Model model) {
return "sample7";
}
}
파일들 살펴보기 : Member.java (DTO)
package com.example.demo.model;
import java.sql.Timestamp;
public class Member {
private int no;
private String id;
private String pw;
private String name;
private Timestamp regdate;
public Member(int no, String id, String pw, String name, Timestamp regdate) {
this.no = no;
this.id = id;
this.pw = pw;
this.name = name;
this.regdate = regdate;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Timestamp getRegdate() {
return regdate;
}
public void setRegdate(Timestamp regdate) {
this.regdate = regdate;
}
}
프로젝트 Thymeleaf : 프로젝트 실행
- 오른쪽 마우스 - Run As - Run on Server 로 실행
프로젝트 Thymeleaf : 코드 설명 1
- index.jsp 에서 "sample1" 으로 요청 (일반 변수를 타임리프로 출력하는 예제)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample1";
</script>
</body>
</html>
- Controller 클래스 SampleController.java 에서 "sample1" 요청 부분만
@RequestMapping("sample1")
public String sample1(Model model) {
// model.addAttribute("greeting", "Hello World");
model.addAttribute("greeting", "안녕 하세요");
return "sample1";
}
- View 페이지로 값을 가져갈때 Model 객체를 사용한다
- View 파일의 prefix, suffix 를 생각할 필요 없다
- return "sample1" 을 하면 templates 폴더 안의 sample1.html 파일로 찾아간다
- View 파일 sample1.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Thymeleaf Test page</h1>
<!-- th:text : 문자열 출력 -->
<h1 th:text="${greeting}">Thymeleaf Test page</h1>
</body>
</html>
타임리프 라이브러리 불러오기 (sample1.html 부분)
<html xmlns:th="http://www.thymeleaf.org">
- 타임리프를 쓰기 위해 불러오는 코드
+ xmlns 는 일종의 네임스페이스(ns) 를 의미함, 즉 th 라는 네임스페이스
타임리프 text 태그로 출력
<!-- th:text : 문자열 출력 -->
<h1 th:text="${greeting}">Thymeleaf Test page</h1>
- 문자열을 출력하기 위해선 th 라는 네임스페이스를 써야하고, text 라는 태그를 사용
- 그 태그 안의 ${greeting} 은 Controller 에서 View 로 오기 전에 Model 에 저장했던 네임값
- 현재는 "안녕하세요" 가 값이 되어 브라우저에 그 문자열이 출력됨
+ 그냥 EL ${greeting} 사용시 제대로 출력되지 않는다, 반드시 타임리프로 태그 안에서 ${greeting} 사용
+ 타임리프 태그 종류
- th:with
- th:unless
- th:inline
- th:text
- 약 10개의 태그들에 익숙해지기
프로젝트 Thymeleaf : 코드 설명 2
- index.jsp 에서 "sample2" 으로 요청 (DTO를 타임리프로 출력하는 예제)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample2";
</script>
</body>
</html>
- Controller 클래스 SampleController.java 에서 "sample2" 요청 부분만
@RequestMapping("sample2")
public String sample2(Model model) {
Member member = new Member(1,"test","1234","홍길동", new Timestamp(System.currentTimeMillis()));
model.addAttribute("member", member);
return "sample2";
}
- DTO Member 객체 member 를 생성하면서 값을 설정했다
- 그 객체 member 를 Model 객체에 저장해서 templates 폴더 아래의 sample2.html View 파일로 이동
- 즉 객체가 Model 에 저장되서 전달되는 경우 어떻게 출력하는지 보자
- View 파일 sample2.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- th:text : DTO 객체 출력 -->
<h1 th:text="${member}">Thymeleaf Test page</h1>
<!-- th:text : 문자열(HTML태그) 출력 -->
<div th:text='${"<h3>"+member.no+"</h3>"}'></div>
<!-- th:utext : 데이터 출력 -->
<div th:utext='${"<h3>"+member.no+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.id+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.pw+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.name+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.regdate+"</h3>"}'></div>
</body>
</html>
- 타임리프 태그들은 모두 네임스페이스(xmls) 으로 th 를 써야한다, 즉 태그들은 th: 로 시작한다
- 넘어온 DTO 객체 member 를 th:text 태그로 출력시 해당 DTO 객체의 주소값이 출력된다
- 넘어온 DTO 객체 member 의 번호 member.no 를 th:text 태그로 출력시, member.getNo() 의 값이 출력된다
- 즉 Model 객체로 DTO가 전달되었을땐 넘어온 객체의 Model 네임값.객체 필드명 으로 그 값을 출력
th:text vs th:utext
th.text
- th:text 를 사용하면 <h3> 태그가 문자로 인식되어 <h3>1</h3> 로 출력된다
<!-- th:text : 문자열(HTML태그) 출력 -->
<div th:text='${"<h3>"+member.no+"</h3>"}'></div>
th.utext
- th:utext 를 사용하면 태그를 문자가 아닌 HTML 태그로 인식해서 1 을 출력
<!-- th:utext : 데이터 출력 -->
<div th:utext='${"<h3>"+member.no+"</h3>"}'></div>
프로젝트 Thymeleaf : 코드 설명 3
- index.jsp 에서 "sample3" 으로 요청 (리스트를 타임리프로 출력하는 예제)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample3";
</script>
</body>
</html>
- Controller 클래스 SampleController.java 에서 "sample3" 요청 부분만
@RequestMapping("sample3")
public String sample3(Model model) {
List<Member> list = new ArrayList<Member>();
for(int i=0; i<10; i++) {
Member member = new Member(1,"test"+i,"1234","홍길동"+i, new Timestamp(System.currentTimeMillis()));
list.add(member);
}
model.addAttribute("list", list);
return "sample3";
}
- 리스트 list 를 Model 객체에 저장해서 templates 폴더 하위의 sample3.html 파일로 이동한다
- View 파일 sample3.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<table border="1" align="center" width="300">
<tr>
<td>ID</td>
<td>NAME</td>
<td>REGDATE</td>
</tr>
<!-- th:each="변수 : ${리스트}" -->
<tr th:each="member : ${list}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
</table>
</body>
</html>
- 타임리프를 쓰므로 타임리프 라이브러리를 불러오고 있다
- Model 객체에 리스트가 넘어왔을때는 th:each 태그를 사용한다, 형식은 th:each="변수 : ${리스트}"
+ JSTL 의 forEach 태그 역할을, 타임리프에서는 each 태그가 수행
- th:each 태그에 "변수 : ${리스트 네임값}" 을 쓰고, 그 th:each 태그 내부의 태그들에서 ${변수.DTO필드명} 으로 출력
- th:each 에 의해 리스트 안의 요소들이 member 에 차례로 저장되고, 출력시 그 ${member.필드명} 으로 출력
날짜 포멧 지정 (sample3.html 부분, 변형)
<!-- th:each="변수 : ${리스트}" -->
<tr th:each="member : ${list}">
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
- th:text 태그 안에 ${#dates.format(날짜값,'포맷')} 으로 포맷을 지정해서 출력한다
+ #dates 는 함수이다, 그 함수의 format() 메소드를 사용하는 것
포맷을 제외하고 그냥 ${member.regdate} 로 출력해보자
<td th:text="${member.regdate}"></td>
- 패턴 미지정시 DTO regdate 필드의 자료형은 Timestamp 이므로, 년월일시분초(1000분의 1초 단위) 로 출력한다
+ 자료형이 Date 이면 년월일만 출력
+ 타임리프는 View 이지만 새로고침만으로 반영되지 않고 서버를 재시작해야 반영된다
타임리프 출력 정리
1. Model 객체에 일반 자료형인 값이 저장되었을때는, 그냥 th:text=${네임값} 으로 출력
2. Model 객체에 DTO 객체가 저장되었을때는, ${네임값.필드명} 으로 출력
3. Model 객체에 리스트인 값이 저장되었을때는, th:each 태그에 "변수명 : ${리스트 네임값}" 을 써서 하나씩 출력
프로젝트 Thymeleaf : 코드 설명 4
- index.jsp 에서 "sample4" 으로 요청 (다양한 타임리프 태그 예제)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample4";
</script>
</body>
</html>
- Controller 클래스 SampleController.java 에서 "sample4" 요청 부분만
@RequestMapping("sample4")
public String sample4(Model model) {
List<Member> list = new ArrayList<Member>();
for(int i=0; i<10; i++) {
Member member = new Member(i,"u000"+i %3, // 3으로 나눈 나머지 id
"p000"+i %3, // 3으로 나눈 나머지 pw
"홍길동"+i,
new Timestamp(System.currentTimeMillis()));
list.add(member);
}
model.addAttribute("list", list);
return "sample4";
}
- List 객체 생성 후 값을 입력해서 그 리스트를 Model 객체에 저장하고 sample4.html 로 이동
- View 페이지 sample4.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<table border="1" align="center" width="300">
<tr>
<td>ID</td>
<td>NAME</td>
<td>REGDATE</td>
</tr>
<tr th:each="member : ${list}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
</table><br>
<!-- 변수 정의 -->
<!-- th:with="변수명='값' " -->
<table border="1" align="center" width="300" th:with="target='u0001'">
<tr>
<td>ID</td>
<td>NAME</td>
<td>REGDATE</td>
</tr>
<!-- member.id 값이 target이 회원만 삼항연산자를 이용해서 secret을 출력 -->
<tr th:each="member : ${list}">
<td th:text="${member.id == target ? 'secret' : member.id}"></td>
<td th:text="${member.name}"></td>
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
</table><br>
<!-- if조건식 -->
<!-- th:if ~ th:unless -->
<table border="1" align="center" width="300" th:with="target='u0001'">
<tr>
<td>ID</td>
<td>NAME</td>
<td>REGDATE</td>
</tr>
<!-- member.id값이 target(u0001)과 같으면 Modify를 출력 하고,
member.id값이 target(u0001)과 같지 않으면 View를 출력 -->
<tr th:each="member : ${list}">
<td th:if="${member.id}">
<a href="/modify" th:if="${member.id == target}">Modify</a>
<p th:unless="${member.id == target}">View</p>
</td>
<td th:text="${member.name}"></td>
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
</table><br>
</body>
</html>
- 세 부분으로 나눠져있다, 각각 each 태그로 표(목록) 를 출력하므로 실행시 3개의 표 출력됨
1) th:each 태그로 리스트 출력
2) th:with 태그로 변수 정의
3) th:if 태그로 if 조건식 사용
- 나눠서 설명
1) th:each 태그로 리스트 출력 부분
<table border="1" align="center" width="300">
<tr>
<td>ID</td>
<td>NAME</td>
<td>REGDATE</td>
</tr>
<tr th:each="member : ${list}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
</table><br>
- each 태그를 tr 태그에 써야한다, 그래야 루프가 돌아갈때마다 tr 태그가 생성됨, 즉 행이 생성됨
- each 태그를 어디에 쓰느냐에 따라 결과가 달라진다, 반복되서 출력되어야하는 태그 안에 th:each 를 써야한다
- 앞에서 리스트 list 가 Model 객체에 저장되어 넘어왔으므로 th:each 태그에 "member : ${list}" 를 작성
- th:each 를 쓴 태그 안의 태그에서 ${model.필드명} 으로 데이터 출력
2) th:with 태그로 변수 정의 부분
<!-- 변수 정의 -->
<!-- th:with="변수명='값' " -->
<table border="1" align="center" width="300" th:with="target='u0001'">
<tr>
<td>ID</td>
<td>NAME</td>
<td>REGDATE</td>
</tr>
<!-- member.id 값이 target이 회원만 삼항연산자를 이용해서 secret을 출력 -->
<tr th:each="member : ${list}">
<td th:text="${member.id == target ? 'secret' : member.id}"></td>
<td th:text="${member.name}"></td>
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
</table><br>
- table 태그 안에 th:with="target='u0001'" 이 있다, target 이란 변수가 생성되고 값 'u0001' 이 저장됨
- td 의 th:text 태그에서 member.id 가 target('u0001') 인 경우 'secret' 를 출력, target 이 아니면 그대로 member.id 를 출력
+ 이때 target 은 변수이므로 ' ' 로 감싸지 않고, 'secret' 은 문자데이터이므로 ' ' 로 감싸야한다
+ 타임리프 태그안에서 연산자 사용 가능, 여기선 삼항 조건 연산자를 사용하고 있다
th:with 태그
- 변수를 만들때 쓰는 태그
- JSTL 의 core 라이브러리 set 태그 역할
- th:with="변수명='값'" 으로 변수를 생성하고 초기화한다
3) th:if 태그로 if 조건식 사용 부분
<!-- if조건식 -->
<!-- th:if ~ th:unless -->
<table border="1" align="center" width="300" th:with="target='u0001'">
<tr>
<td>ID</td>
<td>NAME</td>
<td>REGDATE</td>
</tr>
<!-- member.id값이 target(u0001)과 같으면 Modify를 출력 하고,
member.id값이 target(u0001)과 같지 않으면 View를 출력 -->
<tr th:each="member : ${list}">
<td th:if="${member.id}">
<a href="/modify" th:if="${member.id == target}">Modify</a>
<p th:unless="${member.id == target}">View</p>
</td>
<td th:text="${member.name}"></td>
<td th:text="${#dates.format(member.regdate,'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
</table><br>
th:with 로 정의된 변수의 적용 범위
- table 태그안에 th:with 로 target 변수가 생성되었다
- 그럼 th:with 로 정의된 변수는 table 태그 내부에서만 사용가능하다
+ 리스트가 Model 객체에 저장되어 넘어왔으므로 th:each 태그 사용
- td 태그에서 th:if="${member.id}" 은 'member.id 값이 있으면' 이라는 의미이다
- th:if 태그에 의해 회원의 id 값이 target('u0001') 이면 a 태그 안에서 'Modify' 를 출력시키고,
- th:unless 에 의해 target('u0001') 이 아니면 p 태그 안에서 'View' 를 출력한다
th:if 태그, th:unless 태그
- if 조건식 사용 가능
- if - else 조건식을 th:if=${조건식} 과 th:unless=${조건식} 로 쓰면 된다
ex) th:if="${member.id == target}" 은 'member.id 가 target 과 같으면' 실행됨
- th:unless=${조건식} 은 조건식이 아닌경우 실행된다, 즉 반대의 의미를 가진다
ex) th:unless="${member.id == target}" 은 'member.id 가 target 과 같지 않으면' 실행됨
- 전체 출력 (아래)
프로젝트 Thymeleaf : 코드 설명 5
- index.jsp 에서 "sample5" 으로 요청 (자바스크립트 안에서 타임리프를 사용하는 예제)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample5";
</script>
</body>
</html>
- Controller 클래스 SampleController.java 에서 "sample5" 요청 부분만
@RequestMapping("sample5")
public String sample5(Model model) {
String result = "SUCCESS";
model.addAttribute("result", result);
return "sample5";
}
- View 페이지 sample5.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- javascript안에서 Thymeleaf 사용하기 -->
<!-- script태그에 th:inline="javascript"를 추가하면 script안에서 Thymeleaf를 사용할 수 있다. -->
<script th:inline="javascript">
var result = [[${result}]]; // result = "SUCCESS" 할당됨
document.write(result); // SUCCESS 출력
</script>
<script>
var result = [[${result}]]; // result변수에 값이 할당 안됨
document.write(result); // 아무것도 출력 안됨
</script>
</body>
</html>
자바스크립트안에서 타임리프 사용하는 방법
- script 태그 안에 th:inline="javascript" 코드를 작성해야한다
- 자바스크립트안에서 타임리프를 사용해서 ${result} 를 가져온다, 가져와서 자바스크립트 변수 result 에 저장후 출력
+ 아래쪽은 th:inline="javascript" 코드가 없으므로 출력되지 않는다, 아래 사진의 에러는 아래쪽 코드때문
프로젝트 Thymeleaf : 코드 설명 6
- index.jsp 에서 "sample6" 으로 요청 (여러 자료형의 값을 포맷 함수 사용해서 형식 바꿔서 출력 )
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample6";
</script>
</body>
</html>
- Controller 클래스 SampleController.java 에서 "sample6" 요청 부분만
@RequestMapping("sample6")
public String sample6(Model model) {
model.addAttribute("now", new Date());
model.addAttribute("price", 123456789);
model.addAttribute("title", "This is a just sample");
model.addAttribute("options", Arrays.asList("AAA","BBB","CCC","DDD"));
return "sample6";
}
- Date 객체, int 형 데이터, String 형 데이터, 리스트를 만들어서 Model 객체에 저장해서 sample6.html 로 이동
+ 리스트를 만드는 방법 : new 연산자로 만들기, 또는 값이 정해져있는 데이터가 있을때는 Arrays.asList() 로 리스트 생성가능
- View 페이지 sample6.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 th:text="${now}"></h1>
<h1 th:text="${price}"></h1>
<h1 th:text="${title}"></h1>
<h1 th:text="${options}"></h1>
<!--1.날짜 관련 : #dates -->
<h2 th:text="${#dates.format(now, 'yyyy-MM-dd HH:mm:ss')}"></h2>
<!--2.숫자 관련 : #numbers -->
<h2 th:text="${#numbers.formatInteger(price, 3, 'COMMA')}"></h2>
<!--3.문자 관련 : #strings -->
<h1 th:text="${title}"></h1>
<!-- replace()함수를 이용해서 s를 진하게 출력 -->
<span th:utext="${#strings.replace(title,'s','<b>s</b>')}"></span>
<!-- listSplit()함수를 이용해서 title변수의 값을 리스트로 처리하고 출력 -->
<ul>
<li th:each="str:${#strings.listSplit(title,' ')}">[[${str}]]</li>
</ul>
</body>
</html>
- th:text 가 h1 태그에 들어가있다, 앞에 넘어온 자료형이 모두 다른 데이터인 now, price, title, options 를 th:text 로 출력
- 다음으로는 해당 값들을 형식을 바꿔서 출력
+ JSTL 에서 국제화 라이브러리로 처리했던 내용을 타임리프에서는 # 포맷함수들 사용
1. 날짜 관련 형식 변경하는 방법 : #dates (sample6.html 부분)
<!--1.날짜 관련 : #dates -->
<h2 th:text="${#dates.format(now, 'yyyy-MM-dd HH:mm:ss')}"></h2>
- #dates 포맷 함수로 패턴을 지정
- th:text 태그를 사용하고, "${#dates.format(날짜데이터 네임값, '패턴지정')}" 형식을 써서 날짜를 원하는 형식으로 출력
2. 숫자 관련 형식 변경하는 방법 : #numbers (sample6.html 부분)
<!--2.숫자 관련 : #numbers -->
<h2 th:text="${#numbers.formatInteger(price, 3, 'COMMA')}"></h2>
- #numbers 포맷 함수로 패턴을 지정
- th:text 태그를 사용하고 ${#numbers.formatInteger(숫자데이터 네임값, 몇자리씩 끊어서 출력할지,'끊을 문자')}" 형식을 써서 숫자를 원하는 형식으로 출력
- 여기선 price 에 저장된 숫자값을 3자리 씩 콤마(,) 로 끊어서 출력하라는 의미
3. 문자 관련 형식 변경하는 방법 : #strings (sample6.html 부분)
<!--3.문자 관련 : #strings -->
<h1 th:text="${title}"></h1>
<!-- replace()함수를 이용해서 s를 진하게 출력 -->
<span th:utext="${#strings.replace(title,'s','<b>s</b>')}"></span>
<!-- listSplit()함수를 이용해서 title변수의 값을 리스트로 처리하고 출력 -->
<ul>
<li th:each="str:${#strings.listSplit(title,' ')}">[[${str}]]</li>
</ul>
- #strings 포맷 함수로 패턴을 지정
- 문자를 3번 출력하고 있다 (그냥 출력, #string.replace() 함수 사용,
1) 그냥 문자를 출력
2) th:utext 태그를 사용하고, ${#strings.replace(문자데이터 네임값, '치환될문자', '치환할문자')}
- 현재는 's' 문자를 <b> 태그를 사용해서 굵은 문자를 사용
- 태그를 적용하기 위해서는 th:utext 를 사용해야한다
3) th:each 태그를 사용하고 "반복을 위한 변수명:${#strings.listSplit(문자데이터 네임값, '파싱할 문자')}
- 현재는 title 변수에 들어간 문자데이터를 공복을 기준으로 파싱하고, 그걸 리스트로 만든다
- 리스트로 만들어진 데이터를 th:each 태그에 의해 루프를 돌며 변수 str 에 하나씩 들어가고 그 str 을 출력
- li 태그 안에 th:each 가 들어갔으므로 루프가 돌아갈때마다 li 태그가 생성됨
- 전체 출력
프로젝트 Thymeleaf : 코드 설명 7
- index.jsp 에서 "sample7" 으로 요청 (타임리프로 링크 걸기)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="sample7";
</script>
</body>
</html>
- Controller 클래스 SampleController.java 에서 "sample7" 요청 부분만
@RequestMapping("sample7")
public String sample7(Model model) {
return "sample7";
}
- View 파일 sample7.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<ul> <!-- 링크 걸기 -->
<li><a th:href="@{http://localhost:80/Thymeleaf/sample1}">sample1</a></li>
<li><a th:href="@{/sample1}">sample2</a></li>
<li><a th:href="@{~/sample1}">sample3</a></li>
<!-- 링크를 통해서 값전달 하기 -->
<!-- http://localhost/Thymeleaf/sample1?p1=aaa&p2=bbb -->
<li><a th:href="@{/sample1(p1='aaa', p2='bbb')}">sample4</a></li>
</ul>
</body>
</html>
- 그냥 링크를 걸면 걸리지 않는다, 타임리프 형식을 갖춰서 링크를 걸어야함
타임리프 링크 걸기 4가지 형식
1) a 태그 th:href="@{http://localhost:80/Thymleaf/sample1}" 는 url 주소로 "sample1" 으로 요청하는 형식
- Thymeleaf 는 현재 프로젝트 명, 프로젝트명은 개발시에만 써줘야한다
2) a 태그 th:href="@{/sample1}" 를 통해 "sample1" 로 요청한다
3) a 태그 th:href="@{~/sample1}" 를 통해 "sample1" 로 요청한다 (현재는 오류)
- 즉 1), 2), 3) 은 모두 "sample1" 으로 요청하므로 결과는 똑같다
4) 값을 가져갈땐 th:href="@{/sample1(p1='aaa', p2='bbb')}" 로 p1 변수에 'aaa', p2 변수에 'bbb' 를 저장해서 "sample1" 으로 요청하면서 변수들을 넘긴다
- 위의 주석과 같은 기능이다
+ 이후 Controller 클래스 "sample1" 부분에서 p1, p2 변수값을 받아주는 코드를 쓰면 된다
th:href 태그
- a 태그 안에 th:href="@{url}" 를 사용해서 요청을 한다(링크를 건다)
- 값을가져갈땐 th:href="@{/요청이름(변수1=값, 변수2=값)}" 으로 가져간다
- sample1, sample2 를 클릭했을때
- sample4 를 클릭했을때
프로젝트 Thymeleaf : 코드 설명 8
- index.jsp 에서 "listTest" 으로 요청 (타임리프로 링크 걸기)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="listTest";
</script>
</body>
</html>
- Controller 클래스 HomeController.java
package com.example.demo.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping("/listTest")
public void listTest(Model model) {
List list = new ArrayList();
for(int i=1; i<=20; i++) {
list.add("Data:"+i);
}
model.addAttribute("name", "Sample Data");
model.addAttribute("list", list);
}
}
- 리스트를 생성 후 더미데이터를 넣고 그 리스트를 Model 객체에 저장
- 메소드 리턴 자료형이 Spring 이 아닌 void 이다! 아래쪽에 return 구문이 없다
- 메소드 리턴 자료형이 void 인 경우 경우 요청이름과 동일한 이름을 가진 HTML 파일로 찾아간다
ex) 여기선 요청이름값이 listTest 이므로, View 페이지인 listTest.html 로 이동한다
- View페이지 listTest.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf 예제</title>
</head>
<body>
<p th:text=" 'Hello, ' + ${name} +'!' "/>
<ul>
<li th:each="data : ${list}" th:text="${data}"></li>
</ul>
</body>
</html>
- Model 에 저장된 일반 자료형은 th:text 태그와 ${네임값} 으로 출력
- li 태그 안에 th:each 타임리프 태그를 썼으므로 li 태그가 반복적으로 출력됨, 같은 li 태그 안에 th:text 도 사용하면서 data 를 출력
- index 파일 실행하기
타임리프를 적용한 게시판 프로그램 : 프로젝트 ThymeleafBoard
- Spring Boot 프로젝트 sbboard 와 같은 내용, 타임리프로 출력했다는 점이 다르다
프로젝트 ThymeleafBoard : 실습 준비
- Spring Boot 프로젝트 sbboard 와 같은 내용, 타임리프로 출력했다는 점이 다르다
- templates 폴더 하위에 모든 View 파일들이 html 파일로 저장되어있다
- 또한 타임리프 사용시 기존 방식대로 링크를 걸 수 없으므로 타임리프 태그 를 사용해서 링크를 건다
- board_cont.jsp 부분
파일들 살펴보기 : DataAccessConfig.java
package com.example.demo.config;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
@Configuration
@PropertySource("classpath:/application.properties")
public class DataAccessConfig {
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception{
SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*.xml")
);
factoryBean.setTypeAliasesPackage("com.example.demo.model"); // DTO Alias 설정
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- dataSource() 메소드에서 생성된 객체가 리턴되어 아래의 sqlSessionFactory() 의 매개변수로 자동으로 들어간다
- sqlSessionFactory() 메소드에서 생성된 객체가 리턴되어 아래의 sessionTemplate() 의 매개변수로 자동으로 들어간다
파일들 살펴보기 : board_list.html
- 타임리프로 어떻게 출력하는지 보기
- Controller 클래스에서 넘어오는 값
- board_list.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
<link rel="stylesheet" href="./css/bbs.css" type="text/css">
</head>
<body>
<!-- 게시판 리스트 -->
<div id="bbslist_wrap">
<h2 class="bbslist_title">게시판 목록</h2>
<p th:text=" '글갯수:' + ${listcount} "></p>
<table id="bbslist_t">
<tr align="center" valign="middle" bordercolor="#333333">
<td style="font-family: Tahoma; font-size: 11pt;" width="8%"
height="26">
<div align="center">번호</div>
</td>
<td style="font-family: Tahoma; font-size: 11pt;" width="47%">
<div align="center">제목</div>
</td>
<td style="font-family: Tahoma; font-size: 11pt;" width="14%">
<div align="center">작성자</div>
</td>
<td style="font-family: Tahoma; font-size: 11pt;" width="17%">
<div align="center">날짜</div>
</td>
<td style="font-family: Tahoma; font-size: 11pt;" width="14%">
<div align="center">조회수</div>
</td>
</tr>
<tr th:each="b, i : ${boardlist}" th:with="num=${listcount-(page-1)*10}"
align="center" valign="middle" bordercolor="#333333"
onmouseover="this.style.backgroundColor='F8F8F8'"
onmouseout="this.style.backgroundColor=''">
<td th:with="num=${num - i.index}" th:text="${num}"></td>
<td>
<div align="left">
<span th:if="${b.board_re_lev != 0}">
<span th:each="k : ${#numbers.sequence(1, b.board_re_lev)}">
</span>
<img src="./images/AnswerLine.gif">
</span>
<a th:href="@{/board_cont(board_num=${b.board_num},page=${page},state='cont')}" th:text="${b.board_subject}">
</a>
</div>
</td>
<td th:text="${b.board_name}"></td>
<td th:text="${#dates.format(b.board_date, 'yyyy-MM-dd')}"></td>
<!-- <td th:text="${b.board_date}"></td> -->
<td th:text="${b.board_readcount}"></td>
</tr>
</table>
<div id="bbslist_paging">
<span th:if="${page <=1}" th:text="[이전]"></span>
<span th:if="${page>1}">
<a th:href="@{/board_list(page=${page-1})}" th:text="[이전]"></a>
</span>
<span th:each="a : ${#numbers.sequence(startpage, endpage)}">
<span th:if="${a == page }" th:text="${a}"></span>
<span th:if="${a != page }">
<a th:href="@{/board_list(page=${a})}" th:text="${a}"></a>
</span>
</span>
<span th:if="${page >= maxpage }" th:text="[다음]"></span>
<span th:if="${page < maxpage }">
<a th:href="@{/board_list(page=${page+1})}" th:text="[다음]"></a>
</span>
</div>
<div id="bbslist_w">
<input type="button" value="글쓰기" class="input_button"
onclick="location.href='board_write'">
</div>
</div>
</body>
</html>
th:each 에서 반복 변수와 인덱스 받기 (board_list.html 부분)
- b 에는 ${boardlist} 에서 하나씩 요소를 가져와서 저장하고, i 에는 그 리스트의 인덱스가 들어간다
화면 출력 번호 num 변수 (board_list.html 부분)
1. 위에서 num 변수를 선언하고, th:each 루프가 돌아갈때마다 다시 num 값이 선언된다
2. 아래에서 num - 인덱스 를 해서 루프가 돌아갈때마다 num - 0, num - 1, num - 2 로 재정의한다
+ i.index 에서 index 속성이다
일반 변수 출력 (board_list.html 부분)
댓글, 원문인 경우 구별 위해 if 태그 사용 (board_list.html 부분)
제목 클릭시 상세페이지로 링크걸기 (board_list.html 부분)
- GET 방식으로 "board_cont" 로 요청하며 글 번호, 페이지 번호, state 값을 가지고 간다
연속적인 값 출력 (board_list.html 부분)
- 연속적인 숫자값을 출력할때는 th:each 태그와 #numbers.sequence(시작, 끝) 으로 쉽게 출력 가능하다
- th:each="a : ${#numbers.sequence(startpage, endpage)}" 에 의해 startPage ~ endPage 까지 루프가 돌아가고, 그 값이 a 변수로 들어간다
연속적인 값 출력 2 (board_list.html 부분)
- 나머지 내용은 Spring Boot 프로젝트 (JSP 출력) sbboard 또는 Spring 프로젝트 springboard 와 같은 내용이므로 설명 생략