무한스크롤 추가
- isFetching: true인 동안에는 (데이터가 요청 중) 데이터 요청이 진행 중이므로 함수를 바로 종료하여 중복 요청을 방지
let currentPage = 1;
let isFetching = false;
let totalReplies = 0;
let loadedReplies = 0;
function appendReplies({ replies }) {
let tag = '';
if (replies && replies.length > 0) {
replies.forEach(({ rno, writer, text, createAt }) => {
tag += `
<div id='replyContent' class='card-body' data-reply-id='${rno}'>
<div class='row user-block'>
<span class='col-md-3'>
<b>${writer}</b>
</span>
<span class='offset-md-6 col-md-3 text-right'><b>${getRelativeTime(createAt)}</b></span>
</div><br>
<div class='row'>
<div class='col-md-9'>${text}</div>
<div class='col-md-3 text-right'>
<a id='replyModBtn' class='btn btn-sm btn-outline-dark' data-bs-toggle='modal' data-bs-target='#replyModifyModal'>수정</a>
<a id='replyDelBtn' class='btn btn-sm btn-outline-dark' href='${rno}'>삭제</a>
</div>
</div>
</div>
`;
});
} else {
tag = `<div id='replyContent' class='card-body'>댓글이 아직 없습니다! ㅠㅠ</div>`;
}
document.getElementById('replyData').innerHTML += tag;
loadedReplies += replies.length;
}
export async function fetchInfScrollReplies(pageNo=1) {
if (isFetching) return;
isFetching = true;
const bno = document.getElementById('wrap').dataset.bno;
const res = await fetch(`${BASE_URL}/${bno}/page/${pageNo}`);
const replyResponse = await res.json();
if (pageNo === 1) {
totalReplies = replyResponse.pageInfo.totalCount;
loadedReplies = 0;
document.getElementById('replyCnt').textContent = totalReplies;
document.getElementById('replyData').innerHTML = '';
setupInfiniteScroll();
}
appendReplies(replyResponse);
currentPage = pageNo;
isFetching = false;
hideSpinner();
if (loadedReplies >= totalReplies) {
window.removeEventListener('scroll', scrollHandler);
}
}
async function scrollHandler(e) {
if (
window.innerHeight + window.scrollY >= document.body.offsetHeight + 100
&& !isFetching
) {
showSpinner();
await new Promise(resolve => setTimeout(resolve, 500));
fetchInfScrollReplies(currentPage + 1);
}
}
export function setupInfiniteScroll() {
window.addEventListener('scroll', scrollHandler);
}
fetchAPI PUT (수정요청)
- 댓글 번호를 수정하는 부분에서는 구할 수 없으므로 댓글에서 미리 구해서 수정 모달에 넣어줌
import { BASE_URL } from "./reply.js";
import { fetchInfScrollReplies } from "./getReply.js";
export function modifyReplyClickEvent() {
document.getElementById('replyData').addEventListener('click', e => {
e.preventDefault();
if (!e.target.matches('#replyModBtn')) return;
const text = e.target.closest('.row').querySelector('.col-md-9').textContent;
document.getElementById('modReplyText').value = text;
const rno = e.target.closest('#replyContent').dataset.replyId;
document.querySelector('.modal').dataset.rno = rno;
});
}
document.getElementById('replyModBtn').addEventListener('click', e => {
fetchReplyModify();
});
async function fetchReplyModify() {
const payload = {
rno: document.querySelector('.modal').dataset.rno,
newText: document.getElementById('modReplyText').value,
bno: document.getElementById('wrap').dataset.bno
};
const res = await fetch(BASE_URL, {
method: 'PUT',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(payload)
});
if(!res.ok) {
alert('수정 실패!');
}
document.getElementById('modal-close').click();
window.scrollTo(0, 0);
await fetchInfScrollReplies();
}
ReplyService
- 댓글 수정할 때 필요한 입력값들을 포장하는 dto객체 필요!
ReplyModifyDto
- rno, newText를 받고 bno는 수정완료 후에 새로운 목록을 갱신하기 위해 추가
ReplyApiController
@RequestMapping(method = {RequestMethod.PUT, RequestMethod.PATCH})
public ResponseEntity<?> modify(
@Validated @RequestBody ReplyModifyDto dto
, BindingResult result
) {
log.info("/api/v1/replies : PUT, PATCH");
log.debug("parameter: {}", dto);
if (result.hasErrors()) {
Map<String, String> errors = makeValidationMessageMap(result);
return ResponseEntity
.badRequest()
.body(errors);
}
ReplyListDto replyListDto = replyService.modify(dto);
return ResponseEntity.ok().body(replyListDto);
}
fetchAPI DELETE
import { BASE_URL } from "./reply.js";
import { fetchInfScrollReplies } from "./getReply.js";
const fetchDeleteReply = async (rno) => {
const res = await fetch(`${BASE_URL}/${rno}`, {
method: 'DELETE'
});
if (res.status !== 200) {
alert("삭제에 실패했습니다!")
return;
}
fetchInfScrollReplies();
window.scrollTo(0, 0);
};
export function removeReplyClickEvent() {
document.getElementById('replyData').addEventListener('click', e => {
e.preventDefault();
if (!e.target.matches('#replyDelBtn')) return;
if (!confirm('정말 삭제할까요??')) return;
const rno = e.target.closest('#replyContent').dataset.replyId;
fetchDeleteReply(rno);
});
}