공부함
타임리프 본문
타임리프란?
✅ SSR
백엔드 서버에서 HTML을 동적으로 렌더링 하는데 사용
✅ 네츄럴 탬플릿
순수 HTML을 유지하는 특징이 있다.
JSP 같은 경우 웹브라우저를 거치지 않고 그냥 파일을 열면 코드가 전부 깨진다.
타임리프는 HTML을 유지기 때문에 열어서 보면 렌더링 해서 잘 보인다.
물론 그냥 연다고 동적 렌더링이 되는 것은 아니지만 마크업 한 내용을 볼 수 있다.
✅ 스프링 통합 지원
스프링과 통합되어서 편리한 기능을 많이 제공한다
✅ 타임리프 사용 선언
<html xmlns:th="http://www.thymeleaf.org">
text, utext
@GetMapping("text-basic")
public String textBasic(Model model){
model.addAttribute("data","Hello Spring");
return "basic/text-basic";
}
컨트롤러에서는 위와 같이 model에 값을 넣어 뷰로 넘겨준다.
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
타임리프에서 두가지 방법으로 model에 넘겨준 값을 사용할 수 있다.
th:text="${attribute명}" 문법을 사용할 수 있다.
이 문법은 태그가 반드시 있어야 하며 속성값을 통해 사용한다.
컨텐츠 안에서 직접 출력하려면 [[${data}]] 문법을 사용할 수 있다.
렌더링 결과는 아래와 같다.
escape
@GetMapping("text-basic")
public String textBasic(Model model){
model.addAttribute("data","Hello <b>Spring</b>");
return "basic/text-basic";
}
Spring에 굵은 글자 효과를 의도하고 모델에 이렇게 데이터를 전달해도 실제 렌더링 된 결과는 <b>Spring</b> 이다.
타임리프는 html 엔티티 (html에서 구조를 정하기 위해 쓰는 기호들)에 대해 이렇게 자동으로 이스케이프 처리를 해준다.
unescape
그렇다면 이스케이프 처리를 하지 않고 싶다면 ? 두가지 방법이 있다.
<li>th:text 사용 <span th:utext="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [(${data})]</li>
th:text 대신 th:utext 속성을 사용하거나, [[ .. ]] 대신 [( .. )]를 사용해주면 된다.
주의 : 꼭 필요할 때만 unescape를 사용하자!
변수 Spring EL
변수 표현식 : ${ .. }
타임리프의 변수 표현식 안에는 SpringEL이라는 스프링이 제공하는 표현식을 사용할 수 있다.
@GetMapping("/variable")
public String variable(Model model){
User userA = new User("userA", 10);
User userB = new User("userA", 10);
ArrayList<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;
}
}
model에 User 객체, User 객체의 리스트, User 객체의 Map을 각각 넣어줬다.
SpringEL을 사용해서 객체의 값에 접근할 수 있다.
기본 객체들
@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;
}
}
컨트롤러에서 HttpServletRequest, HttpServletResponse, HttpSession을 받아서 session.setAttribute, model.addAttribute로 값을 넣어준다. 그리고 HelloBean을 컴포넌트 스캔에 의해 빈으로 등록해준다.
session은 사용자가 웹 브라우저를 끄지 않는 이상 계속 유지되는 데이터다.
반면 http request, response는 요청,응답이 끝나면 사라진다.
타임리프에서 session의 데이터를 꺼낼 수 있다.
<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>
html 코드와 렌더링 결과는 이러하다.
${request}, ${response} 객체는 톰캣에서 제공하는 구현체인 Facade 객체가 들어온다.
편의 객체를 사용해 요청 파라미터를 꺼낼 수 있다.
요청을 ?paramData=HelloParam으로 보냈기 때문에 위와 같이 렌더링 된 것이다.
세션에 넣은 값을 ${session.sessionData}로 꺼냈다.
마지막으로 등록된 스프링 빈을 꺼내서 메서드를 호출할 수 있다.
유틸리티 객체
다양한 유틸리티 객체를 제공한다. 필요할 때 찾아서 쓰자.
url
@{ ... } 문법을 사용해 타임리프에서 url 링크를 표현한다.
@{/hello}
@{/hello(username=${name})}
@{/hello/{name}}
@{/hello/{name}(username=${name},age=${age})}
차례대로
/hello
/hello?username=jidam
/hello/jidam
/hello/jidam?username=jidam&age=20
과 같이 적용된다. 경로변수와 쿼리파라미터를 활용할 수 있다.
리터럴
<span th:text="'hello'">
<span th:text="hello">
<span th:text="|hello ${data}|">
타임리프에는 문자, 숫자, 불린, null 리터럴이 있다
문자 리터럴은 항상 ' ' 로 감싸야 한다.
공백이 없는 문자 리터럴의 경우 ' ' 를 생략할 수 있다.
| | 를 사용해서 리터럴 대체 문법을 사용할 수 있다.
속성 값 설정
렌더링 전
<input type="text" name="mock" th:name="jidam"/>
렌더링 후
<input type="text" name="jidam"/>
타임리프는 html 태그에 th:* 속성을 지정해서 동작한다.
기존 속성을 대체하거나 없으면 새로 만든다.
반복문, 조건문
<tr th:each="user : ${users}">
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
</tr>
<tr th:each="user, userStat : ${users}">
반복문은 th:each 문법을 사용한다.
반복의 두번째 파라미터로 반복 상태를 확인할 수 있다.
${userStat.index} , ${userStat.count} : 각각 0, 1부터 시작
<span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
<span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
조건문은 th:if와 th:unless를 사용할 수 있다.
th:block
타임리프 속성을 사용하기 위해서는 html 태그 안에 속성으로 기능을 정의해서 사용한다.
그런데 html 태그를 따로 사용하기 애매한 경우는 <th:block th:each="user : ${users}">와 같이 타임리프의 자체 태그를 활용할 수 있다. <th:block>은 렌더링 시 제거된다.
템플릿 조각
<footer th:fragment="copy">
푸터 자리 입니다.
</footer>
<footer th:fragment="copyParam (param1, param2)">
<p>파라미터 자리 입니다.</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
footer.html에 위와 같이 th:fragment 속성과 값을 준다.
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>
fragmentMain.html에서 템플릿 조각들을 가져다 쓸것이다.
부분 포함 insert의 경우 <div> 태그 안에 template/fragment/footer 파일의 fragment 속성값의 이름이 copy 인 태그를 가져와 넣는 것이다.
replace의 경우 <div> 태그를 <footer>가 아예 대체해버리게 된다.
또한 파라미터도 사용할 수 있다.
이렇게 재사용 해야 하는 템플릿들을 구조화 해 놓고 가져다 쓸 수 있다.
템플릿 레이아웃
css, js같은 파일을 공통으로 쓰고 싶을 때
header에 정해진 틀이 있고 거기에 동적으로 변경을 주고 싶을 때
큰 모양이 있고 그 모양에 내 조각을 맞춰 넣는다 ? . ?
<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>
위 파일이 base.html이고 이 파일을 layoutMain.html에서 가져다 쓴다.
보면 title,links를 파라미터로 받는다.
<!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>
layoutMain.html은 위와 같다. head 태그가 base.html로 replace 된다.
이 때 파라미터로 ~{::title},~{::link}와 같이 전달해 준다. 이렇게 전달하면 파라미터로 태그 자체가 전달된다
즉 <title>메인 타이틀</title> 전체가 파라미터로 전달된다.
link 같은 경우 태그가 2개인데 두개 다 전달되어서 한줄 한줄 렌더링 되어 나오게 된다.
이번에는 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이다. 사이트에 100개의 페이지가 있고 모두 이러한 구성이라고 가정하자.
바뀌는 부분은 "레이아웃 타이틀" , "레이아웃 컨텐츠" 부분 뿐이다.
<!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>
실제 페이지인 layoutExtendMain.html은 이렇게 생겼다.
보면 html 태그 자체를 th:replace로 layoutFile.html로 replace 해버린다. 그리고 이 때 title과 section 태그는 파라미터로 넘긴다.
이렇게 layout을 적극 활용하면 중복되는 레이아웃을 한번에 수정, 관리할 수 있어서 편리하다!
페이지가 많지 않을 때는 템플릿 조각을 써도 괜찮다.
페이지가 많아지면 템플릿 레이아웃으로 관리하자.
'스프링 > 스프링mvc2' 카테고리의 다른 글
메세지, 국제화 (0) | 2023.10.11 |
---|---|
타임리프 스프링 통합, 폼 (0) | 2023.10.10 |