우선 show.mustache
화면의 맨 밑에 댓글에 대한 부분을 만들어 붙임.
댓글 영역에 대한 div를 _comment.mustache
파일로 잡은 후 해당 파일내에 댓글 목록을 출력하는 화면파일과 새 댓글을 작성하는 화면파일을 차례로 가져오도록 만듦.
- _comments.mustache -
<div>
<!-- 댓글 목록 보기 -->
{{>comments/_list}}
<!-- 새 댓글 작성하기 -->
{{>comments/_new}}
</div>
- _list.mustache -
<div id="comments-list">
{{#commentDtos}}
<div class="card m-2" id="comments-{{id}}">
<div class="card-header">
{{nickname}}
</div>
<div class="card-body">
{{body}}
</div>
</div>
{{/commentDtos}}
</div>
- _new.mustache -
<div class="card m-2" id="comments-new">
<div class="card-body">
<!-- 댓글작성 폼 -->
<form>
<div class="m-3">
<label class="form-label">닉네임</label>
<input type="text" class="form-control" id="new-comment-nickname">
</div>
<div class="m-3">
<label class="form-label">댓글 내용</label>
<textarea type="text" class="form-control" rows="3" id="new-comment-body"></textarea>
</div>
{{#article}}
<input type="hidden" id="new-comment-article-id" value="{{id}}">
{{/article}}
<button type="button" class="btn btn-primary" id="comment-create-btn">댓글 작성</button>
</form>
</div>
</div>
ArticleController
에서 @GetMapping("/articles/{id}")
으로 단일 게시글을 조회하는 show
메서드를 실행 할 때 댓글도 불러오도록 수정한다.
- ArticleController -
@GetMapping("/articles/{id}")
public String show(@PathVariable Long id, Model m) {
log.info("id = " + id);
// 1. DB에서 데이터 조회해 가져오기
Optional<Article> articleEntity = articleRepository.findById(id);
List<CommentDto> commentDtos = commentService.comments(id);
// 2. model에 데이터 등록하기
m.addAttribute("article", articleEntity.get());
m.addAttribute("commentDtos", commentDtos);
// 3. 뷰 페이지 반환하기
return "articles/show";
}
댓글 입력
- _new.mustache -
script 추가.
<script>
{
// 댓글 생성 버튼 변수화
const commentCreateBtn = document.querySelector("#comment-create-btn");
// 댓글 클릭 이벤트 감지
commentCreateBtn.addEventListener("click", function() {
// 새 댓글 객체 생성
const comment = {
// 새 댓글의 닉네임
nickname: document.querySelector("#new-comment-nickname").value,
// 새 댓글의 본문
body: document.querySelector("#new-comment-body").value,
// 부모 게시글의 id
article_id: document.querySelector("#new-comment-article-id").value
};
// 댓글 객체 출력
console.log(comment);
// fecth() - 비동기 통신을 위한 api
const url = "/api/articles/" + comment.article_id + "/comments";
fetch(url, {
method: "POST",
headers: {
"Content-Type" : "application/json"
},
body: JSON.stringify(comment) // comment 객체를 JSON 문자열로 변환해 전송
}).then(response => { // 응답을 받아 처리하는 구문
// HTTP 응답 코드에 따른 메시지 출력
const msg = (response.ok) ? "댓글이 등록되었습니다." : "댓글 등록에 실패했습니다.";
alert(msg);
// 현재 페이지 새로고침
window.location.reload();
});
});
}
</script>
현재는 작성완료시 화면을 새로고침하여 재조회하도록 되어있음.
Ajax를 이용해 댓글란만 새로고침할 수 있도록 수정해야 할 것 같음.
기본형태
fetch('API_주소', {
method: "POST", // 요청 메서드
headers: { // 헤더 정보
"Content-Type" : "application/json"
},
body: JSON.stringify(객체) // 전송 데이터
}).then(response => { // 응답을 받아 처리하는 구문
// 응답 처리문
});
dto 객체에 article_id값이 들어오질 않아서(nickname, body값은 잘 들어오는데 article_id는 null로 들어옴) 한참동안 원인찾음.
알고보니 dto의 필드명과 Javascript에서 설정한 id값이 달라서 생긴 문제였음.
<body>
<h1>Hello, <span id="target">World!</span></h1>
<input type="text" placeholder="스프링 부트" />
<button type="button">전송</button>
</body>
<script>
const h1 = document.querySelector("#target");
const btn = document.querySelector("button");
btn.addEventListener("click", function () {
const inputText = document.querySelector("input").value;
console.log("버튼 클릭!");
console.log(inputText);
if (inputText == "스프링 부트") {
h1.innerText = "스프링 부트!";
}
});
</script>
input란에 '스프링 부트'입력 후 전송버튼 클릭 시 h1객체의 텍스트가 변경됨.
수정 버튼을 클릭하면 모달창을 뜨게 만들어 모달창에서 수정작업 후 등록하도로 작성.
- _list.mustache -
<div id="comments-list">
{{#commentDtos}}
<div class="card m-2" id="comments-{{id}}">
<div class="card-header">
{{nickname}}
<!-- Button trigger modal -->
<button type="button" class="btn btn-sm btn-outline-primary"
data-bs-toggle="modal" data-bs-target="#comment-edit-modal"
data-bs-id="{{id}}"
data-bs-nickname="{{nickname}}"
data-bs-body="{{body}}"
data-bs-article-id="{{article_id}}">
수정
</button>
<button type="button"
class="btn btn-sm btn-outline-danger comment-delete-btn"
data-comment-id="{{id}}">삭제</button>
</div>
<div class="card-body">
{{body}}
</div>
</div>
{{/commentDtos}}
</div>
<!-- Modal -->
<div class="modal fade" id="comment-edit-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">댓글 수정</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- 댓글작성 폼 -->
<form>
<div class="m-3">
<label class="form-label">닉네임</label>
<input type="text" class="form-control" id="edit-comment-nickname">
</div>
<div class="m-3">
<label class="form-label">댓글 내용</label>
<textarea type="text" class="form-control" rows="3"
id="edit-comment-body"></textarea>
</div>
<!-- 히든 인풋 -->
<input type="hidden" id="edit-comment-id">
<input type="hidden" id="edit-comment-article-id">
<button type="button" class="btn btn-primary" id="comment-update-btn">수정 완료</button>
</form>
</div>
</div>
</div>
</div>
- _list.mustache -
<script>
{
// 모달요소 선택
const commentEditModal = document.querySelector("#comment-edit-modal");
// 모달 이벤트 감지
commentEditModal.addEventListener("show.bs.modal", function(event) {
// 1. 트리거 버튼 선택
const triggerBtn = event.relatedTarget;
// 2. 데이터 가져오기
const id = triggerBtn.getAttribute("data-bs-id");
const nickname = triggerBtn.getAttribute("data-bs-nickname");
const body = triggerBtn.getAttribute("data-bs-body");
const article_id = triggerBtn.getAttribute("data-bs-article-id");
// 3. 수정 폼에 데이터 반영
document.querySelector("#edit-comment-id").value = id;
document.querySelector("#edit-comment-nickname").value = nickname;
document.querySelector("#edit-comment-body").value = body;
document.querySelector("#edit-comment-article-id").value = article_id;
})
}
{
// 수정 완료 버튼 선택
const commentUpdateBtn = document.querySelector("#comment-update-btn");
// 클릭 이벤트 처리
commentUpdateBtn.addEventListener("click", function(){
// 수정 댓글 객체 생성
const comment = {
id: document.querySelector("#edit-comment-id").value,
nickname: document.querySelector("#edit-comment-nickname").value,
body: document.querySelector("#edit-comment-body").value,
article_id: document.querySelector("#edit-comment-article-id").value
}
console.log(comment);
// 수정 REST API 호출
const url = "/api/comments/" + comment.id;
fetch(url, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(comment)
}).then(response => { // 응답을 받아 처리하는 구문
// HTTP 응답 코드에 따른 메시지 출력
const msg = (response.ok) ? "댓글이 수정되었습니다." : "댓글 수정에 실패했습니다.";
alert(msg);
// 현재 페이지 새로고침
window.location.reload();
});
})
}
</script>
방식은 등록과 유사함.
수정할 댓글의 '수정'버튼을 클릭하면 모달창에 해당 댓글의 id에 대한 데이터를 가져와 보여주고 모달창에서 댓글을 수정 후 수정 완료버튼을 클릭하면 patch로 api전송을 하도록 함.
- _list.mustache -
<script>
// 덧글 삭제
{
// 삭제 버튼 선택
const commentDeleteBtns = document.querySelectorAll(".comment-delete-btn");
// 삭제 버튼 이벤트 처리
// commentDeleteBtn.addEventListener("click", function() {
// console.log("삭제 버튼이 클릭됐습니다..!");
// });
commentDeleteBtns.forEach(btn => {
btn.addEventListener("click", (event) => {
// 이벤트 발생 요소 선택
const commentDeleteBtn = event.target;
// 삭제 댓글 id 가져오기
const commentId = commentDeleteBtn.getAttribute("data-comment-id");
console.log(`삭제 버튼 클릭: ${commentId}번 댓글`);
// 삭제 REST API 호출
const url = `/api/comments/${commentId}`;
fetch(url, {
method: "DELETE"
}).then(response => {
// 댓글 삭제 실패처리
if (!response.ok) {
alert("댓글 삭제 실패...");
return;
}
// 삭제 성공 시 댓글을 화면에서 지우고 메시지 창 띄우기
const target = document.querySelector(`#comments-${commentId}`);
target.remove();
const msg = `${commentId}번 댓글을 삭제했습니다`;
alert(msg);
// 현재 페이지 새로고침
window.location.reload();
})
})
})
}
</script>
이벤트 발생 요소 선택 부분에서 자꾸 해당 컴포넌트를 못가져오길래 한참 삽질했더니 event.target
을 event.terget
으로 오타낸게 원인이었다.
오타를 항상 주의하자...