detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<jsp:include page="../layout/header.jsp"></jsp:include>
<jsp:include page="../layout/nav.jsp"></jsp:include>
<div class="container-md">
// ... (기존 코드)
<!-- 댓글 더보기 버튼 -->
<div>
<button type="button" id="moreBtn" data-page="1"
class="btn btn-outline-dark" style="visibility: hidden">댓글 더보기</button>
</div>
</div>
// ... (기존 코드)
댓글 더보기 버튼을 하나 작성한다.
js에서 사용하기 위해 id를 moreBtn이라고 선언해주었고,
댓글의 첫 페이지 부분을 설정하기 위해 data-page는 1로 주었다.
style에 visibility는 버튼이 시각적으로 보이게 할건지 아닌지 설정하는데
댓글이 일정 숫자 이상이 되면 더보기 버튼이 보이게 하고 싶어서, hidden으로 값을 주고 js에서 처리한다.
PagingHandler.java
package com.basicWeb.www.handler;
import java.util.List;
import com.basicWeb.www.domain.CommentVO;
import com.basicWeb.www.domain.PagingVO;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Setter
@Getter
public class PagingHandler {
// ... (기존 코드)
private List<CommentVO> cmtList;
public PagingHandler(PagingVO pgvo, int totalCount) {
this.pgvo = pgvo;
this.totalCount = totalCount;
this.endPage = (int) Math.ceil(pgvo.getPageNo() / (double) pgvo.getQty()) * pgvo.getQty();
this.startPage = endPage - 9;
int realEndPage = (int)(Math.ceil(totalCount / (double) pgvo.getQty()));
if(realEndPage < endPage) {
this.endPage = realEndPage;
}
this.prev = startPage > 1;
this.next = this.endPage < realEndPage;
}
public PagingHandler(PagingVO pgvo, int totalCount, List<CommentVO>cmtList) {
this(pgvo, totalCount);
this.cmtList = cmtList;
}
}
댓글 더보기 버튼을 만든다는 것은,
게시판에서 페이지네이션을 하듯이, 댓글을 페이지네이션 처리하겠다는 것이다.
그러므로 PagingHandler에 댓글을 리스트로 받는 변수를 선언하고,
private List<CommentVO> cmtList;
그 리스트를 활용할 수있도록 생성자를 만들어준다.
public PagingHandler(PagingVO pgvo, int totalCount, List<CommentVO>cmtList) {
this(pgvo, totalCount);
this.cmtList = cmtList;
}
this(pgvo, totalCount)는 cmtList처럼 하나하나 선언해주어도 되지만,
이미 바로 상위에 있는 생성자에서 선언되어 있기 때문에, 저렇게 묶어서 처리해도 된다.
boardComment.js
// ... (기존 코드)
async function getCommentListFromServer(bno, page) {
try {
const resp = await fetch("/comment/"+bno+"/"+page);
const result = await resp.json();
return result;
} catch (error) {
console.info(error);
}
};
function spreadCommentList(bno, page=1) {
getCommentListFromServer(bno, page).then(result=>{
console.log(result.cmtList);
const ul = document.getElementById('cmtListArea');
if(result.cmtList.length > 0) {
if(page == 1) {
ul.innerHTML = '';
}
for(let cvo of result.cmtList) {
let li = `<li class="list-group-item" data-cno="${cvo.cno}" data-writer="${cvo.writer}">`;
li += `<div class="mb-3">`;
li += `<div class="fw-bold">${cvo.writer} `;
li += `<span class="badge rounded-pill text-bg-warnisng">${cvo.modAt}</span></div>`;
li += `${cvo.content}`;
li += `</div>`;
li += `<button type="button" class="btn btn-sm btn-outline-success cmtModBtn" data-bs-toggle="modal" data-bs-target="#myModal">Eidt</button>`;
li += `<button type="button" class="btn btn-sm btn-outline-danger cmtDelBtn">Delete</button>`;
li += `</li>`;
ul.innerHTML += li;
}
// 더보기 버튼
let moreBtn = document.getElementById('moreBtn');
console.log(moreBtn);
if(result.pgvo.pageNo < result.endPage) {
moreBtn.style.visibility = 'visible';
moreBtn.dataset.page = page + 1;
} else {
moreBtn.style.visibility = 'hidden';
}
} else {
if(page == 1) {
let li = `<li class="list-group-item">댓글이 없습니다.</li>`;
ul.innerHTML = li;
}
}
})
};
document.addEventListener('click',(e)=>{
console.log(e.target);
if(e.target.id == 'moreBtn') {
let page = parseInt(e.target.dataset.page);
spreadCommentList(bnoVal, page)
}
});
boardComment.js에 추가되고 수정된 부분을 코드뭉치 별로 설명하자면 다음과 같다.
첫 번째 코드뭉치 : 서버로부터 댓글 목록을 가져오는 비동기 함수
async function getCommentListFromServer(bno, page) {
try {
const resp = await fetch("/comment/"+bno+"/"+page);
const result = await resp.json();
return result;
} catch (error) {
console.info(error);
}
};
기존의 코드에서 page가 추가되어 수정되었다.
이는 후에, controller에서 @PathVariable로 page값을 떼오기 위함이다.
두 번째 코드뭉치 : 댓글 목록을 받아와 화면에 표시하는 함수
function spreadCommentList(bno, page=1) {
getCommentListFromServer(bno, page).then(result=>{
console.log(result.cmtList);
const ul = document.getElementById('cmtListArea');
if(result.cmtList.length > 0) {
if(page == 1) {
ul.innerHTML = '';
}
for(let cvo of result.cmtList) {
let li = `<li class="list-group-item" data-cno="${cvo.cno}" data-writer="${cvo.writer}">`;
li += `<div class="mb-3">`;
li += `<div class="fw-bold">${cvo.writer} `;
li += `<span class="badge rounded-pill text-bg-warnisng">${cvo.modAt}</span></div>`;
li += `${cvo.content}`;
li += `</div>`;
li += `<button type="button" class="btn btn-sm btn-outline-success cmtModBtn" data-bs-toggle="modal" data-bs-target="#myModal">Eidt</button>`;
li += `<button type="button" class="btn btn-sm btn-outline-danger cmtDelBtn">Delete</button>`;
li += `</li>`;
ul.innerHTML += li;
}
// 더보기 버튼
let moreBtn = document.getElementById('moreBtn');
console.log(moreBtn);
if(result.pgvo.pageNo < result.endPage) {
moreBtn.style.visibility = 'visible';
moreBtn.dataset.page = page + 1;
} else {
moreBtn.style.visibility = 'hidden';
}
} else {
if(page == 1) {
let li = `<li class="list-group-item">댓글이 없습니다.</li>`;
ul.innerHTML = li;
}
}
})
};
함수에 직접적으로 page=1이라고 준 이유는,
댓글 수가 적어서 더보기 버튼이 필요없는 경우를 위해서 기본 값을 준 것이다.
function spreadCommentList(bno, page=1)
cmtList의 길이가 0보다 크다면 댓글이 있다는 뜻이고, 조건문을 주고 ul을 초기화 해줬다.
조건문을 주지 않고 ul을 초기화하면 더보기 버튼을 누를 때 마다 초기화 되어서
댓글이 누적되어 표시되지 않는다.
if(result.cmtList.length > 0) {
if(page == 1) {
ul.innerHTML = '';
}
반복문도 cmtList 기준으로 수정해주었고
for(let cvo of result.cmtList) {
let li = `<li class="list-group-item" data-cno="${cvo.cno}" data-writer="${cvo.writer}">`;
li += `<div class="mb-3">`;
li += `<div class="fw-bold">${cvo.writer} `;
li += `<span class="badge rounded-pill text-bg-warnisng">${cvo.modAt}</span></div>`;
li += `${cvo.content}`;
li += `</div>`;
li += `<button type="button" class="btn btn-sm btn-outline-success cmtModBtn" data-bs-toggle="modal" data-bs-target="#myModal">Eidt</button>`;
li += `<button type="button" class="btn btn-sm btn-outline-danger cmtDelBtn">Delete</button>`;
li += `</li>`;
ul.innerHTML += li;
}
jsp에서 moreBtn이라는 id를 가진 값을 js에서 moreBtn이라는 변수로 받아온다.
댓글의 현재 페이지가 마지막 페이지 보다 작다면 더보기 버튼을 나타내고
더보기 버튼을 클릭시 page가 +1 되도록 설정한다.
더 나타낼 댓글이 없다면 더보기 버튼은 숨긴다.
// 더보기 버튼
let moreBtn = document.getElementById('moreBtn');
console.log(moreBtn);
if(result.pgvo.pageNo < result.endPage) {
moreBtn.style.visibility = 'visible';
moreBtn.dataset.page = page + 1;
} else {
moreBtn.style.visibility = 'hidden';
}
댓글이 없을 때 띄우는 li에 조건문을 추가했다.
조건문을 주지 않으면 더보기 버튼을 클릭해 모든 댓글을 보았음에도 불구하고 버튼이 클릭되고,
모든 댓글이 사라지면서 댓글이 없다고 표시된다.
} else {
if(page == 1) {
let li = `<li class="list-group-item">댓글이 없습니다.</li>`;
ul.innerHTML = li;
}
}
})
};
세 번째 코드뭉치: 특정 버튼을 클릭했을 때 이벤트 함수
document.addEventListener('click',(e)=>{
console.log(e.target);
if(e.target.id == 'moreBtn') {
let page = parseInt(e.target.dataset.page);
spreadCommentList(bnoVal, page)
}
});
화면에서 무언가를 클릭했을 때,
그 id가 moreBtn일 경우, data-page의 값을 js에서 page 값으로 받는다.
그 후, bno와 그 page 값을 활용해 댓글 리스트를 뿌리는 함수를 사용하여 댓글을 뿌린다.
CommentController.java
package com.basicWeb.www.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.basicWeb.www.domain.CommentVO;
import com.basicWeb.www.domain.PagingVO;
import com.basicWeb.www.handler.PagingHandler;
import com.basicWeb.www.service.CommentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/comment/*")
public class CommentController {
// ... (기존 코드)
@GetMapping(value="/{bno}/{page}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PagingHandler> list(@PathVariable("bno") long bno, @PathVariable("page") int page){
log.info(">>> bno >>> " + bno + "/ >>> page >>> " + page);
PagingVO pgvo = new PagingVO(page, 5);
PagingHandler ph = csv.getList(bno, pgvo);
return new ResponseEntity<PagingHandler>(ph, HttpStatus.OK);
}
}
기존의 list함수의 return 값을 PagingHandler로 변경하고, page 번호를 추가로 따온다.
PagingVO를 활용해 페이지 번호와 페이지 당 항목 수를 선언하고,
그렇게 선언된 객체와 bno를 가지고 ph 형식을 만든다.
CommentService.interface
package com.basicWeb.www.service;
import com.basicWeb.www.domain.CommentVO;
import com.basicWeb.www.domain.PagingVO;
import com.basicWeb.www.handler.PagingHandler;
public interface CommentService{
int post(CommentVO cvo);
PagingHandler getList(long bno, PagingVO pgvo);
}
List<CommentVO>에서 PagingHandler로 변경되었다.
CommentServiceImpl.java
package com.basicWeb.www.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.basicWeb.www.domain.CommentVO;
import com.basicWeb.www.domain.PagingVO;
import com.basicWeb.www.handler.PagingHandler;
import com.basicWeb.www.repository.CommentDAO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {
// ... (기존 코드)
@Override
public PagingHandler getList(long bno, PagingVO pgvo) {
List<CommentVO>list = cdao.getList(bno, pgvo);
int cmtCount = cdao.totalCount(bno);
PagingHandler ph = new PagingHandler(pgvo, cmtCount, list);
return ph;
}
}
마찬가지로 List<CommentVO>가 PagingHandler로 변경되었다.
페이지 당 항목 수에 맞추어 데이터를 가져오기 위해서
getList() Method에 pgvo가 추가되었고 이를 list로 담는다.
댓글의 총 개수를 구하는 totalCount() Method를 추가하고 ph 객체를 생성하여 return한다.
CommentDAO.interface
package com.basicWeb.www.repository;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.basicWeb.www.domain.CommentVO;
import com.basicWeb.www.domain.PagingVO;
public interface CommentDAO {
int insert(CommentVO cvo);
List<CommentVO> getList(@Param("bno") long bno, @Param("pgvo")PagingVO pgvo);
int totalCount(long bno);
}
commentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.basicWeb.www.repository.CommentDAO">
<insert id="insert">
INSERT INTO comment (bno, writer, content)
VALUES (#{bno}, #{writer}, #{content})
</insert>
<select id="getList" resultType="com.basicWeb.www.domain.CommentVO">
SELECT * FROM comment
WHERE bno = #{bno}
ORDER BY cno DESC
LIMIT #{pgvo.startPage}, #{pgvo.qty}
</select>
<select id="totalCount" resultType="int">
SELECT COUNT(cno) FROM comment
</select>
</mapper>
<댓글 더보기>
<댓글이 없을 때>
게시판 리스트에 댓글 수를 표시하기 위해서 바로 boardServiceImpl에 Method를 추가해주었다.
boardServiceImpl.java
package com.basicWeb.www.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.basicWeb.www.domain.BoardVO;
import com.basicWeb.www.domain.PagingVO;
import com.basicWeb.www.repository.BoardDAO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@Service
public class BoardServiceImpl implements BoardService{
// ... (기존 코드)
@Override
public List<BoardVO> getList(PagingVO pgvo) {
bdao.getCmtCount();
return bdao.getList(pgvo);
}
// ... (기존 코드)
}
boardDAO.interface
package com.basicWeb.www.repository;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.basicWeb.www.domain.BoardVO;
import com.basicWeb.www.domain.PagingVO;
public interface BoardDAO {
// ... (기존 코드)
void getCmtCount();
}
baordMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.basicWeb.www.repository.BoardDAO">
<!-- ... (기존 코드) -->
<update id="getCmtCount">
UPDATE board b SET cmt_qty = (
SELECT COUNT(bno) FROM comment
WHERE bno = b.bno
)
</update>
<!-- ... (기존 코드) -->
</mapper>
< 게시판 댓글 개수>
[Spring] 17. 댓글 - 리스트 더보기 + 게시판 댓글 개수
(다음 게시물 예고편)
[Spring] 18. 댓글 - 수정(+Modal) / 삭제
얼렁뚱땅 주니어 개발자
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!