게시판 - 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
@박우림,
반갑습니다^^ 질문있으시면 댓글남겨주세요^^
이현종 2020.04.26
안녕하세요 좋은글 보면서 공부중입니다. 질문이있는데 post/show.ejs 부분에서 col-md , col-lg, order 속성이 잘 이해가 안되는데 col속성을 사용함으로써 row클래스 내에서 크기 조절을 해주고 order속성이 시각적 속성을 다루는 거로 봤었는데 이 둘이 post.body와 created에서 어떻게 상호작용을 하는지 뒤의 숫자를 바꿔보면서 테스트해봐도 이해가 잘 안되는데 혹시 간략하게 설명해주실수 있으신가요ㅠ?
I
Ian H 2020.04.27
@이현종,
안녕하세요 bootstrap에서 'col' class는 'row' class가 쓰인 element 내에서 사용되며 이를 grid system이라고 부릅니다. ( https://getbootstrap.com/docs/4.1/layout/grid/ 에서 모든 항목의 예제를 볼 수 있습니다.)
'col'만 사용하는 경우 row의 너비를 균등하게 분배합니다.  'col-숫자'로 사용하는경우 (숫자는 1부터 12까지)로 사용하는 경우 row의 너비를 숫자/12만큼의 너비를 사용합니다. 'col-사이즈-숫자'로 사용하는 경우 해당하는 사이즈에서만 해당하는 숫자로 너비를 바꿉니다.
이때 사이즈는 브라우저에서 표시되는 html body element의 너비를 뜻하며 너비에 따라 xs, sm, md, lg, xl를 사용할 수 있습니다. 각각 너비의 사이즈는 https://getbootstrap.com/docs/4.1/layout/grid/#grid-options 에서 볼 수 있습니다.
'col-order-숫자'는 row안 col들의 순서를 강제로 조절합니다. 마찬가지로 'col-order-사이즈-숫자'로 사용할 수 있습니다.
즉 위 본문에 사용된 'col-md-7 col-lg-8 col-xl-9 order-sm-2 order-md-1'는,
body 사이즈가 sm일 경우 (576px~767px) row에서 두번째 col이 됩니다. body 사이즈가 md일 경우 (768px~991px) row의 7/12만큼의 너비를 가지고 첫번째 col이 됩니다. body 사이즈가 lg일 경우 (992px~1199px) row의 8/12만큼의 너비를 가집니다. body 사이즈가 xl일 경우 (1200px~) row의 9/12만큼의 너비를 가집니다.
숫자를 바꿔가며 테스트해봐도 잘 모르시겠다고 하셨는데, 숫자를 바꾸고 화면의 사이즈도 같이 조절해 가면서 테스트해보시면 보일거에요^^
-
-21 2020.05.26
안녕하세요, 게시판에 작성되는 글 관련해서 질문 드립니다. 글을 작성하고 show로 들어가서 해당 게시물을 볼 때 작성할때는 줄 바꿈을 해서 작성하였는데 show로 보게되면 줄 바꿈 없이 표시가 되고 글에 url을 작성한 후 다시 볼 때 파란색으로 뜨면서 하이퍼링크가 안 되어서 나오는데 이 두개는 어떻게 하는지 혹시 아실까요?
I
Ian H 2020.05.26
@-21,
안녕하세요 textarea에서 작성한 글에는 줄바꿈에서 javascript의 줄바꿈 코드 '\n'를 사용합니다. 하지만 html코드에서는 기본적으로 '\n'을 무시하게 되어 있습니다. 이를 무시하지 않고 줄바꿈을 해주려면 해당 html element에 white-space: pre-line; css를 추가해주면 됩니다. 위의 제 코드를 참고해 보세요. html에서 url은 hyperlink로 자동으로 바뀌지 않습니다. html 속의 url을 찾고, hyperlink로 변경하는 코드를 직접 넣어주셔야 합니다.  https://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links 을 참고해 보세요^^
-
-21 2020.05.27
@Ian H,
너무너무 감사드립니다ㅠㅠㅠ!!! 웹개발할 때 빛같은 존재세요ㅠㅠ 감사합니다
I
Ian H 2020.05.27
@-21,
과찬이세요 ㅋㅋ node.js 공부하는 다른 분들께도 널리 퍼트려주시면 감사하겠습니다^^
-
-21 2020.05.28
@Ian H,
감사합니다! 글 읽다가 잘 이해가 안 되는 부분이 있어서 그런데 저 stackoverflow 글에서 js 라이브러리 예시들은 ejs가 아닌 따로 js 파일을 만들어서 post의 show.ejs 파일과 연동하는 식으로 해야될까요? 아니면 show.ejs 파일 안에 script tag 을 넣어서 할 수 도 있는걸까요?
I
Ian H 2020.05.28
@-21,
html파일에 script 태그를 넣어도 동일하게 작동하지만 일반적으로 css, js 파일은 따로 만드는게 정석입니다.
css, js파일을 따로 만들면 브라우저가 해당 파일을 cache로 저장해 둘 수 있기 때문에 사이트 속도 향상에 도움이 됩니다.
-
-21 2020.05.31
@Ian H,
해결했습니다! 감사합니다:)
루카스 2020.05.31
위의 게시글을 토대로 client라는 메뉴를 하나 더 생성해보았는데요, TypeError: /home/ubuntu/board/views/client/index.ejs:4 이런식으로 나오는데 혹시 해결방법을 아실까요? post 의 모든 파일들에서 복사해 변수명만 client로 바꿔서 진행했고 에러가 나는 곳을 가보니  <!DOCTYPE html> <html>   <head>     <%- include('../partials/head') %>   </head>   <body>     <%- include('../partials/nav') %>  ... post에서는 잘 작동되던 ejs 기능이 안먹네요 ㅠㅠ 제가 놓친곳이 있을까요?
I
Ian H 2020.06.02
@루카스,
에러메세지가 없으니 도와드릴 수가 없네요^^; 에러메세지를 잘 읽어보시면 단서를 찾을 수 있을 겁니다. 혹시 계속 안되면 github에 코드를 올려주세요 한번 봐드릴게요!
P
Peter Kim 2020.06.18
재밌게 잘 배우고 있습니다~ 지금 db 저장 중에 <input type="text" ~ 중에 원하는 저장값을 value항에 value =  <%= dbModel.xxx %> 이런식으로  저장중인데요.
post.js 에 function date 란걸 만들어서
혹시 게시판 내용 항목중에 하나를 <% date %> 로 넣고 싶은데, 우리가 javascript - ejs 이용할때 input 값을 불러오는 기능이 value = <% %> 로 입력을 해놔야 이게  자동입력 식으로 되는데요.
db 입력값도 value = ~~~ 을 이용하고 있잖아요?
value = <% date %> value = <%= dbModel.xxx %> 이런식으로 중복해서 넣어도 되긴 하더라구요. 그런데 value = <%= dbModel.xxx %> value = <% date %> 이런식으로 순사가 바뀌면 또 안되구요..
이게 문제가 있는 방법인거 같은데, 해결할 수 있는 원천적인 기능이 있을까요?
I
Ian H 2020.06.18
@Peter Kim,
안녕하세요^^
우선 html코드에 값을 출력하는 경우에는 반드시 <%= %> 처럼 =를 넣어주어야 합니다. 그리고 html에서 두가지 이상의 attribute(이 경우에는 value)를 사용하는 경우 브라우저가 하나를 선택하게 됩니다. (크롬의 경우에는 항상 첫번째 value만 사용). 
dbModel.xxx가 정확히 어떤 타입의 값이고, date 함수가 어떤 일을 하는 함수인지는 잘 모르겠지만, 두가지를 동시에 사용하고 싶으시다면 date 함수에 dbModel.xxx를 인자로 받아서 value="<%= date(dbModel.xxx) %>" 이런 식으로 사용하시면 되겠습니다!
댓글쓰기

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

UP