본문 바로가기
IT/Template Engine

Thymeleaf Fragment - 1 (Fragment 나누기)

by sgoho01 2020. 5. 25.

이전 포스팅(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

 

댓글