타임리프 1편에 이어서 2편에서도 기본기능 종류에 대해서 소개해드리겠습니다.
속성 값 설정
타임리프는 주로 HTML 태그에 th:* 속성을 지정하는 방식으로 동작합니다.
th:* 로 속성을 적용하면 기존 속성을 대체합니다. 기존 속성이 없으면 새로 만듭니다.
속성 설정
th:* 속성을 지정하면 타임리프는 기존 속성을 th:* 로 지정한 속성으로 대체한다. 기존 속성이 없다면 새로 만든다.
- <input type="text" name="mock" th:name="userA" />
- 타임리프 렌더링 후 <input type="text" name="userA" />
- 기존 name 을 th:name을 통해서 userA로 치환해버린다. 이게 속성 값 설정의 핵심
기존 HTML을 최대한 건들지 않고 살짝 바꿔치기 하면서 속성 값을 변경하는게 타임리프의 특징 !
<body>
<h1>속성 설정</h1>
<input type="text" name="mock" th:name="userA" /> //name을 userA로 치환
<h1>속성 추가</h1>
- th:attrappend = <input type="text" class="text" th:attrappend="class=' large'" /><br/> //append는 뒤에 추가해주므로 앞에 공백추가
- th:attrprepend = <input type="text" class="text" th:attrprepend="class='large '" /><br/> //prepend는 앞에 추가해주므로 뒤에 공백추가
- th:classappend = <input type="text" class="text" th:classappend="large" /><br/> // classappend를 사용하자! 공백추가없이 사용 !
<h1>checked 처리</h1>
- checked o <input type="checkbox" name="active" th:checked="true" /><br/> //th:checked를 사용하여 체크박스를 사용하자 !
- checked x <input type="checkbox" name="active" th:checked="false" /><br/> - checked=false
<input type="checkbox" name="active" checked="false" /><br/>
//checked 란 속성이 들어가 버리면 true,false 상관없이 체크박스에 이미 체크가 되어있다.
// th:checked를 사용하자 !
</body>

속성추가
- th:classappend : class 속성에 자연스럽게 추가한다.
checked 처리
- HTML에서는 <input type="checkbox" name="active" checked="false" /> 이 경우에도 checked 속성이 있기 때문에 checked 처리가 되어버린다.
- 타임리프의 th:checked 는 값이 false 인 경우 checked 속성 자체를 제거한다.
- <input type="checkbox" name="active" th:checked="false" />
→ 타임리프 렌더링 후: <input type="checkbox" name="active" />
- <input type="checkbox" name="active" th:checked="false" />
반복
타임리프에서 반복은 th:each 를 사용합니다. 추가로 반복에서 사용할 수 있는 여러 상태 값을 지원합니다.
<body>
<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>
<h1>반복 상태 유지</h1>
<table border="1">
<tr>
<th>count</th>
<th>username</th>
<th>age</th>
<th>etc</th>
</tr>
<tr th:each="user, userStat : ${users}"> //userStat 생략가능 -> 아래 변수명이 userStat이기 때문에
<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>
</table>
</body>

반복 기능
<tr th:each="user : ${users}">
반복시 오른쪽 컬렉션( ${users} )의 값을 하나씩 꺼내서 왼쪽 변수( user )에 담아서 태그를 반복 실행합니다.
th:each 는 List 뿐만 아니라 배열, java.util.Iterable , java.util.Enumeration 을 구현한 모든 객체를 반복에 사용할 수 있습니다.
Map 도 사용할 수 있는데 이 경우 변수에 담기는 값은 Map.Entry입니다.
반복 상태 유지
<tr th:each="user, userStat : ${users}">
반복의 두번째 파라미터를 설정해서 반복의 상태를 확인 할 수 있습니다.
두번째 파라미터는 생략 가능한데, 생략하면 지정한 변수명( user ) + Stat 가 됩니다.
여기서는 user + Stat = userStat 이므로 생략 가능합니다.
반복 상태 유지 기능
- index : 0부터 시작하는 값
- count : 1부터 시작하는 값
- size : 전체 사이즈
- even , odd : 홀수, 짝수 여부( boolean ) → 사용가능성 좋음 , 게시글의 홀수번째에 특정한 태그를 넣는다던지 색깔을 넣는다던지 하는식으로 활용가능
- first , last :처음, 마지막 여부( boolean )
- current : 현재 객체
조건부 평가
타임리프의 조건식 if, unless(if의 반대)
<body>
<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 lt 20}"></span> //html엔티티를 사용해서 조건문 실행 "<" ">="
<span th:text="'미성년자'" th:unless="${user.age ge 20}"></span> //unless는 조건에 만족하지않을 때 출력(if의 반대)
// if , unless는 조건에 만족을 안했을 때 태그자체가 출력이 안된다 !
</td> </tr>
</table>
<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>
</body>

if, unless
타임리프는 해당 조건이 맞지 않으면 태그 자체를 렌더링하지 않습니다.
만약 다음 조건이 false 인 경우 <span>...<span> 부분 자체가 렌더링 되지 않고 사라집니다.
<span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
switch
*은 만족하는 조건이 없을 때 사용하는 디폴트이다.
주석
<body> <h1>예시</h1>
<span th:text="${data}">html data</span>
<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span> -->
<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]] */--> // 주로사용 !!
<!--/*-->
<span th:text="${data}">html data</span>
<!--*/--> // 이렇게 정리해서도 사용 가능
<h1>3. 타임리프 프로토타입 주석</h1>
<!--/*/
<span th:text="${data}">html data</span> /*/-->
</body>

1. 표준 HTML 주석
자바스크립트의 표준 HTML 주석은 타임리프가 렌더링 하지 않고, 그대로 남겨둔다.
2. 타임리프 파서 주석 (주로 사용!)
타임리프 파서 주석은 타임리프의 진짜 주석이다. 렌더링에서 주석 부분을 제거한다. ( <!--/* 마음 */-→ )
3. 타임리프 프로토타입 주석
타임리프 프로토타입은 약간 특이한데, HTML 주석에 약간의 구문을 더했다.HTML 파일을 웹 브라우저에서 그대로 열어보면 HTML 주석이기 때문에 이 부분이 웹 브라우저가렌더링하지 않는다.타임리프 렌더링을 거치면 이 부분이 정상 렌더링 된다. 쉽게 이야기해서 HTML 파일을 그대로 열어보면 주석처리가 되지만, 타임리프를 렌더링 한 경우에만 보이는 기능이다.
블록
th:block 은 HTML 태그가 아닌 타임리프의 유일한 자체 태그입니다.
<body>
<**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>
</body>
div 태그 2개를 묶어서 사용하고 싶은데 편한 방법이 없을까? → th:block 태그를 사용하는것.
하지만, 일반적인 경우에서는 사용하지 않는것이 좋지만, 일반적인 반복으로 처리하기 어려운 것에 대해서 일부 사용합니다.
자바스크립트 인라인
타임리프는 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공합니다.
자바스크립트 인라인 기능은 다음과 같이 적용하면 됩니다.
<script th:inline="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>

텍스트 렌더링
- var username = [[${user.username}]];
- 인라인 사용 전 var username = userA;
- 인라인 사용 후 var username = "userA";
- 인라인 사용 전 렌더링 결과를 보면 userA 라는 변수 이름이 그대로 남아있습니다.
타임리프 입장에서는 정확하게 렌더링 한 것이지만 아마 개발자가 기대한 것은 다음과 같은 "userA"라는 문자일 것입니다.
결과적으로 userA가 변수명으로 사용되어서 자바스크립트 오류가 발생했습니다.
다음으로 나오는 숫자 age의 경우에는 " 가 필요 없기 때문에 정상 렌더링 됩니다. - 인라인 사용 후 렌더링 결과를 보면 문자 타입인경우 "를 포함 해줍니다.
추가로 자바스크립트 에서 문제가 될 수 있는 문자가 포함되어 있으면 이스케이프 처리도 해줍니다. 예) " → \”
자바스크립트 내추럴 템플릿
타임리프는 HTML 파일을 직접 열어도 동작하는 내추럴 템플릿 기능을 제공한다. 자바스크립트 인라인 기능을 사용하면 주석을 활용해서 이 기능을 사용할 수 있다.
- var username2 = /[[${user.username}]]/ "test username";
- 인라인 사용 전 var username2 = /userA/ "test username";
- 인라인 사용 후 var username2 = "userA";
- 인라인 사용 전 결과를 보면 정말 순수하게 그대로 해석을 해버렸습니다. 따라서 내추럴 템플릿 기능이 동작하지 않고, 심지어 렌더링 내용이 주석처리 되어 버립니다.
- 인라인 사용 후 결과를 보면 주석 부분이 제거되고, 기대한 "userA"가 정확하게 적용됩니다.
객체
타임리프의 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동으로 변환해줍니다.
- var user = [[${user}]];
- 인라인 사용 전 var user = BasicController.User(username=userA, age=10);
- 인라인 사용 후 var user = {"username":"userA","age":10};
- 인라인 사용 전은 객체의 toString()이 호출된 값이다.
인라인 사용 후는 객체를 JSON으로 변환해준다.
자바스크립트 인라인 each
자바스크립트 인라인은 each를 지원하는데, 다음과 같이 사용한다.
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>

템플릿 조각
이전에는 일부 코드 조각을 가지고와서 사용했다면, 이번에는 개념을 더 확장해서 코드 조각을 레이아웃에 넘겨서 사용하는 방법에 대해서 알아봅시다. 예를 들어서 <head> 에 공통으로 사용하는 css , javascript 같은 정보들이 있는데, 이러한 공통 정보들을 한 곳에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같이 사용하면 된다.
템플릿 조각으로 사용할 푸터 코드
<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>
조각을 사용할 코드
<body>
<h1>부분 포함</h1>
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div> // div 태그 내부에 추가
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div> // div 태그를 대체한다.
<h2>부분 포함 단순 표현식</h2>
<div th:replace="template/fragment/footer :: copy"></div> // 단순한 코드라면 ~{...}생략 가능, but 복잡해지면 사용 X
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터 2')}"></div> //파라미터 값을 교체함
</body>

- template/fragment/footer :: copy : template/fragment/footer.html 템플릿에 있는
th:fragment="copy" 라는 부분을 템플릿 조각으로 가져와서 사용한다는 의미입니다.
좀 더 확장된 개념인 템플릿 레이아웃도 알아보자
템플릿 레이아웃
이전에는 일부 코드 조각을 가지고와서 사용했다면, 이번에는 개념을 더 확장해서 코드 조각을 레이아웃에 넘겨서 사용하는 방법에 대해서 알아봅시다. 예를 들어서 <head> 에 공통으로 사용하는 css , javascript 같은 정보들이 있는데, 이러한 공통 정보들을 한 곳에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같이 사용하면 됩니다.
메인 코드(layoutMain)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="template/layout/base :: common_header(~{::title},~{::link})"> // 호출될때, title과 link를 레이아웃에 넘겨준다.
레이아웃에서 렌더링된 코드를 전달해준다.
<title>메인 타이틀</title> // title로 레이아웃에 전달됨
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> // link 태그를 전달한다
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}"> // link 태그를 전달한다
</head> <body> 메인 컨텐츠 </body> </html>
레이아웃 코드(Base)
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title,links)"> //중요! 메인코드에서 전달 받은 값으로 렌더링된다. 공통적인 것들은 놔둔다.
<title th:replace="${title}">레이아웃 타이틀</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}" /> // 메인코드의 link값으로 렌더링
</head>

- common_header(~{::title},~{::link}) 이 부분이 핵심입니다.
- ::title 은 현재 페이지의 title 태그들을 전달한다.
- ::link 는 현재 페이지의 link 태그들을 전달한다.
- 메인 타이틀이 전달한 부분으로 교체되었습니다.
공통 부분은 그대로 유지되고, 추가 부분에 전달한 <link> 들이 포함된 것을 확인할 수 있습니다.
즉, Base라는 거대한 레이아웃 코드에서 내가 원하는 부분만 바꿔서 메인 코드에 전달됩니다.
이 방식은 사실 앞서 배운 코드 조각을 조금 더 적극적으로 사용하는 방식입니다.
쉽게 이야기해서 레이아웃 개념을 두고, 그 레이아웃에 필요한 코드 조각을 전달해서 완성하는 것으로 이해하면 됩니다.
템플릿 레이아웃 확장
앞서 이야기한 개념을 <head> 정도에만 적용하는게 아니라 <html> 전체에 적용할 수도 있습니다.
메인코드
<!DOCTYPE html>
<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title},~{::section})}"
xmlns:th="http://www.thymeleaf.org"> //title과 section 정보를 넘긴다.
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
</body>
</html>
레이아웃 코드
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org"> //html 자체를 설정 , title과 section정보를 받음
<head>
<title th:replace="${title}">레이아웃 타이틀</title> </head>
<body>
<h1>레이아웃 H1</h1>
<div th:replace="${content}">
<p>레이아웃 컨텐츠</p>
</div>
<footer>
레이아웃 푸터
</footer>
</body>
</html>

- layoutFile.html 을 보면 기본 레이아웃을 가지고 있는데, <html> 에 th:fragment 속성이 정의되어 있습니다.
이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용을 전달해서 부분부분 변경하는 것으로 이해하면 됩니다. - layoutExtendMain.html 는 현재 페이지인데, <html> 자체를 th:replace 를 사용해서 변경하는 것을 확인할 수 있습니다.
결국 layoutFile.html 에 필요한 내용을 전달하면서 <html> 자체를 layoutFile.html 로 변경했습니다.
많은 양의 페이지를 효율적으로 관리하는데 템플릿 레이아웃을 이용하면 효과적입니다.