Spring MVC Part 2 - Thymeleaf - 기본기능

타임리프 소개

  • SSR (Server Side Rendering)
    - thymeleaf

  • CSR (Client Side Rendering)
    - javaScript, Reac

  • 네츄럴 템플릿
    타임리프는 순수 HTML을 최대한 유지한다는 특성을 가진다.
    따라서 웹 브라우저에서 파일 자체를 직접 열어도 내용을 확인할 수 있다는 장점을 가지고 이 부분에서 JSP와 차이가 있다.

타임리프 기본 기능

텍스트 - text,utext

 <li>th:text = <span th:text="${data}"></span></li>
 <li>th:utext = <span th:utext="${data}"></span></li>

utext는 unescaped text이다.
타임리프가 제공하는 th:text ,[[...]] 는 기본적으로 escape를 제공한다.

escape란 HTML에서 사용하는 특수 문자(ex)<b>)를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다.( 문자 그대로 나오게 함 )

  • text
\- 기본 : th:text ="${data}"
\- uescape : th:utext = "${data}"
  • utext
\- 기본 : [[${data}]]
\- uescape : [(${data})]

SpringEL 표현식

<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>

Object : user
List : users[0]
Map : userMap['username']

user.username : user의 username을 프로퍼티 접근 -> user.getUsername()
user['username'] : 위와 같음 -> user.getUsername(
user.getUsername() : user의 getUsername() 을 직접 호출한다.

기본 객체들

코드

<!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}
- ${#response}
- - ${#session}
- ${#servletContext}
- ${#locale}
그런데 #request 는 HttpServletRequest 객체가 그대로 제공되기 때문에 데이터를 조회하려면
request.getParameter("data") 처럼 불편하게 접근해야 한다.
이런 점을 해결하기 위해 편의 객체도 제공한다.

  • HTTP 요청 파라미터 접근: param
    예) ${param.paramData}
  • HTTP 세션 접근: session
    예) ${session.sessionData}
  • 스프링 빈 접근: @
    예) ${@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 : 아이디 처리 관련 기능 제공, 뒤에서 설명

필요시에 타임리프 메뉴얼 보고 찾아 쓰기.

url

코드

<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>

기본적으로 @{}형식을 이용해 링크를 지정한다.

두가지를 합쳐서 사용할 수 있다.
ex) http://localhost:8080/hello/data1?param2=data2

리터럴

리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.
- 문자, 숫자, boolean, null
ex) String a = "hello", int a = 10

html상에서 리터럴은 원칙상'' 으로 감싸야 한다.

리터럴 대체(Literal substitutions)

<span th:text="|hello ${data}|">

||으로 감싸주는 것.
마지막의 리터럴 대체 문법을 사용하면 마치 템플릿을 사용하는 것 처럼 편리하다.

연산

자바에서의 연산과 크게 다르지 않지만, HTML 안에서 사용하기 때문에 HTML엔티티를 사용하는 부분을 주의해야 한다.

속성 값 설정

타임리프는 주로 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>
  • 속성추가
    - attrappend : class 뒤에 'large' 추가 (띄어쓰기 직접 입력)
    - attrprepend : class 앞에 'large' 추가 (띄어쓰기 직접 입력)
    - classappend : class 뒤에 'large' 추가 (띄어쓰기 자동)

  • checked 처리
    기존 html은 checked 속성이 있으면 무조건 체크되는 방식인데,
    th:checked 속성을 통해 checked 를 true,false값을 이용해 설정할 수 있다.
    이렇게 했을 때 장점은, 개발자 입장에서는 직관적이기 때문에 편하고 boolean 값을 변수로 받아서 checked를 설정할 수도 있다.

반복

  • th:each
<tr th:each="user : ${users}">
 <td th:text="${user.username}">username</td>
 <td th:text="${user.age}">0</td>
 </tr>

users에 리스트, 맵과 같은 객체를 넣으면 thymeleaf가 차례대로 user 에 값을 꺼내서 태그를 반복 실행한다.

반복상태 유지

지정변수명 + Stat으로 사용 가능
ex) user -> userStat

  • 반복 상태 유지 기능
    - index : 0부터 시작하는 값
    - count : 1부터 시작하는 값
    - size : 전체 사이즈
    - even , odd : 홀수, 짝수 여부( boolean )
    - first , last :처음, 마지막 여부( boolean )
    - current : 현재 객체

조건부 평가

if,unless

<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 < 20}"></span>
            <span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
        </td>
    </tr>
</table>

unless는 if의 반대, 거짓이면 조건이 성립한다.
if,unless는 조건을 만족시키지 않으면 <span> ...</span> 부분 자체가 렌더링 되지 않고 사라진다.

switch

<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>

주석

<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]] */-->

<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->

블록

<th:block> 은 HTML 태그가 아닌 타임리프의 유일한 자체 태그다

<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>

block을 통해 div 여러개를 한 번에 반복할 수 있다.

javascript 인라인

<!-- 자바스크립트 인라인 사용 전 -->
<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>

자바스크립트 인라인을 사용하면 자바스크립트에 맞게 타임리프가 타입을 조정한다.
이스케이프 문자 처리, 자료형 변환, 객체를 JASON형식으로 변환하는 등 의 기능을 타임리프가 수행한다.
또 script 안에서 each를 통해 반복할 수도 있다.

<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
 [# th:each="user, stat : ${users}"]
 var user[[${stat.count}]] = [[${user}]];
 [/]
</script>

템플릿 조각

웹페이지를 개발할 때 공통 영역이 많이 있다. (ex: 페이지 상단 , 하단, 좌측 카테고리 등)
이렇게 공통되는 영역을 복사해서 사용하면 변경해야 할 때 여러 코드를 수정해야만 한다. 이러한 문제를 해결하기 위해서 타임리프는 템플릿 조각과 레이아웃 기능을 지원한다.

  • footer
<!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>
  • fragmentMain
 <!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>

이렇게 main 페이지에서 th:insert,th:replace를 사용해 footer 템플릿에 있는 th:fragment ="copy" 라는 부분을 템플릿으로 가져와 사용한다.
- insert : 현재 태그(div)내부에 추가
- replace : 현재 태그(div)를 대체

마지막 파라미터 사용 코드와 같이 데이터를 동적으로 전달해서 렌더링 할 수도 있다.

템플릿 레이아웃

  • layout base
<!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>
  • layout main
<!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>
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
<footer>
레이아웃 푸터
</footer>
</body>
</html>

base 파일을 보면 태그에 th:fragment 속성이 정의되어 있다. 이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용들을 전달해서 부분부분 변경한다.
즉 layout main 은 html 자체를 base로 replace 해서 사용하는 걸 볼 수 있다.

좋은 웹페이지 즐겨찾기