Search

Spring 활용 - 타임리프

타임리프

타임리프

서버 사이드 HTML 렌더링 (SSR)
네츄럴 템플릿 : 순수 HTML을 최대한 유지하는 특징, 웹으로 열면 엥간하면 제대로 뜬다.
스프링의 다양한 기능을 편리하게 사용할 수 있게 지원함
타임리프 사용 선언
<html xmlns: th="http://www.thymleaf.org">

기본 표현식

1.
간단한 표현
변수 표현식: ${}
선택 변수 표현식: *{}
메시지 표현식: #{}
링크 URL표현식: @{}
조각 표현식: ~{}
2.
리터럴
텍스트: 'one text', 'Another one!'
숫자: 0,34,3.0,12.3
불린: true, false
널: null
리터럴 토큰: one,sometext,main
3.
문자 연산
문자 합치기: +
리터럴 대체: | The name is ${name}|
4.
산술 연산
Binary operators: +,-,*,/,%
Minus sign (unary operator): -
5.
불린 연산:
Binary operators: and, or
Boolean negation (unary operator): !, not
6.
비교와 동등:
비교: >,<,≥, ≤
동등 연산:==, ≠
7.
조건 연산:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
8.
특별한 토큰:
No-Operation: _

텍스트 - text, utext

타임리프는 기본적으로 HTML 태그의 속성에 기능을 정의해서 동작
HTML의 콘텐츠(content)에 데이터를 출력할 때는 다음과 같이 th:text를 사용하면 된다.
HTML 영역 안에 직접 데이터를 출력하고 싶으면 다음과 같이 [[...]] 를 사용하면 된다.
<ul> <li>th:text = <span th:text = "${data}"></span></li> <li>th:utext = <span th:utext = "${data}"></span></li> </ul> <h1><span th:inline="none">[[...]] vs [(...)]</span> </h1> <ul> <li><span th:inline="none">[[...]] =</span> [[${data}]]</li> <li><span th:inline="none">[(...)] =</span> [(${data})]</li> </ul>
HTML
복사

변수 - SpringEL

변수 표현식 : ${...}
지역변수 선언: th:with
<h1>지역 변수 - (th:with) </h1> <div th:with = "first=${users[0]}"> <p>처음 사람의 이름은 <span th:text = "${first.username}"></span></p> </div>
HTML
복사

기본 객체들

타임리프는 기본 객체들을 제공한다
${#request}
${#response}
${#session}
${#servletContext}
${#locale}

편의 객체들

${param.paramData}
${session.sessionData}
${@helloBean.hello('Spring!')}

유틸리티 객체들

타임리프는 문자,숫자,날짜, URI 등을 편리하게 다루는 다양한 유틸리티 객체들을 제공
#message: 메시지, 국제화 처리
#uris: URI 이스케이프 지원
#dates: java.util.Date 서식 지원
#calendars: java.util.Calendar 서식 지원
#temporals: 자바 8 날짜 서식 지원
#numbers: 숫자 서식 지원
#strings: 문자 관련 편의 기능
#objects: 객체 관련 기능 제공
#bools: boolean 관련 기능 제공
#arrays: 배열관련 기능 제공
#lists,#sets,#maps: 컬렉션 관련 기능 제공
#ids: 아이디 처리 관련 기능 제공, 뒤에서 설명
찾아서 보면 됨

URI 링크

단순한 URL

@{/hello} → /hello

쿼리 파라미터

@{/hello(param1=${param1} , param2=${param2})}
→ /hello?param1=data1&param2=data2
→ ()에 있는 부분은 쿼리 파라미터로 처리됨

경로 변수

@{/hello/{param1}/{param2}(param1 = ${param1}, param2=${param2})}
→ /hello/data1/data2

경로 변수 + 쿼리 파라미터

@{/hello/{param1}(param1=${param1},param2=${param2})}
→ /hello/data1?param2=data2
경로 변수와 쿼리 파라미터를 함께 사용할 수 있다.
상대경로,절대경로,프로토콜 기준을 표현할 수도 있다.
/hello: 절대경로
hello: 상대경로

리터럴

문자: 'hello'
숫자: 10
불린: true, false,
null: null
문자 리터럴은 항상 작은 따옴표로 감싸야함
Ex) <span th:text = "'hello world!'"></span>
리터럴 대체
<span th:text = "|hello ${data}|">

연산

비교연산: HTML엔티티를 사용해야하는 부분을 주의
>(gt) , <(lt)m ≥(ge), ≤(le), !(not) ==(eq) ≠(neq)
조건식: 자바 조건식과 유사
Elvis: 연산자: 조건식의 편의 버전
No-operation: _인 경우 마치 타임리프가 실행되지 않는 것처럼 동작한다.

속성

th:* 로 속성을 지정하면 기존 속성을 th:* 로 지정한 속성으로 대체함. 기존 속성이 없다면 새로 만듬
속성 추가
th:attrappend
th:attrprepend
th:classappend
checked 처리
HTML에서는 checked라는 속성만 있어도 checked 처리가 되어버림
타임리프의 th:checked는 값이 false인 경우 checked 속성 자체를 제거함

반복

th:each를 사용함
반복에서 사용할 수 있는 여러 상태 값을 지원
<tr th:each = "user, userStat : ${users}">

조건문

타임리프는 해당 조건이 맞지 않으면 태그 자체를 렌더링하지 않는다.
만약 다음 조건이 false인 경우 <span></span>이 아예 렌더링이 안됨
<span th:text = "'미성년자'" th:if="${user.age lt 20}"></span>
switch
*은 만족하는 조건이 없을 때 사용하는 디폴트이다.

주석

1.
표준 HTML 주석
a.
<!— —>
2.
타임리프 파서 주석
a.
<!—/*—>
<!—*/—>
3.
타임리프 프로토타입 주석
a.
<!—/*/ ~~~~ /*/—>
HTML 파일을 웹에서 열면 HTML주석이라 보이지 않음
타임리프 렌더링을 거치면 이 부분이 정상 렌더링이 됨

블록

<th:block>은 HTML 태그가 아닌 타임리프의 유일한 자체 태그이다.
<th:block th:each = "user: ${users}"> <div> 사용자 이름1 <span th:text="${user.name}"></span> 사용자 나이1 <span th:text="${user.age}"></span> </div> <div> 요약<span th:text="${user.name} + '/' + ${user.age}"></span> </div> </th:block>
HTML
복사

자바스크립트 인라인

1.
텍스트 렌더링
문자 타입인 경우 "를 포함해줌. 문제가 될만한 이스케이프 처리도 알아서 해줌
2.
내츄럴 템플릿
파일을 그냥 열어도 안 깨진다.
var username2 = /*[[${user.username}]]*/ "test username";
파일을 그냥 열면 test username이라는 문자열이 들어가고, 렌더링하면 ${}값이 들어간다.
3.
객체
객체를 JSON으로 자동으로 변환해준다.
4.
인라인 each
<script th:inline = "javascript"> [# th:each = "user, userStat: ${users}"] var user[[${stat.count}]] = [[${user}]]; [/] </script>
HTML
복사

템플릿 조각

여러 페이지들을 조각 내놓고 그걸 여러 군데 반복적으로 쓰는 것
<footer th:fragment="copy"> 푸터 자리입니다. </footer> <footer th:fragment="copyParam (param1,param2)"> <p> 파라미터 자리입니다.</p> <p th:text = "${param1}"></p> <p th:text = "${param2}"></p> </footer>
HTML
복사
<div th:insert="~{template/fragment/footer :: copy}"></div> <div th:replace="~{template/fragment/footer :: copy}"></div> <div th:replace = "template/fragmenet/footer :: copy}"></div> <div th:replace = "~{template/fragment/footer :: copyParam('데이터1', '데이터2')}"></div>
HTML
복사

템플릿 레이아웃1

코드 조각을 레이아웃에 넘겨서 사용하는 방법
common_header(~{::title}, ~{::link} ) 이 부분이 핵심
::title은 현재 페이지의 title 태그들을 전달
::link는 현재 페이지의 link 태그들을 전달

템플릿 레이아웃2

템플릿 레이아웃 확장
앞서 이야기한 개념을 <head> 정도에만 적용하는게 아니라 <html>전체에 적용할 수도 있다.
<html>에 th:fragment 속성이 정의되어있다.
이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용을 전달해서 부분부분 변경하는 것

타임리프 - 스프링 통합과 폼

추가되는 기능들

SpringEL문법 통합
편리함 폼관리를 위한 추가 속성
폼 컴포넌트 기능
스프링의 메시지, 국제화 기능의 편리한 통합
스프링의 검증, 오류 처리 통합
스프링의 변환 서비스 통합

입력 폼 처리

th:object → 커맨드 객체를 지정한다
*{...} → 선택 변수 식이라고 한다. th:object에서 선택된 객체에 접근
th:field → HTML 태그의 id,name,value 속성을 자동으로 처리해준다.
<input type = "text" th:filed = "*{itemName}"/>
—> <input type = "text" id = "itemName" name = "itemName" th:value = "*{itemName}"/>
th:object를 적용하려면 먼저 해당 오브젝트 정보를 넘겨줘야한다.
등록 폼이기 때문에 데이터가 비어있는 오브젝트를 만들어서 뷰에 전달
<form action = "item.html" th:action th:object+"${item}" method = "post"> <div> <label for = "itemName">상품명</label> <input type = "text" id = "itemName" th:field = "*{itemName}" class = "form-control" placeholder = "이름을 입력하세요"> </div>
HTML
복사

체크 박스 - 단일

체크 박스를 선택하지 않았을 때는 open이라는 필드 자체가 서버로 전송되지 않는다.
즉, 체크 됬는지, 안됬는지에 대한 값 자체가 넘어가지 않는다.
이를 위해 스프링 MVC는 히든 필드를 인식할 수 있도록 하는데,
open이라고 했을 때 _open이라는 히든 필드가 넘어오면, 체크를 해제했다고 인식할 수 있다. 히든 필드는 항상 전송되는데, 따라서 체크를 해제했으면 open은 전송X, _open만 전송 → 이걸 인식해서 false를 뱉는다.
이 때 타임리프가 제공하는 폼 기능을 사용해보자

th:field="*{~~}"

이 속성을 사용하면 체크박스의 히든 필드도 자동으로 생성되있다.
th:field를 사용하면 값이 true인 경우 체크를 자동으로 처리해줌

체크 박스 - 멀티

ModelAttribute의 특별한 사용법

등록 폼, 상세화면, 수정 폼에서 모두 서울,부산,제주라는 체크박스를 반복해서 보여줘야함.
@ModelAttribute를 따로 빼서 사용하면 해당 컨트롤러가 반환하는 Model에 값을 다 담아서 준다.
<div> <div>등록 지역</div> <div th:each="region : ${regions}" class="form-check form-check-inline"> <input type="checkbox" th:field="*{regions}" th:value="${region.key}" class="form-check-input"> <label th:for="${#ids.prev('regions')}" th:text="${region.value}" class="form-check-label">서울</label> </div> </div>
JavaScript
복사
멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다.
반복해서 생성할 때 id는 모두 달라야함. 체크박스를 each루프 안에서 반복해서 만들 때 임의로 1,2,3숫자를 뒤에 붙여준다.
ids.prev('regions')를 이용해서 동적으로 생성되는 id값을 이용하도록 함.
타임리프의 체크 확인
멀티 체크 박스에서 등록 지역을 선택해서 저장하면, 조회 시에 checked 속성이 추가된 것을 확인할 수 있다
타임리프는 th:field에 지정한 값과 th:value의 값을 비교해서 체크를 자동으로 처리해준다.

라디오 박스

여러 선택지 중에 하나를 선택할 때 사용할 수 있다.
Enum → item.values() :해당 Enum의 정보를 배열로 반환
체크 박스는 수정 시 체크를 해제하면 아무 값도 넘어가지 안힉 때문에 별도의 히든 필드로 해결
라디오 버튼은 이미 선택되어있다면, 수정 시에도 항상 하나를 선택해야함. 따라서 별도의 히든 필드를 사용할 필요가 없다.

셀렉트 박스

뭐 비슷하다
<select> <option>사용하는 거 빼면 다 똑같음