게시판 기능 구현 4 : 게시판 홈(게시글 목록 페이지) 만들기 - 스프링, jsp, 오라클, mybatis
드디어 6개의 데이터를 모두 담아서 boardHome.jsp 파일로 왔다.
boardHome.jsp 파일에서는 가져온 6개의 데이터를 활용해서, 현재 페이지의 게시글 목록도 보여주고, 페이지네이션도 진행할 것이다.
1단계 : 현재 페이지의 게시글 목록 보여주기.
위 이미지 중
이 부분을 구현해볼 것이다.
grid 를 사용했는데, grid 에 대해서는 https://studiomeal.com/archives/533 여기 참고하면 된다. 아주 잘 정리되어 있는 좋은 글이라 별도로 이 포스팅에서는 다루지 않는다.
<div class="board">
<span>글번호</span>
<span>제목</span>
<span>작성자</span>
<span>작성날짜</span>
<span>조회수</span>
</div>
<div class="board-2">
<c:forEach var="post" items="${currentPagePosts}">
<div class="post-row"> <!-- 각 게시물을 감싸는 div 추가 -->
<span>${post.id}</span>
<a href="#" class="post-link" data-postid="${post.id}">
<c:if test="${post.isPrivate == 1}">
<i class="fa-solid fa-lock" style="color:red;"></i>
</c:if>
${post.title}
</a>
<span>${post.userId}</span>
<span>${post.writingDate}</span>
<span>${post.viewNumber}</span>
</div>
</c:forEach>
</div>
/* css */
.board{
display: grid;
grid-template-columns: 1fr 4fr 2fr 1fr 1fr;
align-items: center;
justify-items: center;
font-size: 12px;
}
.board-2{
border-bottom: 1px solid lightgray;
}
.post-row{
display: grid;
grid-template-columns: 1fr 4fr 2fr 1fr 1fr;
align-items: center;
justify-items: center;
border-bottom: 1px solid lightgray;
font-size: 12px;
min-height: 40px; /*각 행의 높이 조절*/
}
boardHome.jsp 전체 코드와 boardHome.css(boardHome.jsp 파일과 연결된 css 파일) 전체 코드는 맨 아래에 두었다.
위 코드는 현재 필요한 부분만 가져왔다.
어쨋든, 현재 중요한 포인트는 뭐냐면, 우리가 담아줬던 6개의 데이터 중 1. 현재 페이지에 보여질 게시글(PostsDTO)을 담은 List자료구조. 를 <c:forEach> 문으로 반복을 돌리면서, 하나씩 꺼낸다는 것이다.
중간에 있는
<c:if test="${post.isPrivate == 1}">
<i class="fa-solid fa-lock" style="color:red;"></i>
</c:if>
이 코드는 font-awesome 에서 가져온 아이콘인데, 비밀글이라면 빨간 자물쇠를 보여주도록 하였다.
2단계 : 페이지네이션 구현하기
아래와 같은 걸 페이지네이션이라고 한다.
boardHome.jsp 중 아래 부분이 페이지네이션을 하는 부분이다.
<div id="pagination">
<!-- 페이지네이션 -->
<!-- 이전 그룹 링크 -->
<c:if test="${currentGroupFirstPage != 1}">
<a href="/board-home?page=${currentGroupFirstPage - pageGroupSize}">« 이전</a>
</c:if>
<!-- 현재 페이지 그룹의 페이지 링크 forEach 문 돌림 -->
<c:forEach var="i" begin="${currentGroupFirstPage}" end="${currentGroupLastPage}">
<c:choose>
<c:when test="${i == currentPage}">
<span class="current-page" style="color:#e06500; ">${i}</span>
</c:when>
<c:otherwise>
<a href="/board-home?page=${i}">${i}</a>
</c:otherwise>
</c:choose>
   
</c:forEach>
<!-- 다음 그룹 링크 -->
<c:if test="${currentGroupLastPage != totalPages}">
<a href="/board-home?page=${currentGroupLastPage + 1}">다음 »</a>
</c:if>
</div>
<c:if test="${currentGroupFirstPage != 1}">
<a href="/board-home?page=${currentGroupFirstPage - pageGroupSize}">« 이전</a>
</c:if>
이 부분부터 보자.
6개의 데이터 중 4. 현재 페이지 그룹의 첫번째 페이지 이거를 이용해서 만약, 현재 페이지 그룹의 첫번째 페이지가 1 이 아니라면, 이전 버튼을 누를 수 있게 해두었다. 참고로, a 태그의 href 속성은 지금 boardHome.jsp 이 jsp파일로 인도했던 그 컨트롤러로 get요청을 보내도록 설정되어 있다. 이때, 쿼리스트링으로는 6개의 데이터 중
4. 현재 페이지 그룹의 첫번째 페이지 에서 6. 몇개의 페이지를 하나의 그룹으로 묶었는지. 를 뺏다.
예를 들어, 9개의 페이지를 하나의 그룹으로 묶었고, 현재 페이지가 속한 그룹의 첫번째 페이지가 10 페이지인 경우.
10 - 9 = 1 이 되서, localhost:8080/board-home?page=1 로 get 요청을 하게 되는 것이다.
---
<c:if test="${currentGroupLastPage != totalPages}">
<a href="/board-home?page=${currentGroupLastPage + 1}">다음 »</a>
</c:if>
이건, 6개의 데이터 중
5. 현재 페이지 그룹의 마지막 페이지 가 3. 총 페이지 수 와 같지 않다면,
그러면, 뒤에 더 있다는 뜻이므로, 다음 버튼을 보여주도록 하였다.
다음 을 누르면 보내지는 get요청의 url 의 쿼리스트링으로는 5. 현재 페이지 그룹의 마지막 페이지 + 1 로 했다.
---
페이지네이션의 핵심이자 마지막은,
<c:forEach var="i" begin="${currentGroupFirstPage}" end="${currentGroupLastPage}">
<c:choose>
<c:when test="${i == currentPage}">
<span class="current-page" style="color:#e06500; ">${i}</span>
</c:when>
<c:otherwise>
<a href="/board-home?page=${i}">${i}</a>
</c:otherwise>
</c:choose>
   
</c:forEach>
<c:forEach> 문으로 반복을 돌리고 있는데, 단순히 i 부터 시작해서 1씩 증가하면서 반복을 하는 코드이다.
그런데, 이렇게 단순한 코드로 어떻게 현재 그룹에 속한 페이지들을 표현하고, 현재 선택된 페이지는 또 어떻게 표현할 수 있냐면, 우리가 model 에 담아준 6개의 데이터를 잘 활용하면 된다.
var="i" 가 1씩 증가하면서 반복을 할 건데,
i 의 시작지점이
6개의 데이터 중 4. 현재 페이지 그룹의 첫번째 페이지 부터 시작하게 되고, +1씩 증가하다가 어디서 멈출거냐면, 5. 현재 페이지 그룹의 마지막 페이지 에서 멈출것이다.
이때, <c:choose> <c:when> <c:otherwise> 를 사용해서 i 의 값이 현재 페이지(2. 현재 boardHome.jsp 파일에서 렌더링하고 있는 페이지가 "몇 페이지" 인지.)라면 단순히 span 태그가 나오도록 하고, 현재 페이지가 아니라면 a 태그로 하여 클릭하면 get요청이 가도록 하였다.
이렇게 해서 페이징까지 완료해보았다.
다음 포스팅은 게시판 홈에 있는 게시글 중 하나를 클릭했을 때 게시글의 상세가 보여지는 것을 해볼 것이다. + 좋아요 버튼
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<c:import url="/jsp/bootstrapconfig/index.jsp"/>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %> <!-- 필요한 경우에 추가 -->
<%@ page import="firstportfolio.wordcharger.DTO.WritingDTOSelectVersion" %>
<html>
<head>
<link rel="stylesheet" href="../../css/board/boardHome.css">
<script src="https://kit.fontawesome.com/670206db20.js" crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', function(){
document.querySelectorAll('.post-link').forEach(function(element){
element.addEventListener('click', function(event){
event.preventDefault(); // 기본 앵커 태그 동작 방지.
var postId = this.getAttribute('data-postid');
console.log('AAAAAAAAAAAAAAAAAAA');
//우선, 글을 클릭한 사용자가 글을 쓴 사람과 같은 사람인지부터 확인해줘야함.
//동일인이라면, 굳이 비밀번호를 안물어보기 위함이다.
fetch('/is-this-you?postId=' + postId)
.then(response => {
if(!response.ok){
throw new Error('Network response was not ok');
}
return response.text();
})
.then(data => {
console.log(data);
console.log('ABBBBBBBBBBBBBBB');
if(data == 'yes'){
window.location.href="/show-writing?postId="+postId;
}else{
//fetch 이용해서 통신. 비밀글이 있는지 여부를 확인해줄거임.
fetch('/is-this-secret?postId=' + postId)
.then(response => {
if(!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('ccccccccccccccccccccc');
console.log(data.isThisSecret);
if(data.isThisSecret == 'no'){ // 비밀글이 아닌 경우
window.location.href="/show-writing?postId="+postId;
}else{ //비밀글인 경우
console.log('dddddddddddddddddddddddd');
let input = prompt('게시물의 비밀번호를 입력하세요');
if(input == data.writingPassword){ //비밀번호가 일치한다면
window.location.href="/show-writing?postId="+postId;
} else{
alert('비밀번호가 틀렸습니다.');
}
}
})
.catch(error => console.error('There has been a problem with your fetch operation:', error));
}
})
.catch(error => console.error('There has been a problem with your fetch operation:', error));
});
})
});
</script>
</head>
<body>
<!--네브 바 -->
<c:import url="/jsp/common/loginedNavbar2.jsp" />
<!--네브 바 종료 -->
<!-- 게시물을 수정할 수 없는 사람이 수정을 시도했을 경우에만 띄워질 alert 창 -->
<% if (request.getAttribute("hecker") != null) { %>
<script>
// alert 창으로 메시지 띄우기
alert("<%= request.getAttribute("hecker") %>");
</script>
<% } %>
<!-- ---------------------------- -->
<div id="board-container">
<div id="page-title">
<div></div>
<div>게시판</div>
<div></div>
</div>
<div id="page-body">
<div></div>
<div>
<div></div>
<div>
<div class="board">
<span>글번호</span>
<span>제목</span>
<span>작성자</span>
<span>작성날짜</span>
<span>조회수</span>
</div>
</div>
<div>
<div class="board-2">
<c:forEach var="post" items="${currentPagePosts}">
<div class="post-row"> <!-- 각 게시물을 감싸는 div 추가 -->
<span>${post.id}</span>
<a href="#" class="post-link" data-postid="${post.id}">
<c:if test="${post.isPrivate == 1}">
<i class="fa-solid fa-lock" style="color:red;"></i>
</c:if>
${post.title}
</a>
<span>${post.userId}</span>
<span>${post.writingDate}</span>
<span>${post.viewNumber}</span>
</div>
</c:forEach>
</div>
<div id="post-make-div">
<a href="/writing-page" class="post-make" > 글 작성 </a>
<a href="/board-home" class="post-make" > 최신순 </a>
<a href="/board-home-order-by-like-num" class="post-make" > 좋아요순 </a>
<a href="/board-home-order-by-view-num" class="post-make" > 조회수순 </a>
</div>
<div id="find-posts">
<!--제목, 작성자, 내용 으로 게시글 찾기 -->
<form action="/find-posts-by-title-writer-content" method="post">
<select name="byWhatType">
<option value="title">제목</option>
<option value="writer">작성자</option>
<option value="content">내용</option>
</select>
<input type="text" name="hintToFind" />
<button type="submit"> 찾기 </button>
</form>
</div>
<div id="pagination">
<!-- 페이지네이션 -->
<!-- 이전 그룹 링크 -->
<c:if test="${currentGroupFirstPage != 1}">
<a href="/board-home?page=${currentGroupFirstPage - pageGroupSize}">« 이전</a>
</c:if>
<!-- 현재 페이지 그룹의 페이지 링크 forEach 문 돌림 -->
<c:forEach var="i" begin="${currentGroupFirstPage}" end="${currentGroupLastPage}">
<c:choose>
<c:when test="${i == currentPage}">
<span class="current-page" style="color:#e06500; ">${i}</span>
</c:when>
<c:otherwise>
<a href="/board-home?page=${i}">${i}</a>
</c:otherwise>
</c:choose>
   
</c:forEach>
<!-- 다음 그룹 링크 -->
<c:if test="${currentGroupLastPage != totalPages}">
<a href="/board-home?page=${currentGroupLastPage + 1}">다음 »</a>
</c:if>
</div>
</div>
</div>
<div>
</div>
</div>
</div>
</body>
</html>
#board-container{
padding-top: 8vh;
width: 100vw;
height: 150vh;
display:flex;
flex-direction: column;
align-items: center;
font-family: 'S-CoreDream-3Light';
}
#page-title{
display:flex;
width:100%;
flex-basis: 15%;
}
#page-title>div:nth-child(1),
#page-title>div:nth-child(3){
flex-basis:10%;
}
#page-title>div:nth-child(2){
flex-basis:80%;
display:flex;
justify-content:center;
align-items:center;
font-size:30px;
}
#page-body{
display:flex;
width:100%;
flex-basis: 85%;
}
#page-body>div:nth-child(1),
#page-body>div:nth-child(3){
flex-basis:10%;
}
#page-body>div:nth-child(2){
flex-basis:80%;
display:flex;
flex-direction:column;
}
#page-body>div:nth-child(2)>div:nth-child(1){
flex-basis: 2%;
border-top: 3px solid #001942;
font-size: 12px;
}
#page-body>div:nth-child(2)>div:nth-child(2){
flex-basis: 4%;
border-bottom: 1px solid lightgray;
}
#page-body>div:nth-child(2)>div:nth-child(3){
flex-basis: 94%;
}
#page-body>div:nth-child(2)>div:nth-child(4){
flex-basis: 7%;
}
#page-body>div:nth-child(2)>div:nth-child(5){
flex-basis: 7%;
}
.board{
display: grid;
grid-template-columns: 1fr 4fr 2fr 1fr 1fr;
align-items: center;
justify-items: center;
font-size: 12px;
}
.board-2{
border-bottom: 1px solid lightgray;
}
.post-row{
display: grid;
grid-template-columns: 1fr 4fr 2fr 1fr 1fr;
align-items: center;
justify-items: center;
border-bottom: 1px solid lightgray;
font-size: 12px;
min-height: 40px; /*각 행의 높이 조절*/
}
a{
text-decoration: none;
color: black;
}
#post-make-div{
height: 5vh;
}
.post-make{
display: inline-flex;
margin-top: 2px;
height: 80%;
align-items:center;
border: 1px solid black;
background-color: white;
color: black;
border-radius: 4px;
transition: background-color 0.5s, color 0.5s;
}
.post-make:hover{
background-color: black;
color: white;
}
#find-posts{
display:flex;
justify-content: center;
align-items: flex-start;
height: 5vh;
}
#pagination{
display:flex;
justify-content: center;
align-items: center;
height: 5vh;
}