16. 댓글 기능
const selectCommentList = () => {
fetch("/comment?boardNo=" + boardNo)
.then(response => response.json())
.then(commentList => {
console.log(commentList);
const ul = document.querySelector("#commentList");
ul.innerHTML = "";
for(let comment of commentList){
const commentRow = document.createElement("li");
commentRow.classList.add("comment-row");
if(comment.parentCommentNo != 0)
commentRow.classList.add("child-comment");
if(comment.commentDelFl == 'Y')
commentRow.innerText = "삭제된 댓글 입니다";
else{
const commentWriter = document.createElement("p");
commentWriter.classList.add("comment-writer");
const profileImg = document.createElement("img");
if(comment.profileImg == null)
profileImg.src = userDefaultImage;
else
profileImg.src = comment.profileImg;
const nickname = document.createElement("span");
nickname.innerText = comment.memberNickname;
const commentDate = document.createElement("span");
commentDate.classList.add("comment-date");
commentDate.innerText = comment.commentWriteDate;
commentWriter.append(profileImg, nickname, commentDate);
commentRow.append(commentWriter);
const content = document.createElement("p");
content.classList.add("comment-content");
content.innerText = comment.commentContent;
commentRow.append(content);
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const childCommentBtn = document.createElement("button");
childCommentBtn.innerText = "답글";
childCommentBtn.setAttribute("onclick",
`showInsertComment(${comment.commentNo}, this)`);
commentBtnArea.append(childCommentBtn);
if(loginMemberNo != null && 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);
}
ul.append(commentRow);
}
});
}
const addContent = document.querySelector("#addComment");
const commentContent = document.querySelector("#commentContent");
addContent.addEventListener("click", e => {
if(loginMemberNo == null){
alert("로그인 후 이용해 주세요");
return;
}
if(commentContent.value.trim().length == 0){
alert("내용 작성 후 등록 버튼을 클릭해 주세요");
commentContent.focus();
return;
}
const data = {
"commentContent" : commentContent.value,
"boardNo" : boardNo,
"memberNo" : loginMemberNo
};
fetch("/comment", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : JSON.stringify(data)
})
.then(response => response.text())
.then(result => {
if(result > 0){
alert("댓글이 등록 되었습니다");
commentContent.value = "";
selectCommentList();
} else{
alert("댓글 등록 실패");
}
})
.catch(err => console.log(err));
})
const showInsertComment = (parentCommentNo, 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("+parentCommentNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "insertCancel(this)");
commentBtnArea.append(insertBtn, cancelBtn);
textarea.after(commentBtnArea);
}
const insertCancel = (cancelBtn) => {
cancelBtn.parentElement.previousElementSibling.remove();
cancelBtn.parentElement.remove();
}
const insertChildComment = (parentCommentNo, btn) => {
const textarea = btn.parentElement.previousElementSibling;
if(textarea.value.trim().length == 0){
alert("내용 작성 후 등록 버튼을 클릭해 주세요");
textarea.focus();
return;
}
const data = {
"commentContent" : textarea.value,
"boardNo" : boardNo,
"memberNo" : loginMemberNo,
"parentCommentNo" : parentCommentNo
};
fetch("/comment", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : JSON.stringify(data)
})
.then(response => response.text())
.then(result => {
if(result > 0){
alert("답글이 등록 되었습니다");
selectCommentList();
} else{
alert("답글 등록 실패");
}
})
.catch(err => console.log(err));
}
const deleteComment = commentNo => {
if(!confirm("삭제 하시겠습니까?")) return;
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;
const showUpdateComment = (commentNo, btn) => {
const temp = document.querySelector(".update-textarea");
if(temp != null){
if(confirm("수정 중인 댓글이 있습니다. 현재 댓글을 수정 하시겠습니까?")){
const commentRow = temp.parentElement;
commentRow.after(beforeCommentRow);
commentRow.remove();
} else{
return;
}
}
const commentRow = btn.closest("li");
beforeCommentRow = commentRow.cloneNode(true);
let beforeContent = commentRow.children[1].innerText;
commentRow.innerHTML = "";
const textarea = document.createElement("textarea");
textarea.classList.add("update-textarea");
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);
}
const updateCancel = (btn) => {
if(confirm("취소 하시겠습니까?")){
const commentRow = btn.closest("li");
commentRow.after(beforeCommentRow);
commentRow.remove();
}
}
const updateComment = (commentNo, btn) => {
const textarea = btn.parentElement.previousElementSibling;
if(textarea.value.trim().length == 0){
alert("댓글 작성 후 수정 버튼을 클릭해 주세요");
textarea.focus();
return;
}
const data = {
"commentNo" : commentNo,
"commentContent" : textarea.value
}
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));
}
boardDetail.html 구문 추가

16-1) 댓글 목록 조회
@RestController
@RequestMapping("comment")
@RequiredArgsConstructor
public class CommentController {
private final CommentService service;
@GetMapping("")
public List<Comment> select(@RequestParam("boardNo") int boardNo) {
return service.select(boardNo);
}
}
✅ mapper 작성 전 DB 샘플 데이터 삽입 및 계층형 쿼리 조회해보기

<select id="select">
SELECT LEVEL, C.* FROM
(SELECT COMMENT_NO, COMMENT_CONTENT,
TO_CHAR(COMMENT_WRITE_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') COMMENT_WRITE_DATE,
BOARD_NO, MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG, PARENT_COMMENT_NO, COMMENT_DEL_FL
FROM "COMMENT"
JOIN MEMBER USING(MEMBER_NO)
WHERE BOARD_NO = #{boardNo}) C
WHERE COMMENT_DEL_FL = 'N'
OR 0 != (SELECT COUNT(*) FROM "COMMENT" SUB
WHERE SUB.PARENT_COMMENT_NO = C.COMMENT_NO
AND COMMENT_DEL_FL='N')
START WITH PARENT_COMMENT_NO IS NULL
CONNECT BY PRIOR COMMENT_NO = PARENT_COMMENT_NO
ORDER SIBLINGS BY COMMENT_NO
</select>

16-2) 댓글 등록
@PostMapping("")
public int insert(@RequestBody Comment comment) {
return service.insert(comment);
}
<insert id="insert">
INSERT INTO "COMMENT"
VALUES(SEQ_COMMENT_NO.NEXTVAL, #{commentContent}, DEFAULT, DEFAULT, #{boardNo}, #{memberNo},
<if test="parentCommentNo != 0">
#{parentCommentNo}
</if>
<if test="parentCommentNo == 0">NULL</if>
)
</insert>

16-3) 수정/삭제
@PutMapping("")
public int update(@RequestBody Comment comment) {
return service.update(comment);
}
@DeleteMapping("")
public int delete(@RequestBody int commentNo) {
return service.delete(commentNo);
}
<update id="update">
UPDATE "COMMENT" SET
COMMENT_CONTENT = #{commentContent}
WHERE COMMENT_NO = #{commentNo}
</update>
<delete id="delete">
UPDATE "COMMENT" SET
COMMENT_DEL_FL = 'Y'
WHERE COMMENT_NO = #{commnetNo}
</delete>