Vue.js 사용 하기 4 ) PostgreSQL로 간단 게시판 작성 해보기 - 조회 및 수정 기능 추가

작성2021.08.28

수정2021.08.28

읽기00 분

Programming/javascrlpt

반응형

Vue.js 사용 하기 3) postgreSQL 게시판에서 Modal 이용하기 에 이어지는 글 입니다.

​

마지막으로 Vue를 활용해 게시판을 조금 더 "사용할 수 있음" 에 가깝게 수정 해 보았습니다.

1

​

일단 개별 글의 "조회" 기능을 추가 하였습니다.

이에 따라 게시판 목록 에서는 삭제 버튼이 사라졌습니다.

2

각 글의 제목을 누르면 개별 글을 조회 할 수 있도록 하였습니다.

​

수정 버튼을 누르면 게시글 수정이 가능합니다.

image-20210828223556605

게시글 수정은 게시글 작성 모달을 그대로 재활용하며 상황에 따라 다르게 작동하도록 하여 코드의 양을 줄이고 변경에 용이하도록 하였습니다.

​

image-20210828223646654

게시글 작성을 보면 아시겠지만 위의 "수정"과 거의 동일합니다.

​

서버쪽은 전혀 변한게 없고 vueboard.html 파일만 변경 하였습니다. 전체코드를 먼저 한번에 올리고 파트별로 코드를 차근차근 확인해보겠습니다.

​

전체코드

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <!-- CSS only -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
    <!-- JavaScript Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
    <link rel="stylesheet" type="text/css" href="/style.css" />
    <title>title</title>
     <script type="text/x-template" id="modal-template">
      <transition name="modal">
        <div class="modal-mask">
          <div class="modal-wrapper">
            <div class="modal-container">
              <div class="modal-header">
                <slot name="header">
                  default header
                </slot>
              </div>
              <div class="modal-body">
                <slot name="body">
                  default body
                </slot>
              </div>
              <div class="modal-footer">
                <slot name="footer">
                  <button class="modal-default-button" @click="$emit('close')">
                    close
                  </button>
                </slot>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </script>
</head>

<body>
    <div id="board">
        <h2>Vue.js Board</h2>
        <table class="table">
            <thead>
                <th>글번호</th>
                <th>제목</th>
                <th>작성자</th>
                <th>내용</th>
            </thead>
            <tbody>
                <tr v-if="!boardList">
                    <td>게시글이 없습니다.</td>
                </tr>
                <tr v-for="b in boardList">
                    <td v-text="b.boardNo"></td>
                    <td class="title" v-on:click="retrieve(b.boardNo)" v-text="b.title ? b.title : 'untitled'"></td>
                    <td v-text="b.writer"></td>
                    <td v-text="b.content"></td>
                </tr>
            </tbody>
        </table>
        <button class="btn btn-success" id="show-modal" @click="create()">작성</button>
        <modal v-if="showModal" @close="showModal = false">
            <h3 slot="header" v-text="'게시글 '+menu[status]"></h3>
            <div slot="body">
                <input hidden v-model="board.boardNo"></input>
                제목 <input :disabled="status=='view'" v-model="board.title"></input><br/>
                작성자 <input :disabled="status=='view'" v-model="board.writer"></input><br/>
                내용 <input :disabled="status=='view'" v-model="board.content"></input>
                <button class="btn btn-info" v-if="status=='view'" v-on:click.prevent="status='edit'" >수정</button>
                <button class="btn btn-success" v-else v-on:click.prevent="save" :disabled="!board.title||!board.writer||!board.content">저장</button>
                <button class="btn btn-danger" v-if="status!='new'" v-on:click.prevent="remove(board.boardNo)">삭제</button>
            </div>
        </modal>
    </div>
<script>
    Vue.component("modal", {
        template: "#modal-template"
      });

    let board = new Vue({
        el: '#board',
        data: {
            boardList : []
            ,menu : {
                new : '작성'
                ,edit : '수정'
                ,view : '조회'
            }
            ,board : {
                boardNo : ''
                ,title : ''
                ,writer : ''
                ,content : ''
            }
            ,showModal : false
            ,status : ''
        },
        mounted : function(){
            this.load()
        } ,
        methods:{
            load : function(){
                $.ajax({
                    url : '/restapi/board'
                }).done(function(data){
                    board.boardList = data.payload;
                })
            },
            remove : function(boardNo){
                this.showModal=false;
                $.ajax({
                    url : '/restapi/board'
                    ,method : 'post'
                    ,data : {
                        boardNo : boardNo
                        ,_method : 'delete'
                    }
                }).done(function(data){
                    board.load();
                })
            },
            retrieve : function(boardNo){
                this.showModal=true;
                this.status='view';
                let that = this;
                $.ajax({
                    url : '/restapi/board/'+boardNo
                }).done(function(data){
                    that.board = data.payload;
                })
            },
            create : function(){
                this.board = {};
                this.showModal=true;
                this.status='new';
            },
            save : function(){
                let that = this;
                $.ajax({
                    url : '/restapi/board'
                    ,data : {
                        boardNo : that.status=='new' ? '' : that.board.boardNo
                        ,title : that.board.title
                        ,writer : that.board.writer
                        ,content : that.board.content
                    }
                    ,method : 'POST'
                }).done(function(data){
                    that.showModal = false;
                    board.load();
                })
            },
        }
    })
</script>
</body>
</html>

​

Modal 템플릿 부분

     <script type="text/x-template" id="modal-template">
      <transition name="modal">
        <div class="modal-mask">
          <div class="modal-wrapper">
            <div class="modal-container">
              <div class="modal-header">
                <slot name="header">
                  default header
                </slot>
              </div>
              <div class="modal-body">
                <slot name="body">
                  default body
                </slot>
              </div>
              <div class="modal-footer">
                <slot name="footer">
                  <button class="modal-default-button" @click="$emit('close')">
                    close
                  </button>
                </slot>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </script>

아래에서 모달에 사용할 템플릿을 미리 정의하는 부분입니다. 크게 신경 쓰지 않아도 됩니다.

​

HTML body

<div id="board">
        <h2>Vue.js Board</h2>
        <table class="table">
            <thead>
                <th>글번호</th>
                <th>제목</th>
                <th>작성자</th>
                <th>내용</th>
            </thead>
            <tbody>
                <tr v-if="!boardList">
                    <td>게시글이 없습니다.</td>
                </tr>
                <tr v-for="b in boardList">
                    <td v-text="b.boardNo"></td>
                    <td class="title" v-on:click="retrieve(b.boardNo)" v-text="b.title ? b.title : 'untitled'"></td>
                    <td v-text="b.writer"></td>
                    <td v-text="b.content"></td>
                </tr>
            </tbody>
        </table>
        <button class="btn btn-success" id="show-modal" @click="create()">작성</button>
        <modal v-if="showModal" @close="showModal = false">
            <h3 slot="header" v-text="'게시글 '+menu[status]"></h3>
            <div slot="body">
                <input hidden v-model="board.boardNo"></input>
                제목 <input :disabled="status=='view'" v-model="board.title"></input><br/>
                작성자 <input :disabled="status=='view'" v-model="board.writer"></input><br/>
                내용 <input :disabled="status=='view'" v-model="board.content"></input>
                <button class="btn btn-info" v-if="status=='view'" v-on:click.prevent="status='edit'" >수정</button>
                <button class="btn btn-success" v-else v-on:click.prevent="save" :disabled="!board.title||!board.writer||!board.content">저장</button>
                <button class="btn btn-danger" v-if="status!='new'" v-on:click.prevent="remove(board.boardNo)">삭제</button>
            </div>
        </modal>
    </div>

언뜻 보면 복잡해 보일 수도 있는데 막상 30 줄 밖에 안되고 자세히 보면 아주 간단합니다.

크게 테이블 부분과 modal 부분 이렇게 두 부분으로 나뉘는데요.

​

일단 사전 정보가 필요하기 때문에 Vue 코드의 data 부분을 살짝 먼저 보면

        data: {
            boardList : []
            ,menu : {
                new : '작성'
                ,edit : '수정'
                ,view : '조회'
            }
            ,board : {
                boardNo : ''
                ,title : ''
                ,writer : ''
                ,content : ''
            }
            ,showModal : false
            ,status : ''
        },
  • boardList를 선언해서 게시글 목록을 받아 올 배열을 미리 선언 해 둡니다.

  • menu 객체는 status에 따라 (게시글 작성중인지, 조회중인지 수정중인지) 모달창에 상태를 한글로 표현 하기 위한 변수입니다.

  • board는 조회/작성/수정에서 모두 필요해 데이터 바인딩을 위한 객체입니다.

  • showModal은 모달을 보여줄지 말지를 컨트롤 하기 위한 변수이며

  • status 는 현 상태가 작성중인지 조회중인지 수정중인지에 대한 정보를 저장해 화면을 동적으로 렌더링 하기 위한 변수 입니다.

    ​

다시 body로 돌아와서.

                <tr v-for="b in boardList">
                    <td v-text="b.boardNo"></td>
                    <td class="title" v-on:click="retrieve(b.boardNo)" v-text="b.title ? b.title : 'untitled'"></td>
                    <td v-text="b.writer"></td>
                    <td v-text="b.content"></td>
                </tr>

이전 글과 크게 달라진 점은 없지만, 일단 board 객체가 추가되며 이름이 겹쳐 iterator 순회할 때 이름을 board 에서 b 로 변경 하였습니다. 또한 제목이 없을 경우 아무것도 없으면 클릭하기가 불편해서 untitle 를 보여주도록 했습니다.

​

Modal

        <modal v-if="showModal" @close="showModal = false">
            <h3 slot="header" v-text="'게시글 '+menu[status]"></h3>
            <div slot="body">
                <input hidden v-model="board.boardNo"></input>
                제목 <input :disabled="status=='view'" v-model="board.title"></input><br/>
                작성자 <input :disabled="status=='view'" v-model="board.writer"></input><br/>
                내용 <input :disabled="status=='view'" v-model="board.content"></input>
                <button class="btn btn-info" v-if="status=='view'" v-on:click.prevent="status='edit'" >수정</button>
                <button class="btn btn-success" v-else v-on:click.prevent="save" :disabled="!board.title||!board.writer||!board.content">저장</button>
                <button class="btn btn-danger" v-if="status!='new'" v-on:click.prevent="remove(board.boardNo)">삭제</button>
            </div>
        </modal>

​

모달 부분은 게시글 작성/조회/수정 시 모두 한 화면을 돌려가며 사용 하기 때문에 조건문이 꽤 걸렸습니다.

제일 위에 header 에서 h3의 v-text를 "'게시글 '+menu[status]" 로 걸었기 때문에,

            ,menu : {
                new : '작성'
                ,edit : '수정'
                ,view : '조회'
            }

위의 선언에 따라, new 일 경우 "게시글 작성", edit의 경우 "게시글 수정", view의 경우 "게시글 조회" 처럼 간단하게 상황에 따라 다른 텍스트를 보여 주도록 했습니다.

​

또한 조회의 경우에도 input 태그를 사용하다 보니 :disabled="status=='view'" 를 통해 조회 할 때는 텍스트를 수정 하지 못하도록 해 두었습니다.

​

<button class="btn btn-success" v-else v-on:click.prevent="save" :disabled="!board.title||!board.writer||!board.content">저장</button>

저장 버튼은 위와 같이 해 두었기 때문에 "게시글 조회"가 아닐 경우에만 보여지며, 또한 제목, 작성자, 내용 중 어느 하나라도 입력하지 않으면 버튼이 비활성화 되도록 해 두었습니다.

​

script

<script>
    Vue.component("modal", {
        template: "#modal-template"
      });

    let board = new Vue({
        el: '#board',
        data: {
            boardList : []
            ,menu : {
                new : '작성'
                ,edit : '수정'
                ,view : '조회'
            }
            ,board : {
                boardNo : ''
                ,title : ''
                ,writer : ''
                ,content : ''
            }
            ,showModal : false
            ,status : ''
        },
        mounted : function(){
            this.load()
        } ,
        methods:{
            load : function(){
                $.ajax({
                    url : '/restapi/board'
                }).done(function(data){
                    board.boardList = data.payload;
                })
            },
            remove : function(boardNo){
                this.showModal=false;
                $.ajax({
                    url : '/restapi/board'
                    ,method : 'post'
                    ,data : {
                        boardNo : boardNo
                        ,_method : 'delete'
                    }
                }).done(function(data){
                    board.load();
                })
            },
            retrieve : function(boardNo){
                this.showModal=true;
                this.status='view';
                let that = this;
                $.ajax({
                    url : '/restapi/board/'+boardNo
                }).done(function(data){
                    that.board = data.payload;
                })
            },
            create : function(){
                this.board = {};
                this.showModal=true;
                this.status='new';
            },
            save : function(){
                let that = this;
                $.ajax({
                    url : '/restapi/board'
                    ,data : {
                        boardNo : that.status=='new' ? '' : that.board.boardNo
                        ,title : that.board.title
                        ,writer : that.board.writer
                        ,content : that.board.content
                    }
                    ,method : 'POST'
                }).done(function(data){
                    that.showModal = false;
                    board.load();
                })
            },
        }
    })
</script>

마지막으로 Vue.js 코드 입니다.

약간은 복잡 해 보일 수 있는데요, JPA를 사용하다 보니 save function 에서 boardNo를 비워 보낼지, 아니면 채워 보낼지만 간단하게 변경 하면 상황에 따라 수정,삭제가 되기 때문에 어렵지 않게 함수의 재활용이 가능 했습니다.

​

대부분은 이전 글에 있었으니 간단하게 조회 함수만 짚고 넘어가면..

            retrieve : function(boardNo){
                this.showModal=true;
                this.status='view';
                let that = this;
                $.ajax({
                    url : '/restapi/board/'+boardNo
                }).done(function(data){
                    that.board = data.payload;
                })
            },

게시글 조회시에는 showModal 을 true로 해서 모달을 보여 주며 status 를 view 로 변경 합니다.

후에 restapi 에서 특정 게시글을 조회하는 요청을 보내 간단하게 Vue의 board 객체만 변경 해 주면, 이미 데이터가 바인딩 되어 있기 때문에 화면을 간단하게 렌더링 해서 바로 보여줍니다.

​

이렇게 시간이 조금 날 때 마다 게시판을 수정 해 완성 해 보았는데요.

특별한 기능이 있는 게시판은 아니지만 Vue.js 를 이용해 게시판을 만들어 보니 확실히 어렵지 않게 싱글페이지 게시판을 만들 수 있었고 그 편리함 때문에 즐겁게 코딩 할 수 있었습니다.

​

Vue.js 의 기본적인 사용 방법에 익숙 해 지면 조금 더 복잡한 기능들을 하나 둘 씩 더 사용 해 보도록 하겠습니다.

이것으로 Vue.js로 게시판 만들기 시리즈를 마치겠습니다. 수고하셨습니다.

​

해당 프로젝트의 코드는 https://github.com/Shane-Park/markdownBlog/tree/master/projects/vuejs 에서 모두 확인 하실 수 있습니다. 수고하셨습니다.

반응형