AJAX 댓글 시스템

AJAX 댓글 시스템

페이지 이동 없이 댓글을 작성, 수정, 삭제하는 방법을 학습합니다.

페이지 이동 없는 댓글 작성

기본 구조

<!-- 댓글 작성 폼 -->
<form action="./" method="post" onsubmit="return procFilter(this, insert_comment)">
    <input type="hidden" name="module" value="board" />
    <input type="hidden" name="act" value="procBoardInsertComment" />
    <input type="hidden" name="document_srl" value="{$oDocument->document_srl}" />
    <input type="hidden" name="comment_srl" value="" />
    <input type="hidden" name="parent_srl" value="" />

    <textarea name="content" rows="4" cols="50"></textarea>

    <div class="btnArea">
        <button type="submit" class="btn btn-primary">댓글 작성</button>
    </div>
</form>

JavaScript 처리

// 댓글 작성 후 콜백
function completeInsertComment(ret_obj) {
    var error = ret_obj.error;
    var message = ret_obj.message;
    var document_srl = ret_obj.document_srl;
    var comment_srl = ret_obj.comment_srl;

    if(error != 0) {
        alert(message);
    } else {
        // 댓글 목록 새로고침
        loadCommentList(document_srl);
        // 폼 초기화
        jQuery('#comment_form')[0].reset();
    }
}

// 댓글 목록 불러오기
function loadCommentList(document_srl) {
    var params = {
        document_srl: document_srl,
        cpage: 1
    };

    exec_xml('board', 'getBoardCommentPage', params, function(ret) {
        jQuery('#comment-list').html(ret.html);
    });
}

페이지 이동 없는 댓글 수정

수정 버튼 추가

<div class="comment-item" id="comment_{$comment->comment_srl}">
    <div class="comment-content">{$comment->getContent()}</div>
    <div class="comment-actions" cond="$comment->isGranted()">
        <a href="#" onclick="toggleModifyComment({$comment->comment_srl}); return false;">수정</a>
        <a href="#" onclick="deleteComment({$comment->comment_srl}); return false;">삭제</a>
    </div>

    <!-- 수정 폼 (숨김) -->
    <div class="modify-form" id="modify_form_{$comment->comment_srl}" style="display:none;">
        <form onsubmit="return modifyComment(this)">
            <input type="hidden" name="comment_srl" value="{$comment->comment_srl}" />
            <textarea name="content" rows="3">{$comment->getContentText()}</textarea>
            <button type="submit">수정</button>
            <button type="button" onclick="toggleModifyComment({$comment->comment_srl})">취소</button>
        </form>
    </div>
</div>

수정 JavaScript

// 수정 폼 토글
function toggleModifyComment(comment_srl) {
    jQuery('#comment_' + comment_srl + ' .comment-content').toggle();
    jQuery('#modify_form_' + comment_srl).toggle();
}

// 댓글 수정
function modifyComment(form) {
    var params = {
        comment_srl: form.comment_srl.value,
        content: form.content.value
    };

    exec_xml('board', 'procBoardUpdateComment', params, function(ret) {
        if(ret.error != 0) {
            alert(ret.message);
        } else {
            // 수정된 내용 반영
            jQuery('#comment_' + params.comment_srl + ' .comment-content').html(ret.content);
            toggleModifyComment(params.comment_srl);
        }
    });

    return false;
}

페이지 이동 없는 댓글 삭제

삭제 함수

function deleteComment(comment_srl) {
    if(!confirm('댓글을 삭제하시겠습니까?')) return false;

    var params = {
        comment_srl: comment_srl
    };

    exec_xml('board', 'procBoardDeleteComment', params, function(ret) {
        if(ret.error != 0) {
            alert(ret.message);
        } else {
            // 댓글 항목 제거
            jQuery('#comment_' + comment_srl).fadeOut(300, function() {
                jQuery(this).remove();
                // 댓글 수 업데이트
                updateCommentCount();
            });
        }
    });
}

// 댓글 수 업데이트
function updateCommentCount() {
    var count = jQuery('.comment-item').length;
    jQuery('.comment-count').text('[' + count + ']');
}

대댓글 작성

대댓글 폼

<div class="reply-form" id="reply_form_{$comment->comment_srl}" style="display:none;">
    <form onsubmit="return insertReply(this, {$comment->comment_srl})">
        <input type="hidden" name="document_srl" value="{$oDocument->document_srl}" />
        <input type="hidden" name="parent_srl" value="{$comment->comment_srl}" />
        <textarea name="content" rows="2" placeholder="답글을 입력하세요"></textarea>
        <button type="submit">답글 작성</button>
        <button type="button" onclick="toggleReplyForm({$comment->comment_srl})">취소</button>
    </form>
</div>

대댓글 JavaScript

// 답글 폼 토글
function toggleReplyForm(comment_srl) {
    jQuery('#reply_form_' + comment_srl).toggle();
}

// 답글 작성
function insertReply(form, parent_srl) {
    var params = {
        document_srl: form.document_srl.value,
        parent_srl: parent_srl,
        content: form.content.value
    };

    exec_xml('board', 'procBoardInsertComment', params, function(ret) {
        if(ret.error != 0) {
            alert(ret.message);
        } else {
            // 댓글 목록 새로고침
            loadCommentList(params.document_srl);
            // 폼 초기화
            form.reset();
            toggleReplyForm(parent_srl);
        }
    });

    return false;
}

실시간 업데이트

자동 새로고침

// 30초마다 댓글 목록 새로고침
setInterval(function() {
    if(window.document_srl) {
        loadCommentList(window.document_srl);
    }
}, 30000);

// 새 댓글 알림
function checkNewComments() {
    var params = {
        document_srl: window.document_srl,
        last_comment_srl: getLastCommentSrl()
    };

    exec_xml('board', 'getNewCommentCount', params, function(ret) {
        if(ret.new_count > 0) {
            showNewCommentNotification(ret.new_count);
        }
    });
}

추천/비추천 기능

경고창 없이 동작

// 댓글 추천
function voteComment(comment_srl, point) {
    var params = {
        target_srl: comment_srl,
        point: point
    };

    exec_xml('board', 'procBoardVoteUpComment', params, function(ret) {
        if(ret.error != 0) {
            alert(ret.message);
        } else {
            // 추천수 업데이트
            var voted_count = parseInt(ret.voted_count);
            jQuery('#voted_count_' + comment_srl).text(voted_count);

            // 버튼 비활성화
            jQuery('#vote_button_' + comment_srl).addClass('voted').attr('disabled', true);
        }
    }, ['error', 'message', 'voted_count']);
}

성능 최적화

댓글 페이징

// 댓글 페이지 로드
function loadCommentPage(page) {
    var params = {
        document_srl: window.document_srl,
        cpage: page
    };

    jQuery('#comment-list').addClass('loading');

    exec_xml('board', 'getBoardCommentPage', params, function(ret) {
        jQuery('#comment-list').removeClass('loading').html(ret.html);
        // 스크롤 위치 조정
        scrollToComments();
    });
}

댓글 템플릿 캐싱

// 템플릿 캐싱
var commentTemplate = null;

function getCommentTemplate() {
    if(!commentTemplate) {
        commentTemplate = jQuery('#comment-template').html();
    }
    return commentTemplate;
}

// 템플릿을 사용한 댓글 렌더링
function renderComment(comment_data) {
    var template = getCommentTemplate();
    return template.replace(/\{([^}]+)\}/g, function(match, key) {
        return comment_data[key] || '';
    });
}

모범 사례

  1. 에러 처리: 모든 AJAX 요청에 적절한 에러 처리
  2. 로딩 표시: 사용자에게 진행 상황 표시
  3. 중복 방지: 요청 중 버튼 비활성화
  4. 접근성: 키보드 네비게이션 지원
  5. 모바일 최적화: 터치 이벤트 고려

다음 단계

AJAX 댓글 시스템을 구현했다면, 리스트 고급 기능을 학습하세요.