[Spring] 스프링 부트를 이용한 게시판 - View(+타임리프)

전주은·2023년 1월 14일
0
post-thumbnail

View 구현

👀들어가기 전에..

게시글 리스트

header.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <header class="p-3 bg-dark text-white">
        <div class="container">
            <div class="d-flex flex-wrap align-items-center justify-content-lg-start">
                <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                    <li><a id="home" href="#" class="nav-link px-2 text-secondary">Home</a></li>
                    <li><a id="hashtag" href="#" class="nav-link px-2 text-secondary">HashTags</a></li>
                </ul>

                <div class="text-end">
                    <span id="username" class="text-white me-2">username</span>
                    <a role="button" id="login" type="button" class="btn btn-outline-light me-2">Login</a>
                    <a role="button" id="logout" type="button" class="btn btn-outline-light me-2">Logout</a>
                </div>
            </div>
        </div>
    </header>
</body>
</html>

❗❗html과 th.xml 이름을 꼭 맞춰줘야한다!❗❗

index.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="Ryuzy">
    <title>게시판 페이지</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
    <link href="/css/search-bar.css" rel="stylesheet">
    <link href="/css/table-header.css" rel="stylesheet">
</head>

<body>

<header id="header">
    헤더 삽입
    <hr>
</header>

    <main class="container">
        <div class="row">
            <div class="card card-margin search-form">
                <div class="card-body p-0">
                    <form method="get" id="search-form">
                        <div class="row">
                            <div class="col-12">
                                <div class="row no-gutters">
                                    <div class="col-lg-3 col-md-3 col-sm-12 p-0">
                                        <label for="search-type" hidden>검색 유형</label>
                                        <select class="form-control" id="search-type" name="searchType">
                                            <option>제목</option>
                                            <option>본문</option>
                                            <option>id</option>
                                            <option>닉네임</option>
                                            <option>해시태그</option>
                                        </select>
                                    </div>
                                    <div class="col-lg-8 col-md-6 col-sm-12 p-0">
                                        <label for="search-value" hidden>검색어</label>
                                        <input type="text" placeholder="검색어..." class="form-control" id="search-value" name="searchValue">
                                    </div>
                                    <div class="col-lg-1 col-md-3 col-sm-12 p-0">
                                        <button type="submit" class="btn btn-base">
                                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search">
                                                <circle cx="11" cy="11" r="8"></circle>
                                                <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
                                            </svg>
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
        <div class="row">
            <table class="table" id="article-table">
                <thead>
                    <tr>
                        <th class="title col-6"><a>제목</a></th>
                        <th class="hashtag col-2"><a>해시태그</a></th>
                        <th class="user-id"><a>작성자</a></th>
                        <th class="created-at"><a>작성일</a></th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="title"><a>첫글</a></td>
                        <td class="hashtag">#java</td>
                        <td class="user-id">김사과</td>
                        <td class="created-at"><time>2023-01-03</time></td>
                    </tr>
                    <tr>
                        <td>두번째글</td>
                        <td>#spring</td>
                        <td>김사과</td>
                        <td>2023-01-03</td>
                    </tr>
                    <tr>
                        <td>세번째글</td>
                        <td>#java</td>
                        <td>김사과</td>
                        <td>2023-01-03</td>
                    </tr>
                </tbody>
            </table>
        </div>
        <div class="row">
            <div class="d-grid gap-2 d-md-flex justify-content-md-end">
                <a class="btn btn-primary me-md-2" role="button" id="write-article">글쓰기</a>
            </div>
        </div>
        <div class="row">
            <nav id="pagination" aria-label="Page navigation example">
                <ul class="pagination justify-content-center">
                    <li class="page-item"><a class="page-link" href="#">Previous</a></li>
                    <li class="page-item"><a class="page-link" href="#">1</a></li>
                    <li class="page-item"><a class="page-link" href="#">Next</a></li>
                </ul>
            </nav>
        </div>
    </main>

    <footer id="footer">
        <hr>
        푸터 삽입
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
</body>
</html>

index.th.xml

sel로 태그를 선택하여 다양한 기능을 넣을 수 있다.

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

    <attr sel="main" th:object="${articles}">
        <attr sel="#search-form" th:action="@{/articles}" th:method="get"/>
        <attr sel="#search-type" th:remove="all-but-first">
            <attr sel="option[0]"
                  th:each="searchType : ${searchTypes}"
                  th:value="${searchType.name}"
                  th:text="${searchType.description}"
                  th:selected="${param.searchType != null && (param.searchType.toString == searchType.name)}"/>
        </attr>
        <attr sel="#search-value" th:value="${param.searchValue}"/>

        <attr sel="#article-table">
            <attr sel="thead/tr">
                <attr sel="th.title/a" th:text="'제목'" th:href="@{/articles(
                    page=${articles.number},
                    sort='title' + (*{sort.getOrderFor('title')} != null ? (*{sort.getOrderFor('title').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
                <attr sel="th.hashtag/a" th:text="'해시태그'" th:href="@{/articles(
                    page=${articles.number},
                    sort='hashtag' + (*{sort.getOrderFor('hashtag')} != null ? (*{sort.getOrderFor('hashtag').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
                <attr sel="th.user-id/a" th:text="'작성자'" th:href="@{/articles(
                    page=${articles.number},
                    sort='userAccount.userId' + (*{sort.getOrderFor('userAccount.userId')} != null ? (*{sort.getOrderFor('userAccount.userId').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
                <attr sel="th.created-at/a" th:text="'작성일'" th:href="@{/articles(
                    page=${articles.number},
                    sort='createdAt' + (*{sort.getOrderFor('createdAt')} != null ? (*{sort.getOrderFor('createdAt').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
            </attr>

            <attr sel="tbody" th:remove="all-but-first">
                <attr sel="tr[0]" th:each="article : ${articles}">
                    <attr sel="td.title/a" th:text="${article.title}" th:href="@{'/articles/'+${article.id}}"/>
                    <attr sel="td.hashtag" th:text="${article.hashtag}"/>
                    <attr sel="td.user-id" th:text="${article.nickname}"/>
                    <attr sel="td.created-at/time" th:datetime="${article.createdAt}" th:text="${#temporals.format(article.createdAt, 'yyyy-MM-dd')}"/>

                </attr>
            </attr>
        </attr>
    </attr>
    <attr sel="#write-article" sec:authorize="isAuthenticated()" th:href="@{/articles/form}"/>
    <attr sel="#pagination">
        <attr sel="li[0]/a" th:text="'previous'"
              th:href="@{/articles(page=${articles.number - 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
              th:class="'page-link' + (${articles.number} <= 0 ? ' disabled':'')" />
        <attr sel="li[1]" th:class="page-item" th:each="pageNumber : ${paginationBarNumbers}">
            <attr sel="a" th:text="${pageNumber + 1}" th:href="@{/articles(page=${pageNumber}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
                  th:class="'page-link' + (${pageNumber} == ${articles.number} ? ' disabled' : '')"/>

        </attr>
        <attr sel="li[2]/a" th:text="'next'"
              th:href="@{/articles(page=${articles.number + 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
              th:class="'page-link' + (${articles.number} >= ${articles.totalPages - 1} ? ' disabled':'')" />
    </attr>
</thlogic>

이 코드는 Thymeleaf 템플릿 엔진을 사용한 HTML 코드입니다. Thymeleaf는 서버 측에서 실행되며, HTML 페이지를 생성하는 데 사용됩니다. Thymeleaf 템플릿 엔진을 사용하여 HTML을 동적으로 렌더링합니다.

는 XML 형식의 문서임을 나타내는 선언부입니다.

< thlogic> 태그는 Thymeleaf에서 사용되는 커스텀 태그로, Thymeleaf와 함께 사용될 때만 해석되고, 일반 HTML에서는 무시됩니다.

< attr> 태그는 Thymeleaf에서 사용되는 커스텀 태그로, HTML 태그의 속성(attribute) 값을 바인딩합니다.

th:replace 속성은 해당 태그의 내용을 다른 태그로 대체합니다. 위 코드에서는 #header와 #footer라는 ID를 가진 태그를 header :: header와 footer :: footer로 대체하는 것을 의미합니다. 이는 header.html과 footer.html 파일에서 정의된 코드를 가져와 해당 위치에 삽입하는 역할을 합니다.

th:object 속성은 데이터 객체를 지정하는 역할을 합니다. 위 코드에서는 articles라는 객체를 main 태그에 지정합니다. 이를 통해 articles 객체의 데이터를 main 태그 안에서 참조할 수 있습니다.

th:each 속성은 반복문을 실행합니다. 위 코드에서는 #search-type 태그 안에 있는 option[0] 태그를 찾아 각각의 searchTypes 객체의 name 속성과 description 속성 값을 바인딩하고, selected 속성은 param.searchType이 null이 아니면서 param.searchType.toString이 searchType.name과 같으면 true가 되도록 설정합니다.

th:value 속성은 해당 태그의 값(value)을 바인딩합니다. 위 코드에서는 #search-value 태그에 param.searchValue 값을 설정합니다.

th:text 속성은 해당 태그의 텍스트(text)를 바인딩합니다. 위 코드에서는 th:text 속성 안에 표현식(expression)을 사용하여 article 객체의 title, hashtag, nickname, createdAt 속성 값을 텍스트로 설정합니다.

th:href 속성은 해당 태그의 링크 주소(href)를 바인딩합니다. 위 코드에서는 th:href 속성 안에 표현식을 사용하여 page, sort, searchType, searchValue 등의 값을 포함한 주소를 설정합니다.

th:datetime 속성은 해당 태그의 날짜/시간(datetime) 값을 바인딩합니다. 위 코드에서는 article.createdAt 값을 th:datetime 속성에 바인딩하고, th:text 속성 안에 표현식을 사용하여 yyyy-MM-dd 형식으로 포맷팅한 값을 텍스트로 설정합니다.

위의 코드로 다음의 기능을 구현한 것입니다!
1. 검색기능
2. sort기능
3. 페이징기능
4. header/footer 붙여 넣는 기능

타임리프 문법을 자세히 배우고 싶다면 클릭

상세페이지

detail.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="Ryuzy">
  <title>게시글 페이지</title>

  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head>

<body>

<header id="header">
  헤더 삽입
  <hr>
</header>

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

      <div class="row g-5">
        <section class="col-md-3 col-lg-4 order-md-last">
          <aside>
            <p><span id="nickname" class="nick-name">김사과</span></p>
            <p><a id="email" class="u-url" rel="me" href="mailto:apple@apple.com">apple@apple.com</a></p>
            <p><time id="created-at" datetime="2022-01-03T00:00:00">2023-01-03</time></p>
            <p><span id="hashtag">#java</span></p>
          </aside>
        </section>

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

      <div class="row g-5" id="article-buttons">
        <form id="delete-article-form">
          <div class="pb-5 d-grid gap-2 d-md block">
            <a class="btn btn-success me-md-2" role="button" id="update-article">수정</a>
            <button class="btn btn-danger me-md-2" type="submit">삭제</button>
          </div>
        </form>
      </div>

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

              <ul id="article-comments" class="row col-md-10 col-lg-8 pt-3">
                <li>
                  <form class="comment-form">
                    <input type="hidden" class="article-id">
                    <div class="row">
                      <div class="col-md-10 col-lg-9">
                        <strong>김사과</strong>
                        <time><small>2023-01-03</small></time>
                        <p>
                          Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br>
                          Lorem ipsum dolor sit amet
                        </p>
                      </div>
                      <div class="col-2 mb-3">
                        <button type="submit" class="btn btn-outline-danger" id="delete-comment-button">삭제</button>
                      </div>
                    </div>
                  </form>
                </li>
                <li>
                  <div>
                    <strong>김사과</strong>
                    <time><small>2023-01-03</small></time>
                    <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 id="pagination" aria-label="Page navigation">
          <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.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
</body>
</html>

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/pre" th:text="*{content}"/>
    </attr>

    <attr sel="article-buttons">
        <attr sel="#delete-article-form" th:action="'/articles/' + *{id} + '/delete'" th:method="post">
            <attr sel="#update-article" th:href="'/articles/' + *{id} + '/form'"/>
        </attr>
    </attr>

    <attr sel=".article-id" th:name="articleId" th:value="*{id}"/>
    <attr sel="#comment-form" th:action="@{/comments/new}" th:method="post">
        <attr sel="#comment-textbox" th:name="content"/>
    </attr>

    <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 sel="form" th:action="'/comments/' + ${articleComment.id} + '/delete'" th:method="post">
                <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>

    <attr sel="#pagination">
        <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>
</thlogic>

th:replace : 대상 요소를 다른 페이지 또는 fragment로 대체합니다.
th:object : 객체를 현재 태그의 컨텍스트 객체로 설정합니다.
th:text : 요소의 텍스트 값을 설정합니다.
th:datetime : 날짜 및 시간 값을 설정합니다.
th:name : 폼(form) 데이터의 이름을 설정합니다.
th:value : 폼(form) 데이터의 값(value)을 설정합니다.
th:each : 반복문(loop)을 생성합니다.
th:action : 폼(form) 데이터의 전송 대상 URL을 설정합니다.
th:href : 링크의 대상 URL을 설정합니다.
th:remove : 요소를 제거합니다.
삼항연산자를 이용하여 다양하게 구현할 수 있습니다.

삼항 연산자
(조건) ? (참일 때 값) : (거짓일 때 값)
결과값: true or false

글작성/수정

form.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="Ryuzy">
  <title>게시판 페이지</title>

  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
  <link href="/css/search-bar.css" rel="stylesheet">
  <link href="/css/table-header.css" rel="stylesheet">
</head>

<body>

  <header id="header">
    헤더 삽입
    <hr>
  </header>

  <div class="container">
    <header id="article-form-header" class="py-5 text-center">
      <h1>게시글 작성</h1>
    </header>

    <form id="article-form">
      <div class="row mb-3 justify-content-md-center">
        <label for="title" class="col-sm-2 col-lg-1 col-form-label text-sm-end">제목</label>
        <div class="col-sm-8 col-lg-9">
          <input type="text" class="form-control" id="title" name="title" required>
        </div>
      </div>
      <div class="row mb-3 justify-content-md-center">
        <label for="content" class="col-sm-2 col-lg-1 col-form-label text-sm-end">본문</label>
        <div class="col-sm-8 col-lg-9">
          <textarea class="form-control" id="content" name="content" rows="5" required></textarea>
        </div>
      </div>
      <div class="row mb-4 justify-content-md-center">
        <label for="hashtag" class="col-sm-2 col-lg-1 col-form-label text-sm-end">해시태그</label>
        <div class="col-sm-8 col-lg-9">
          <input type="text" class="form-control" id="hashtag" name="hashtag">
        </div>
      </div>
      <div class="row mb-5 justify-content-md-center">
        <div class="col-sm-10 d-grid gap-2 d-sm-flex justify-content-sm-end">
          <button type="submit" class="btn btn-primary" id="submit-button">저장</button>
          <button type="button" class="btn btn-secondary" id="cancel-button">취소</button>
        </div>
      </div>
    </form>
  </div>

  <footer id="footer">
    푸터 삽입
  </footer>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>

</body>
</html>

form.th.xml

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

    <attr sel="#article-form-header/h1" th:text="${formStatus} ? '게시글 ' + ${formStatus.description} : _"/>

    <attr sel="#article-form" th:action="${formStatus?.update} ? '/articles/' + ${article.id} + '/form' : '/articles/form'" th:method="post">
        <attr sel="#title" th:value="${article?.title} ?: _"/>
        <attr sel="#content" th:value="${article?.content} ?: _"/>
        <attr sel="#hashtag" th:value="${article?.hashtag} ?: _"/>
        <attr sel="#submit-button" th:text="${formStatus?.description} ?: _"/>
        <attr sel="#cancel-button" th:onclick="'history.back()'"/>
    </attr>
</thlogic>

해시태그 게시판

search-hashtag.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="Ryuzy">
    <title>게시판 페이지</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
    <link href="/css/search-bar.css" rel="stylesheet">
    <link href="/css/table-header.css" rel="stylesheet">
</head>
<body>
    <header id="header">
        헤더 삽입
        <hr>
    </header>

    <main class="container">
        <header class="py-5 text-center">
            <h1>해시태그</h1>
        </header>

        <section class="row">
            <div id="hashtags" class="col-9 d-flex flex-wrap justify-content-evenly">
                <div class="p-2">
                    <h2 class="text-center 1h-lg font-monospace">
                        <a href="#">#java</a>
                    </h2>
                </div>
            </div>
        </section>

        <hr>

        <table class="table" id="article-table">
            <thead>
            <tr>
                <th class="title col-6"><a>제목</a></th>
                <th class="hashtag col-2"><a>해시태그</a></th>
                <th class="user-id col"><a>작성자</a></th>
                <th class="created-at col"><a>작성일</a></th>
            </tr>
            </thead>
            <tbody>
            <tr>
                <td class="title"><a>첫글</a></td>
                <td class="hashtag">#java</td>
                <td class="user-id">김사과</td>
                <td class="created-at"><time>2023-01-03</time></td>
            </tr>
            <tr>
                <td>두번째글</td>
                <td>#spring</td>
                <td>김사과</td>
                <td>2023-01-03</td>
            </tr>
            <tr>
                <td>세번째글</td>
                <td>#java</td>
                <td>김사과</td>
                <td>2023-01-03</td>
            </tr>
            </tbody>
        </table>
        <nav id="pagination" aria-label="Page navigation example">
            <ul class="pagination justify-content-center">
                <li class="page-item"><a class="page-link" href="#">Previous</a></li>
                <li class="page-item"><a class="page-link" href="#">1</a></li>
                <li class="page-item"><a class="page-link" href="#">Next</a></li>
            </ul>
        </nav>
    </main>

    <footer id="footer">
        <hr>
        푸터 삽입
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>

</body>
</html>

search-hashtag.th.xml

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

    <attr sel="main" th:object="${articles}">
        <attr sel="#hashtags" th:remove="all-but-first">
            <attr sel="div" th:each="hashtag : ${hashtags}">
                <attr sel="a" th:class="'text-reset'" th:text="${hashtag}" th:href="@{/articles/search-hashtag(
                    page=${param.page},
                    sort=${param.sort},
                    searchValue=${hashtag}
                )}"/>
            </attr>
        </attr>

        <attr sel="#article-table">
            <attr sel="thead/tr">
                <attr sel="th.title/a" th:text="'제목'" th:href="@{/articles(
                    page=${articles.number},
                    sort='title' + (*{sort.getOrderFor('title')} != null ? (*{sort.getOrderFor('title').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
                <attr sel="th.hashtag/a" th:text="'해시태그'" th:href="@{/articles(
                    page=${articles.number},
                    sort='hashtag' + (*{sort.getOrderFor('hashtag')} != null ? (*{sort.getOrderFor('hashtag').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
                <attr sel="th.user-id/a" th:text="'작성자'" th:href="@{/articles(
                    page=${articles.number},
                    sort='userAccount.userId' + (*{sort.getOrderFor('userAccount.userId')} != null ? (*{sort.getOrderFor('userAccount.userId').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
                <attr sel="th.created-at/a" th:text="'작성일'" th:href="@{/articles(
                    page=${articles.number},
                    sort='createdAt' + (*{sort.getOrderFor('createdAt')} != null ? (*{sort.getOrderFor('createdAt').direction.name} != 'DESC' ? ',desc' : '') : ''),
                    searchType=${param.searchType},
                    searchValue=${param.searchValue}
                )}"/>
            </attr>

            <attr sel="tbody" th:remove="all-but-first">
                <attr sel="tr[0]" th:each="article : ${articles}">
                    <attr sel="td.title/a" th:text="${article.title}" th:href="@{'/articles/'+${article.id}}"/>
                    <attr sel="td.hashtag" th:text="${article.hashtag}"/>
                    <attr sel="td.user-id" th:text="${article.nickname}"/>
                    <attr sel="td.created-at/time" th:datetime="${article.createdAt}" th:text="${#temporals.format(article.createdAt, 'yyyy-MM-dd')}"/>

                </attr>
            </attr>
        </attr>
    </attr>

    <attr sel="#pagination">
        <attr sel="li[0]/a" th:text="'previous'"
              th:href="@{/articles(page=${articles.number - 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
              th:class="'page-link' + (${articles.number} <= 0 ? ' disabled':'')" />
        <attr sel="li[1]" th:class="page-item" th:each="pageNumber : ${paginationBarNumbers}">
            <attr sel="a" th:text="${pageNumber + 1}" th:href="@{/articles(page=${pageNumber}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
                  th:class="'page-link' + (${pageNumber} == ${articles.number} ? ' disabled' : '')"/>

        </attr>
        <attr sel="li[2]/a" th:text="'next'"
              th:href="@{/articles(page=${articles.number + 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
              th:class="'page-link' + (${articles.number} >= ${articles.totalPages - 1} ? ' disabled':'')" />
    </attr>
</thlogic>

0개의 댓글