Thymeleaf란
- 회원가입을 진행하기 전에 먼저 메인 페이지를 만들어보도록 하겠습니다.
- Thymeleaf란 HTML, XML, JavaScript, CSS 및 일반 텍스트까지 처리할 수 있는 웹 및 독립 환경을 위한 서버 사이드 Java 템플릿 엔진입니다.
- JSP의 태그 라이브러리를 보면 브라우저가 이해할 수 없는 코드가 포함되어 있는 반면에, Thymeleaf는 브라우저가 이해할 수 있는 코드이기 때문에 퍼블리셔와 협업할 때도 좋은 시너지를 낼 수 있습니다.
- 또한 스프링 부트가 Thymeleaf를 지원하기 때문에 사용하기가 좋습니다.
- (출처: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html)
Thymeleaf 의존성 추가
- Thymeleaf를 사용하기 위해서는 의존성을 추가해야 합니다.
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
}
- Thymeleaf 같은 템플릿 엔진은
resources
폴더에 templates
폴더안에 생성합니다.
- JSP는 스프링 부트가 지원하지 않아서 따로
webapp > WEB-INF > views
라는 폴더를 만들지만 Thymeleaf는 스프링 부트가 지원해서 따로 폴더 생성 없이 templates
폴더안에 생성합니다.
static
폴더는 정작 파일들(html, css, js 등등)을 두는 곳입니다.
메인 화면 만들기
- Thymeleaf와 Bootstrap을 이용하여 만들어보도록 하겠습니다.
- Bootstrap은 반응형 웹을 쉽고 빠르게 만들 수 있는 도구입니다.
- 참고로 Community(무료버전)는
html
, css
, javascript
등을 지원해주지 않기 때문에 VSCode
를 이용하겠습니다.
- 이 부분은
html
, css
등은 알고 있다는 전제하에 만들어서 따로 코드 설명은 하지 않겠습니다.
- UI는 Bootstrap의 https://getbootstrap.com/docs/5.0/examples/ 의
Sticky footer with fixed navbar
을 참고하여 만들었습니다.
- 먼저 Bootstrap만으로 코드를 작성하겠습니다.
- 폴더 구조는 다음과 같습니다.
index.html
<!doctype html>
<html lang="en" class="h-100">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link href="./css/sticky-footer-navbar.css" rel="stylesheet">
<link rel="stylesheet" href="./css/style.css">
<title>블로그</title>
</head>
<body class="d-flex flex-column h-100">
<header>
<!-- Fixed navbar -->
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">내가 만든 블로그</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link" href="/user/login">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/user/save">회원가입</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</header>
<!-- Begin page content -->
<main class="flex-shrink-0">
<div class="container">
<div class="p-2"></div>
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<a href="#" class=".a-title">
<h3 class="mb-0 title" style="padding-bottom: 10px;">제목</h3>
</a>
<p class="card-text mb-auto">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia repellendus dolore quibusdam voluptatibus libero consectetur, autem assumenda quis accusamus ratione qui. Nobis sit cumque deleniti, facilis praesentium magni voluptates
perspiciatis!LoremLoremLoremLoremLoremLoremLoremLorem
</p>
<div class="mb-1 text-muted" style="padding-top: 15px;">날짜</div>
</div>
</div>
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<a href="#" class=".a-title">
<h3 class="mb-0 title" style="padding-bottom: 10px;">제목</h3>
</a>
<p class="card-text mb-auto">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia repellendus dolore quibusdam voluptatibus libero consectetur, autem assumenda quis accusamus ratione qui. Nobis sit cumque deleniti, facilis praesentium magni voluptates
perspiciatis!LoremLoremLoremLoremLoremLoremLoremLorem
</p>
<div class="mb-1 text-muted" style="padding-top: 15px;">날짜</div>
</div>
</div>
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<a href="#" class=".a-title">
<h3 class="mb-0 title" style="padding-bottom: 10px;">제목</h3>
</a>
<p class="card-text mb-auto">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia repellendus dolore quibusdam voluptatibus libero consectetur, autem assumenda quis accusamus ratione qui. Nobis sit cumque deleniti, facilis praesentium magni voluptates
perspiciatis!LoremLoremLoremLoremLoremLoremLoremLorem
</p>
<div class="mb-1 text-muted" style="padding-top: 15px;">날짜</div>
</div>
</div>
</div>
</main>
<footer class="footer mt-auto py-3 bg-light">
<div class="container">
<span class="text-muted">© azurealstn</span>
</div>
</footer>
<!-- Option 1: Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
html
안에 데이터들은 모두 정적 데이터라 제가 일단 지어놓았습니다. (어느정도 화면 구성을 알기 위해)
style.css
.card-text {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
white-space: normal;
overflow: hidden;
}
.a-title {
text-decoration: none;
color: #4e423b;
}
.a-title:hover {
color: #15751b;
text-decoration: underline;
}
- 내용에서 3줄이 넘어가면 ...으로 표시되게 css의 clamp를 이용해주었습니다.
- 나머지
style
은 본인에 맞게 해주시면 됩니다ㅎㅎ..
sticky-footer-navbar.css
/* Custom page CSS
-------------------------------------------------- */
/* Not required for template or sticky footer method. */
main > .container {
padding: 60px 15px 0;
}
화면
- 대충 요런 화면이 나옵니다.
- 이제 안에 검색, 회원가입, 로그인, 페이징 등등을 구현할 겁니다.
Thymeleaf 적용
- JSP를 사용할 때
<%@ include file="layout/header.jsp"%>
이런 코드를 해서 하나의 페이지안에 세분화 작업을 했습니다. (header, main, footer 이렇게..)
- Thymeleaf도
fragment
, replace
문법을 이용하여 똑같이 작업해주겠습니다.
- 폴더 구조는 다음과 같이 만들겠습니다.
- thymeleaf 공식 홈페이지에 보면 fragment 템플릿이 다음과 같습니다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>
- 이 부분에
header
태그를 가져오겠습니다.
header.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head(title)">
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link href="../static/sticky-footer-navbar.css" rel="stylesheet">
<link rel="stylesheet" href="../static/style.css">
<title th:text="${title}">블로그</title>
</head>
<body>
<header th:fragment="header">
<!-- Fixed navbar -->
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">내가 만든 블로그</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link" href="/user/login">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/user/save">회원가입</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</header>
</body>
</html>
<head th:fragment="head(title)">
head 태그 안에 반복되므로 적어줍니다.
- 여기서는 title 태그가 페이지마다 바뀌는데 이 때는 파라미터로 받아서 사용하시면 됩니다. title 태그 안에
<title th:text="${title}">
이렇게 적어주시면 파라미터로 받을 수 있습니다.
- 그 다음 반복되는 부분인 header 태그 안에다
<header th:fragment="header">
를 적어주시면 됩니다.
- 이제
index.html
에 돌아와 다음과 같이 고쳐줍니다.
index.html
<!doctype html>
<html lang="en" class="h-100">
<head th:replace="layout/header :: head ('블로그')"></head>
<body class="d-flex flex-column h-100">
<header th:replace="layout/header :: header"></header>
<!-- Begin page content -->
<main class="flex-shrink-0">
<div class="container">
<div class="p-2"></div>
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<a href="#" class="a-title">
<h3 class="mb-0 title" style="padding-bottom: 10px;">제목</h3>
</a>
<p class="card-text mb-auto">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia repellendus dolore quibusdam voluptatibus libero consectetur, autem assumenda quis accusamus ratione qui. Nobis sit cumque deleniti, facilis praesentium magni voluptates
perspiciatis!LoremLoremLoremLoremLoremLoremLoremLorem
</p>
<div class="mb-1 text-muted" style="padding-top: 15px;">날짜</div>
</div>
</div>
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<a href="#" class="a-title">
<h3 class="mb-0 title" style="padding-bottom: 10px;">제목</h3>
</a>
<p class="card-text mb-auto">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia repellendus dolore quibusdam voluptatibus libero consectetur, autem assumenda quis accusamus ratione qui. Nobis sit cumque deleniti, facilis praesentium magni voluptates
perspiciatis!LoremLoremLoremLoremLoremLoremLoremLorem
</p>
<div class="mb-1 text-muted" style="padding-top: 15px;">날짜</div>
</div>
</div>
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<a href="#" class="a-title">
<h3 class="mb-0 title" style="padding-bottom: 10px;">제목</h3>
</a>
<p class="card-text mb-auto">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia repellendus dolore quibusdam voluptatibus libero consectetur, autem assumenda quis accusamus ratione qui. Nobis sit cumque deleniti, facilis praesentium magni voluptates
perspiciatis!LoremLoremLoremLoremLoremLoremLoremLorem
</p>
<div class="mb-1 text-muted" style="padding-top: 15px;">날짜</div>
</div>
</div>
</div>
</main>
<footer class="footer mt-auto py-3 bg-light">
<div class="container">
<span class="text-muted">© azurealstn</span>
</div>
</footer>
<!-- Option 1: Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
- head 태그안에
<head th:replace="layout/header :: head ('블로그')"></head>
이렇게 적어주시고 뒤에 파라미터를 적어주시면 됩니다.
- header 태그안에
<header th:replace="layout/header :: header"></header>
이렇게 써주고, 그 안에 내용은 모두 지웠습니다.
th:replace
를 쓰고 그 안에 경로를 넣고, fragment 선언했던 header를 넣어주면 됩니다.
footer.html
도 똑같이 작업해줍니다.
footer.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body class="d-flex flex-column h-100">
<footer class="footer mt-auto py-3 bg-light" th:fragment="footer">
<div class="container">
<span class="text-muted">© azurealstn</span>
</div>
</footer>
</body>
</html>
index.html
<!doctype html>
<html lang="en" class="h-100" xmlns:th="http://www.thymeleaf.org">
<head>
...
<link th:href="@{./css/sticky-footer-navbar.css}" rel="stylesheet">
<link rel="stylesheet" th:href="@{./css/style.css}">
<title>블로그</title>
</head>
...
<footer th:replace="layout/footer :: footer"></footer>
</html>
- link 태그에도
th:href
문법을 사용해 th:href="@{./css/style.css}"
와 같이 변경해줍니다.
다음으로
- 이 파트는 좀... 코드가 보기 힘들 것 같고.. 따로 Thymeleaf, bootstrap을 간단하게 배워서 직접 UI를 짜는 게 더 효율적일 것 같다는 생각이 듭니다.
- Spring Boot와 Thymeleaf 적용에 대한 좋은 유튜브 강의가 있으니 이를 참고해주세요. (좋은 영상)
- 역시 테스트 작성한게 없지만 마무리는 테스트 전체 실행해주시고, 문제가 없다면 정말 다음 번에 회원 가입을 진행해보도록 하겠습니다.
References
댓글