✅ 완성 모습
이 페이지는 maxPost, maxPage 5로 구성 된 페이지이다. 아래 설명은 10으로 가정한다.
https://my-first-board.herokuapp.com/
✅ 학습 목표
- 페이징 기능을 구현할 수 있다.
- 내부적으로 사용되는 변수의 기능을 이해할 수 있다.
- noSQL 방식에서의 페이징 기법을 이해할 수 있다.
✅ 사용 기술
✅ 구현 하기
✔ 컨트롤러 만들기
export const pagingController = async (req, res) => {
const { page } = req.query;
try {
const totalPost = await Post.countDocuments({});
if (!totalPost) {
throw Error();
}
let {
startPage,
endPage,
hidePost,
maxPost,
totalPage,
currentPage
} = paging(page, totalPost);
const board = await Post.find({})
.sort({ createAt: -1 })
.skip(hidePost)
.limit(maxPost);
res.render("home", {
board,
currentPage,
startPage,
endPage,
maxPost,
totalPage,
});
} catch (error) {
res.render("home", { board: [] });
}
};
✔ 설명
- (1) : req.query 객체를 통해서 URL의 쿼리스트링을 가져옴. ex) http://페이징구현/?page=1
- (2) : 현재 DB에 존재하는 모든 Post(게시물)의 총 개수를 가져옴.
- (3) : 만약 총 개시물이 없을 경우 Error를 던져 (7)번으로 분기함.
- (4) : paging에 필요한 변수들을 만들기 위해 page와 totalPost를 아규먼트로 함수 호출하여 ES6 비구조화 할당으로 리턴 받는다.
- (5) : db에서 (4)에서 받아온 변수를 토대로 필요한 게시물들을 받아오자. 여기서 mongoose의 sort와 skip, limit를 쓰고 있는데 sort는 내림차순으로 정렬하겠다는 것이고, skip은 제외할 게시물, limit은 제외할 게시물을 제외하고 그 다음부터 찾을 게시물이다. 즉 hidePost가 20이고 limit가 5라면 21~25 게시물을 받아온다.
- (6) : 받아온 db 데이터와 화면에서 렌더링 하기 위해 필요한 변수들을 템플릿으로 전달한다.
- (7) : (3)번에서 에러로 분기 했을 경우는 게시물이 없는 경우이다. 빈 board 객체를 템플릿으로 전달한다.
✔ paging 함수
const paging = (page, totalPost) => {
const maxPost = 10;
const maxPage = 10;
let currentPage = page ? parseInt(page) : 1;
const hidePost = page === 1 ? 0 : (page - 1) * maxPost;
const totalPage = Math.ceil(totalPost / maxPost);
if (currentPage > totalPage) {
currentPage = totalPage;
}
const startPage = Math.floor(((currentPage - 1) / maxPage)) * maxPage + 1;
let endPage = startPage + maxPage - 1;
if (endPage > totalPage) {
endPage = totalPage;
}
return { startPage, endPage, hidePost, maxPost, totalPage, currentPage };
};
export default paging;
- (1) maxPost : 한 페이지에 표시 하고자하는 최대 게시물의 수
- (2) maxPage : 한 페이지에 표시 하고자하는 최대 페이지의 수
- (3) currentPage : 현재 페이지, 만약 쿼리스트링으로 현재 페이지(page)를 받아 왔을 경우 parseInt 메서드를 통해 정수화하고 아닐 경우 1
- (4) hidePost : DB에서 게시물을 불러올때 제외하고 불러올 게시물의 수 만약 page가 1일 경우 제외 할 게시물이 없음으로 0, 있을 경우 -1을 하여 maxPost를 곱해준다. -1을 하는 이유는 예를 들어 3페이지를 출력 할 경우 1페이지는 1~10의 게시물, 2페이지는 11~20의 게시물, 3페이지는 21~30의 게시물을 출력 해야하는데 -1을 해주지 않으면 31부터 40의 게시물을 skip 하게 된다.
- (5) totalPage : 총 페이징 해야 할 페이지의 수 totalPost(DB에 존재하는 게시물의 수) / maxPost를 하면 간단히 구할 수 있다. Math.ceil 메서드를 통해 나머지가 생길 경우를 대비한다. Math.ceil 메서드는 나머지를 무시하고 올림한다. floor을 쓰지 않고 ceil을 쓰는 이유는 만약 floor을 썼을 경우 totalPost가 33이면 33 / 5는 6이 된다. 33개의 게시물을 표시 하기 위해선 총 7개의 페이지가 필요하다.
- (6) : 화면 UI를 통하지 않고 URL에서 직접 접근 하는 경우가 생길 수 있다. 그럴 경우를 대비해서 currentPage(현재 페이지)가 totalPage(총 페이지)보다 클 경우 currentPage를 마지막 페이지(totalPage의 개수가 마지막 페이지가 된다)로 바꾸어 준다.
- (7) startPage : 화면에 표시할 페이지의 시작 번호를 구한다. 예를 들어 현재 3페이지일 경우 startPage는 1이 나와야 한다. currentPage에서 -1을 빼주고 maxPage와 나눈다. -1을 하는 이유는 만약 현재 페이지가 20페이지일 경우 startPage는 11이 되어야 한다. -1을 해주지 않으면 startPage가 21페이지가 되어버린다. 그러고나서 maxPage를 곱해주고 + 1을 해준다.
- (8) endPage : 화면에 표시할 페이지의 마지막 번호를 구한다. startPage는 이시점에서 이미 결정이 되어 있기 때문에 그냥 maxPage를 더한 다음 -1 해주면 된다. 만약 startPage가 11 일경우 endPage는 20 이여야 한다. 11 + 10 - 1 하면 20이 된다.
- (9) : endPage가 totalPage보다 큰 경우 예를 들어 totalPage의 개수는 36일때 startPage는 31일 것이다. 이때 endPage는 40이 될터인데 총 page는 36개이면 충분하다. 이것을 방지하기 위하여 치환 해준다.
- (10) : 이제 페이징을 하기 위한 변수는 모두 생성했으니 객체로 리턴한다.
✔ 템플릿 만들기
if startPage > maxPost
a.prev_btn.pageBtn(href=`${routes.home}?page=${startPage-1}`)
i.fas.fa-angle-double-left
else
a.prev_btn.pageBtn.disabled(href="javascript:void(0);")
i.fas.fa-angle-double-left
-for (let i = startPage; i <= endPage ; i++)
if i === currentPage
a.pageBtn.current(href=`${routes.home}?page=${i}`)
span=i
else
a.pageBtn(href=`${routes.home}?page=${i}`)
span=i
if endPage < totalPage
a.next_btn.pageBtn(href=`${routes.home}?page=${endPage+1}`)
i.fas.fa-angle-double-right
else
a.next_btn.pageBtn.disabled(href="javascript:void(0);")
i.fas.fa-angle-double-right
✔ 화면 구성
- 다음 페이지 버튼으로 넘어갈 수 있다. (만약 현재 페이지가 15일 경우 21페이지로) 조건을 만족하지 못 할 경우 disiable 처리한다. (만약 위치한 페이지가 페이징할 마지막 페이지들에 위치할 경우)
- 이전 페이지 버튼으로 넘어갈 수 있다. (만약 현재 페이지가 15일 경우 10페이지로) 조건을 만족하지 못 할 경우 disiable 처리한다. (만약 위치한 페이지가 페이징할 첫번째 페이지들에 위치할 경우)
- 현재 페이지는 .current 선택자를 이용하여 다른 색으로 표시한다.
✔ 설명
- (1) : startPage가 maxPost 보다 크다는 것은 현재 페이지가 페이징할 첫번째 페이지가 아님을 의미한다.(11이상) 이전페이지 버튼을 활성화 한다.
- (2) : 조건을 만족하지 못 할 경우 비활성화 한다.
- (3) : startPage부터 endPage까지 반복하며 페이지 버튼을 출력한다.
- (4) : 현제 페이지의 경우 다른 색상으로 표시한다.
- (5) : endPage가 totalPage보다 작다는 것은 현재 페이지가 페이징할 마지막 페이지가 아님을 의미한다. 다음페이지 버튼을 활성화 한다.
- (6) : 조건을 만족하지 못 할 경우 비활성화 한다.
끄읏!
잘봤습니다 감사해요 :) 제 프로젝트에 도움이 됐습니다