게시판 - 페이지 기능 만들기 2

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 게시판 만들기(고급) / 게시판 - 페이지 기능 만들기 1에서 이어집니다.

board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard fc06140
git reset --soft 7f522a4
npm install
atom .

board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard fc06140
git reset --soft 7f522a4
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/fc061407d9cd7f87a4a8fa18ac5ea300cb686d1b


이전 강의에서 게시판에 페이지 기능을 추가해 봤습니다. 페이지 기능은 /posts route에 page, limit query string을 추가하여 조정하는데, /posts 페이지를 벗어나는 순간 이 정보들을 잃게 됩니다. 예를 들어 한페이지에 5개의 게시물만 표시하도록 했더라도, 게시물을 클릭하여 post의 show 페이지로 갔다가 back 버튼을 눌러서 다시 post의 index 페이지로 돌아오면 limit=5 query string이 사라져서 다시 10개의 게시물이 표시되게 됩니다.

이걸 고치려면 여러가지 방법이 있는데, 저는 post와 관련된 모든 route, view에 이전 query string들을 계속하여 전달하는 방법을 사용하겠습니다.

예를 들어 index에서 show로 넘어갈 때 limit이나 page의 querystring을 전달해주고, show에서 index로 넘어 올 때 다시 limit과 page를 전달해서 페이지에 표시될 게시물 수, 현재페이지 정보를 유지할 수 있게 하는 방법입니다.

폴더 구조

코드 - js

// util.js

...

util.getPostQueryString = function(req, res, next){
  res.locals.getPostQueryString = function(isAppended=false, overwrites={}){    
    var queryString = '';
    var queryArray = [];
    var page = overwrites.page?overwrites.page:(req.query.page?req.query.page:'');
    var limit = overwrites.limit?overwrites.limit:(req.query.limit?req.query.limit:'');

    if(page) queryArray.push('page='+page);
    if(limit) queryArray.push('limit='+limit);

    if(queryArray.length>0) queryString = (isAppended?'&':'?') + queryArray.join('&');

    return queryString;
  }
  next();
}

module.exports = util;

util.getPostQueryString함수는 res.localsgetPostQueryString함수를 추가하는 middleware입니다. 이렇게 res.locals에 추가된 변수나 함수는 view에서 바로 사용할 수 있고, res.locals.getPostQueryString의 형식으로 route에서도 사용할 수 있게 됩니다.

res.locals.getPostQueryString함수의 기본역할은 req.query로 전달 받은 query에서 page, limit을 추출하여 다시 한줄의 문자열로 만들어 반환하는 것입니다.

2개의 파라메터를 optional로 받는데, 첫번째로 파라메터는 생성할 query string이 기존의 query string에 추가되는(appended) query인지 아닌지를 boolean으로 받습니다. 만약 추가되는 query라면 '&'로 시작하고, 아니라면 '?'로 시작하는 query string을 만듭니다.

두번째 파라메터는 req.query의 page나 limit을 overwrite하는 파라메터입니다. 예를들어 req.query.page의 값를 무시하고 page를 무조건 1로 하는 query를 만들고 싶다면 {page:1}을 전달하면 됩니다.

// index.js

...
var passport = require('./config/passport');
var util = require('./util'); <!-- 1 -->
...

// Routes
app.use('/', require('./routes/home'));
app.use('/posts', util.getPostQueryString, require('./routes/posts')); <!-- 1 -->
app.use('/users', require('./routes/users'));

...

util.getPostQueryString미들웨어를 posts route이 request되기 전에 배치하여 모든 post routes에서 res.locals.getPostQueryString를 사용할 수 있게 하였습니다.

// routes/posts.js

...

// create
      ...
      return res.redirect('/posts/new'+res.locals.getPostQueryString()); // 1
    }
    res.redirect('/posts'+res.locals.getPostQueryString(false, {page:1})); //2

// update
      ..
      return res.redirect('/posts/'+req.params.id+'/edit'+res.locals.getPostQueryString()); // 1
    }
    res.redirect('/posts/'+req.params.id+res.locals.getPostQueryString()); // 1
  ...

// destroy
    ...
    res.redirect('/posts'+res.locals.getPostQueryString()); // 1
  ...

1. post의 routes에서 redirect가 있는 경우 res.locals.getPostQueryString함수를 사용하여 query string을 계속 유지하도록 합니다. 물론 해당 route로 page, limit query string들이 전달되어야 합니다. 이 부분은 view에서 설정해줘야 합니다.

여기서 생성되는 query string은 기존의 query string에 추가되는 게 아니고(isAppended = false), 값을 overwrite하지도 않으므로 파라메터 전달 없이 res.locals.getPostQueryString()로 호출합니다. 

2. 새 글을 작성한 후에는 무조건 첫번째 page를 보여주도록 page query를 1로 overwrite해줍니다. overwrite은 res.locals.getPostQueryString함수의 두번째 파라메터이죠. 첫번째 파라메터는 optional이지만 첫번째 파라메터없이 두번째 파라메터를 전달할 수 없으므로, (false, {page:1})를 사용합니다.

코드 - ejs

<!-- views/posts/index.ejs -->

              ...
     <!-- 1 --> <a href="/posts/<%= post._id %><%= getPostQueryString() %>"><div class="ellipsis"><%= post.title %></div></a>
              ...

          <% if(isAuthenticated){ %>
 <!-- 2 --> <a class="btn btn-primary" href="/posts/new<%= getPostQueryString() %>">New</a>
          <% } %>
        ...

            <li class="page-item <%= previousBtnEnabled?'':'disabled' %>">
   <!-- 3 --> <a class="page-link" href="/posts<%= getPostQueryString(false, {page:currentPage-1}) %>" <%= previousBtnEnabled?'':'tabindex=-1' %>>«</a>
            </li>
            <% for(i=1;i<=maxPage;i++){ %>
              <% if(i==1 || i==maxPage || (i>=currentPage-offset && i<=currentPage+offset)){ %>
     <!-- 4 --> <li class="page-item <%= currentPage==i?'active':'' %>"><a class="page-link" href="/posts<%= getPostQueryString(false, {page:i}) %>"> <%= i %> </a></li>
              ...
            <% } %>
            <li class="page-item <%= nextBtnEnabled?'':'disabled' %>">
   <!-- 5 --> <a class="page-link" href="/posts<%= getPostQueryString(false, {page:currentPage+1}) %>" <%= nextBtnEnabled?'':'tabindex=-1' %>>»</a>
            </li>
          ...

1. 게시판에서 게시물 제목을 눌러 show route으로 가는 경우 page, limit query string을 전달해 줍니다. view에서는 res.locals의 항목들을 바로 사용할 수 있기때문에 res.locals.getPostQueryString가 아니라 getPostQueryString로 호출합니다.

2. New 버튼을 눌러 new route으로 가는 경우에도 마찬가지입니다.

3. pagination의 이전 페이지 버튼은 page가 현재페이지-1로 되도록overwrite합니다. 

4. pagination의 페이지 버튼들입니다 page가 i로 되도록 overwrite합니다.

5. pagination의 다음 페이지 버튼입니다. page가 현재페이지+1로 되도록 overwrite합니다.

<!-- views/posts/new.ejs -->

    ...

      <form action="/posts<%= getPostQueryString() %>" method="post"> <!-- 1 -->
        ...

 <!-- 2 --> <a class="btn btn-primary" href="/posts<%= getPostQueryString() %>">Back</a>          ...

1,2. new view에서는 form의 action url과 뒤로가기 버튼에 getPostQueryString함수가 사용되었습니다.

<!-- views/posts/show.ejs -->

      ...
        <a class="btn btn-primary" href="/posts<%= getPostQueryString() %>">Back</a> <!-- 1 -->
        <% if(isAuthenticated && post.author && currentUser.id == post.author.id){ %>
<!-- 2 --><a class="btn btn-primary" href="/posts/<%= post._id %>/edit<%= getPostQueryString() %>">Edit</a>
<!-- 3 --><form action="/posts/<%= post._id %>?_method=delete<%= getPostQueryString(true) %>" method="post" class="d-inline">
            <a class="btn btn-primary" href="javascript:void(0)" onclick="confirm('Do you want to delete this?')?this.parentElement.submit():null;">Delete</a>
          </form>
        <% } %>
      ...

1,2,3. show view에서는 뒤로가기버튼, 수정 버튼, 삭제버튼의 form에getPostQueryString함수가 사용되었습니다. 삭제버튼의 form에는 이미 ?_method=delete query가 달려 있으므로 isAppended파라메터가 true가 되어야 합니다.

<!-- views/posts/edit.ejs -->

    ...

      <form action="/posts/<%= post._id %>?_method=put<%= getPostQueryString(true) %>" method="post"> <!-- 1 --> 
        ...

<!-- 2 --><a class="btn btn-primary" href="/posts<%= getPostQueryString() %>">Back</a>
          ...

1,2. edit view에서는 form의 action url과 뒤로가기 버튼에 getPostQueryString함수가 사용되었습니다.

실행 결과

게시판에 표시할 페이지 수, 페이지를 변경한 후에 게시물을 클릭해 봅시다.

url에 page, limit의 query 로 추가되어 있는 것을 볼 수 있습니다.

edit버튼을 눌러봅시다.

여전히 page, limit의 query가 유지되고 있습니다.

back버튼을 눌러 게시물 목록까지 돌아옵시다.

page, limit이 query에 의해 동일한 목록이 표시됩니다.

마치며...

이제 제법 쓸만한 게시판이 되어 가는 것 같습니다. 다음에는 게시물 검색 기능을 추가해 보겠습니다. 수고하셨습니다!

댓글

댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP