개발자일지/Spring

Spring MVC 2편 - Thymeleaf

어쩌다한번 2022. 3. 17. 13:37

탬플릿 중 가장 최선이라는 타임리프

정작 현업에서는 JSP를 사용하는 곳도 아직 많다고 들었지만

JSP는 금방 익힌다고 한다.

개인적으로 Thymeleaf사용하는 곳에 입사하고싶다

 

text, utext

컨트롤러에서 처리한 파라미터를

${data}라는 형식으로 꺼내 사용할 수 있다.

 

th:text 태그를 사용하는 경우

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

컨텐츠 안에 직접 출력하는 경우

<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>

 

< , > 문자를 출력하려하면 HTML태그의 시작,끝 문자와 동일하기 때문에

태그가 아닌 문자로 출력될 수 있도록 ESCAPE라는 것을 자동으로 해준다

오히려 HTML태그를 출력하도록 바꾸고싶다면 UNESCAPE를 하면 되는데

th:utext나

[(${data})]와 같이 입력하면 된다.


SpringEL

앞서 사용했듯 변수를 사용할 때는 ${...}의 형태로 사용한다

또 변수를 표현하는 방법은 여러가지고, list 나 map또한 사용할 수 있다.

//Object
${user.username} = userA
${user['username']} = userA
${user.getUsername()} = userA

//List
${users[0].username} = userA
${users[0]['username']} = userA
${users[0].getUsername()} = userA

//Map
${userMap['userA'].username} = userA
${userMap['userA']['username']} = userA
${userMap['userA'].getUsername()} = userA

th:with를 사용해서 지역변수를 선언해서 사용 할 수 있다. 지역변수인 만큼 선언한 태그 안에서만 사용가능하다.

<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
<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>

model에 데이터를 넣어서 전송해도 되지만 직접적으로 request, response를 사용 할 수도 있다.

또  직접 파라미터를 간편하게 가져올 수도 있다.(param.paramData)

스프링빈도 사용 가능하다.

 

url

data1, data2라는 파라미터가 있을때 URL를 작성하는 방법

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

/는 경로, /없이 괄호안에 있는 것은 쿼리파라미터로 인식한다.


리터럴

숫자, 문자와 같은 일반 글자

 

타임리프에서 사용할 때는 작은 따옴표 ' ' 로 감싸야한다.

하지만 중간 공백이 없는 리터럴은 작은따옴표가 없어도 리터럴로 인식해준다.

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

리터럴 문자끼리  + 로 연산을 할 수 있는데, 중간 중간에 데이터가 들어가야하는 경우 매우 귀찮을 수 있다

| | 안에 작성하면 연산자 없이 편하게 이어 쓸 수 있다.

 

연산

    <ul>
    <li>산술 연산 <ul>
        <li>10 + 2 = <span th:text="10 + 2"></span></li>
        <li>10 % 2 == 0 = <span th:text="10 % 2 == 0"></span></li>
    </ul>
    </li> <li>비교 연산
    <ul>
        <li>1 > 10 = <span th:text="1 &gt; 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>
    </ul>
</li>
    <li>조건식
        <ul>
            <li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)?'짝수':'홀수'"></span></li>
        </ul>
    </li>
    <li>Elvis 연산자<ul>
        <li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가 없습니다.'"></span></li>
        <li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?:'데이터가 없습니다.'"></span></li> </ul></li>
    <li>No-Operation<ul>
            <li>${data}?: _ = <span th:text="${data}?: _ ">데이터가 없습니다.</span></li>
            <li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li>
        </ul> </li>
</ul>

기본적으로 자바와 같으나

부등호 사용에서 HTML 엔티티를 사용해야한다.

> (gt), < (lt), >= (ge), <= (le), ! (not), == (eq), != (neq, ne)

Elvis연산자는 ?: 모양이 엘비스 프레슬리를 닮아서 지어진 이름이다.

해당 데이터가 있다면 그대로 출력하되 없다면 다음의 내용을 출력한다.

_ 를 출력하는 경우에는 타임리프 태그를 무시하고 HTML의 내용만 출력한다.


반복

<h1>반복</h1>
    <tr th:each="user : ${users}">
        <td th:text="${user.username}">username</td>
        <td th:text="${user.age}">0</td>
    </tr>
<h1>반복 상태 유지</h1>
    <tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">username</td>
        <td th:text="${user.username}">username</td>
        <td th:text="${user.age}">0</td>
        <td>
            index = <span th:text="${userStat.index}"></span>
            count = <span th:text="${userStat.count}"></span>
            size = <span th:text="${userStat.size}"></span>
            even? = <span th:text="${userStat.even}"></span>
            odd? = <span th:text="${userStat.odd}"></span>
            first? = <span th:text="${userStat.first}"></span>
            last? = <span th:text="${userStat.last}"></span>
            current = <span th:text="${userStat.current}"></span>
        </td>
    </tr>

th:each로 반복을 사용할 수 있다. for로 배열요소를 꺼내는 것과 비슷하다

타임리프는 여기에 더해서 반복에 대한 정보도 제공하는데 반복의 두번째 파라미터를 사용해서

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

위의 정보를 알 수 있다.

두번째 파라미터를 생략할 경우, 첫번째 파라미터와 Stat이 붙은 단어를 자동으로 추가한다.

 

조건문

<h1>if</h1>
    <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>

<h1>switch</h1>
    <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>

if문과 switch둘 다 사용 가능하다

해당 조건이 만족하지 않으면 해당 태그 자체가 출력되지 않는다.

 

주석

 

1. HTML주석

<!--   -->

그대로 남겨두되 렌더링만 안함(보이긴함)

 

2. 타임리프 파서 주석

<!--/*     */-->

아예 출력안됨(파일을 직접 열면 보임)

 

3. 타임리프 프로토타입 주석

<!--/*/      /*/-->

파일을 직접 열면 안보이고

타임리프로 렌더링을 해야 보임

 


블록

div 하나를 기준으로 반복하고싶다면 th:each를 사용하면 되는데

두개의 div를 함께 반복하고 싶을때 사용한다.

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

타임리프의 자체태그이며 렌더링하면 보이지않는다.

 


타임리프에서 javascript

원래 자바스크립트를 사용할 때는 <script>태그를 사용하는데

타임리프와 함께 사용하면 몇가지 문제가 발생할 수 있다.

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

텍스트 변수를 사용하는 경우, 변수에 큰 따옴표 " 를 감싸줘야한다.

따라서 변수마다 사용자가 큰 따옴표를 적어야하는 번거로움이 있는데

타임리프에서 제공하는 자바스크립트 인라인

<script th:inline="javascript">을 사용하면

텍스트 변수마다 알아서 큰 따옴표로 감싸준다

또 렌더링되는 경우에만 나타나는 내추럴 탬플릿도 사용이 가능하며

객체를 출력할때 JSON형식으로 나타내준다.


템플릿 조각, 레이아웃

 

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

웹페이지에서 공통영역을 만들고 싶은 경우, 각 메이지마다 똑같은 공통영역을 만들기보다

공통영역을 따로 만들고 필요할때 가져오면 더 만들기 편할 것이다.

위 방법을 사용하면 템플릿을 조각으로 사용하는 것 처럼 공통부분 처리를 쉽게 할 수 있다

insert는 위의 경우, div안에 복사할 부분을 집어넣는 것이고

replace는 div를 포함해서 복사할 부분으로 대체한다. 따라서 원래의 div부분에 덧씌워진다.

 

<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 태그들을 전달한다.

결과를 보자.
메인 타이틀이 전달한 부분으로 교체되었다.
공통 부분은 그대로 유지되고, 추가 부분에 전달한 <link> 들이 포함된 것을 확인할 수 있다.

이 방식은 사실 앞서 배운 코드 조각을 조금 더 적극적으로 사용하는 방식이다. 쉽게 이야기해서 레이아웃 개념을 두고, 그 레이아웃에 필요한 코드 조각을 전달해서 완성하는 것으로 이해하면 된다.
<!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>
<!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>