💗너무 어려운 댓글 기능 구현..화이또,,,🫶
🫧VS Code
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="commentArea">
<div class="comment-list-area">
<ul id="commentList">
<c:forEach items="${board.commentList}" var="comment">
<li class="comment-row <c:if test='${comment.parentNo !=0 }'>child-comment</c:if>">
<p class="comment-writer">
<c:if test="${empty comment.profileImage}" >
<%-- 없을 경우 --%>
<img src="/resources/images/user.png">
</c:if>
<c:if test="${!empty comment.profileImage}" >
<%-- 있을 경우 --%>
<img src="${comment.profileImage}">
</c:if>
<span>${comment.memberNickname}</span>
<span class="comment-date"></span>
</p>
<p class="comment-content">${comment.commentContent}</p>
<div class="comment-btn-area">
<button onclick="showInsertComment(${comment.commentNo},this)">답글</button>
<c:if test="${loginMember.memberNo == comment.memberNo}" >
<button onclick="showUpdateComment(${comment.commentNo},this)">수정</button>
<button onclick="deleteComment(${comment.commentNo})">삭제</button>
</c:if>
</div>
</li>
</c:forEach>
</ul>
</div>
<div class="comment-write-area">
<textarea id="commentContent"></textarea>
<button id="addComment">
댓글<br>
등록
</button>
</div>
</div>
function selectCommentList(){
fetch("/comment?boardNo="+ boardNo)
.then(response => response.json())
.then(cList => {
console.log(cList);
const commentList = document.getElementById("commentList");
commentList.innerHTML = "";
for(let comment of cList){
const commentRow = document.createElement("li");
commentRow.classList.add("comment-row");
if(comment.parentNo != 0) commentRow.classList.add("child-comment");
const commentWriter = document.createElement("p");
commentWriter.classList.add("comment-writer");
const profileImage = document.createElement("img");
if( comment.profileImage != null ){
profileImage.setAttribute("src", comment.profileImage);
}else{
profileImage.setAttribute("src", "/resources/images/user.png");
}
const memberNickname = document.createElement("span");
memberNickname.innerText = comment.memberNickname;
const commentDate = document.createElement("span");
commentDate.classList.add("comment-date");
commentDate.innerText = "(" + comment.commentCreateDate + ")";
commentWriter.append(profileImage , memberNickname , commentDate);
const commentContent = document.createElement("p");
commentContent.classList.add("comment-content");
commentContent.innerHTML = comment.commentContent;
commentRow.append(commentWriter, commentContent);
if(loginMemberNo != ""){
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const childCommentBtn = document.createElement("button");
childCommentBtn.setAttribute("onclick", "showInsertComment("+comment.commentNo+", this)");
childCommentBtn.innerText = "답글";
commentBtnArea.append(childCommentBtn);
if( loginMemberNo == comment.memberNo ){
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
updateBtn.setAttribute("onclick", "showUpdateComment("+comment.commentNo+", this)");
const deleteBtn = document.createElement("button");
deleteBtn.innerText = "삭제";
deleteBtn.setAttribute("onclick", "deleteComment("+comment.commentNo+")");
commentBtnArea.append(updateBtn, deleteBtn);
}
commentRow.append(commentBtnArea);
}
commentList.append(commentRow);
}
})
.catch(err => console.log(err));
}
const addComment = document.getElementById("addComment");
const commentContent = document.getElementById("commentContent");
addComment.addEventListener("click", e => {
if(loginMemberNo == ""){
alert("로그인 후 이용해주세요.");
return;
}
if(commentContent.value.trim().length == 0){
alert("댓글을 작성한 후 버튼을 클릭해주세요.");
commentContent.value = "";
commentContent.focus();
return;
}
const data={ "commentContent" : commentContent.value
, "memberNo" : loginMemberNo
, "boardNo" : boardNo};
fetch("/comment", {
method: "POST"
,headers:{"Content-Type": "application/json"}
,body : JSON.stringify(data)
})
.then(resp => resp.text())
.then(result => {
if(result > 0){
alert("댓글이 등록되었습니다.");
commentContent.value = "";
selectCommentList();
} else {
alert("댓글 등록에 실패했습니다...");
}
})
.catch(err => console.log(err));
});
function deleteComment(commentNo){
if( confirm("정말로 삭제 하시겠습니까?") ){
fetch("/comment",{
method: "DELETE"
,headers: {"Content-Type": "application/json"}
,body : commentNo
})
.then(resp => resp.text())
.then(result => {
if(result > 0){
alert("삭제되었습니다");
selectCommentList();
}else{
alert("삭제 실패");
}
})
.catch(err => console.log(err));
}
}
let beforeCommentRow;
function showUpdateComment(commentNo, btn){
const temp = document.getElementsByClassName("update-textarea");
if(temp.length > 0){
if(confirm("다른 댓글이 수정 중입니다. 현재 댓글을 수정 하시겠습니까?")){
temp[0].parentElement.innerHTML = beforeCommentRow;
}else{
return;
}
}
const commentRow = btn.parentElement.parentElement;
beforeCommentRow = commentRow.innerHTML;
let beforeContent = commentRow.children[1].innerHTML;
commentRow.innerHTML = "";
const textarea = document.createElement("textarea");
textarea.classList.add("update-textarea");
beforeContent = beforeContent.replaceAll("&", "&");
beforeContent = beforeContent.replaceAll("<", "<");
beforeContent = beforeContent.replaceAll(">", ">");
beforeContent = beforeContent.replaceAll(""", "\"");
textarea.value = beforeContent;
commentRow.append(textarea);
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
updateBtn.setAttribute("onclick", "updateComment("+commentNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "updateCancel(this)");
commentBtnArea.append(updateBtn, cancelBtn);
commentRow.append(commentBtnArea);
}
function updateCancel(btn){
if(confirm("댓글 수정을 취소하시겠습니까?")){
btn.parentElement.parentElement.innerHTML = beforeCommentRow;
}
}
function updateComment(commentNo, btn){
const commentContent = btn.parentElement.previousElementSibling.value;
const data= {
"commentNo": commentNo,
"commentContent": commentContent
}
fetch("/comment",{
method: "PUT",
headers : {"Content-Type" : "application/json"}
,body: JSON.stringify(data)
})
.then(resp => resp.text())
.then(result => {
if(result > 0){
alert("댓글이 수정되었습니다.");
selectCommentList();
}else{
alert("댓글 수정 실패");
}
})
.catch(err => console.log(err));
}
function showInsertComment(parentNo, btn){
const temp = document.getElementsByClassName("commentInsertContent");
if(temp.length > 0){
if(confirm("다른 답글을 작성 중입니다. 현재 댓글에 답글을 작성 하시겠습니까?")){
temp[0].nextElementSibling.remove();
temp[0].remove();
} else{
return;
}
}
const textarea = document.createElement("textarea");
textarea.classList.add("commentInsertContent");
btn.parentElement.after(textarea);
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const insertBtn = document.createElement("button");
insertBtn.innerText = "등록";
insertBtn.setAttribute("onclick", "insertChildComment("+parentNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "insertCancel(this)");
commentBtnArea.append(insertBtn, cancelBtn);
textarea.after(commentBtnArea);
}
function insertCancel(btn){
btn.parentElement.previousElementSibling.remove();
btn.parentElement.remove();
}
function insertChildComment(parentNo, btn){
const commentContent = btn.parentElement.previousElementSibling.value;
if(commentContent.trim().length == 0){
alert("답글 작성 후 등록 버튼을 클릭해주세요.");
btn.parentElement.previousElementSibling.value = "";
btn.parentElement.previousElementSibling.focus();
return;
}
const data = {"commentContent": commentContent,
"memberNo": loginMemberNo,
"boardNo": boardNo,
"parentNo": parentNo
}
fetch("/comment",{
method : "POST"
, headers : {"Content-Type" : "application/json"}
, body : JSON.stringify(data)
})
.then(resp => resp.text())
.then(result => {
if(result > 0){
alert("답글이 등록되었습니다.");
selectCommentList();
} else {
alert("답글 등록에 실패했습니다...");
}
})
.catch(err => console.log(err));
}
🫧Spring
package edu.kh.project.board.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import edu.kh.project.board.model.dto.Comment;
import edu.kh.project.board.model.service.CommentService;
@RestController
public class CommentController {
@Autowired
private CommentService service;
@GetMapping(value = "/comment" , produces = "application/json; charset=UTF-8")
@ResponseBody
public List<Comment> select( @RequestParam("boardNo") int boardNo ) {
return service.select(boardNo);
}
@PostMapping("/comment")
public int insert(@RequestBody Comment comment) {
return service.insert(comment);
}
@DeleteMapping("/comment")
public int delete( @RequestBody int commentNo ){
return service.delete(commentNo);
}
@PutMapping("/comment")
public int update( @RequestBody Comment comment) {
return service.update(comment);
}
}
package edu.kh.project.board.model.service;
import java.util.List;
import edu.kh.project.board.model.dto.Comment;
public interface CommentService {
List<Comment> select(int boardNo);
int insert(Comment comment);
int delete(int commentNo);
int update(Comment comment);
}
package edu.kh.project.board.model.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import edu.kh.project.board.common.utility.Util;
import edu.kh.project.board.model.dao.CommentDAO;
import edu.kh.project.board.model.dto.Comment;
@Service
public class CommentServiceImpl implements CommentService{
@Autowired
private CommentDAO dao;
@Override
public List<Comment> select(int boardNo) {
return dao.select(boardNo);
}
@Transactional(rollbackFor = Exception.class)
@Override
public int insert(Comment comment) {
comment.setCommentContent(Util.XSSHandling(comment.getCommentContent()));
return dao.insert(comment);
}
@Transactional(rollbackFor = Exception.class)
@Override
public int delete(int commentNo) {
return dao.delete(commentNo);
}
@Transactional(rollbackFor = Exception.class)
@Override
public int update(Comment comment) {
comment.setCommentContent(Util.XSSHandling(comment.getCommentContent()));
return dao.update(comment);
}
}
package edu.kh.project.board.model.dao;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import edu.kh.project.board.model.dto.Comment;
@Repository
public class CommentDAO {
@Autowired
private SqlSessionTemplate sqlsession;
public List<Comment> select(int boardNo) {
return sqlsession.selectList("boardMapper.selectCommentList", boardNo);
}
public int insert(Comment comment) {
return sqlsession.insert("commentMapper.insert",comment);
}
public int delete(int commentNo) {
return sqlsession.update("commentMapper.delete",commentNo);
}
public int update(Comment comment) {
return sqlsession.update("commentMapper.update",comment);
}
}
💗mybatis-config.xml
<mappers>
<mapper resource="/mappers/comment-mapper.xml" />
</mappers>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="commentMapper">
<insert id="insert">
INSERT INTO "COMMENT"
VALUES(SEQ_COMMENT_NO.NEXTVAL,
#{commentContent},
DEFAULT, DEFAULT,
#{boardNo}, #{memberNo},
<if test="parentNo == 0">NULL</if>
<if test="parentNo != 0">#{parentNo}</if>
)
</insert>
<update id="delete">
UPDATE "COMMENT" SET
COMMENT_DEL_FL ='Y'
WHERE COMMENT_NO = #{commentNo}
</update>
<update id="update">
UPDATE "COMMENT" SET
COMMENT_CONTENT=#{commentContent}
WHERE COMMENT_NO=#{commentNo}
</update>
</mapper>