본문 바로가기

Programming/SpringBoot

[SpringBoot] 간단한 게시판 만들기 #7 - Thymeleaf를 사용한 Fornt view 구현

반응형

Thymeleaf는 View Template이다.

 

common.html

공통 부분을 따로 구현하여, 다른 HTML 파일에서 replace 하여 사용

<!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, shrink-to-fit=no"/>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"/>
    <link href="starter-template.css" th:href="@{/css/starter-template.css}" rel="stylesheet"/>
    <title th:text="${title}">Hello, Spring Boot!</title>
  </head>
  <body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:fragment="menu(menu)">
      <a class="navbar-brand" href="#">Board</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>

      <div class="collapse navbar-collapse" id="navbarsExampleDefault">
        <ul class="navbar-nav mr-auto">
          <li class="nav-item" th:classappend="${menu} == 'home'? 'active'">
            <a class="nav-link" href="#" th:href="@{/}">
              홈
              <span class="sr-only" th:if="${menu} == 'home'">(current)</span>
            </a>
          </li>
          <li class="nav-item" th:classappend="${menu} == 'board'? 'active'">
            <a class="nav-link" href="#" th:href="@{/board/list}">
              게시판
              <span class="sr-only" th:if="${menu} == 'board'">(current)</span>
            </a>
          </li>
        </ul>
      </div>
    </nav>
  </body>
</html>

 

 

detail.html

글 상세 보기 부분을 구현

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head th:replace="fragments/common :: head('detail.')">
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"/>
    <link href="starter-template.css" rel="stylesheet"/>

    <title>detail</title>
  </head>

  <body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('home')"></nav>

    <main role="main" class="container">
      <div class="starter-template">
      
        <table class ="table table-bordered">
          <thead>
            <caption>글 읽기</caption>
          </thead>
          <tbody>
            <tr>
              <th>제목</th>
              <td th:text="${boardDto.title}"></td>
            </tr>
            <tbody>
            <tr>
              <th>작성자</th>
              <td th:text="${boardDto.writer}"></td>
            </tr>
            <tr>
              <th>작성일</th>
              <td th:inline="text">[[${#temporals.format(boardDto.createdDate, 'yyyy-MM-dd HH:mm')}]]</td>
            </tr>
            <tr>
              <th>본문 내용</th>
              <td th:text="${boardDto.content}"></td>
            </tr>
          </tbody>
        </table>

        <!-- 수정/삭제 -->
        <div>
            <a th:href="@{'/board/post/edit/' + ${boardDto.id}}">
                <button class="btn btn-primary">수정</button>
            </a>
            <form id="delete-form" th:action="@{'/board/post/' + ${boardDto.id}}" method="post">
                <input type="hidden" name="_method" value="delete"/>
                <button class="btn btn-warning" id="delete-btn">삭제</button>
            </form>
        </div>

        <!-- 변수 셋팅 -->
        <script th:inline="javascript">
            /*<![CDATA[*/
            var boardDto = /*[[${boardDto}]]*/ "";
            /*]]>*/
        </script>

        <!-- script -->
        <script th:inline="javascript" th:src="@{/js/board.js}"></script>

      </div>
    </main>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" 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.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

 

 

list.html

게시판 글 내역들을 보는 페이지

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head th:replace="fragments/common :: head('게시판입니다.')">
    <!-- @{...}는 타임리프의 기본 링크 표현 구문 -->
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
    integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous" />
    <link href="css/starter-template.css" rel="stylesheet"/>

    <title>게시판입니다.</title>
  </head>

  <body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('home')"></nav>

    <main role="main" class="container">
      <div class="starter-template">

        <table class="table table-bordered">
            <!-- CONTENTS !-->
            <thead>
                <tr>
                    <th class="col-md-1">번호</th>
                    <th class="col-md-4">글제목</th>
                    <th class="col-md-3">작성자</th>
                    <th class="col-md-2">작성일</th>
                    <th class="col-md-2">수정일</th>
                </tr>
            </thead>

            <tbody>
            <!-- CONTENTS !-->
            <tr th:each="board : ${boardList}">
                <td th:text="${board.id}"></td>
                <td><a th:href="'/board/post/'+${board.id}" th:text="${board.title}"></a></td>
                <td th:text="${board.writer}"></td>
                <td th:text="${board.createdDate} ? ${#temporals.format(board.createdDate,'yyyy-MM-dd HH:mm')} : ${board.createdDate}"></td>
                <td th:text="${board.modifiedDate} ? ${#temporals.format(board.modifiedDate,'yyyy-MM-dd HH:mm')} : ${board.modifiedDate}"></td>
            </tr>
            </tbody>
        </table>
        <!-- Pagination -->
        <div style="margin: 10px">
            <span th:each="pageNum : ${pageList}" th:inline="text">
                <a th:href="@{'/board/list/?page=' + ${pageNum}}">[[${pageNum}]]</a>
            </span>
            <!-- 글쓰기 버튼 -->
            <a style="float: right" class="btn btn-primary" th:href="@{/board/post}">글쓰기</a>
        </div>

        <!-- 검색 form -->
        <form action="/board/search" method="GET">
            <div>
                <input name="keyword" type="text" placeholder="검색어를 입력해주세요.">
                <button class="btn btn-primary">검색하기</button>
            </div>

        </form>

      </div>
    </main>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" 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.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

 

 

update.html

'수정하기' 버튼을 클릭하면 나오는 페이지

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head th:replace="fragments/common :: head('detail')">
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"/>
    <link href="starter-template.css" rel="stylesheet"/>

    <script type="text/javascript" th:src="@{/js/board.js}"></script>

    <title>detail</title>
  </head>

  <body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('home')"></nav>

    <main role="main" class="container">
      <div class="starter-template">
        <form role="update-form" th:action="@{'/board/post/edit/' + ${boardDto.id}}" method="post">
          <input type="hidden" name="_method" value="put"/>
          <input type="hidden" name="id" th:value="${boardDto.id}"/>

          제목 : <input type="text" name="title" th:value="${boardDto.title}"> <br>
          작성자 : <input type="text" name="writer" th:value="${boardDto.writer}"> <br>
          <textarea name="content" th:text="${boardDto.content}"></textarea><br>

          <div class="box-footer">
            <button type="submit" class="btn btn-primary">수정</button>
            <button type="submit" class="btn btn-warning">취소</button>
          </div>
        </form>

      </div>
    </main>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" 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.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

 

 

write.html

글쓰기 페이지

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head th:replace="fragments/common :: head('게시판입니다.')">
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>

    <!-- Bootstrap CSS -->
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
      integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
      crossorigin="anonymous"
    />
    <link href="starter-template.css" rel="stylesheet" />

    <title>게시판입니다.</title>
  </head>

  <body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('home')"></nav>

    <main class="container">
      <div class="page-header">
        <h1>게시글 등록</h1>
      </div>
      <div class="starter-template">
           <form class="form-horizontal" th:action="@{/board/post}" method="post">
                <div class="form-group">
                    <label style="float: left" for="title" class="col-sm-2 control-label">제목</label>
                        <div class="col-sm-10" style="margin-top: 10px;">
                            <input type="title" class="form-control" id="title" name="title" placeholder="제목"/>
                    </div>
                </div>

                <div class="form-group">
                    <label style="float: left" for="writer" class="col-sm-2 control-label">작성자</label>
                        <div class="col-sm-10" style="margin-top: 10px;">
                            <input type="writer" class="form-control" id="writer" name="writer" placeholder="작성자"/>
                    </div>
                </div>

            <div class="form-group">
               <label style="float: left" for="content" class="col-sm-2 control-label">내용</label>
               <div class="col-sm-10">
                  <textarea class="form-control" id="content" name="content" placeholder="내용을 입력해 주세요."></textarea>
               </div>
            </div>

            <div class="btn_wrap text-center">
               <a th:href="@{/board/list}" class="btn btn-secondary">뒤로 가기</a>
               <button type="input" class="btn btn-primary waves-effect waves-light">저장 하기</button>
            </div>
           </form>
      </div>
    </main>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" 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.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

 

 

1. Index Page 접속

 

 

 

2. Google 로그인 접속

 

3. 게시판 접근

현재 아무런 게시글이 없는 상태이다.

 

'게시판' 클릭을 하게 되면 '/board/list' HTTP 경로로 접근한 뒤 Controller는 현재 BoardList를 Service를 통해 전달받은 뒤 해당 Data를 for loop을 돌아 thymeleaf 템플릿을 통해 보여줄 것이다.

 

4. 게시글 등록

'저장 하기'를 클릭하면 POST 메서드로 Controller에게 요청하게 되며, /board/post 경로로 HTTP 요청을 보내게 될 것이다.

 

Service에게 DTO를 전달하면서 요청을 하게 되며 Service는 DB에 데이터를 저장하게 된다.

 

후에 Controller는 /board/list로 리디렉션 처리를 하게 된다.

 

 

 

5. 페이지네이션

글은 총 6개가 있지만 Pagination을 통해 한 페이지당 4개의 글만 보이도록 되어 있다.

 

6. 게시글 내용 상세 보기

HTTP 요청으로 /board/post/{id}을 보내면 Controller는 해당 id에 해당하는 작성자, 제목, .. 등의 내용을 Service에게 요청하고, Service는 DB로부터 Data를 받아와서 Controller에게 전달한다.

 

Controller는 이를 model 객체를 통해 View로 전달해주게 된다.

 

7. '수정' 버튼을 클릭하면 수정할 수 있는 페이지가 생성된다.

 

8. '삭제' 버튼을 클릭하면 삭제할 수 있게 된다.

순정 상태로 돌아옴

 

 

9. DBeaver를 통해 User, Board에 데이터가 인입되는 것을 확인할 수 있다.

 

 

 

지금까지 간단하게 SpringBoot을 통해 게시판을 구현해보았다.

 

전반적인 MVC 개념 및 웹 서비스 개념을 잡기 좋았던 것 같고 Annotation을 통해 쉽게 웹 서비스를 만들 수 있었다.

반응형