728x90
스프링부트에서 타임리프(Thymeleaf)로 프로젝트를 진행 중 2번 이상 다시 찾아본 개념에 대해 정리해놓으려 합니다.
기본 타임리프 문서 : https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#standard-htmlxml-comments
Tutorial: Using Thymeleaf
1 Introducing Thymeleaf 1.1 What is Thymeleaf? Thymeleaf is a Java library. It is an XML/XHTML/HTML5 template engine able to apply a set of transformations to template files in order to display data and/or text produced by your applications. It is better s
www.thymeleaf.org
수시로 업데이트!
1. 해당 조건이 만족할 때 컨텐츠 나타내기 (조건문 th:if)
<button class="btn btn-outline-secondary" th:if="${board.member.username == #httpServletRequest.remoteUser}" type="submit">삭제</button>
- th:if문이 true로 나오면 컨텐츠 표시, 아니면 숨김
- #httpServletRequest.remoteUser -> 현재 로그인한 유저 아이디
<div id="uploadForm" th:if="${param.boardId == null}">
<div id="uploadElement">
<input id="uploadInput" type="file" class="btn btn-outline-primary" name="files" accept="image/*"
onchange="setThumbnail(event);" multiple /> <span style="font-size: small;"> * jpeg / png 타입의
이미지를
7개까지
등록해주세요.</span>
</div>
<a id="reset" class="mt-3 btn btn-danger" onclick="resetImg()">Reset</a>
</div>
- th:if문과 param(boardId가 url에 없을 때 div 태그가 보임)을 이용해서 컨텐츠를 보이고 안보이게 한다.
<div th:if="${menu} == 'home' or ${menu} == 'addAdmin'"></div>
- 자바에서와 같이 || 나 && 사용 불가능
- 대신 or 또는 and 키워드를 사용
2. 컨트롤러로 값 전달
<form th:if="${board.member.username == #httpServletRequest.remoteUser}" th:action="@{/board/delete}"
method="post">
<input type="hidden" th:name="boardId" th:value="${board.id}">
<button class="btn btn-outline-secondary" type="submit">delete</button>
</form>
@PostMapping("/delete")
public String boardDelete(Long boardId) {
boardService.delete(boardId);
return "redirect:/board/list";
}
- form 태그 안에 input 태그를 생성
- input태그의 th:name으로 컨트롤러에 매핑할 변수값을 지정
- 버튼 클릭 -> input태그의 th:value를 이용해서 값 제출
2-1. 체크박스 값 서버로 전달
<form class="row g-3 justify-content-end" method="post" th:action="@{/board/myPost/delete}">
<table class="table caption-top table-bordered table-hover">
<tbody>
<tr th:each="board : ${boardList}">
<td class="mt-5 text-center" scope="row">
<div class="checkbox">
<input type="checkbox" name="boardIdList" th:value="${board.id}">
</div>
</td>
<td class="mt-5 text-center" scope="row" th:text="${board.id}">1</td>
<td><a th:text="${board.title}" th:href="@{/board/post(id=${board.id})}"
style="text-decoration:none; color:black;">제목</a></td>
<td class="text-center" th:text="${#dates.format(board.createDate, 'yyyy/MM/dd HH:mm')}">작성일
</td>
<td class="text-center" th:text="${board.writer}">작성자</td>
</tr>
</tbody>
</table>
<button type="submit" class="btn btn-danger">Delete</button>
</form>
@PostMapping("/myPost/delete")
public String boardDelete(@RequestParam(required = false) List<String> boardIdList) {
if(boardIdList == null) return "redirect:/board/myPost";
if(boardIdList.size() > 0) {
for(int i = 0; i < boardIdList.size(); i ++) {
boardService.temporaryDelete(Long.parseLong(boardIdList.get(i)));
}
}
return "redirect:/board/myPost";
}
- form태그 안에 다음과 같이 체크 박스 name과 value를 지정. (th:each로 인해 게시글 개수만큼 체크박스 생성)
- 체크박스에 체크하고 버튼으로 form을 제출하면 컨트롤러에 List 형태로 전달됨.
3. th:fragment 2개 이상 인자 전달
<!-- Page -->
<nav th:replace="fragments/pagingCommon :: pagination(${pagination}, 'trash')"></nav>
- 해당 코드는 board/trash 안에 작성된 페이징 관려 nav태그
- th:replace="경로 :: 프래그먼트 명(${컨트롤러로부터 받은 객체 또는 모델명})
- th:replace="경로 :: 프래그먼트 명('넘길 문자')"
- trash는 4번에서 설명
<body>
<nav aria-label="Page navigation example" th:fragment="pagination(pagination, menu)">
<ul class="pagination justify-content-center">
<th:block th:with="start = ${pagination.startPage}, end = ${pagination.endPage}">
<li class="page-item" th:classappend="${pagination.prev == false} ? 'disabled'">
<a class="page-link" th:href="@{'/board/' + ${menu}(page=${start - 1}, range=${pagination.range - 1},
searchText=${param.searchText})}">Previous</a>
</li>
<li class="page-item" th:classappend="${i == pagination.page} ? 'disabled'" th:if="${i != 0}"
th:each="i : ${#numbers.sequence(start, end)}"><a class="page-link" href="#" th:href="@{'/board/' + ${menu}(page=${i},
range=${pagination.range}, searchText=${param.searchText})}" th:text="${i}">1</a></li>
<li class="page-item" th:classappend="${pagination.next == false} ? 'disabled'">
<a class="page-link" th:href="@{'/board/'+ ${menu}(page=${end + 1}, range=${pagination.range + 1},
searchText=${param.searchText})}" href="#">Next</a>
</li>
</th:block>
</ul>
</nav>
</body>
<페이징 관련 fragment 소스>
- th:replace로 호출한 프레그먼트가 있는 html 파일에서 th:fragment="프리그먼트명(받은 값들)" 로 보내준다.
4. th:href 안에 변수 넣어 사용하기 (링크 컨트롤)
- 페이징 소스를 사용하는 화면 곳곳마다 이동하는 링크는 다를테니 'list', 'myPost', 'trash' 같은 값을 replace할 때 인자로 보내서 th:href를 처리할 것이다.
- 해당 처리를 하지 않은 코드는 th:href="@{/board/trash/(page=${start ...} ... )}"
- 처리 후 코드 : th:href="@{'/board/' + ${menu}(page=${start ...} ... )}"
5. 시큐리티 관련 메소드
사용을 위해 의존성 추가 (gradle)
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
<a th:href="@{/loginForm}" class="btn btn-secondary fw-bolder me-4" sec:authorize="!isAuthenticated()">Login</a>
<form class="d-flex me-4" th:action="@{/logout}" sec:authorize="isAuthenticated()">
<span class="text-white me-4 mt-2 fw-bolder" sec:authentication="name">사용자</span>
<a th:href="@{/admin}" class="btn btn-danger" style="font-weight: bold" sec:authorize="hasRole('ROLE_ADMIN')">관리자 화면</a>
<button th:href="@{/logout}" class="btn btn-secondary fw-bolder">Logout</button>
</form>
- sec:authorize="isAuthenticated() -> 로그인한 사용자에게 보여줌
- sec:authentication="name" -> 로그인한 사용자 이름을 표시
- sec:authorize="hasRole('ROLE_ADMIN')" -> 권한이 ROLE_ADMIN인 사용자에게만 보여줌
참고, Authentication : 로그인, Authroization : 권한
6. Validator 관련 내용 (에러 내용 표시) + th:object, th:field, th:errors
<form th:action="@{/join}" method="post" th:object="${member}">
<div class="form-floating mt-4">
<input type="text" class="form-control" th:field="*{username}" th:classappend="${#fields.hasErrors('username')} ? 'is-invalid'" id="floatingInput" placeholder="Username">
<label for="floatingInput">Username</label>
<div th:if="${#fields.hasErrors('username')}" th:errors="*{username}" id="validationServer03Feedback" class="invalid-feedback text-start">
Username Error
</div>
</div>
<div class="form-floating">
<input type="password" class="form-control mt-2" th:field="*{password}" th:classappend="${#fields.hasErrors('password')} ? 'is-invalid'" id="floatingPassword" placeholder="Password">
<label for="floatingPassword">Password</label>
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" id="validationServer03Feedback" class="invalid-feedback text-start" style="margin-bottom: 8px;">
Password Error
</div>
</div>
<div class="form-floating">
<input type="email" class="form-control" th:field="*{email}" th:classappend="${#fields.hasErrors('email')} ? 'is-invalid'" id="floatingPassword" placeholder="Email">
<label for="floatingPassword">example@board.com</label>
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" id="validationServer03Feedback" class="invalid-feedback text-start" style="margin-top: 8px;">
Email Error
</div>
</div>
<div class="checkbox mb-3">
</div>
<button class="w-100 btn btn-lg btn-primary mt-2" type="submit">Get started!</button>
<a type="button" class="w-100 btn btn-lg btn-primary mt-2" th:href="@{/}">exit</a>
</form>
- form 태그 안에서 사용된 타임리프의 th:if가 달린 3개의 div 태그에서 에러를 출력해준다.
- th:if=${#fields.hasErrors('필드명')} : 에러가 발생한 경우 true 값을 반환하여 div태그를 표시해준다.
- form 태그의 th:object="{member}" : GetMapping 때 전달받은 Member의 키 값을 넣어 전달 받은 객체
- th:field="*{username}" : 위 설명을 참고해서 Member.getUsername()을 View에서 사용할 수 있도록 한 값이다. -> ${member.username} 과 같다고 생각!
- th:errors="*{password}" : password에 대한 에러 내용을 표시해준다.
7. th:block - th:with 로 변수 정의, th:each - #numbers.sequence(from, to), 링크 url
<body>
<nav aria-label="Page navigation example" th:fragment="pagination(pagination)">
<ul class="pagination justify-content-center">
<th:block th:with="start = ${pagination.startPage}, end = ${pagination.endPage}">
<li class="page-item" th:classappend="${pagination.prev == false} ? 'disabled'">
<a class="page-link" th:href="@{/board/list(page=${start - 1}, range=${pagination.range - 1},
searchText=${param.searchText})}">Previous</a>
</li>
<li class="page-item" th:classappend="${i == pagination.page} ? 'disabled'"
th:each="i : ${#numbers.sequence(start, end)}"><a class="page-link" href="#" th:href="@{/board/list(page=${i},
range=${pagination.range}, searchText=${param.searchText})}" th:text="${i}">1</a></li>
<li class="page-item" th:classappend="${pagination.next == false} ? 'disabled'">
<a class="page-link" th:href="@{/board/list(page=${end + 1}, range=${pagination.range + 1},
searchText=${param.searchText})}" href="#">Next</a>
</li>
</th:block>
</ul>
</nav>
</body>
- th:block 태그에 th:with로 사용할 변수에 값을 초기화해서 사용가능.
- th:each에 #numbers.sequence(from, to)를 사용해서 from에서부터 to까지 i변수에 넣어 사용
- th:href="@{/board/list(page=${end + 1}, range=${pagination.range + 1}" : board/list?page=...&range=...
7.2 th:block th:with 조건문 사용
<tbody>
<th:block th:with="no = ${param.page != null} ? (${#numbers.formatInteger(param.page, 1)} - 1) * 10 : 0">
<tr th:each="member : ${memberList}">
<td class="mt-5 text-center" scope="row">
<div class="checkbox">
<input type="checkbox" name="memberIdList" th:value="${member.id}">
</div>
</td>
.
.
.
</tr>
</th:block>
</tbody>
</table>
- 삼항 연산자를 통해 특정 변수를 선언하고 값을 초기화한다.
- #numbers.formatInteger(int형으로 변환할 변수, 자릿수) : no변수는 url에 page 파라미터가 없을 경우는 기본값 0
- 이를 활용하면 파라미터의 값이 있을 때와 없을 때 변수값을 다르게 설정할 수 있다. 즉, 파라미터 값 유무에 따라 다르게 처리 할 수 있다.
8. 타임리프에서 날짜 형식 바꾸기
<td class="text-center" th:text="${#dates.format(board.createDate, 'yyyy/MM/dd HH:mm')}">작성일</td>
9. th:each 활용
index | 현재 반복 인덱스 (0부터 시작) |
count | 현재 반복 인덱스 (1부터 시작) |
size | 총 개수 |
current | 현재 요소 |
even | 현재 요소가 짝수인지 여부 (index 기준) |
odd | 현재 요소가 홀수인지 여부 (index 기준) |
first | 현재 요소가 첫번째인지 여부 |
last | 현재 요소가 마지막인지 여부 |
- 사용방법
<tr th:each="member : ${memberList}">
<td class="text-center" th:text="${memberStat.count}">count</td>
<td class="text-center" th:text="${memberStat.index}">index</td>
</tr>
10. 타임리프 문자열 비교
<p th:text="${#strings.equals(member.role, 'ROLE_ADMIN') ? 'ADMIN' : 'USER'}">권한</p>
- #strings.equals를 이용해 첫 인자와 두번째 인자가 같은지 체크할 수 있다.
'Web > Tymeleaf' 카테고리의 다른 글
[타임리프] Thymeleaf에서 컨트롤러로 JPA 복합키 필드명 전달 (0) | 2021.12.19 |
---|