본문 바로가기
BackEnd/Project

[Board] Ch03. 게시글 페이지 기능 구현

by 개발 Blog 2024. 8. 10.

공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.

 

게시글 페이지 뷰 템플릿 수정

지난 시간에 게시판 페이지를 구현한 후, 이번에는 게시글 상세 페이지 뷰를 구현하였다.

이전에는 간단한 목업 데이터를 사용하여 정적인 HTML 페이지를 만들었지만, 이번에는 실제 DB에서 가져온 데이터를 동적으로 뿌리는 페이지를 만들었다. 뷰 템플릿은 Thymeleaf를 사용하여 동적으로 데이터를 바인딩한다.

 

수정 전 detail.html 코드

이전 버전의 detail.html 파일은 단순한 HTML 페이지로 구성되어 있었다. 데이터는 하드코딩된 정적인 값으로 채워졌으며, 페이지가 서버에서 동적으로 처리되지 않았다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="Eunchan Kim">
    <title>게시글 페이지</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <link href="/css/articles/article-content.css" rel="stylesheet">

</head>
<body>

<header id="header"></header>
    <main class="container">
        <header class="py-5 text-center">
            <h1>첫 번째 글</h1>
        </header>

        <div class="row g-5">
            <section class="col-md-5 col-lg-4 order-md-last">
                <aside>
                    <p><span>Eunchan</span></p>
                    <p><a href="mailto:eunchan@gmail.com">eunchan@mail.com</a></p>
                    <p><time datetime="2024-01-01T00:00:00">2024-01-01</time></p>
                    <p><span>#java</span></p>
                </aside>
            </section>

            <article class="col-md-7 col-lg-8">
                <pre>본문<br><br></pre>
            </article>
        </div>

        <div class="row g-5">
            <section>
                <form class="row g-3">
                    <div class="col-8">
                        <label for="comment-textbox" hidden>댓글</label>
                        <textarea class="form-control" id="comment-textbox" placeholder="댓글 쓰기.." rows="3"></textarea>
                    </div>
                    <div class="col-auto">
                        <label for="comment-submit" hidden>댓글 쓰기</label>
                        <button class="btn btn-primary" id="comment-submit" type="submit">쓰기</button>
                    </div>
                </form>

                <ul class="row col-7">
                    <li>
                        <div>
                            <strong>Eunchan</strong>
                            <small><time>2024-01-01</time></small>
                            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br>Lorem ipsum dolor sit amet</p>
                        </div>
                    </li>
                    <li>
                        <div>
                            <strong>Eunchan</strong>
                            <small><time>2024-01-01</time></small>
                            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br>Lorem ipsum dolor sit amet</p>
                        </div>
                    </li>
                </ul>

        </section>
        </div>

        <div class="row g-5">
            <nav aria-label="Page navigation example">
                <ul class="pagination">
                    <li class="page-item">
                        <a class="page-link" href="#" aria-label="Previous">
                            <span aria-hidden="true">&laquo; prev</span>
                        </a>
                    </li>
                    <li class="page-item">
                        <a class="page-link" href="#" aria-label="Next">
                            <span aria-hidden="true">next &raquo;</span>
                        </a>
                    </li>
                </ul>
            </nav>
        </div>
    </main>

    <footer id="footer"></footer>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>

</html>
  • article 태그 안에 본문 내용이 하드코딩된 상태로 <pre> 태그 안에 "본문"이라는 텍스트와 줄 바꿈만 포함되어 있었다.
  • 헤더와 푸터 또한 서버에서 동적으로 로딩되지 않고, 단순히 HTML 내에 포함되어 있는 정적인 구조였다.

수정 후 detail.html 코드

수정된 detail.html은 기존의 정적인 페이지를 Thymeleaf를 활용한 동적인 페이지로 변환한 것이다.

이 페이지는 실제 DB에서 데이터를 가져와 화면에 출력할 수 있도록 변경되었다.

<header id="header">
    헤더 삽입부
    <hr>
</header>
<main id="article-main" class="container">
    <header id="article-header" class="py-5 text-center">
        <h1>첫 번째 글</h1>
    </header>

	...
    
	<article id="article-content" class="col-md-7 col-lg-8">
    	<div style="white-space: pre-wrap;">본문<br><br></div>
	</article>
    
    ...
  • header와 main 태그는 이전과 동일하지만, 이 부분에 대해 Thymeleaf 템플릿을 통해 데이터를 동적으로 삽입할 수 있도록 준비되었다.
  • 본문 내용이 <pre> 태그에서 <div style="white-space: pre-wrap;">으로 변경되었다. 이를 통해 줄바꿈과 공백이 유지되면서도, 텍스트가 긴 경우 자동으로 줄바꿈이 적용될 수 있게 되었다. 또한, 서버에서 데이터를 받아와 동적으로 바인딩될 수 있도록 수정되었다.

수정 전 detail.th.xml 코드

<?xml version="1.0"?>
<thlogic>
    <attr sel = "#header" th:replace="header :: header"/>
    <attr sel = "#footer" th:replace="footer :: footer"/>
</thlogic>
  • 기능: 이 코드는 기본적인 템플릿 구조를 설정하는데 사용되었다. #header와 #footer에 대해 외부 템플릿 파일에서 각각의 헤더와 푸터를 가져와서 삽입하는 역할을 한다.
  • 한계: 이 코드에서는 단순히 헤더와 푸터만 동적으로 가져오는 역할을 하고, 본문이나 다른 동적인 데이터에 대한 처리가 없다.

수정 후 detail.th.xml 코드

<?xml version="1.0"?>
<thlogic>
    <attr sel="#header" th:replace="header :: header" />
    <attr sel="#footer" th:replace="footer :: footer" />

    <attr sel="#article-main" th:object="${article}">
        <attr sel="#article-header/h1" th:text="*{title}" />
        <attr sel="#nickname" th:text="*{nickname}" />
        <attr sel="#email" th:text="*{email}" />
        <attr sel="#created-at" th:datetime="*{createdAt}" th:text="*{#temporals.format(createdAt, 'yyyy-MM-dd HH:mm:ss')}" />
        <attr sel="#hashtag" th:text="*{hashtag}" />
        <attr sel="#article-content/div" th:text="*{content}" />

        <attr sel="#article-comments" th:remove="all-but-first">
            <attr sel="li[0]" th:each="articleComment : ${articleComments}">
                <attr sel="div/strong" th:text="${articleComment.nickname}" />
                <attr sel="div/small/time" th:datetime="${articleComment.createdAt}" th:text="${#temporals.format(articleComment.createdAt, 'yyyy-MM-dd HH:mm:ss')}" />
                <attr sel="div/p" th:text="${articleComment.content}" />
            </attr>
        </attr>

        <attr sel="#pagination">
            <attr sel="ul">
                <attr sel="li[0]/a"
                      th:href="*{id} - 1 <= 0 ? '#' : |/articles/*{id - 1}|"
                      th:class="'page-link' + (*{id} - 1 <= 0 ? ' disabled' : '')"
                />
                <attr sel="li[1]/a"
                      th:href="*{id} + 1 > ${totalCount} ? '#' : |/articles/*{id + 1}|"
                      th:class="'page-link' + (*{id} + 1 > ${totalCount} ? ' disabled' : '')"
                />
            </attr>
        </attr>
    </attr>
</thlogic>
  • 헤더와 푸터: 이전과 동일하게 #header와 #footer에 외부 템플릿을 삽입하는 역할을 수행한다.
  • 동적 데이터 바인딩:
    • #article-main에 바인딩된 객체: th:object="${article}"를 통해 article 객체를 바인딩하고, 해당 객체의 속성을 페이지 내에서 동적으로 사용할 수 있도록 설정한다.
    • 제목, 닉네임, 이메일 등: 각각 article 객체의 속성들이 th:text를 통해 HTML 요소에 바인딩된다.
    • 본문: 본문 내용이 th:text="*{content}"를 통해 바인딩되며, 스타일로 white-space: pre-wrap;이 적용된 div 태그 내에 출력된다. 이로 인해 줄 바꿈과 공백이 유지되면서 텍스트가 출력된다.
  • 댓글 및 페이징 처리:
    • 댓글: th:each를 사용해 articleComments 리스트 내의 댓글들을 순차적으로 출력하며, 각 댓글의 작성자 닉네임, 작성 시간, 댓글 내용 등을 바인딩한다.
    • 페이징 처리: th:href와 th:class를 통해 이전 페이지 및 다음 페이지 링크를 동적으로 구성하며, 첫 페이지 또는 마지막 페이지에서의 링크 비활성화 기능이 포함된다.

 

                                                                                      

 

게시판 서비스 뷰 기능 구현 작업이 완료되었다. 

게시글 페이지와 목록 페이지에서 동적으로 데이터를 표시할 수 있게 되었으며, 로그인 페이지는 스프링 시큐리티로 기본 기능이 구현된 상태이다. 이번 작업에서는 게시글 상세 페이지의 뷰를 개선하여 동적 데이터 바인딩을 구현했고, `white-space: pre-wrap;` 스타일로 본문의 줄 바꿈과 공백을 유지했다. 이를 통해 게시글의 다양한 정보를 유연하게 조회할 수 있게 되었다.