끊임없이 검증하라

나에게 당연할지라도

Project

P1_게시판 프로젝트_3_thymeleaf layout

fadet 2022. 4. 25. 19:42

* 이 포스트는 전 배달의민족, 현재 인프런에 계시고 유튜브 개발바닥의 크리에이터이신 개발자 이동욱님의 '스프링부트와 AWS로 혼자 구현하는 웹 서비스'를 기반으로 작성된 코드를 기반으로 진행중인 프로젝트에 대한 글임을 알립니다. 포스트 맨 아래에 관련 링크가 있습니다. 

책 부분이 끝난 후부터는 코드를 커스텀하는 과정을 포스팅합니다.
책 부분이 궁금하시면 ready부터 보시길 추천합니다.
참고 : https://github.com/kth1017/S1

 

이번에는 view 페이지들의 공통 부분을 layout으로 묶어 템플릿 분리를 먼저 진행하고 이어서 css 파일도 분리하겠습니다.

 

1. thymeleaf layout

#템플릿 분리 설명

- thymeleaf의 템플릿 분리 기능은 크게 fragment와 layout으로 나눌 수 있습니다. 예시를 들어

이런 경우 공통 부분을 layoutFile으로 만들고 글1 부분, 글2 부분이 있는 공통 부분에 대해서는 layoutFile에서 'th:replace=변수'로 처리하고 html을 시작할 때 'th:fragment=변수'로 불러와서 렌더링시키면 됩니다. 우측의 서로 다른 부분은 index1, index2에 각각 작성해놓으면 됩니다. 코드를 보면

// layoutFile
<!DOCTYPE HTML>
<html th:fragment="layout (head, main)" xmlns:th="http://www.thymeleaf.org">
<head th:replace="${head}">

 

// index1, index2
<!DOCTYPE HTML>
<html th:replace="~{layout/layoutFile :: layout(~{::head}, ~{::main})}" xmlns:th="http://www.thymeleaf.org">
<head>

 

위의 경우 head부분과 main을 제외하고 다 공통 처리를 하는 코드입니다. 위처럼 주로 타임리프 태그를 html 태그 안에 작성합니다.

 

두 번째는 두 페이지의 내용이 거의 일치하지 않는데 공통 부분이 하나 있을 경우 위처럼 layout으로 관리하기 적절하지 않습니다. 이럴때는 fragment인 템플릿 조각을 주로 사용합니다. 공통 부분을 fragmentFile에서 common 조각으로 작성해두고 index1, index2에서 공통 처리할 부분에 replace 처리하면 됩니다.

// fragmentFile
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="common">
...
</div>
// index1, index2
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:insert="~{template/fragment/footer :: common}">
...
</div>

위 코드에선 replace가 아닌 insert를 여기서 사용했는데 두 태그의 기능은 같고 단지 insert는 <div> 아래 새로운 태그를 만들고 replace는 <div> 태그 자체가 바뀌어 렌더링되는겁니다.

 

2. layoutFile.html

제 경우는 header, content, footer 세 구획으로 나누고 content빼고 나머지는 공통처리 할 예정입니다. 과거 div를 써서 나누기도 했지만 최근에는 SEO나 다른 요소도 고려해서 html5 문법을 지키는게 낫다고 합니다. 위에서 소개한 방법 중 layoutFile을 만들어 템플릿을 분리하는 것으로 하겠습니다. 

<!DOCTYPE HTML>
<html th:fragment="layout (head, main)" xmlns:th="http://www.thymeleaf.org">
<head th:replace="${head}">
    <title>fadet's project Home</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<header>
    <h1 class="title"><a href="/">fadet의 프로젝트 페이지입니다</a></h1>
    <nav>
            <span style="font-family: 'Nanum Gothic', sans-serif;" th:if="${userName} != null"><span id="user">[[${userName}]]</span>(으)로 로그인 중입니다
            <a href="/logout" class="btn btn-outline-light" role="button">Logout</a>
            </span>
        <ul th:if="${userName} == null">
            <li>login as</li>
            <li><a href="/oauth2/authorization/google"><img width="50px" height="50px" src="img/logo/google.png" /></a></li>
            <li><a href="/oauth2/authorization/naver"><img width="50px" height="50px" src="img/logo/naver.png" /></a></li>
        </ul>
    </nav>
</header>
<main th:replace="${main}">
    <div>분할 영역</div>
</main>
<footer>
    <h2>링크</h2>
    <table style="margin-left: 3%">
        <tr>
            <td class="img"><img width="30px" height="30px" src="img/logo/nortion.png" /></td>
            <td class="text"><a target="_blank" href="https://bit.ly/3rlwOH7">bit.ly/3rlwOH7</a></td>
        </tr>
        ...
    </table>
</footer>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

<!--index.js 추가-->
<script src="/js/app/index.js"></script>
</body>
</html>

 

3 index.html

<!DOCTYPE HTML>
<html th:replace="~{layout/layoutFile :: layout(~{::head}, ~{::main})}" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>fadet's project Home</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" th:href="@{/css/index.css}"/>
</head>
<body>
    <header>index header</header>
    <main>
        <p><br></p>
        <div style="display: flex">
            <h2 style="margin-right: 10px">게시글</h2><a href="/posts-list role="button" class="btn btn-outline-light">글목록 이동</a>
        </div>
            <p><br></p>
            <div class="row">
                <div class="col-sm-6">
                    <table class="table table-hover" style="color: white">
                        <thead>
                        <tr>
                            <th style="width: 3%;">No</th>
                            <th style="width: 10%">제목</th>
                            <th style="width: 5%;">작성자</th>
                            <th style="width: 10%;">최종수정일</th>
                        </tr>
                        </thead>
                        <tbody id="tbody">
                        <tr th:each="post : ${posts}">
                            <td style="width: 3%;">[[${post.id}]]</td>
                            <td class="bold" style="width: 10%"><a th:href="@{/posts/update/{postId}(postId=${post.id})}">[[${post.title}]]</a></td>
                            <td style="width: 5%">[[${post.author}]]</td>
                            <td style="width: 10%">[[${post.modifiedDate}]]</td>
                        </tr>
                        </tbody>
                    </table>
                </div>
            </div>
    </main>
    <footer>index footer</footer>
</body>
</html>

맨 처음 설명한 내용만 읽어보셨으면 어려운 내용은 없을 것이라 생각합니다.

 

3 index.css

#css 분리

- 코드를 보시다보면 head부분은 공통 처리하지 않았는데, 이는 페이지마다 다른 css파일을 받기 위해서 그렇습니다. html에 css를 남겨두면 매우 지저분하기때문에 css파일을 분리한 후 html이 불러오고 또한 추후 페이지를 추가했을때 서로 다른 css 파일을 쓰게끔 할 예정입니다. 

 

#font

- css에서 폰트의 경우 import url로 처리했습니다.

@import url('https://fonts.googleapis.com/css2?family=Nanum+Gothic&family=Roboto+Condensed:wght@300&display=swap');
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+KR:wght@100&display=swap');
h1{
    display: inline-block; margin: 2rem;
    font-family: 'IBM Plex Sans KR', sans-serif; color: white;
}

 

#flex box

- header의 경우 float같은 전통적인 방법부터 grid 등 다른 방법도 존재하는데 저는 flex box를 쓰는게 가장 편한 것 같아서 사용했습니다. 다른 방법은 검색해보시면 많이 나오는 것 같네요.

header{
    background-color: rgba(0, 0, 0, 0.5);
    display: flex; justify-content: space-between; width: 100%; height: 120px;
}
nav{
    display: inline-block; vertical-align: bottom; margin: 2rem;
    color: white;

코드처럼 header를 display:flex; justify-content: space-between; 자식 요소인 h2, nav를 desplay: inline-block으로 작성하면

 

이렇게 사이에 공간을 두고 배치됩니다. 만약 메뉴바를 두실 분들도 이렇게 하시면 우측 로그인 이미지 대신 메뉴바가 들어갈겁니다.


 

이번 포스트까지 thymeleaf로 기본적인 view 틀을 작성했습니다. 다음 포스트부턴 페이징, 검색, 검증 기능을 추가할 예정입니다. 사실 이 부분 코딩할때 저는 view에서 글 목록 페이지를 하나 추가했었는데 다음 포스트에서 함께 소개하는 것이 좋을 것 같네요.

 

refer

이동욱님 블로그의 관련 포스트 : https://jojoldu.tistory.com/539?category=717427

개발바닥 유튜브 :https://www.youtube.com/channel/UCSEOUzkGNCT_29EU_vnBYjg

 

개발바닥

본격 세계최초 DEV 엔터테인먼트 토크쇼 두 스타트업 개발자의 요절복통 이야기 구독 안하면 장애남!!

www.youtube.com

이동욱님 github의 해당 repository : https://github.com/jojoldu/freelec-springboot2-webservice