이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.
이 게시물의 소스코드는 게시판 만들기 / 게시판 - 접근제한에서 이어집니다.
board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.
board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.
- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/e11a6f976ba0180ad636ea137cb9a4f09287fd38
지금은 페이지 기능이 없어서 아무리 게시물이 많아도 post의 index 한 페이지에 모든 게시물이 나타납니다. 일반적으로 게시판이라면 페이지 기능이 있어서 한 페이지에 일정한 수의 게시물만 보여주죠.
1. post의 index route(/posts
)에 page, limit을 query로 추가하여, /posts?page=2&limit=10
의 형태로 한페이지에 표시될 게시물 수(limit)와 보여줄 페이지(page)를 정하여 해당 게시물들을 보여주는 기능을 추가해 봅시다.
2. 다음으로 post의 index view에 다음페이지, 이전페이지, 페이지 번호등을 나열하여 이용자가 페이지를 이동할 수 있도록 합니다. Bootstrap의 pagination 컴포넌트(https://getbootstrap.com/docs/4.1/components/pagination)를 사용합니다.
3. 마지막으로 post의 index view에 한페이지에 몇개의 게시물을 보여줄 지를 정하는 drop-down메뉴를 추가해 봅시다.
위 기능들을 추가하여 아래와 같이 게시판을 업그레이드해봅시다.
post index route를 먼저 살펴봅시다.
// routes/posts.js // Index router.get('/', async function(req, res){ // 1 var page = Math.max(1, parseInt(req.query.page)); // 2 var limit = Math.max(1, parseInt(req.query.limit)); // 2 page = !isNaN(page)?page:1; // 3 limit = !isNaN(limit)?limit:10; // 3 var skip = (page-1)*limit; // 4 var count = await Post.countDocuments({}); // 5 var maxPage = Math.ceil(count/limit); // 6 var posts = await Post.find({}) // 7 .populate('author') .sort('-createdAt') .skip(skip) // 8 .limit(limit) // 8 .exec(); res.render('posts/index', { posts:posts, currentPage:page, // 9 maxPage:maxPage, // 9 limit:limit // 9 }); }); ...
1. async
키워드가 function
키워드 앞에 추가되었습니다. 이 함수안에는 await
키워드를 사용하는데, await
키워드를 사용하는 함수는 반드시 async
키워드를 function
키워드 앞에 붙여야 합니다. await
키워드가 하는 일은 5번에서 설명합니다.
2. Query string으로 전달받은 page, limit을 req.query
를 통해 읽어옵니다.
parseInt
함수를 사용한 이유: Query string은 문자열로 전달되기 때문에 숫자가 아닐 수도 있고, 정수(소수점이 없음)를 읽어내기 위해 사용했습니다.
Math.max
함수를 사용한 이유: page, limit은 양수여야 합니다 최소 1이 되어야 합니다.
3. 만약 정수로 변환될 수 없는 값이 page, limit에 오는 경우 기본값을 설정해 줍니다. 이 값은 해당 query string이 없는 경우에도 사용됩니다.
4. skip은 무시할 게시물의 수를 담는 변수입니다. 페이지당 10개의 게시물이 있고, 현재 3번째 페이지를 만들려면, DB에서 처음 20개의 게시물을 무시하고 21번째부터 10개의 게시물을 보여주는 것이죠.
5. Promise 앞에 await
키워드를 사용하면, 해당 Promise가 완료될 때까지 다음 코드로 진행하지 않고 기다렸다가 해당 Promise가 완료되면 resolve된 값을 반환(return)합니다. Post.countDocuments({})
함수를 사용해서 {}
에 해당하는({}
== 조건이 없음, 즉 모든) post의 수를 DB에서 읽어 온 후 count변수에 담았습니다.
6. 전체 게시물 수(count)를 알고, 한페이지당 표시되야 할 게시물의 수(limit)을 알면, 전체 페이지 수를 계산할 수 있습니다. 이 값을 maxPage변수에 담습니다.
7. 기존의 Post.find({})
도 await
를 사용하여 검색된 posts를 바로 변수에 담을 수 있게 하였습니다.
8. skip
함수는 일정한 수만큼 검색된 결과를 무시하는 함수, limit
함수는 일정한 수만큼만 검색된 결과를 보여주는 함수입니다.
9. 현재 페이지 번호(currentPage), 마지막 페이지번호(maxPage), 페이지당 보여줄 게시물 수(limit)은 view로 전달하여 view에서 사용할 수 있게 합니다.
<!-- views/posts/index.ejs --> ... </table> <div class="row mb-3"> <div class="col-2"> <!-- 1 --> <% if(isAuthenticated){ %> <a class="btn btn-primary" href="/posts/new">New</a> <% } %> </div> <nav class="col-8"> <% <!-- 2 --> var offset = 2; var previousBtnEnabled = currentPage>1; var nextBtnEnabled = currentPage<maxPage; %> <!-- 3 --><ul class="pagination pagination-sm justify-content-center align-items-center h-100 mb-0"> <li class="page-item <%= previousBtnEnabled?'':'disabled' %>"> <a class="page-link" href="/posts?page=<%= currentPage-1 %>&limit=<%= limit %>" <%= previousBtnEnabled?'':'tabindex=-1' %>>«</a> </li> <% for(i=1;i<=maxPage;i++){ %> <% if(i==1 || i==maxPage || (i>=currentPage-offset && i<=currentPage+offset)){ %> <li class="page-item <%= currentPage==i?'active':'' %>"><a class="page-link" href="/posts?page=<%= i %>&limit=<%= limit %>"> <%= i %> </a></li> <% } else if(i==2 || i==maxPage-1){ %> <li><a class="page-link">...</a></li> <% } %> <% } %> <li class="page-item <%= nextBtnEnabled?'':'disabled' %>"> <a class="page-link" href="/posts?page=<%= currentPage+1 %>&limit=<%= limit %>" <%= nextBtnEnabled?'':'tabindex=-1' %>>»</a> </li> </ul> </nav> </div> <form action="/posts" method="get"> <!-- 4 --> <div class="form-row"> <div class="form-group col-3"> <label>Show</label> <select name="limit" class="custom-select" onchange="this.parentElement.parentElement.parentElement.submit()"> <option value="5" <%= limit==5?'selected':'' %>>5 posts</option> <option value="10" <%= limit==10?'selected':'' %>>10 posts</option> <option value="20" <%= limit==20?'selected':'' %>>20 posts</option> </select> </div> </div> </form> </div> </body> </html>
1. New 버튼을 pagination과 같은 줄에 놓기 위해 Bootstrap grid(https://getbootstrap.com/docs/4.1/layout/grid) css (row
, col-?
)를 사용하였습니다.
2. pagination에 사용될 변수들입니다.
offset
: 현재페이지 좌우로 몇개의 페이지들을 보여줄지를 정하는 변수입니다.
previousBtnEnabled
: 이전 페이지 버튼은 현재페이지가 1보다 큰 경우에만 활성화됩니다.
nextBtnEnabled
: 다음 페이지 버튼은 현재페이지가 마지막페이지(maxPage)보다 작은 경우에만 활성화됩니다.
3. 기본적인 구조는 https://getbootstrap.com/docs/4.0/components/pagination에서 가져왔고, 페이지번호 버튼 위치에 맞게 이동해야할 route를 /posts?page=page&limit=limit
만들어 줍니다.
중간의 페이지번호 버튼들을 출력하는 반복문의 조건을 잘 살펴봅시다.
if(i==1 || i==maxPage || (i>=currentPage-offset && i<=currentPage+offset))
만약, 페이지번호 버튼이 1페이지, 혹은 마지막 페이지 인 경우, 혹은 현재페이지와 offset 이내의 차이가 있는 경우에는 페이지 번호 버튼을 만듭니다.
else if(i==2 || i==maxPage-1)
만약, 1번 조건에 페이지번호 2와 마지막 바로 이전 페이지번호 버튼이 맞지 않는다면 ... 를 보여줍니다.
위 두 조건에 모두 맞지 않는 페이지번호 버튼들은 화면에 표시하지 않습니다.
4. 화면에 표시될 게시물의 수를 바꿀 수 있는 form을 만들었습니다. form의 method를 get으로 하면, form 항목들이 action route의 query string으로 전달됩니다. onchange항목을 사용하여 drop-down의 값이 변경되면 자동으로 form이 submit되도록 하였습니다.
form-row
와 col-3
를 사용하였는데, 나머지 공간에는 나중에 검색 기능을 넣기 위해 비워뒀습니다.
게시판에 접속하면 처음에는 /posts route에 page, limit query가 없으므로 post index route에서 page = 1, limit = 10을 기본값으로 하여 페이지를 생성합니다.
drop-down 메뉴를 이용해서 페이지당 표시될 게시물을 5개로 바꿔 봅시다.
주소가 /posts?limit=5로 바뀌고 5개의 게시물이 표시됩니다. pagination의 페이지 번호 버튼이 어떻게 바뀌는지도 잘 보세요.
다음페이지로 이동해 봅시다.
주소가 /posts?page=2&limit=5로 바뀌었습니다.
주소에 넣는 값에 따라 게시판에 보이는 결과를 마음대로 바꿀 수 있습니다. limit을 1로 페이지를 5로 해봅시다.
페이지 버튼이 너무 많아지만 첫번째 페이지, 마지막 페이지, 현재페이지와 3이상 차이나는 페이지들은 ...로만 표시됩니다. drop-down메뉴에는 limit = 1 옵션이 없으므로 그냥 첫번째 옵션이 표시됩니다.
pagination을 통해 페이지를 자유롭게 이동할 수 있게 되었습니다. 하지만 지금의 게시판은 2번째 페이지의 게시물에 들어갔다가 back버튼을 누르면 1번째 페이지로 나오게 됩니다. 다음 강의에서는 이 부분을 고쳐보도록 하겠습니다.
댓글
이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기