✅타임리프 실습 이유
- JSP와 유사하게 ${ } 이용 가능
- Model에 담긴 객체를 화면에서 JS로 처리하기 편리함.
- 연산이나 포맷과 관련된 기능을 지원함.
- .html 파일 생성 시 문제가 없고 별도의 확장자를 이용하지 않음.
🤍Thymeleaf 이용 프로젝트 생성
Spring Boot DevTools / Lombok / Spring Web / Thymeleaf 추가

타임리프를 이용하는 프로젝트는 변경 후 만들어진 결과를 보관(캐싱)하지 않도록 설정해두는 것이 편리함.
application.properties 파일에 아래 코드 추가
spring.thymeleaf.cache=false
SampleController 클래스 생성
@Controller
@RequestMapping("/sample")
@Log4j2
public class SampleController {
    @GetMapping("/ex1")
    public void ex1(){
        log.info("ex1...........");
    }
 }
ex1.html 생성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${'Hello World'}"></h1>
</body>
</html>
/sample/ex1 실행

SampleDTO 클래스 생성
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder(toBuilder = true)
public class SampleDTO {
    private Long sno;
    private String first;
    private String last;
    private LocalDateTime regTime;
}🤍타임리프 기본 사용법 (1) 반복문
 @GetMapping("/ex2")
    public void exModel(Model model){
        List<SampleDTO> list = IntStream.rangeClosed(1,20).asLongStream().mapToObj(i->{
            SampleDTO dto = SampleDTO.builder().sno(i).first("First.."+i).last("Last.."+i).regTime(LocalDateTime.now()).build();
            return dto;
        }).collect(Collectors.toList());
        //SampleDTO 타입의 객체를 20개 추가하고 이를 Model에 담아 전송함.
        model.addAttribute("list",list);
    }
Thymeleaf에서 반복문은 th:each라는 속성을 이용함.
th:each = "변수 : ${목록}"
ex2.html 파일 생성
- <li> 태그 내에 dto라는 변수를 만들고 컨트롤러 Model로 전달된 데이터는 ${list}를 이용해서 처리함.
- [[ ]] : 인라인 표현식으로 별도의 태그 속성으로 지정하지 않고 사용할 때 유용하게 쓸 수 있다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="dto:${list}">
            [[${dto}]]
        </li>
    </ul>
</body>
</html>
/sample/ex2 실행 결과

✅반복문의 상태(state) 객체
- 순번, 인덱스 번호, 홀수&짝수 등을 지정할 수 있음.
- index는 0부터 시작하고 count는 1부터 시작함.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="dto, state :${list}">
            [[${state.index}]] --- [[${dto}]]
        </li>
    </ul>
</body>
</html>
실행 결과를 보면 0부터 시작하는 번호가 추가된 것을 확인할 수 있다.

🤍타임리프 기본 사용법 (2) 제어문
타임리프의 제어문 처리는 th:if ~ unless 등을 이용할 수도 있고 삼항연산자 스타일을 사용할 수도 있음.
ex) sno의 값이 5의 배수인 것만 출력하고 싶을 때
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="dto, state :${list}" th:if="${dto.sno %5 ==0}">
           [[${dto}]]
        </li>
    </ul>
</body>
</html>
출력 결과

th:if와 th:unless를 이용하면 상황에 맞게 다른 내용을 출력할 수 있다.
ex)sno가 5로 나눈 나머지가 0인 경우 sno만을 출력하고 그렇지 않다면 SampleDTO의 first를 출력하고 싶을 때
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="dto, state :${list}">
            <span th:if="${dto.sno %5 ==0}" th:text="${'-------------'+dto.sno}"></span>
            <span th:unless="${dto.sno %5 ==0}" th:text="${dto.first}"></span>
        </li>
    </ul>
</body>
</html>
실행 결과

🤍타임리프 기본 사용법 (3) inline 속성
- 인라인 속성은 주로 자바스크립트 처리에서 유용함.
- 내부적으로 RedirectAttributes를 이용하여 /ex3으로 result와 dto라는 이름의 데이터를 심어서 전달함. result는 단순한 문자열이지만 dto는 SampleDTO의 객체임.
GetMapping({"/exInline"})
    public String exInline(RedirectAttributes redirectAttributes){
        log.info("exInline...........");
        SampleDTO dto = SampleDTO.builder().sno(100L).first("First..100").last("Last..100").regTime(LocalDateTime.now()).build();
        redirectAttributes.addFlashAttribute("result","success");
        redirectAttributes.addFlashAttribute("dto",dto);
        return "redirect:/sample/ex3";
    }
<script> 태그에 사용된 th:inline 속성이 핵심임.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${result}"></h1>
    <h1 th:text="${dto}"></h1>
    <script th:inline="javascript">
        var msg = [[${result}]];
        var dto = [[${dto}]];
    </script>
</body>
</html>
실행 결과를 보면
별도의 처리 없이 자바스크립트 부분이 만들어짐, dto는 JSON 포맷의 문자열이 된 것을 볼 수 있다.

🤍타임리프 기본 사용법 (4) 링크 처리
먼저 exModel()을 재사용하도록 아래와 같이 코드 수정.
  @GetMapping({"/ex2", "/exLink"})
    public void exModel(Model model){
        List<SampleDTO> list = IntStream.rangeClosed(1,20).asLongStream().mapToObj(i->{
            SampleDTO dto = SampleDTO.builder().sno(i).first("First.."+i).last("Last.."+i).regTime(LocalDateTime.now()).build();
            return dto;
        }).collect(Collectors.toList());
        //SampleDTO 타입의 객체를 20개 추가하고 이를 Model에 담아 전송함.
        model.addAttribute("list",list);
    }
exLink.html 생성
- @{ } 로 구성된 링크를 처리함.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="dto : ${list}">
            <a th:href="@{/sample/exView}">[[${dto}]]</a>
        </li>
    </ul>
</body>
</html>
실행 결과

🤍타임리프 기본 사용법 (5) 레이아웃
✅타임리프의 레이아웃 기능은 크게 2가지 방법이 있다.
- JSP의 include와 같이 특정 부분을 가져와서 포함하는 방식
- 특정 부분을 파라미터로 전달해서 내용에 포함하는 방식
SampleController 코드 작성
@GetMapping("/exLayout1")
public void exLayout1(){
    log.info("exLayout................");
}
fragment1.html 생성
특정 부분을 다른 내용으로 변경하려면 th:insert or th:replace를 사용할 수 있다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Fragment Test</h1>
    <h1>Layout 1 - 1</h1>
    <div th:replace="~{/fragments/fragment1 :: part1}"></div>
    <h1>Layout 1 - 2</h1>
    <div th:replace="~{/fragments/fragment1 :: part2}"></div>
    <h1>Layout 1 - 3</h1>
    <th:block th:replace="~{/fragments/fragment1 :: part3}"></th:block>
</body>
</html>
exLayout1.html 생성 (exLayout.thml 내용을 fragment1.html의 th:fragment에서 가져다 쓸 예정)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Fragment Test</h1>
    <h1>Layout 1 - 1</h1>
    <div th:replace="~{/fragments/fragment1 :: part1}"></div>
    <h1>Layout 1 - 2</h1>
    <div th:replace="~{/fragments/fragment1 :: part2}"></div>
    <h1>Layout 1 - 3</h1>
    <th:block th:replace="~{/fragments/fragment1 :: part3}"></th:block>
</body>
</html>
실행 결과

- th:replace를 이용할 때 '::' 뒤에는 fragment의 이름을 지정하거나 CSS의 #ID같은 선택자를 이용할 수 있다, 만약 '::' 이하를 생략하면 해당 파일 전체 내용을 가져올 수 있다.
fragment2.html 작성
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <hr/>
        <h2>Fragment2 File</h2>
        <h2>Fragment2 File</h2>
        <h2>Fragment2 File</h2>
        <hr/>
    </div>
</body>
</html>
fragment2.html 전체를 가져오도록 exLayout1.html 수정
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Fragment Test</h1>
    <div style="border: 1px solid blue">
    <th:block th:replace="~{/fragments/fragment2}"></th:block>
    </div>
    <h1>Layout 1 - 1</h1>
    <div th:replace="~{/fragments/fragment1 :: part1}"></div>
    <h1>Layout 1 - 2</h1>
    <div th:replace="~{/fragments/fragment1 :: part2}"></div>
    <h1>Layout 1 - 3</h1>
    <th:block th:replace="~{/fragments/fragment1 :: part3}"></th:block>
</body>
</html>
실행 화면

🤍타임리프 기본 사용법 (6) 레이아웃 템플릿 만들기
layout1.html 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="setContent(content)">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            width: 100vw;
            height: 20vh;
            background-color: aqua;
        }
        .content{
            width: 100vw;
            height: 70vh;
            background-color: lightgray;
        }
        .footer{
            width: 100vw;
            height: 10vh;
            background-color: green;
        }
    </style>
    <div class="header">
        <h1>HEADER</h1>
    </div>
    <div class="content">
        <th:block th:replace="${content}">
        </th:block>
    </div>
    <div class="footer">
        <h1>FOOTER</h1>
    </div>
</body>
</th:block>
</html>
exTemplate.html 작성
<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/layout1 :: setContent(~{this::content})}">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <th:block th:fragment="content">
        <h1>exTemplate Page</h1>
    </th:block>
</body>
</th:block>
</html>
컨트롤러 수정
@GetMapping({"/exLayout1", "/exLayout2","/exTemplate"})
    public void exLayout1(){
        log.info("exLayout................");
    }
실행 결과

🤍부트스트랩 템플릿 적용하기

basic.html 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="setContent(content)">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>Simple Sidebar - Start Bootstrap Template</title>
        <!-- Favicon-->
        <link rel="icon" type="image/x-icon" th:href="@{assets/favicon.ico}" />
        <!-- Core theme CSS (includes Bootstrap)-->
        <link th:href="@{/css/styles.css}" rel="stylesheet" />
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
        <script th:src="@{/js/scripts.js}"></script>
    </head>
    <body>
    <div class="d-flex" id="wrapper">
        <!-- Sidebar-->
        <div class="border-end bg-white" id="sidebar-wrapper">
            <div class="sidebar-heading border-bottom bg-light">Start Bootstrap</div>
            <div class="list-group list-group-flush">
                <a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Dashboard</a>
                <a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Shortcuts</a>
                <a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Overview</a>
                <a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Events</a>
                <a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Profile</a>
                <a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Status</a>
            </div>
        </div>
        <!-- Page content wrapper-->
        <div id="page-content-wrapper">
            <!-- Top navigation-->
            <nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
                <div class="container-fluid">
                    <button class="btn btn-primary" id="sidebarToggle">Toggle Menu</button>
                    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                    <div class="collapse navbar-collapse" id="navbarSupportedContent">
                        <ul class="navbar-nav ms-auto mt-2 mt-lg-0">
                            <li class="nav-item active"><a class="nav-link" href="#!">Home</a></li>
                            <li class="nav-item"><a class="nav-link" href="#!">Link</a></li>
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="#!">Action</a>
                                    <a class="dropdown-item" href="#!">Another action</a>
                                    <div class="dropdown-divider"></div>
                                    <a class="dropdown-item" href="#!">Something else here</a>
                                </div>
                            </li>
                        </ul>
                    </div>
                </div>
            </nav>
            <!-- Page content-->
            <div class="container-fluid">
                <th:block th:replace = "${content}"></th:block>
            </div>
        </div>
    </div>
</th:block>
</body>
</html>
컨트롤러 수정
@GetMapping({"/exLayout1", "/exLayout2","/exTemplate", "/exSidebar"})
    public void exLayout1(){
        log.info("exLayout................");
    }
exSidebar.html 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <th:block th:fragment="content">
        <h1>exSidebar Page</h1>
    </th:block>
</body>
</th:block>
</html>
실행 결과를 보면 레이아웃이 유지되는 상황에서 아래쪽에는 exSidebar.html의 내용을 그대로 출력하고 있는 것을 볼 수 있다.

'💻 my code archive > 🏷️JAVA & Spring(Boot)' 카테고리의 다른 글
| [스프링부트 블로그 만들기] 부트스트랩 적용하여 메인 화면 구현하기 (0) | 2022.03.17 | 
|---|---|
| [스프링부트 블로그 만들기] 테이블 생성하고 CRUD 테스트 하기 (0) | 2022.03.17 | 
| 스프링부트 공부기록(19) - MariaDB 설치, JPARepository 인터페이스 사용, 테스트 코드 CRUD 연습 (0) | 2022.03.16 | 
| 스프링부트 공부기록(18) - 프로젝트 준비, 컨트롤러 실습, jar 파일 실행해보기 (0) | 2022.03.16 | 
| [스프링부트 블로그 만들기] lombok 세팅 및 사용해보기 (0) | 2022.03.16 |