게시판 - Front End 개발

소스코드

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

이 게시물의 소스코드는 게시판 만들기 / 게시판 - Back End 개발에서 이어집니다.

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

git reset --hard
git pull
git reset --hard 9d1396d
git reset --soft 79ba957
npm install
atom .

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

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard 9d1396d
git reset --soft 79ba957
npm install
atom .

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


지난번 포스팅에 이어서 post의 front end 개발을 해봅시다.

폴더구조

코드 - front-end js

// public/js/script.js

$(function(){
  function get2digits (num){
    return ('0' + num).slice(-2);
  }

  function getDate(dateObj){
    if(dateObj instanceof Date)
      return dateObj.getFullYear() + '-' + get2digits(dateObj.getMonth()+1)+ '-' + get2digits(dateObj.getDate());
  }

  function getTime(dateObj){
    if(dateObj instanceof Date)
      return get2digits(dateObj.getHours()) + ':' + get2digits(dateObj.getMinutes())+ ':' + get2digits(dateObj.getSeconds());
  }

  function convertDate(){
    $('[data-date]').each(function(index,element){
      var dateString = $(element).data('date');
      if(dateString){
        var date = new Date(dateString);
        $(element).html(getDate(date));
      }
    });
  }

  function convertDateTime(){
    $('[data-date-time]').each(function(index,element){
      var dateString = $(element).data('date-time');
      if(dateString){
        var date = new Date(dateString);
        $(element).html(getDate(date)+' '+getTime(date));
      }
    });
  }

  convertDate();
  convertDateTime();
});

public/js/script.js파일은 node.js 서버에서 사용하는 코드가 아니고, client의 브라우저에서 사용하게 될 JavaScript입니다. 그래서 public 폴더에 들어 있으며, head.ejs파일에 이 파일을 불러오는 코드가 작성됩니다.

MEAN스택 공부에 크게 중요한 부분이 아니기 때문에 코드자체를 설명하지는 않고, convertDate함수, convertDateTime함수가 하는 일만 살펴보겠습니다. 나머지 get2digits, getDate, getTime는 이 두 함수 안에서 사용되는 함수입니다.

convertDate함수는 html element중에 data-date이 있는 것을 찾습니다. 예를 들어 이런것들이요:

<span data-date="2020-01-08T20:08:24.586Z"></span>
data-date에 날짜데이터가 들어 있으면, 해당 데이터를 년-월-일의 형태로 변환해서 element의 텍스트 데이터로 넣습니다.
<span data-date="2020-01-08T20:08:24.586Z">2020-01-08</span>

결국 웹페이지상에서 이용자는 '2020-01-08'이라는 글짜만 보게 됩니다.

convertDateTime함수는 data-date-time을 찾아서 년-원-일 시:분:초의 형태로 변환해서 출력합니다.

이렇게 하는 이유는 JavaScript에서 날짜/시간을 원하는 형태(format)으로 만들기 위해서입니다. JavaScript는 일정한 형태로만 날짜/시간을 출력하는데, '2020-01-01'과 같은 형태로 출력하려면, moment같은 외부 라이브러리를 사용하거나, 저처럼 이렇게 직접 해당 함수를 만들어 주어야 합니다.

이전에 작성된 게시판 만들기 강의에서는 이러한 날짜 변환을 서버에서 했었는데요, 서버가 해외에 있는 경우 시간이 해당 지역의 시간대로 변경되는 문제가 있어서 client에서 변환하는 것으로 변경하였습니다.

**참고로 이 스크립트는 jQuery를 사용하여 작성되었습니다. 이 코드가 어떻게 작동하는지를 알고 싶으신 분들은 jQuery를 검색해서 공부해 보시기 바랍니다.

코드 - ejs

<!-- views/partials/head.ejs -->

...

<!-- web font -->
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">

<!-- my css -->
<link rel="stylesheet" href="/css/master.css"> <!-- 1 -->

<!-- my js -->
<script src="/js/script.js"></script>

<title>My Website</title>

1. 바로 위에서 설명한 public/js/script.js 스크립트를 client에서 호출하는 코드가 추가되었습니다.

<!-- views/partials/nav.ejs -->

    ...
      <ul class="navbar-nav">
        <li class="nav-item"><a href="/" class="nav-link">Home</a></li>
        <li class="nav-item"><a href="/about" class="nav-link">About</a></li>
        <li class="nav-item"><a href="/posts" class="nav-link">Board</a></li> <!-- 1 -->
      </ul>
    ...

1. 세번째 메뉴 board가 추가되었습니다.

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

<!DOCTYPE html>
<html>
  <head>
    <%- include('../partials/head') %>
  </head>
  <body>
    <%- include('../partials/nav') %>

    <div class="container mb-3">

      <h2 class="mb-3">Board</h2>

      <table class="board-table table table-sm border-bottom">

        <thead class="thead-light">
          <tr>
            <th scope="col">Title</th>
            <th scope="col" class="date">Date</th>
          </tr>
        </thead>

        <tbody>
          <% if(posts == null || posts.length == 0){ %>
            <tr>
              <td colspan=2> There is no data to show :( </td>
            </tr>
          <% } %>
          <% posts.forEach(function(post) { %>
            <tr>
              <td>
                <a href="/posts/<%= post._id %>"><div class="ellipsis"><%= post.title %></div></a>
              </td>
              <td class="date">
                <span data-date="<%= post.createdAt %>"></span> <!-- 1 -->
              </td>
            </tr>
          <% }) %>
        </tbody>

      </table>

      <div>
        <a class="btn btn-primary" href="/posts/new">New</a>
      </div>

    </div>
  </body>
</html>

Post의 index를 table로 표시합니다.

1. data-date가 <span> element에 사용되었습니다. public/js/script.js에 의해 post.createdAt, 즉 게시물 작성시간이 년-월-일의 형태로 출력됩니다.

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

<!DOCTYPE html>
<html>
  <head>
    <%- include('../partials/head') %>
  </head>
  <body>
    <%- include('../partials/nav') %>

    <div class="container mb-3">

      <nav aria-label="breadcrumb"> <!-- 1 -->
        <ol class="breadcrumb p-1 pl-2 pr-2">
          <li class="breadcrumb-item"><a href="/">Home</a></li>
          <li class="breadcrumb-item"><a href="/posts">Board</a></li>
          <li class="breadcrumb-item active" aria-current="page">New Post</li>
        </ol>
      </nav>

      <form action="/posts" method="post">

        <div class="form-group">
          <label for="title">Title</label>
          <input type="text" id="title" name="title" value="" class="form-control">
        </div>
        
        <div class="form-group">
          <label for="body">Body</label>
          <textarea id="body" name="body" rows="5" class="form-control"></textarea>
        </div>

        <div>
          <a class="btn btn-primary" href="/posts">Back</a>
          <button type="submit" class="btn btn-primary">Submit</button>
        </div>

      </form>

    </div>
  </body>
</html>

1. https://getbootstrap.com/docs/4.4/components/breadcrumb 을 사용하였습니다. 브레드 크럼이란, 빵 부스러기라는 뜻인데, 아마 '헨젤과 그레텔'에서 빵 부스러기로 지나온 길을 남긴것에서 이름을 가져온 것 같습니다.

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

<!DOCTYPE html>
<html>
  <head>
    <%- include('../partials/head') %>
  </head>
  <body>
    <%- include('../partials/nav') %>

    <div class="container mb-3">

      <nav aria-label="breadcrumb">
        <ol class="breadcrumb p-1 pl-2 pr-2">
          <li class="breadcrumb-item"><a href="/">Home</a></li>
          <li class="breadcrumb-item"><a href="/posts">Board</a></li>
          <li class="breadcrumb-item active" aria-current="page"><%= post.title %></li>
        </ol>
      </nav>

      <div class="card">
        <h5 class="card-header p-2"><%= post.title %></h5>
        <div class="row"> <!-- 1 -->

          <div class="col-md-7 col-lg-8 col-xl-9 order-sm-2 order-md-1"> <!-- 1 -->
            <div class="post-body p-2"><%= post.body %></div>
          </div>

          <div class="col-md-5 col-lg-4 col-xl-3 order-sm-1 order-md-2"> <!-- 1 -->
            <div class="post-info card m-2 p-2"> 
              <div><span>Created</span> : <span data-date-time="<%= post.createdAt %>"></span></div> <!-- 2 -->
              <% if(post.updatedAt) { %>
                <div><span>Updated</span> : <span data-date-time="<%= post.updatedAt %>"></span></div> <!-- 2 -->
              <% } %>
            </div>
          </div>

        </div>
      </div>

      <div class="mt-3">
        <a class="btn btn-primary" href="/posts">Back</a>
        <a class="btn btn-primary" href="/posts/<%= post._id %>/edit">Edit</a>
        <form action="/posts/<%= post._id %>?_method=delete" 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>
      </div>

    </div>
  </body>
</html>

1. bootstrap의 row, col-? class를 사용해서 html element 안의 공간을 분할할 수 있습니다. row class element안에 col-? class element를 넣은 구조인데요, ?에는 기본적으로 1에서 12사이의 숫자가 들어갈 수 있습니다. 예를들어 row class element안에 col-6 class element를 2개 넣어 주면 row class element안의 공간이 6:6 즉 반반으로 나누어집니다. col-sm-?, col-md-?와 같이 사이즈를 지정해주면, 화면이 작을 때(sm), 보통일 때(md), 클 때(lg), 아주 클 때(xl)로 나누어서 적용시킬 수 있습니다. 자세한 설명은 https://getbootstrap.com/docs/4.4/layout/grid 에서 읽어주세요.

2. data-date-time이 Created, Updated 옆의 <span> element에 사용되었습니다. 게시물이 작성된 시간, 수정된 시간이 년-월-일 시:분:초의 형태로 출력됩니다.

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

<!DOCTYPE html>
<html>
  <head>
    <%- include('../partials/head') %>
  </head>
  <body>
    <%- include('../partials/nav') %>

    <div class="container mb-3">

      <nav aria-label="breadcrumb">
        <ol class="breadcrumb p-1 pl-2 pr-2">
          <li class="breadcrumb-item"><a href="/">Home</a></li>
          <li class="breadcrumb-item"><a href="/posts">Board</a></li>
          <li class="breadcrumb-item"><a href="/posts/<%= post._id %>"><%= post.title %></a></li>
          <li class="breadcrumb-item active" aria-current="page">Edit Post</li>
        </ol>
      </nav>

      <form action="/posts/<%= post._id %>?_method=put" method="post">

        <div class="form-group">
          <label for="title">Title</label>
          <input type="text" id="title" name="title" value="<%= post.title %>" class="form-control">
        </div>

        <div class="form-group">
          <label for="body">Body</label>
            <textarea id="body" name="body" rows="5" class="form-control"><%= post.body %></textarea>
        </div>

        <div>
          <a class="btn btn-primary" href="/posts/<%= post._id %>">Back</a>
          <button type="submit" class="btn btn-primary">Submit</button>
        </div>

      </form>

    </div>
  </body>
</html>

edit은 역시 new와 구성이 비슷합니다. form action에 ?_method=put 넣는 것을 잊지 마세요.

코드 - css

/* public/css/master.css */

body {
  font-family: 'Open Sans', sans-serif;
}
.breadcrumb-item {
  font-size: 0.8em !important;
}
.ellipsis{
  display: block;
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis; /* 1 */
}

.board-table {
  table-layout: fixed;
}
.board-table .date {
  width: 100px;
}

.post-body{
  white-space: pre-line; /* 2 */
}
.post-info{
  font-size: 0.8em;
}

1. ellipsis는 해당 element의 text가 너무 길 경우, 넘치는 text를 ...로 표시해줍니다.

2. 이 부분이 없으면 게시물 본문의 줄바꿈(/n)이 표현되지 않습니다. 왜냐면 HTML에서의 줄바꿈은 <br/>이기 때문이죠. 이 부분을 통해 /n이 제대로 줄바꿈을 표현할 수 있게 됩니다.

나머지 CSS는 따로 설명하지 않겠습니다. 각각의 CSS가 어떤 일을 하는지 궁금하다면, 해당 CSS를 지운 후 해당 CSS를 사용하는 부분이 어떻게 바뀌는지를 살펴보세요.

실행결과

처음에는 아무 글이 없죠. new 버튼으로 새 post를 만들 수 있습니다.

글 생성 부터 지우기까지 제대로 작동하는지 테스트해봅시다.

마치며...

지금까지 CRUD가능한 게시판을 만들어봤습니다. 주소록 만들기 예제와 아직까지는 크게 다르지 않죠. 다음 강의부터 본격적으로 새로운 내용을 익혀봅시다!

bootstrap은 하나하나 다 설명드릴 순 없고, https://getbootstrap.com/docs/4.4 에 예제들이 있으니 보신 후 필요할때 가져다가 쓰면 됩니다.
저도 모든 class를 정확하게 이해하고 쓰는 것은 아니고 예제를 보면서 써보고 싶은게 있으면 가져다가 쓰고 있습니다.

댓글

-
-21 2020.01.18
안녕하세요! 블로그 잘 보고 있습니다. 질문 사항이 있어 댓글을 남기게 되었는데요, github에서 똑같은 코드를 가지고 와서 하는 중인데 about과 home을 누르면 잘 라우팅이 되는데 board를 누르면 페이지가 안 뜨고 무한 로딩이 뜨네요.. 몽구스 연결도 다 했는데ㅠ 로그인도 정보 다 입력하고 submit을 눌러도 로딩만 계속 되네요.. 혹시 왜 그런지 알 수 있을까요?
I
Ian H 2020.01.20
@-21,
안녕하세요? 사이트 실행시 'DB connected'라는 메세지가 정상적으로 출력되나요?(index.js 15번째 줄에 의한 메세지) 만약 DB connected라는 메세지가 정상적으로 출력된다면, DB에 관련 collection들을 모두 지우신 후에 다시 실행해 보세요~
-
-21 2020.01.21
학교가 방화벽 차단을 해서 그런거였네요ㅠㅠ 핸드폰 핫스팟으로 하니까 잘 돼요ㅠ 감사합니다!
I
Ian H 2020.01.21
@-21,
아ㅠㅠ 원인을 찾으셨다니 다행입니다!
박우림 2020.03.21
많이 배우고 있습니다. 감사합니다!
I
Ian H 2020.03.22
@박우림,
반갑습니다^^ 질문있으시면 댓글남겨주세요^^
댓글쓰기

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

UP