IT/Template Engine

Thymeleaf Fragment - 1 (Fragment 나누기)

sgoho01 2020. 5. 25. 15:33

이전 포스팅(Thymeleaf layout dialect)에서 타임리프를 사용하면서 공통 사용되는 레이아웃을 나누는

방법을 사용했었는데 이전 방법보다 조금더 간단하게 Fragment로 레이아웃을 나누는 방법을 알아보자.

 

 

Fragment로 레이아웃을 나누고 각 페이지에서 공통으로 사용되는 Fragment로 파라미터를 넘길수도 있다.

 

Thymeleaf Fragment - 1 : Fragment를 나누기
Thymeleaf Fragment - 2 : 파라미터 전달, 사용하기

 

 

 

Thymeleaf Fragment 나누기

개발환경은 Springboot + Thymeleaf를 사용하여 프로젝트를 만들었고

프로젝트에서 Thymeleaf를 사용하기위해 Thymeleaf 의존성 주입,

Web을 사용하기 위해 Spring-boot-starter-web 의존성을 주입받았다.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 

 

Fragment 구조는 아래와 같이 나누어 생성하였다.

  • header (html 헤더 영역)

  • nav (상단의 메뉴 영역)

  • footer (하단의 footer 영역)

디렉토리 구조

 

각 공통으로 사용되는 페이지는 fragments 디렉토리에 생성하고

다른 페이지에서 해당 fragment(공통부분)를 불러와서 사용 할 수 있도록 설정할것이다.

 

 

먼저 header.html에서는 페이지의 head 부분에서 해당하는 코드를 작성했다.

그리고 Thymeleaf를 사용한다는 선언으로 html 태그에 xmlns:th="http://www.thymeleaf.org"를 추가한다.

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

    <head th:fragment="fragment-header">
        <title>Thymeleaf Fragment</title>

        <!-- Bootstrap -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
        <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

    </head>

</html>

 

head 부분을 fragment로 선언하려면 head태그에 th:fragment="fragment명"을 작성해준다.

선언한 fragment명으로 다른 페이지에서 해당 fragment를 가져올 수 있다.

th:fragment는 해당 부분을 fragment로 선언한다는 의미

 

다른 페이지 화면에서는 이 선언한 fragment를 가져와보자.

 

 

index.html 에서 fragment-header를 가져오려면 th:replace로 fragment를 불러온다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <div th:replace="/fragments/header.html :: fragment-header"></div>
</head>
<body>

    Index Page
    
</body>
</html>

 

Index 페이지에서 공통으로 사용하기 위해  fragment로 선언해놓은 fragment-header를 가져오려면

head 부분에 th:replace="fragment 위치 :: fragment명" 을 호출하여 fragment를 불러올수 있다.

th:replace는 해당 태그에 fragment로 선언한 코드로 바꾼다는 의미

 

 

위 2가지 코드를 작성 후 실행한뒤 개발자 도구를 열어서 확인해보면 head부분에 fragment-header에서 작성한 코드를  가져온걸 확인 할 수 있다.

 

 

 

head와 같은 방식으로 footer와 nav도 작성해보자.

 

footer.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <footer th:fragment="fragment-footer" class="footer">
        <div class="row justify-content-center">
            <img class="mb-2" alt="" width="20" src="data:image/png;base64,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">
            <small class="d-block mb-3 ml-3 text-muted">&copy;GHSONG 2020</small>
        </div>
    </footer>
</html>

 

nav.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <div th:fragment="fragment-nav">
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <a class="navbar-brand" href="#">상단메뉴</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-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 mr-auto">
                    <li class="nav-item active">
                        <a class="nav-link" href="#" th:href="@{/index}">메인 <span class="sr-only">(current)</span></a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#" th:href="@{/index2}">설정</a>
                    </li>
                </ul>
                <form class="form-inline my-2 my-lg-0">
                    <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
                    <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
                </form>
            </div>
        </nav>
    </div>
</html>

 

공통으로 사용될 fragment를 선언 후 페이지에서 fragmet를 불러온다.

 

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <div th:replace="/fragments/header.html :: fragment-header"></div>
</head>
<body>

    <div th:replace="/fragments/nav.html :: fragment-nav"></div>

    <!-- 페이지 소스 -->
    <div class="jumbotron jumbotron-fluid">
        <div class="container">
            <h1 class="display-4">Index</h1>
            <p class="lead">ghsong tistory</p>
        </div>
    </div>
    <!-- 페이지 소스 -->

    <footer th:replace="/fragments/footer.html :: fragment-footer"></footer>

</body>
</html>

 

fragment로 화면을 구성하고 각 페이지의 소스를 작성하면 레이아웃을 나눌수 있다.

 

 

다른 페이지도 작성하여 비교해보자.

 

index2.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <div th:replace="/fragments/header.html :: fragment-header"></div>
    <style>
        .jumbotron {background-color: aqua !important;}
    </style>
</head>
<body>

    <div th:replace="/fragments/nav.html :: fragment-nav"></div>

    <div class="jumbotron jumbotron-fluid">
        <div class="container">
            <h1 class="display-4">Index2</h1>
            <p class="lead">ghsong tistory</p>
        </div>
    </div>

    <div th:replace="/fragments/footer.html :: fragment-footer"></div>

</body>
</html>

 

index2 페이지에서는 공통 head 부분 말고도 이 페이지에서만 사용할 코드를 넣었는데

head 부분에 fragment 선언부분 아래에 <style> 태그를 작성하면 해당 페이지에서만 사용할 style을 추가 할 수 있다.

이 style 태그는 index 페이지에서는 적용되지 않는다.

 

index.html 화면

 

index2.html 화면

 

 

이처럼 Thymeleaf를 사용할때 fragment를 사용하여 th:fragmet로 선언, th:replace로 호출하여 레이아웃을

보다 쉽게 나눌 수 있다.

 

git source : https://github.com/sgoho01/Thymeleaf-Fragments/tree/fragment