관리 메뉴

bright jazz music

Thymeleaf 타임리프 기본 + Simple Sidebar 본문

Framework/Spring

Thymeleaf 타임리프 기본 + Simple Sidebar

bright jazz music 2022. 6. 28. 21:34
이 글은 구멍가게코딩단의 "코드로 배우는 스프링 부트 웹 프로젝트" 챕터 3의 내용을 다룬다.

챕터 4를 진행하려면 챕터 3가 선행되어야 하는 부분이 있기 때문이다.

난 그걸 모른 채로 챕터 4부터 시작했다. 

따라서 이 포스팅에서 중간에 챕터 3의 내용을 끼워 둔다.

 

 

 

 

//SampleController.java

package com.example.guestbook.controller;

import com.example.guestbook.dto.SampleDTO;
import javassist.tools.rmi.Sample;
import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Controller
@RequestMapping("/sample")
@Log4j2
public class SampleController {

    @GetMapping("/ex1")
    public void ex1(){
        log.info("this is ex1-----------");
    }

    @GetMapping({"/ex2", "/exLink"}) //value의 속성값을 {}로 설정. 하나 이상의 url 매핑가능.
    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());

                model.addAttribute("list", list);

        /*
        * SampleDTO 타입의 객체를 20개 추가하고 이를 Model에 담아서 전송
        *
        *         public String exModel(Model model) {
                        return "/sample/ex2"; 이렇게 하지 않아도 페이지에 바인딩 됨. 신기.


        * /exLink 경로에 관한 데이터는 exLink.html로 전달된다.
        * */


    }

    @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);
        //위 두 줄의 코드를 이용하여 result와 dto를 '/ex3'에 전달

        return "redirect:/sample/ex3";
    }

    @GetMapping({"/ex3"})
    public void ex3() {
        log.info("ex3");
    }

    @GetMapping({"/exLayout1", "/exLayout2", "/exTemplate", "/exSidebar"})
    public void exLayout1(){
        log.info("exLayout..........");
    }


}

 

 

 

<!--ex1.html-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- xmlns 설정은 코드완성을 도와준다. -->
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${'Hello World'}"></h1>

</body>
</html>

 

<!--ex2.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}">-->
        <li th:each="dto, state : ${list}">
            [[${state.index}]] --- [[${dto}]]
            <!--
                대괄호+대괄호: 인라인태그로 별도의 태그 속성으로 지정하지 않고 사용하고 싶을 때 사용.
                state : 상태 객체. 상태객체를 이용하면 순번이나 인덱스 번호, 홀/짝수 등을 지정 가능
                       상태객체에서는 index 또는 count라는 속성을 이용할 수 있다.
                        index는 0부터 시작하고 count는 1부터 시작한다는 것이 차이이다.
            -->
        </li>
    </ul>
    <ul>
        <li th:each="dto, state : ${list}">

            [[${state.count}]] --- [[${dto}]]
            <!--
                대괄호+대괄호: 인라인태그로 별도의 태그 속성으로 지정하지 않고 사용하고 싶을 때 사용.
                state : 상태 객체. 상태객체를 이용하면 순번이나 인덱스 번호, 홀/짝수 등을 지정 가능
                       상태객체에서는 index 또는 count라는 속성을 이용할 수 있다.
                        index는 0부터 시작하고 count는 1부터 시작한다는 것이 차이이다.
            -->

        </li>
    </ul>

    <ul>
        <li th:each="dto, state : ${list}" th:if="${dto.sno % 5 == 0}">
            [[${dto}]]

            <!--
            Thymeleaf의 제어문 처리는 th:if ~ unless 등을 이용할 수도 있고,
            삼항연산자 스타일을 사용할 수도 있다.
            -->
        </li>
    </ul>

    <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>

            <!--
            다른 언어들은 if~else를 하나의 묶음으로 처리하지만 Thymeleaf는 단독으로 처리한다.
            위의 코드는 (th:if)sno가 5의 배수인 경우 sno만을 출력하고,
            (th:unless)가 5의 배수가 아닌 경우 SampleDTO의 first만을 출력하라는 코드이다.
            -->
        </li>
    </ul>

    <ul>
        <li th:each="dto, state : ${list}" th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}">

            <!--
            타임리프는 단순 if와 같이 두개의 항만으로도 삼항연산자를 처리할 수 있다.
            위의 예제는 sno가 5의 배수인 경우 sno만을 출력하고 나머지는 first를 출력하는 예제이다.
            -->
        </li>
    </ul>

    <style>
        .target {
            background-color: red;
            /*
                이와 같은 방식을 적용하면 특정 조건에만 css 클래스를 지정하는 작업 등을 쉽게 할 수 있다.
                아래 코드는 sno가 5의 배수인 경우 특정한 css를 적용하는 예제이다.
            */
        }
    </style>
    <ul>
        <li th:each="dto, state : ${list}" th:class="${dto.sno % 5 == 0} ? 'target'" th:text="${dto}">
        </li>
    </ul>

    <ul>
        <th:block th:each="dto: ${list}">
            <li th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}"></li>
        </th:block>
        <!--
            th:block은 별도의 태그가 필요하지 않기 때문에 반드시 태그에 붙어서 th:text나 th:value 등을 써야 하는
            제약이 없다. 위 코드는 삼항연산자를 블록을 사용해서 나타낸 것이다.
            th:block은 실제 화면에서는 html로 처리되지 않기 때문에 위와 같이 루프 등을 별도로 처리하는 용도로 자주 사용됨.
        -->
    </ul>


</body>
</html>

 

 

 

 

 

<!--ex3.html-->

<!DOCTYPE html>
<html lang="en" xmlns="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>

    <!--
        inline 속성은 주로 javaScript 처리에 유용하다. 위에서는 속성값이 javascript로 지정되었다.
        브라우저에서 이 페이지를 개발자 모드로 확인하면 문자열은 자동으로 ""이 붙어 문자열이 된다.
        객체의 경우는 자동으로 그 속성이 Json 포맷에 문자열이 되어 들어간다.

        var msg = "success";
        var dto = {"sno":100,"first":"First...100","last":"Last...100","regTime":"2022-06-27T12:19:26.2162662"};

        위 코드를 자바스크립트 객체화 해서 사용하려면 JSON.parse(); 와 같은 형태로 ""를 추가해서 사용할 수 있다.
    -->


</body>
</html>

 

<!--exLayout1.html-->

<!--
    타임리프의 기능 중 특정 부분을 다른 내용으로 변경할 수 있는 것이 있음.
    th:insert와 th:replace임. th:include도 있었지만 폐기됨.

    th:replace : 기존 내용을 완전히 대체하는 방식
    th:insert : 기존 내용의 바깥쪽 태그는 그대로 유지하면서 추가되는 방식
-->

<!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:insert="~{/fragments/fragment1 :: part2}"></div>

    <h1>Layout 1 - 3</h1>
    <th:block th:replace="~{/fragments/fragment1 :: part3}"></th:block>

<!--
개발자 모드로 브라우저 페이지를 보면 th:insert를 이용하는 경우에는
<div> 태그 내에 다시 <div> 태그가 생성된 것을 확인 가능하다.
'th:replace'를 이용할 때 '::' 뒤에는 fragment의 이름을 지정하거나 css의 #id와 같은 선택자를 이용 가능

만약 :: 이하를 생략하면 해당 파일 전체 내용을 가져온다. 맨 위의 fragment2.html 참조
-->

</body>
</html>
<!-- fragment1.html

    타임리프의 기능 중 특정 부분을 다른 내용으로 변경할 수 있는 것이 있음.
    th:insert와 th:replace임. th:include도 있었지만 폐기됨.

    th:replace : 기존 내용을 완전히 대체하는 방식
    th:insert : 기존 내용의 바깥쪽 태그는 그대로 유지하면서 추가되는 방식식
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:fragment="part1">
        <h2>Part 1 내용</h2>
    </div>

    <div th:fragment="part2">
        <h2>Part 2 내용</h2>
    </div>

    <div th:fragment="part3">
        <h2>Part 3 내용</h2>
    </div>
</body>
</html>
<!--fragment2.html-->

<div>
  <hr/>
  <h2>Fragment2 File</h2>
  <h2>Fragment2 File</h2>
  <h2>Fragment2 File</h2>
  <hr/>
</div>

 

 

<!--
exLayout2.html

화면 구성과 관련된 기능은 없다.
대신 상단에 target을 사용할 때 파라미터 2개를 사용한다.-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:replace="~{/fragments/fragment3:: target(~{this:: #ulFirst}, ~{this:: #ulSecond})}">

    <!-- target에 들어갈 값-->
    <ul id="ulFirst">
        <li>AAA</li>
        <li>BBB</li>
        <li>CCC</li>
    </ul>

    <!-- target에 들어갈 값-->
    <ul id="ulSecond">
        <li>111</li>
        <li>222</li>
        <li>333</li>
    </ul>

</th:block>

 

<!--
fragment3.html

    기존의 jsp와 달리 Thymeleaf를 이용하면 특정한 태그를 파라미터처럼 전달해서
    다른 th:fragment에서 사용할 수 있다.
-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:fragment="target(first, second)">
    <!-- target 부분에는 first와 second라는 파라미터를 받을 수 있도록 구성.
            내부적으로는 th:block으로 표현.
            실제 target을 사용하는 작업은 exLayout2.html에서 진행-->
        <style>
            .c1 {
                background-color: red;
            }

            .c2 {
                background-color: blue;
            }
        </style>

        <div class="c1">
            <th:block th:replace = "${first}"></th:block>
        </div>

        <div class="c2">
            <th:block th:replace = "${second}"></th:block>
        </div>

    </div>
</body>
</html>

 

<!--exLink.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://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>
    <!-- 모든 링크가 /exView로 연결됨. sno와 같은 파라미터를 추가해 주기 위해서는 아래와 같이 써야함  -->

    <ul>
        <li th:each="dto : ${list}">
            <a th:href="@{/sample/exView(sno=${dto.sno})}">[[${dto}]]</a>
        </li>
    </ul>
    <!--  링크르 누르면 해당 링크의 sno가 표시됨. http://localhost:8080/sample/exView?sno=11  -->

    <ul>
        <li th:each="dto : ${list}">
            <a th:href="@{/sample/exView/{sno}(sno = ${dto.sno})}">[[${dto}]]</a>
        </li>
    </ul>
    <!--  /exLink/3 과 같이 sno를 path로 사용하려는 경우 http://localhost:8080/sample/exView/11 -->

    <!--
    Thymeleaf의 기본 객체와 LocalDateTime
    타임리프는 내부적으로 여러 종류의 basic objects를 지원한다. 이를 사용하여 문자, 숫자, 웹에서 쓰이는 파라미터,
    request, response, session 등을 응용할 수 있다.

    JSP에서 이러한 기능을 이용하려면 별도의 JSTL 설정이 필요하다. 그러나 타임리프는 #numbers나, #dates 등을
    별도의 설정 없이 사용할 수 있다. 예를 들어 화면에 출력하는 sno를 모두 5자리로 만들어야 하는 경우. 아래와 같다,
   -->
    <ul>
       <li th:each="dto : ${list}">
           [[${#numbers.formatInteger(dto.sno, 5)}]]
       </li>
    </ul>
    <!--
        내장객체들이 많은 기능을 지원하지만 java8의 LocalDate, LocalDateTime에 대해서는 복잡하다.
        따라서 https://github.com/thymeleaf/thymeleaf-extras-java8time 을 이용하자.
        이를 build.gradle에 추가해 줘야 한다. implementation()
    -->

    <ul>
        <li th:each="dto : ${list}">
            [[${dto.sno}]] ------ [[${#temporals.format(dto.regTime, 'yyyy/MM/dd')}]]
        </li>
    </ul>


</body>
</html>

<!--exTemplate.html-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:replace="~{/layout/layout1 :: setContent(~{this::content})}">
    <th:block th:fragment="content">
        <h1>exTemplate page 하하하</h1>
    </th:block>

</th:block>

 

 

<!--exSidebar.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}"> <!-- basic.html -->
    <th:block th:fragment="content">
        <h1>exSidebar Page</h1>
    </th:block>
</th:block>
<!--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="@{/dist/assets/favicon.ico}" />
    <!-- Core theme CSS (includes Bootstrap)-->
<!--    <link href="css/styles.css" rel="stylesheet" />-->
    <link th:href="@{/dist/css/styles.css}" rel="stylesheet" />
</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"> 실제로 전달되는 부분 -->
            <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>
<!--            <h1 class="mt-4">Simple Sidebar</h1>-->
<!--            <p>The starting state of the menu will appear collapsed on smaller screens, and will appear non-collapsed on larger screens. When toggled using the button below, the menu will change.</p>-->
<!--            <p>-->
<!--                Make sure to keep all page content within the-->
<!--                <code>#page-content-wrapper</code>-->
<!--                . The top navbar is optional, and just for demonstration. Just create an element with the-->
<!--                <code>#sidebarToggle</code>-->
<!--                ID which will toggle the menu when clicked.-->
<!--            </p>-->
        </div>
    </div>
</div>
<!-- Bootstrap core JS-->
<script th:src="@{https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js}"></script>
<!-- Core theme JS-->
<script th:src="@{js/scripts.js}"></script>
</body>
</th:block>
</html>

 

Comments