타임리프(thymeleaf) 정리

이동건 (불꽃냥펀치)·2024년 12월 3일
0

타임리프(thymeleaf)

타임리프의 특징

  • 서버 사이드 렌더링: 타임리프는 서버에서 HTML을 동적으로 렌더링하는 용도로 사용한다.
  • 네츄럴 템플릿: 타임리프는 HTML을 최대한 유지하려는 특징이 있어 타입리프로 작성한 파일은 웹 브라우저에서 파일을 직접 열거나 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있다.
  • 스프링 통합 지원: 타임리프는 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다.

타임리프 기본 표현식

  • 간단한 표현
    • 변수 표현식: ${...}
    • 선택 변수 표현식: *{...}
    • 메시지 표현식: #{...}
    • 링크 URL 표현식: @{...}
    • 조각 표현식: ~{...}
  • 리터럴
    • 텍스트: 'one text'
    • 숫자: 1,2,3,4
    • 불린: true,false
    • 널: null
    • 리터럴 토큰: one, sometext , main
  • 문자 연산
    • 문자합치기: +
    • 리터럴 대체: | ... |
  • 산술 연산
    •   +,- , * , / , %
  • 불린 연산
    • and / or

텍스트

타임리프는 기본적으로 HTML 테크의 속성에 기능을 정의해서 동작한다. HTML의 콘텐츠에 데이터를 출력할 때는 다음과 같이 th:text="${지정값}"를 사용하면 된다

HTML 태그의 속성이 아닌 콘텐츠 영역안에서 직접 데이터를 출력하고 싶으면 [[...]]를 사용하면 된다.

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

Escape

HTML문서는 th:text로 "Hello <b>Spring!</b>"를 입력하게 하면 웹브라우저에서 그대로 "Hello <b>Spring!</b>"라고 노출된다.

웹브라우저는 <를 HTML 태그의 시작으로 인식해서 문자로 표현할 수 있는 방법이 필요하다.

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

타임리프가 제공하는 이스케이프 기능을 사용하고 싶지 않다면 th:utext 또는 [(...)] 둘 중에 하나를 선택해서 사용하면 된다.


변수

Object

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

List

  • users[0].username : List에서 첫 번째 회원을 찾고 username 프로퍼티 접근
  • list.get(0).getUsername()
  • users[0]['username']
  • users[0].getUsername() : List에서 첫 번째 회원을 찾고 메서드 직접 호출

Map

  • userMap['userA'].username : Map에서 userA를 찾고, username 프로퍼티 접근
  • map.get("userA").getUsername()
  • userMap['userA']['username']
  • userMap['userA'].getUsername() : Map에서 userA를 찾고 메서드 직접 호출


지역변수

th:with를 사용하면 지역변수를 선언해서 사용이 가능하지만, 지역변수는 선언한 태그 내에서만 사용할 수 있다.


기본 객체

@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";
}
<li>Request Parameter = <span th:text="${param.paramData}"></span></li>
<li>session = <span th:text="${session.sessionData}"></span></li>

#message : 메시지, 국제화 처리
#uris : URI 이스케이프 지원
#dates : java.util.Date 서식 지원
#calendars : java.util.Calendar 서식 지원
#temporals : 자바8 날짜 서식 지원
#numbers : 숫자 서식 지원
#strings : 문자 관련 편의 기능
#objects : 객체 관련 기능 제공
#bools : boolean 관련 기능 제공
#arrays : 배열 관련 기능 제공
#lists , #sets , #maps : 컬렉션 관련 기능 제공


URL 링크

타임리프에서는 URL을 생성할 때 @{...} 문법을 사용하면 된다.

<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})}">
    	pathvariable + query parameter
    </a>
</li>
  • 쿼리 파라미터 = @{/hello(param1=${param1}, param2=${param2})}
    • /hello?param1=data1&param2=data2
  • 경로 변수 = @{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
    • /hello/data1/data2
    • URL경로상에 변수가 있으면 ()부분은 경로 변수로 처리된다.
  • 경로변수 + 쿼리파라미터
    • /hello/data1?param2=data2
    • 경로 변수와 쿼리파라미터를 함께 사용할 수 있다.


리터럴

리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.

타임리프에서는 문자 리터럴은 항상 ''로 감싸야 한다. 하지만 항상 ''로 감싸는 것은 너무 귀찮은 일이라 공백없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해 작은 따옴표를 생략할 수 있다.

<span th:text="hello world!"></span> 이런 경우 공백이 있어 하나의 의미있는 토큰으로도 인식되지 않아 오류가 발생한다.

<span th:text="'hello world!'"></span> 이렇게 '로 감싸면 정상 동작한다.

<span th:text="|hello ${data}|"> 리터럴의 대체 문법을 사용하면 마치 템플릿을 사용하는 것처럼 편리하다.

연산

<li>10 + 2 = <span th:text="10 + 2"></span></li>
<li>10 % 2 == 0 = <span th:text="10 % 2 == 0"></span></li>
<li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)? '짝 수':'홀수'"></span></li>
<li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가 없습 니다.'"></span></li>
<li>${nullData}?: '데이터가 없습니다.'= <span th:text="${nullData}?: '데 이터가 없습니다.'"></span></li>
<li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다.</span></
li>
  • 조건식: 자바와 비슷하다.

  • no-operation(_): _인 경우 아무것도 실행이 되지 않으며, 태그내의 내용이 출력된다.



반복

 @GetMapping("/each")
 public String each(Model model) {
     addUsers(model);
     return "basic/each";
 }
 
private void addUsers(Model model) {
     List<User> list = new ArrayList<>();
     list.add(new User("userA", 10));
     list.add(new User("userB", 20));
     list.add(new User("userC", 30));
     model.addAttribute("users", list);
 }
<h1>기본 테이블</h1> <table border="1">
     <tr>
         <th>username</th>
         <th>age</th>
     </tr>
     <tr th:each="user : ${users}">
         <td th:text="${user.username}">username</td>
         <td th:text="${user.age}">0</td>
     </tr>
</table>
     <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>

반복 기능

<tr th:each="user : ${users}">

  • 반복시 오른쪽 컬렉션에서 값을 하나씩 꺼내서 왼쪽 변수에 담아서 태그를 반복 실행한다.
  • th:eachList뿐만 아니라 배열,java.util.Iterable , java.util.Enumeration을 구현한 모든 객체를 반복에 사용할 수 있다.
  • Map도 사용할 수 있는데 이 경우 변수에 담기는 값은 Map.Entry이다.


반복 기능

<tr th:each="user, userStat : ${users}">

  • 반복의 두번째 파라미터를 설정해서 반복의 상태를 확인할 수 있다.
  • 두번째 파라미터는 생략이 가능하고, 생략시 지정한 변수명 + Stat가 된다.


반복 상태 유지 기능

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


조건부 평가

 <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 lt 20}">
            </span> <span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
		</td>
</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>
  • 조건이 false인 경우 해당 부분은 렌더링 되지 않고 사라진다.
    <span th:text="'미성년자'" th:if="${user.age lt 20}"></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>
	<div>
		요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
     </div>

user 객체에 전달되지 않은 새로운 내용을 반복하고 싶을 때 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>
<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>

th:insert 를 사용하면 현재 태그( div ) 내부에 추가한다.

th:replace 를 사용하면 현재 태그( div )를 대체한다.

앞서 이야기한 개념을 <head> 정도에만 적용하는게 아니라 <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> 
 <!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>

layoutFile.html 을 보면 기본 레이아웃을 가지고 있는데, <html>th:fragment 속성이 정의되어 있다. 이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용을 전달해서 부분부분 변경하는 것으로 이해하면 된다.

layoutExtendMain.html 는 현재 페이지인데, <html> 자체를 th:replace 를 사용해서 변경하는 것을 확인 할 수 있다. 결국 layoutFile.html 에 필요한 내용을 전달하면서 <html> 자체를 layoutFile.html 로 변경 한다.








출처: https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-1/dashboard

profile
자바를 사랑합니다

0개의 댓글

관련 채용 정보