Spring Boot Thymeleaf Layout Dialect 사용하기

STS Spring Boot Thymeleaf Layout 셋팅

Bootstrap 3 기반의 관리자 템플릿 중 gentelella 템플릿을 이용하여 Thymeleaf로 Layout을 잡아보겠습니다.

gentelella 템플릿

아래 화면을 top, left, footer, content로 나눕니다. img_03

gentelella 템플릿 다운로드

https://github.com/ColorlibHQ/gentelella 에서 초록색 Clone or download 버튼을 눌러서 zip파일로 다운받습니다.

탬플릿 확인

아래와 같은 구조로 되어있습니다. production 폴더에 있는 index.html 파일을 열어보면 전체적인 템플릿을 확인할 수 있습니다. img_04

프로젝트 생성

STS 기준으로 설명합니다.

단축키 Alt + Shift + N, Spring Starter Project 선택 img_01

JPA와 MySQL은 선택하지 않습니다. 선택했다면 주석처리하거나 application.properties에 관련 설정을 입력해줍니다. img_02

프로젝트가 생성될때까지 조금 기다리면 아래와 같은 구조의 프로젝트가 생성됩니다. img_05

application.properties 설정

캐시 기능은 잠시 꺼둡니다.

spring.thymeleaf.cache=false

이클립 Thymeleaf 플러그인 설치

개발 편의를 위해 아래 플러그인을 설치합니다. Thymeleaf - http://www.thymeleaf.org/eclipse-plugin-update-site/

thymeleaf-layout-dialect 라이브러리 추가

thymeleaf로 레이아웃을 설정하기위한 라이브러리 build.gradle에 추가 compile group: ‘nz.net.ultraq.thymeleaf’, name: ‘thymeleaf-layout-dialect’

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	compile group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect'
}

추가후 아래와 같이 build.gradle파일 우클릭후 그리과 같이 선택하여 라이브러리 추가 img_06

gentelella 템플릿 추가 및 최종 thymeleaf 템플릿 확인

src/main/resources/static 폴더아래 압축해제한 gentelella 템플릿 파일을 복사합니다. static 폴더는 spring boot에서 정적 자원이 위치하는 폴더입니다. src/main/resources/templates 폴더는 동적인 자원이 위치하는 폴더입니다. 아래 이미지가 최종 완성된 형태의 구조입니다.

폴더 생성

templates 폴더 아래 fragments, layout, sample 3개 폴더를 생성합니다.

전체 레이아웃(화면)

production/plain_page.html 파일을 브라우져로 열어보면 아래와 같은 화면이 열립니다. 아래 화면을 기준으로 작업을 시작해 보겠습니다. img_08

Controller

간단한 Controller를 생성합니다. plain.html파일을 보여줍니다.

package com.pet.sample.cmm.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SampleController {

    @GetMapping("/sample/plain")
    public void plain() {

        System.out.println("/sample/plain");
    }
}

각 페이지 소스

layout.html

페이지 전체에서 사용되는 공통 레이아웃입니다. plain_page.html 파일 내용 전체를 붙여넣고 단계별로 각 파일을 분리합니다.

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

    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <!-- Meta, title, CSS, favicons, etc. -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- title -->
        <title layout:title-pattern="$CONTENT_TITLE :: $LAYOUT_TITLE">sample</title>
        <!-- /title -->

        <!-- Bootstrap -->
        <link th:href="@{/vendors/bootstrap/dist/css/bootstrap.min.css}" rel="stylesheet">
        <!-- Font Awesome -->
        <link th:href="@{/vendors/font-awesome/css/font-awesome.min.css}" rel="stylesheet">
        <!-- NProgress -->
        <link th:href="@{/vendors/nprogress/nprogress.css}" rel="stylesheet">

        <!-- Custom Theme Style -->
        <link th:href="@{/build/css/custom.min.css}" rel="stylesheet">

        <!-- 사용자 스타일 영역 -->
        <th:block layout:fragment="userStyle"></th:block>
    </head>

<body class="nav-md">
    <div class="container body">
        <div class="main_container">

            <!-- left menu -->
            <div th:replace="~{fragments/left::leftFragment}"></div>
            <!-- left menu -->

            <!-- top navigation -->
            <div th:replace="~{fragments/top::topFragment}"></div>
            <!-- /top navigation -->

            <!-- page content -->
            <div th:replace="~{fragments/content::contentFragment}"></div>
            <!-- /page content -->

            <!-- footer content -->
            <footer th:replace="~{fragments/footer::footerFragment}"></footer>
            <!-- /footer content -->

        </div>
    </div>

        <!-- jQuery -->
        <script th:src="@{/vendors/jquery/dist/jquery.min.js}"></script>
        <!-- Bootstrap -->
        <script th:src="@{/vendors/bootstrap/dist/js/bootstrap.min.js}"></script>
        <!-- FastClick -->
        <script th:src="@{/vendors/fastclick/lib/fastclick.js}"></script>
        <!-- NProgress -->
        <script th:src="@{/vendors/nprogress/nprogress.js}"></script>

        <!-- Custom Theme Scripts -->
        <script th:src="@{/build/js/custom.min.js}"></script>

        <!-- 사용자 스크립트 영역 -->
        <th:block layout:fragment="userScript"></th:block>

</body>
</html>

left.html

layout.html 에서 left 메뉴 부분을 잘라서 생성했습니다.

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

<div class="col-md-3 left_col" th:fragment="leftFragment">
    <div class="left_col scroll-view">
        <div class="navbar nav_title" style="border: 0;">
            <a href="index.html" class="site_title"><i class="fa fa-paw"></i> <span>Gentelella Alela!</span></a>
        </div>

        <div class="clearfix"></div>

        <!-- menu profile quick info -->
        <div class="profile clearfix">
            <div class="profile_pic">
                <img th:src="@{production/images/img.jpg}" alt="..." class="img-circle profile_img">
            </div>
            <div class="profile_info">
                <span>Welcome,</span>
                <h2>John Doe</h2>
            </div>
            <div class="clearfix"></div>
        </div>
        <!-- /menu profile quick info -->

        <br />

        <!-- sidebar menu -->
        <div id="sidebar-menu" class="main_menu_side hidden-print main_menu">
            <div class="menu_section">
                <h3>General</h3>
                <ul class="nav side-menu">
                    <li><a><i class="fa fa-home"></i> Home <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="index.html">Dashboard</a></li>
                            <li><a href="index2.html">Dashboard2</a></li>
                            <li><a href="index3.html">Dashboard3</a></li>
                        </ul></li>
                    <li><a><i class="fa fa-edit"></i> Forms <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="form.html">General Form</a></li>
                            <li><a href="form_advanced.html">Advanced Components</a></li>
                            <li><a href="form_validation.html">Form Validation</a></li>
                            <li><a href="form_wizards.html">Form Wizard</a></li>
                            <li><a href="form_upload.html">Form Upload</a></li>
                            <li><a href="form_buttons.html">Form Buttons</a></li>
                        </ul></li>
                    <li><a><i class="fa fa-desktop"></i> UI Elements <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="general_elements.html">General Elements</a></li>
                            <li><a href="media_gallery.html">Media Gallery</a></li>
                            <li><a href="typography.html">Typography</a></li>
                            <li><a href="icons.html">Icons</a></li>
                            <li><a href="glyphicons.html">Glyphicons</a></li>
                            <li><a href="widgets.html">Widgets</a></li>
                            <li><a href="invoice.html">Invoice</a></li>
                            <li><a href="inbox.html">Inbox</a></li>
                            <li><a href="calendar.html">Calendar</a></li>
                        </ul></li>
                    <li><a><i class="fa fa-table"></i> Tables <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="tables.html">Tables</a></li>
                            <li><a href="tables_dynamic.html">Table Dynamic</a></li>
                        </ul></li>
                    <li><a><i class="fa fa-bar-chart-o"></i> Data Presentation <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="chartjs.html">Chart JS</a></li>
                            <li><a href="chartjs2.html">Chart JS2</a></li>
                            <li><a href="morisjs.html">Moris JS</a></li>
                            <li><a href="echarts.html">ECharts</a></li>
                            <li><a href="other_charts.html">Other Charts</a></li>
                        </ul></li>
                    <li><a><i class="fa fa-clone"></i>Layouts <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="fixed_sidebar.html">Fixed Sidebar</a></li>
                            <li><a href="fixed_footer.html">Fixed Footer</a></li>
                        </ul></li>
                </ul>
            </div>
            <div class="menu_section">
                <h3>Live On</h3>
                <ul class="nav side-menu">
                    <li><a><i class="fa fa-bug"></i> Additional Pages <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="e_commerce.html">E-commerce</a></li>
                            <li><a href="projects.html">Projects</a></li>
                            <li><a href="project_detail.html">Project Detail</a></li>
                            <li><a href="contacts.html">Contacts</a></li>
                            <li><a href="profile.html">Profile</a></li>
                        </ul></li>
                    <li><a><i class="fa fa-windows"></i> Extras <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="page_403.html">403 Error</a></li>
                            <li><a href="page_404.html">404 Error</a></li>
                            <li><a href="page_500.html">500 Error</a></li>
                            <li><a href="plain_page.html">Plain Page</a></li>
                            <li><a href="login.html">Login Page</a></li>
                            <li><a href="pricing_tables.html">Pricing Tables</a></li>
                        </ul></li>
                    <li><a><i class="fa fa-sitemap"></i> Multilevel Menu <span class="fa fa-chevron-down"></span></a>
                        <ul class="nav child_menu">
                            <li><a href="#level1_1">Level One</a>
                            <li><a>Level One<span class="fa fa-chevron-down"></span></a>
                                <ul class="nav child_menu">
                                    <li class="sub_menu"><a href="level2.html">Level Two</a></li>
                                    <li><a href="#level2_1">Level Two</a></li>
                                    <li><a href="#level2_2">Level Two</a></li>
                                </ul></li>
                            <li><a href="#level1_2">Level One</a></li>
                        </ul></li>
                    <li><a href="javascript:void(0)"><i class="fa fa-laptop"></i> Landing Page <span class="label label-success pull-right">Coming Soon</span></a></li>
                </ul>
            </div>

        </div>
        <!-- /sidebar menu -->

        <!-- /menu footer buttons -->
        <div class="sidebar-footer hidden-small">
            <a data-toggle="tooltip" data-placement="top" title="Settings"> <span class="glyphicon glyphicon-cog" aria-hidden="true"></span>
            </a> <a data-toggle="tooltip" data-placement="top" title="FullScreen"> <span class="glyphicon glyphicon-fullscreen" aria-hidden="true"></span>
            </a> <a data-toggle="tooltip" data-placement="top" title="Lock"> <span class="glyphicon glyphicon-eye-close" aria-hidden="true"></span>
            </a> <a data-toggle="tooltip" data-placement="top" title="Logout" href="login.html"> <span class="glyphicon glyphicon-off" aria-hidden="true"></span>
            </a>
        </div>
        <!-- /menu footer buttons -->
    </div>
</div>

</html>

top.html

layout.html 에서 top 부분을 잘랐습니다.

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

<div class="top_nav" th:fragment="topFragment">
    <div class="nav_menu">
        <nav>
            <div class="nav toggle">
                <a id="menu_toggle"><i class="fa fa-bars"></i></a>
            </div>

            <ul class="nav navbar-nav navbar-right">
                <li class=""><a href="javascript:;" class="user-profile dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <img th:src="@{/production/images/img.jpg}" alt="">John Doe <span
                        class=" fa fa-angle-down"></span>
                </a>
                    <ul class="dropdown-menu dropdown-usermenu pull-right">
                        <li><a href="javascript:;"> Profile</a></li>
                        <li><a href="javascript:;"> <span class="badge bg-red pull-right">50%</span> <span>Settings</span>
                        </a></li>
                        <li><a href="javascript:;">Help</a></li>
                        <li><a href="login.html"><i class="fa fa-sign-out pull-right"></i> Log Out</a></li>
                    </ul></li>

                <li role="presentation" class="dropdown"><a href="javascript:;" class="dropdown-toggle info-number" data-toggle="dropdown" aria-expanded="false"> <i class="fa fa-envelope-o"></i>
                        <span class="badge bg-green">6</span>
                </a>
                    <ul id="menu1" class="dropdown-menu list-unstyled msg_list" role="menu">
                        <li><a> <span class="image"><img src="images/img.jpg" alt="Profile Image" /></span> <span> <span>John Smith</span> <span class="time">3 mins ago</span>
                            </span> <span class="message"> Film festivals used to be do-or-die moments for movie makers. They were where... </span>
                        </a></li>
                        <li><a> <span class="image"><img src="images/img.jpg" alt="Profile Image" /></span> <span> <span>John Smith</span> <span class="time">3 mins ago</span>
                            </span> <span class="message"> Film festivals used to be do-or-die moments for movie makers. They were where... </span>
                        </a></li>
                        <li><a> <span class="image"><img src="images/img.jpg" alt="Profile Image" /></span> <span> <span>John Smith</span> <span class="time">3 mins ago</span>
                            </span> <span class="message"> Film festivals used to be do-or-die moments for movie makers. They were where... </span>
                        </a></li>
                        <li><a> <span class="image"><img src="images/img.jpg" alt="Profile Image" /></span> <span> <span>John Smith</span> <span class="time">3 mins ago</span>
                            </span> <span class="message"> Film festivals used to be do-or-die moments for movie makers. They were where... </span>
                        </a></li>
                        <li>
                            <div class="text-center">
                                <a> <strong>See All Alerts</strong> <i class="fa fa-angle-right"></i>
                                </a>
                            </div>
                        </li>
                    </ul></li>
            </ul>
        </nav>
    </div>
</div>

</html>

content.html

layout.html 에서 content 부분을 잘랐습니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">

<div class="right_col" role="main" th:fragment="contentFragment">
    <div class="">
        <div class="page-title">

            <!-- content title -->
            <div class="title_left" layout:fragment="contentTitle">
                <h3>sample content title</h3>
            </div>
            <!-- /content title -->

            <div class="title_right">
                <div class="col-md-5 col-sm-5 col-xs-12 form-group pull-right top_search">
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="Search for..."> <span class="input-group-btn">
                            <button class="btn btn-default" type="button">Go!</button>
                        </span>
                    </div>
                </div>
            </div>
        </div>

        <div class="clearfix"></div>

        <div class="row">

        <!-- content body -->
            <div class="col-md-12 col-sm-12 col-xs-12" layout:fragment="contentBody">
                여기에 본문 입력
            </div>
        <!-- content body -->

        </div>
    </div>
</div>

footer.html

layout.html 에서 footer부분을 잘랐습니다.

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

<footer th:fragment="footerFragment">
    <div class="pull-right">
        Gentelella - Bootstrap Admin Template by <a href="https://colorlib.com">Colorlib</a>
    </div>
    <div class="clearfix"></div>
</footer>

</html>

plain.html

실제 개발하는 소스입니다. 위에서 만든 레이아웃에 layout:fragment 이름이 같은 위치에 실제 소스내용이 반영됩니다.

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/layout}">

<head>

    <!-- title -->
    <title>plain</title>
    <!-- /title -->

    <!-- 사용자 style -->
    <th:block layout:fragment="userStyle">
        <style>
        </style>
    </th:block>
    <!-- /style -->

</head>

<body>

    <!-- content title -->
    <div class="title_left" layout:fragment="contentTitle">
        <h3>Plain Page2</h3>
    </div>
    <!-- /content title -->


    <!-- content body -->
    <div class="col-md-12 col-sm-12 col-xs-12" layout:fragment="contentBody">
        plain page
    </div>
    <!-- /content body -->


    <!-- 사용자 script -->
    <th:block layout:fragment="userScript">
        <script>
        </script>
    </th:block>
    <!-- /script -->

</body>
</html>