본문 바로가기
Projects/NodeBoard

AJAX로 댓글달기 기능 구현

by DawIT 2021. 3. 6.
320x100

댓글 기능을 구현해야 하는데, 댓글을 달거나 삭제할 때마다 페이지를 새로고침한다면 자원 낭비일 것이고, 사용자 입장에서도 별로 좋은 느낌은 받지 못한다.

 

그래서 AJAX를 사용해서 비동기(새로고침을 하지 않는)방식으로 댓글을 구현했다.

 

먼저 댓글을 위한 REPLY 테이블을 DB에 만들어야 한다. REPLY 테이블의 COLUMN은 다음과 같다.

 

REPLY 테이블

 

  • ID : 각각의 댓글에 부여되는 식별값
  • POST_ID : 해당 댓글이 속한 글의 ID
  • ROOT_REPLY_ID : 답글의 경우 존재하는 타겟 댓글 ID (일반 댓글일 경우 NULL)
  • AUTHOR : 댓글 작성자
  • CONTENT : 댓글 내용
  • isLogined : 로그인 사용자의 댓글인지 여부
  • PASSWORD : 암호화된 댓글 작성 암호

 

일단 답글은 지금 추가하지 않고 후에 추가할 것이다.

 

var getReply = function(postId,callback){
    db.query('SELECT ID,POST_ID,ROOT_REPLY_ID,AUTHOR,CONTENT,isLogined FROM REPLY WHERE POST_ID=?',[postId],(err,result)=>{
        if (err){
            console.log(err);
            callback(false);
        }
        else{
            callback(result);
        }
    });
}

var deleteReply = function(id,plainPassword,callback){
    useCrypto(plainPassword,(password)=>{
        db.query('SELECT PASSWORD FROM REPLY WHERE ID=?',[id],(err,result)=>{
            if (err){
                callback(1);
                console.log(err);
                return;
            }

            if (result[0].PASSWORD == password){
                db.query('DELETE FROM REPLY WHERE ID=?',[id],(err)=>{
                    if (err){
                        callback(2);
                        console.log(err);
                        return;
                    }

                    callback(-1);
                });
            } else{
                callback(0);
            }
        });
    });
};

var writeReply = function(data,callback){
    useCrypto(data.password,(password)=>{
        db.query('INSERT INTO REPLY (`POST_ID` ,`AUTHOR`, `CONTENT`, `isLogined`, `PASSWORD`) VALUES (?, ?, ?, ?, ?)',[data.postId,data.writer,data.content,data.isLogined,password],(err)=>{
            if(err){
                callback(1);
                console.log(err);
                return;
            } else{
                callback(-1);
            }
        });
    });
};

 

먼저 db-query.js의 getReply, deleteReply, writeReply 함수이다. getReply는 단순히 인자인 postId를 통해 해당 글의 댓글을 가져온다.

 

deleteReply는 댓글의 ID와 평문 암호를 받아서 암호화하고 DB의 값과 비교하여 같으면 댓글을 삭제한다. callback으로 돌려주는 정수 값은 코드인데, 이 값에 따라 프론트에서 결과 처리를 한다 (-1이면 성공, 0이면 암호 불일치, 1이면 에러)

 

writeReply는 댓글을 작성하는 기능을 한다. 받아오는 정보는 해당 글의 ID와 댓글작성자, 내용, 로그인 여부, 평문 비밀번호이다. 여기서 평문 비밀번호는 이전에 정의한 useCrypto함수로 암호화한뒤 저장한다.

 

router.post('/deleteReply',function(req,res,next){
    db.deleteReply(req.body.id,req.body.plainPassword,(code)=>{
        res.send({code: code});
    });
});

router.post('/refreshReply',function(req,res,next){
    db.getReply(req.body.id,(replyList)=>{
        res.send({replyList: replyList});
    });
});

router.post('/writeReply',function(req,res,next){
    db.writeReply(req.body,(code)=>{
        res.send({code: code})
    });
});

 

댓글 기능 때문에 reply.js 를 따로 생성하고 라우팅 하기는 좀 그래서 그냥 view.js 에 댓글에 관한 url을 라우팅했다.

 

view.js에서는 댓글 삭제, 댓글리스트 갱신, 댓글 작성에 해당하는 url을 연결해준다. 여기서 해줄 일은 별로 없고 db-query.js 파일의 알맞은 함수만 호출하여 정보를 전달해주면 된다.

 

HTML

<div id="reply-upper">
    댓글
    <a onclick="refreshReply(<%=id%>)" onmouseover="this.style.cursor='pointer'"><img src="/images/refresh.svg" alt="새로고침"></a>
</div>
<div id="reply-area">
    <% for (let reply of replyList) { %>
    <div id="reply">
        <div id="reply-bar">
            <%=reply.AUTHOR%>
            <a onclick="deleteReply(<%=reply.ID%>,<%=id%>)" onmouseover="this.style.cursor='pointer'">X</a>
        </div>
        <hr>
        <div id="reply-body"><%=reply.CONTENT%></div>
    </div>
    <% } %>
</div>
<div id="reply-write-form">
    <div id="reply-info">
        <input type="text" id="reply-writer" placeholder="작성자" value="익명">
        <hr>
        <input type="password" id="reply-password" placeholder="비밀번호" placeholder="제목">
    </div>
    <textarea id="reply-content" placeholder="내용"></textarea>
    <button type="button" class="btn btn-dark" onclick="writeReply(<%=id%>)">작성</button>
</div>

 

CSS

#reply-upper{
  margin-top: 10px;
  text-align: left;
}
#reply-area{
  margin-top: 5px;
  border: 1px solid black;
}
#reply-bar, #reply-body{
  text-align:left;
  margin: 1px;
  margin-left: 10px;
}
#reply-bar{
  font-size: 20px;
}
#reply-bar > a{
  float: right;
  margin-right: 10px;
}
#reply-write-form{
  display: flex;;
  margin-top: 10px;
  border-top: 1px solid black;
  border-bottom: 1px solid black;
}
#reply-write-form input,textarea{
  border: none;
}
#reply-write-form textarea{
  resize: none;
  border-left: 2px solid whitesmoke;
  border-right: 2px solid whitesmoke;
  flex: 1;
}
#reply-write-form button{
  margin: 3px;
  width: 54px;
}
#reply-info{
  display: flex;
  flex-direction: column;
  width: 150px;
  margin-right: 5px;
}

 

view.ejs의 html과 CSS코드이다. 나름 깔끔하게 한다고 만들었는데 나쁘지는 않은 것 같다.

 

적용 모습

 

JS

function refreshReply(postId){
    $.ajax({
        url: '/view/refreshReply',
        datatype: 'json',
        type: 'POST',
        data: {id : postId},
        success: function(result){
            $('#reply-area').empty();
            replyList = result.replyList;
            for (let reply of replyList){
                var body = `
                <div id="reply">
                <div id="reply-bar">
                ${reply.AUTHOR}
                <a onclick="deleteReply(${reply.ID},${reply.POST_ID})" onmouseover="this.style.cursor='pointer'">X</a>
                </div>
                <hr>
                <div id="reply-body">${reply.CONTENT}</div>
                </div>
                `;
                var replyDiv = $(body);
                $('#reply-area').append(replyDiv);
            }
        }
    });
};

function deleteReply(id,postId){
    var plainPassword = prompt('댓글 작성시 입력한 비밀번호를 입력하세요');
    if (plainPassword == '' || plainPassword == undefined)
        return;
    $.ajax({
        url: '/view/deleteReply',
        datatype: 'json',
        type: 'POST',
        data: {
            id : id,
            plainPassword : plainPassword,
        },
        success: function(result){
            if (result.code == -1){
                refreshReply(postId);
            } else{
                if (result.code == 0)
                    alert('비밀번호가 일치하지 않습니다.');
                else
                    alert('에러가 발생했습니다. ERRORCODE : '+result.code);
            }
        }
    });
};

function writeReply(postId){
    var writer = $('#reply-writer').val();
    var password = $('#reply-password').val();
    var content = $('#reply-content').val();
    var isLogined = 0;

    if (writer.length < 2){
        alert('작성자를 2자 이상 입력해주세요');
        return;
    } else if(writer.length > 10){
        alert('작성자는 최대 10자를 넘을 수 없습니다');
        return;
    } else if (password.length < 4){
        alert('비밀번호를 4자 이상 입력해주세요');
        return;
    } else if (password.length > 20){
        alert('비밀번호는 최대 20자를 넘을 수 없습니다');
        return;
    } else if (content.length < 2){
        alert('내용을 2자 이상 입력해주세요');
        return;
    } else if (content.length > 1000){
        alert('내용은 최대 1000자를 넘을 수 없습니다');
        return;
    }

    var data = {writer,password,isLogined,content,postId};
    
    $.ajax({
        url: '/view/writeReply',
        datatype: 'json',
        type: 'POST',
        data: data,
        success: function(result){
            if (result.code == -1){
                refreshReply(postId);
            } else{
                alert('에러가 발생했습니다. ERRORCODE : '+result.code);
            }
            $('#reply-content').val('');
        }
    });
};

 

AJAX요청을 보내줄 functions.js의 코드이다.

 

refreshReply의 경우 일단 reply-area의 내용을 모두 지우고 다시 DB에서 해당 글의 댓글 목록을 가져온다. jQuery로 div 내용을 그냥 입력하고 하나씩 append해주면 된다. 일일이 createElement, setAttribute할 필요가 없어서 jQuery가 이럴 때 참 편한 것 같다.

 

deleteReply는 댓글의 password를 prompt로 받아서 일치하는지 확인하고 댓글을 삭제하는 기능을 담당한다. 그리고 삭제가 완료되었다면 댓글 리스트를 한번 refresh한다. 여기서 prompt로 비밀번호를 입력할 때 * 로 보이게 하고 싶었는데 어떻게 하는지 도저히 못 찾겠어서 그냥 진행했다.

 

writeReply는 reply-write-form에 있는 값들을 가져와서 유효성 검사를 하고, data객체에 해당 정보들을 담아서 댓글 작성 요청을 보낸다. 여기서도 댓글 작성에 성공하였다면 댓글 리스트를 한번 refresh하고, 댓글 내용 부분을 비운다.

'Projects > NodeBoard' 카테고리의 다른 글

passportjs를 이용한 구글 로그인  (0) 2021.03.17
댓글 답글 기능 구현  (0) 2021.03.09
추천하기 기능 구현  (0) 2021.03.03
글 검색 기능과 페이징 개선  (1) 2021.03.02
글 수정 기능과 글 리스트 페이징  (0) 2021.02.27

댓글