-
0925. 댓글 화면 출력(댓글 작성, 수정-확인 및 취소, 삭제)Spring 2023. 9. 25. 18:55
🌱댓글 화면 출력
댓글이 화면상에 보이게 할 것이다.
[구현할 기능]- 댓글 목록보기
- 새 댓글 작성(작성자 아닌 회원만)
- 댓글 수정(수정 확인 + 수정 취소)
- 댓글 삭제
* JSON
JS Object 를 JSON 문자열로 바꾸는 것 : 직렬화
JSON 문자열을 JS Object 로 바꾸는 것 : 역직렬화 parse( ) - 문자열을 해석해서 처리하는 역할
📍rest.js 작업
댓글 등록
⭐method, headers, body를 잘 봐야 한다.
rest.js 에 추가//fetch의 메서드 옵션을 post로 지정 async function rest_create(url, data) { try { let res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) return await res.json(); } catch(e) { console.log(e); } }
CommentMapper.xml 에서 필수항목 확인 (⭐구현 기능마다 확인할 것이다.)
get.jsp
(상세보기에서 댓글이 보이므로)
우선 하드코딩된 것 보기//댓글 생성 let comment = { bno : bno, writer : '${username}', //얜 문자열이니 따옴표 넣은 것 content : "추가 글입니다." //js에서는 큰 따옴표 생략 가능하지만 JSON에서는 필수 }; console.log(comment); console.log(JSON.stringify(comment))
+) ❤️let comment 대괄호 안에 형식은 "key 이름 : 변수명" 이렇게 3개가 제시되어있는데
자바스크립트에서는 key이름과 변수명이 동일한 경우 변수명만 써도 key로 등록된다. (bno = bno 항목)let comment = { bno, //key이름과 변수명이 같으면 js에서는 변수명만 써도 key로 등록 writer : '${username}', //얜 문자열이니 따옴표 넣은 것 content : "추가 글입니다." //js에서는 큰 따옴표 생략 가능하지만 JSON에서는 필수.(JSON 작은 따옴표 못 씀) };
로그인 상태에서 실행하면 댓글이 추가된다.//댓글 생성 let comment = { bno, //key이름과 변수명이 같으면 js에서는 변수명만 써도 key로 등록 writer : '${username}', //얜 문자열이니 따옴표 넣은 것 content : "추가 글입니다." //js에서는 큰 따옴표 생략 가능하지만 JSON에서는 필수.(JSON 작은 따옴표 못 씀) }; console.log(comment); console.log(JSON.stringify(comment)); comment = await rest_create(url, comment); // 생성한 댓글 콘솔로 확인하는 두 줄 console.log(comment);
댓글 수정
rest.js에 put 호출 코드 추가/* PUT */ async function rest_create(url, data) { try { let res = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) return await res.json(); } catch(e) { console.log(e); } }
수정할 댓글의 id 확인 후 진행(36번 글 맞는지)
+) 생성되는 글의 순서 정하기
수정 xml 필수 항목 확인rest.js
/* PUT */ async function rest_modify(url, data){ try { let res = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) return await res.json(); } catch(e) { console.log(e); } }
get.jsp 가 빠졌는데 어차피 다 수정할 거라 get.jsp 코드는 "📍화면 구성"부터 볼 것
댓글 삭제
삭제 xml 필수 항목 확인delete문 rest.js
/* DELETE */ async function rest_delete(url) { try { let res = await fetch(url, { method: "DELETE" }); return await res.json(); } catch(e) { console.log(e); } }
get.jsp
//삭제 let result = await rest_delete(url + "/5"); console.log(result);
사라짐
Json 처리 안 하고 문자열로 리턴하기
과거의 get.jsp 흔적 (안 쓸 거임)
/* // 산업 혁명 const bno = ${board.bno} const url = '/api/board/' + bno + '/comment'; console.log(url); //삭제 let result = await rest_delete(url + "/5"); console.log(result); // 수정 let comment = { no: 5, content: "수정한 글이옵니다." }; comment = await rest_modify(url + "/5", comment); console.log(comment); //댓글 생성 let comment = { bno, //key이름과 변수명이 같으면 js에서는 변수명만 써도 key로 등록 writer : '${username}', //얜 문자열이니 따옴표 넣은 것 content : "추가 글입니다." //js에서는 큰 따옴표 생략 가능하지만 JSON에서는 필수.(JSON 작은 따옴표 못 씀) }; console.log(comment); console.log(JSON.stringify(comment)); comment = await rest_create(url, comment); // 생성한 댓글 콘솔로 확인하는 두 줄 console.log(comment); //목록 추출 let data = await rest_get(url); console.log(data); }); */ /* //철기 //bno 하드코딩 변수 처리 const bno = ${board.bno} const url = '/api/board/' + bno + '/comment'; console.log(url); //목록 추출 let data = await rest_get(url); console.log(data); }); */ //청동기 //rest.js로 뺀 후 스크립트 처리 /*const url = '/api/board/36/comment/1'; let data = await rest_get(url); console.log(data); */ //신석기 // async로 가기 위한 준비 /* let res = await fetch('/api/board/36/comment'); //GET 요청 let data = await res.json()); console.log(data); }); */ //구석기 //callback .then 사용 /* fetch('/api/board/36/comment') //경로만 가져오기, 두 번째 매개변수 없으니 GET요청 .then((res) {//익명함수 function, 리턴값의 메서드 then을 호출 -> response 객체 console.log('응답 수신'); // 위에 두 줄이 res=>console.log(res) 와 같음 return res.json(); //Json 문자열을 실제 객체(promise)로 바꿔라 "역직렬화" }) //callback 1개 등록 .then(function(data) { console.log('Json 데이터 변환완료'); console.log(data); //data라는 댓글 배열 }); console.log('fetch 호출 완료'); }); */
📍화면 구성
다 지우고 얘만 두자 이제
🖤moment : 날짜 포맷(날짜 처리)하는 자바스크립트 라이브러리
get.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@include file="../layouts/header.jsp"%> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script> <script src="/resources/js/comment.js"></script> <script src="/resources/js/rest.js"></script> <script> //댓글 기본 URL 상수 - 전역 상수 const COMMENT_URL = '/api/board/${param.bno}/comment/'; $(document).ready(async function() { $('.remove').click(function(){ if(!confirm('정말 삭제할까요?')) return; document.forms.removeForm.submit(); }); let bno = ${param.bno}; // 글번호 let writer = '${username}'; // 작성자(로그인 유저) }); </script>
댓글 목록 보여줄 div 태그를 추가
<div class="my-5"> 댓글 목록 <hr> <div class="comment-list"></div> </div>
이제 comment-list div태그 사이에 댓글목록이 어떻게 보일지 처리
이렇게 get.jsp
<div class="my-5"> 댓글 목록 <hr> <div class="comment-list"> <div class="comment my-3" data-no="10" data-writer="admin"> <div class="comment-title my-2 d-flex justify-content-between"> <div> <strong class="writer"> <img src="/security/avatar/sm/admin" class="avatar-sm"> admin </strong> <span class="text-muted ml-3 comment-date"> 2023-09-23 10:10 </span> </div> <div class="btn-group"> <button class="btn btn-light btn-sm comment-update-show-btn"> <i class="fa-solid fa-pen-to-square"></i> 수정 </button> <button class="btn btn-light btn-sm comment-delete-btn"> <i class="fa-solid fa-times"></i> 삭제 </button> </div> </div> <div class="comment-body"> <div class="comment-content">댓글 내용입니다.</div> </div> <div class="reply-list ml-5"> <!-- 답글 목록 출력 영역 --> </div> </div> </div> </div>
이제 댓글이 보여요!
✨하드코딩 수정
백틱(`) : 다중 문자열 정의, 템플릿 문자열`${}` 을 위해 사용
❗현재는 js 파일이지 jsp 파일이 아니므로 $썼다고 EL 아니다!
get.jsp의 버튼 그룹을 잘라내서 넣어준 상황
comment.js 생성
const commentUpdatable = ` <button class="btn btn-light btn-sm comment-update-show-btn"> <i class="fa-solid fa-pen-to-square"></i> 수정 </button> <button class="btn btn-light btn-sm comment-delete-btn"> <i class="fa-solid fa-times"></i> 삭제 </button> `;
create comment 템플릿 함수작업 (get.jsp의 댓글목록 잘라내서 가져오기)매개변수 : comment, writer
하드코딩 부분 백틱 써서 ${}로 바꿔 수정
🐲파란 블록 맨 윗 줄 data-no 부분을 보면
사용자 정의 속성 이므로 data 대시 뒤에 있는 no가 속성(attribute)이다.
.data(속성명) : 제시되면 읽어달란 뜻
.data(속성명, 값) : 제시된 속성명을 해당 값으로 지정해 달라.
comment.jsfunction createCommentTemplate(comment, writer) { return ` <div class="comment my-3" data-no="${comment.no}" data-writer="${comment.writer}"> <div class="comment-title my-2 d-flex justify-content-between"> <div> <strong class="writer"> <img src="/security/avatar/sm/${comment.writer}" class="avatar-sm"> ${comment.writer} </strong> <span class="text-muted ml-3 comment-date"> ${moment(comment.regDate).format('YYYY-MM-DD hh:mm')} </span> </div> <div class="btn-group"> ${writer && (writer == comment.writer) ? commentUpdatable : ''} </div> </div> <div class="comment-body"> <div class="comment-content">${comment.content}</div> </div> <div class="reply-list ml-5"> <!-- 답글 목록 출력 영역 --> </div> </div> `; }
삼항연산자 파트
로그인 조건 + 본인 글인지 + 버튼 보여주기아까 나온 moment의 쓰임
댓글 목록 배열로 불러오기 + 원댓글 api로 불러오기
of 써서 루프 for문 (에러 남) - 이어서..
이대로 진행하면 잘못된 부분 있음comment.js
function loadComments(bno, writer) { let comments = []; // API로 불러오기 comments = rest_get(COMMENT_URL); for(let comment of comments) { const commentEl = createCommentTemplate(comment, writer); $('.comment-list').append($(commentEl)); } }
여기서 >> $('.comment-list') 라인이 선택한 게 get.jsp의 comment-list
🖤
append : 새로 등록한 글을 목록보기의 제일 뒤에 추가
preppend : 새 댓글을 목록보기의 제일 앞에 추가
예시(붙여서 나열하니까 사이즈가 안 줄여짐;; 이미지가 너무 크다)
get.jsp 에 댓글 목록 불러오기 추가loadComments(bno, writer); // 댓글 목록 불러오기
이렇게 하면~~ 에러가 난다.
async 를 comment.js 에 추가 안 함 ㅎ
⭐❗Uncaught TypeError: comments is not iterable
comment.js 에서 function 부분 async 와 await를 빼먹었다.await를 걸어주지 않아 원하던 데이터를 기다리지 않고 지나가서 promise 객체가 덩그러니 놓여있다.
=> comments가 배열이 아니고 promise 객체라고 인식되어 루프를 못 돌린다!고친 버전
async function loadComments(bno, writer) { let comments = []; // API로 불러오기 comments = await rest_get(COMMENT_URL); for(let comment of comments) { const commentEl = createCommentTemplate(comment, writer); $('.comment-list').append($(commentEl)); } }
출력화면
📍댓글 생성
(댓글 목록 div태그 위에 삽입함)
get.jsp
<!-- 새 댓글 작성 (작성자 아니어야 가능)--> <c:if test="${username != board.writer }"> <div class="bg-light p-2 rounded my-5"> <div>${username == null ? '댓글을 작성하려면 먼저 로그인하세요' : '댓글 작성' }</div> <div> <textarea class="form-control new-comment-content" rows="3" ${username == null ? 'disabled' : '' }></textarea> <div class="text-right"> <button class="btn btn-primary btn-sm my-2 comment-add-btn" ${username == null ? 'disabled' : '' } > <i class="fa-regular fa-comment"></i> 확인 </button> </div> </div> </div> </c:if>
<출력화면 3종류>
✅댓글 추가 버튼 처리 get.jsp
//댓글 추가 버튼 처리 $('.comment-add-btn').click(function(e) { createComment(bno, writer); });
input 타입으로 댓글을 입력했으므로 추가할 때는 내용이 들어간 value를 불러와야 한다.
>> .val()comment.js
function createComment(bno, writer){ const content = $('.new-comment-content').val(); console.log(content); }
💡댓글 입력 가이드 이벤트 처리
빈 문자열일 경우 => 내용을 입력하세요
내용이 있으면 => 추가할까요 물어봄 >> comment 객체 (key 이름과 변수명이 같으니 한 줄로 간단히)
comment.js// 댓글 입력 가이드 이벤트 처리 function createComment(bno, writer) { const content = $('.new-comment-content').val(); if(!content) { alert('내용을 입력하세요.'); $('.new-comment-content').focus(); return; } if(!confirm('댓글을 추가할까요?')) return; let comment = { bno, writer , content } console.log(comment); }
등록은 아직 ㄴㄴ 서버전송 안 됨
등록했다치고 comment no에 임의 글번호를 넣어서 후속작업 보고 연결
comment.js
등록되면 댓글입력창에 기존 입력한 거 지워야 한다.
>>$('.new-comment-content').val(''); 이게 기존 입력한 거 클리어하는 역할// REST로 등록 // comment = rest_post(COMMENT_URL, comment); comment.no = 10;// 테스트 글번호 // 등록 성공 후 DOM 처리 const commentEl = createCommentTemplate(comment, writer); $('.comment-list').prepend($(commentEl)); $('.new-comment-content').val(''); }
추가 잘 되고 댓글입력칸 클리어 잘 됨 (DB에 등록된 건 아님❗❗ 확인용이라 새로고침하면 사라짐❗)
새로고침하면..
댓글 서버등록
이제 comment.no = 10; 부분은 테스트였으니 지우고
api 호출 코드로 변경
앞에 async 추가하고 await 붙인 후comment.js
// REST로 등록 comment = await rest_create(COMMENT_URL, comment);
📍댓글 수정 및 삭제
수정
확인을 누르든 취소를 누르든 textarea 부분이 갱신되어야 한다.(사라지거나-확인 / 복원되거나-취소)
comment.js 상단에서 클래스 이름될 부분 확인get.jsp 에서 댓글 수정 이벤트 처리가 되는지 확인용
이벤트 핸들러를 걸어야 뜬다.
$('.comment-update-show-btn').click(function(){ alert('수정 click'); }); console.log($('.comment-update-show-btn'));
그러나 반응하지 않는다.
댓글 목록 안에 영역인 comment-list를 이용해야 반응이 일어난다. 기존 목록 + 방금 추가한 수정도 같이 적용된다.
🖤parent.on(이벤트명, 대상, 이벤트 핸들러);
*이벤트 위임 기법
이벤트명 : click
대상 : 버튼명
이벤트 핸들러 : 메서드명(get,create,update,delete)과 버튼이름의 조합
div 3을 선택하면 div 1,2,3 모두 반응하고 가장 먼저 반응하는 것은 가장 구체적인 div3이다.
=> 이벤트 버블링(bubbling) "propagation 전파"
반대로도 진행 가능(부모 먼저 처리 - 그 다음 자식)
=> 캡쳐링 capturing
방식은 선택이 가능하다.
default는 버블링이고 특수한 경우 캡쳐링이다..전에 쓰인 on
el.on('이벤트명','핸들러');
.click( )
on은 동일한 이벤트를 여러 개 걸 수 있다는 점만 알고 있었다.
지금은 변형된 형식인 이벤트 위임 기법을 쓰는 것
>> parent.on(이벤트명, 대상, 이벤트 핸들러);
타겟을 css 선택자로 사용 가능하다.
1. 실제 이벤트는 parent에게 걸고 2. 이벤트 발생 시 타겟이 일치하는지 확인하면 3. 그제서야 이벤트핸들러를 호출한다.
사용목적 : 대상이 나중에 동적으로 추가되기 때문에 미리 이벤트 핸들러를 걸 수 없는 경우 사용을 위함이다.
자식에게 미리 못 거니까 부모에게 걸어놓음 (댓글은 계속 새로 추가되고 수정, 삭제될 수 있으니까)
*이벤트 객체를 콘솔로 찍어보면 어떤 자식에서 실제로 이벤트가 발생하는지 알 수 있다.get.jsp
//댓글 추가 버튼 처리 $('.comment-add-btn').click(function(e) { createComment(bno, writer); }); $('.comment-list').on('click', '.comment-update-show-btn', function(e){ console.log('수정 버튼 클릭!', $(this)); //this는 comment-update-show-btn 클래스 중 하나가 선택된다. }); });
댓글 수정 전 기존 내용 숨기기
🖤
display : none
.css('display', 'none') 사라짐
'block' , 'inline' : 보여줌
❤️
자주 쓰는 거라 편하게 쓰라고 설정되어 있다.
.show( ), .hide( ) 로 쓰면 된다.
block, inline => show
none => hide
댓글 수정 화면 보여주기
// 댓글 수정 화면 만들기 function createCommentEditTemplate(comment) { return ` <div class="bg-light p-2 rounded comment-edit-block"> <textarea class="form-control mb-1 comment-editor"> ${comment.content} </textarea> <div class="text-right"> <button class="btn btn-light btn-sm py-1 comment-update-btn"> <i class="fa-solid fa-check"></i> 확인</button> <button class="btn btn-light btn-sm py-1 comment-update-cancel-btn"> <i class="fa-solid fa-undo"></i> 취소</button> </div> </div> `; }
⭐.closest : 부모를 찾는 것
자신과 가장 가까이에 있는 $(this) 선택자를 가진 클래스를 찾는다. => 여기선 comment class
⭐.find : 자식을 찾는 것
find를 써서 comment의 자식인 .comment-content 로 해야 선택한 댓글이 수정된다.(안 하면 같은 id가 쓴 첫 번째인 다른 댓글이 수정되어버린다.)
commentEl을 기준으로 자식을 찾음.//댓글 수정 화면 보여주기 function showUpdateComment(e) { const commentEl = $(this).closest('.comment'); const no = commentEl.data("no"); const contentEl = commentEl.find('.comment-content'); const comment = { no, content: contentEl.html() }; console.log(comment); }
함수에 대한 참조를 주기 (익명함수 아님)
get.jsp
hide 동작이제 수정버튼 누르면 댓글이 버튼이랑 같이 사라진다.
이제 이 사라진 부분에 textarea를 집어넣는 것.
댓글 수정화면 출력 완성 comment.js
//댓글 수정 화면 보여주기 function showUpdateComment(e) { const commentEl = $(this).closest('.comment'); const no = commentEl.data("no"); const contentEl = commentEl.find('.comment-content'); const comment = { no, content: contentEl.html() }; //콘솔에 수정버튼 누를 때마다 해당 댓글이 찍힌다. //console.log('수정 버튼 클릭!', $(this)); //this는 comment-update-show-btn 클래스 중 하나가 선택된다. //수정 버튼 누르면 버튼과 함께 댓글 내용 사라짐 contentEl.hide(); commentEl.find('.btn-group').hide(); //사라진 댓글 사이에 끼우는 템플릿 const template = createCommentEditTemplate(comment); const el = $(template); commentEl.find('.comment-body').append(el); }
수정 버튼 누르면 기존 내용과 함께 textarea 나옴
이제 확인 버튼 눌러서 수정을 완료해 보자..
comment.js 다른 형식으로 받아야 하는데
no를 parseInt로 정수로 안 바꾸면 문자열 인식 때문에 따옴표가 붙는다.
<Comment update api 호출>
hidden 되어있던 '.comment-content' 를 find해서 찾고
editContentEL.remove() 하면 댓글수정창이 버튼 포함해서 전부 사라짐. (const에서 comment-edit-block 영역) 수정창!
맨밑에 show로 보여준다는 건 수정 삭제 버튼.(현재 로그인한 유저에 해당하는 댓글들의 수정 삭제 버튼)
수정 끝났으니 다시 보여줘야한다.
✅댓글 수정(확인) 버튼
comment.js
// 댓글 수정하기 async function updateComment(commentEl, writer) { if(!confirm('수정할까요?')) return; const editContentEl = commentEl.find('.comment-edit-block'); // 수정 창 const content = editContentEl.find('.comment-editor').val(); // 수정 내용 const no = parseInt(commentEl.data("no")); let comment = { no, writer, content }; console.log('수정', comment); // COMMENT UPDATE API 호출..... // comment = rest_modify(COMMENT_URL + comment.no, comment); const contentEl = commentEl.find('.comment-content') editContentEl.remove(); // comment-edit-block 수정창 삭제 contentEl.html(comment.content); // 변경된 내용으로 화면 내용 수정 contentEl.show(); commentEl.find('.btn-group').show(); //로그인한 유저에 맞춰서 해당 댓글의 수정 및 삭제 버튼 보여주기 }
이제 api 호출해야하니 주석 처리된 메서드 가져오자
await 빠져있으니 추가해서..이제 새로고침해도 수정된 댓글 호출되는 코드
// 댓글 수정하기 async function updateComment(commentEl, writer) { if(!confirm('수정할까요?')) return; const editContentEl = commentEl.find('.comment-edit-block'); // 수정 창 const content = editContentEl.find('.comment-editor').val(); // 수정 내용 const no = parseInt(commentEl.data("no")); let comment = { no, writer, content }; console.log('수정', comment); // COMMENT UPDATE API 호출..... comment = await rest_modify(COMMENT_URL + comment.no, comment); const contentEl = commentEl.find('.comment-content') editContentEl.remove(); // comment-edit-block 수정창 삭제 contentEl.html(comment.content); // 변경된 내용으로 화면 내용 수정 contentEl.show(); commentEl.find('.btn-group').show(); //로그인한 유저에 맞춰서 해당 댓글의 수정 및 삭제 버튼 보여주기 }
✅댓글 수정(취소) 버튼
// 댓글 수정 취소 function cancelCommentUpdate(e) { const commentEl = $(this).closest('.comment'); commentEl.find('.comment-content') .show(); //.css('display', 'block'); commentEl.find('.comment-edit-block').remove(); commentEl.find('.btn-group').show(); }
이벤트 핸들러 걸어야 한다.
cancelCommentUpdate
=> get.jsp
수정 확인, 취소 버튼// 댓글 수정, 삭제 버튼 처리 - 이벤트 버블링(이벤트 처리 위임) // 댓글 수정 확인 버튼 클릭 $('.comment-list').on('click', '.comment-update-btn', function (e){ const el = $(this).closest('.comment'); updateComment(el, writer); }); // 수정 취소 버튼 클릭 $('.comment-list').on('click', '.comment-update-cancel-btn', cancelCommentUpdate); });
✅댓글 삭제버튼 처리
리턴을 안 받아도 await는 붙여줘야 한다.
comment.js// 댓글 삭제 async function deleteComment(e) { if(!confirm('댓글을 삭제할까요?')) return; const comment = $(this).closest('.comment') //부모에서 찾기 const no = comment.data("no"); //글번호 추출 // api 호출 await rest_delete(COMMENT_URL + no); //body없으니 url만 넘기기 comment.remove(); }
get.jsp
// 댓글 삭제 버튼 $('.comment-list').on('click', '.comment-delete-btn', deleteComment);
여기까지의 코드 모음
get.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@include file="../layouts/header.jsp"%> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script> <script src="/resources/js/comment.js"></script> <script src="/resources/js/rest.js"></script> <script> //댓글 기본 URL 상수 - 전역 상수 const COMMENT_URL = '/api/board/${param.bno}/comment/'; $(document).ready(async function() { $('.remove').click(function(){ if(!confirm('정말 삭제할까요?')) return; document.forms.removeForm.submit(); }); let bno = ${param.bno}; // 글번호 let writer = '${username}'; // 작성자(로그인 유저) loadComments(bno, writer); // 댓글 목록 불러오기 //댓글 추가 버튼 처리 $('.comment-add-btn').click(function(e) { createComment(bno, writer); }); $('.comment-list').on('click', '.comment-update-show-btn', showUpdateComment); //console.log('수정 버튼 클릭!', $(this)); //this는 comment-update-show-btn 클래스 중 하나가 선택된다. // 댓글 수정, 삭제 버튼 처리 - 이벤트 버블링(이벤트 처리 위임) // 댓글 수정 확인 버튼 클릭 $('.comment-list').on('click', '.comment-update-btn', function (e){ const el = $(this).closest('.comment'); updateComment(el, writer); }); // 댓글 수정 취소 버튼 클릭 $('.comment-list').on('click', '.comment-update-cancel-btn', cancelCommentUpdate); // 댓글 삭제 버튼 $('.comment-list').on('click', '.comment-delete-btn', deleteComment); }); </script> <h1 class="page-header"> <i class="far fa-file-alt"></i> ${board.title} </h1> <div class="d-flex justify-content-between"> <div> <i class="fas fa-user"></i> ${board.writer} </div> <div> <i class="fas fa-clock"></i> <fmt:formatDate pattern="yyyy-MM-dd" value="${board.regDate}" /> </div> </div> <hr> <div>${board.content}</div> <!-- 새 댓글 작성 (작성자 아니어야 가능)--> <c:if test="${username != board.writer }"> <div class="bg-light p-2 rounded my-5"> <div>${username == null ? '댓글을 작성하려면 먼저 로그인하세요' : '댓글 작성' }</div> <div> <textarea class="form-control new-comment-content" rows="3" ${username == null ? 'disabled' : '' }></textarea> <div class="text-right"> <button class="btn btn-primary btn-sm my-2 comment-add-btn" ${username == null ? 'disabled' : '' } > <i class="fa-regular fa-comment"></i> 확인 </button> </div> </div> </div> </c:if> <div class="my-5"><i class="fa-regular fa-comments"></i> 댓글 목록 <hr> <div class="comment-list"> </div> </div> <div class="mt-4"> <a href="${cri.getLink('list')}" class="btn btn-primary list"> <i class="fas fa-list"></i> 목록 </a> <c:if test="${username == board.writer}"> <a href="${cri.getLinkWithBno('modify', board.bno) }" class="btn btn-primary modify"> <i class="far fa-edit"></i> 수정 </a> <a href="#" class="btn btn-danger remove"> <i class="fas fa-trash-alt"></i> 삭제 </a> </c:if> </div> <%-- <form id="listForm" action="/board/list" method="get" > <input type="hidden" name="pageNum" value="${cri.pageNum}"/> <input type="hidden" name="amount" value="${cri.amount}"/> <input type="hidden" name="type" value="${cri.type}"/> <input type="hidden" name="keyword" value="${cri.keyword}"/> </form> <form id="modifyForm" action="/board/modify" method="get" > <input type="hidden" id="bno" name="bno" value="${board.bno}"/> <input type="hidden" name="pageNum" value="${cri.pageNum}"/> <input type="hidden" name="amount" value="${cri.amount}"/> <input type="hidden" name="type" value="${cri.type}"/> <input type="hidden" name="keyword" value="${cri.keyword}"/> </form> --%> <form action="remove" method="post" name="removeForm"> <!-- csrf 토큰 안 넣으면 404 에러 --> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token }" /> <input type="hidden" name="bno" value="${board.bno}" /> <input type="hidden" name="pageNum" value="${cri.pageNum}" /> <input type="hidden" name="amount" value="${cri.amount}" /> <input type="hidden" name="type" value="${cri.type}" /> <input type="hidden" name="keyword" value="${cri.keyword}" /> </form> <%@include file="../layouts/footer.jsp"%>
comment.js
const commentUpdatable = ` <button class="btn btn-light btn-sm comment-update-show-btn"> <i class="fa-solid fa-pen-to-square"></i> 수정 </button> <button class="btn btn-light btn-sm comment-delete-btn"> <i class="fa-solid fa-times"></i> 삭제 </button> `; function createCommentTemplate(comment, writer) { return ` <div class="comment my-3" data-no="${comment.no}" data-writer="${comment.writer}"> <div class="comment-title my-2 d-flex justify-content-between"> <div> <strong class="writer"> <img src="/security/avatar/sm/${comment.writer}" class="avatar-sm"> ${comment.writer} </strong> <span class="text-muted ml-3 comment-date"> ${moment(comment.regDate).format('YYYY-MM-DD hh:mm')} </span> </div> <div class="btn-group"> ${writer && (writer == comment.writer) ? commentUpdatable : ''} </div> </div> <div class="comment-body"> <div class="comment-content">${comment.content}</div> </div> <div class="reply-list ml-5"> <!-- 답글 목록 출력 영역 --> </div> </div> `; } async function loadComments(bno, writer) { let comments = []; // API로 불러오기 comments = await rest_get(COMMENT_URL); for(let comment of comments) { const commentEl = createCommentTemplate(comment, writer); $('.comment-list').append($(commentEl)); } } // 댓글 입력 가이드 이벤트 처리 async function createComment(bno, writer) { const content = $('.new-comment-content').val(); if(!content) { alert('내용을 입력하세요.'); $('.new-comment-content').focus(); return; } if(!confirm('댓글을 추가할까요?')) return; let comment = { bno, writer , content } console.log(comment); // REST로 등록 comment = await rest_create(COMMENT_URL, comment); // 등록 성공 후 DOM 처리 const commentEl = createCommentTemplate(comment, writer); $('.comment-list').prepend($(commentEl)); $('.new-comment-content').val(''); } // 댓글 수정 화면 만들기 function createCommentEditTemplate(comment) { return ` <div class="bg-light p-2 rounded comment-edit-block"> <textarea class="form-control mb-1 comment-editor"> ${comment.content} </textarea> <div class="text-right"> <button class="btn btn-light btn-sm py-1 comment-update-btn"> <i class="fa-solid fa-check"></i> 확인</button> <button class="btn btn-light btn-sm py-1 comment-update-cancel-btn"> <i class="fa-solid fa-undo"></i> 취소</button> </div> </div> `; } // 댓글 수정 화면 보여주기 function showUpdateComment(e) { const commentEl = $(this).closest('.comment'); const no = commentEl.data("no"); const contentEl = commentEl.find('.comment-content'); const comment = { no, content: contentEl.html().trim() }; console.log(comment); //수정 버튼 누르면 버튼과 함께 댓글 내용 사라짐 contentEl.hide(); commentEl.find('.btn-group').hide(); //사라진 댓글 사이에 끼우는 템플릿 const template = createCommentEditTemplate(comment); const el = $(template); commentEl.find('.comment-body').append(el); } // 댓글 수정하기 async function updateComment(commentEl, writer) { if(!confirm('수정할까요?')) return; const editContentEl = commentEl.find('.comment-edit-block'); // 수정 창 const content = editContentEl.find('.comment-editor').val(); // 수정 내용 const no = parseInt(commentEl.data("no")); let comment = { no, writer, content }; console.log('수정', comment); // COMMENT UPDATE API 호출..... comment = await rest_modify(COMMENT_URL + comment.no, comment); const contentEl = commentEl.find('.comment-content') editContentEl.remove(); // comment-edit-block 수정창 삭제 contentEl.html(comment.content); // 변경된 내용으로 화면 내용 수정 contentEl.show(); commentEl.find('.btn-group').show(); //로그인한 유저에 맞춰서 해당 댓글의 수정 및 삭제 버튼 보여주기 } // 댓글 수정 취소 function cancelCommentUpdate(e) { const commentEl = $(this).closest('.comment'); commentEl.find('.comment-content') .show(); //.css('display', 'block'); commentEl.find('.comment-edit-block').remove(); commentEl.find('.btn-group').show(); } // 댓글 삭제 async function deleteComment(e) { if(!confirm('댓글을 삭제할까요?')) return; const comment = $(this).closest('.comment') //부모에서 찾기 const no = comment.data("no"); //글번호 추출 // api 호출 await rest_delete(COMMENT_URL + no); //body없으니 url만 넘기기 comment.remove(); }
rest.js
async function rest_get(url) { try { let res = await fetch(url); return await res.json(); } catch(e) { console.log(e); } } /*이클립스의 자바스크립트 분석기가 await가 뭔지 몰라서 나는 에러이므로 브라우저에서는 괜찮다.*/ /*fetch의 메서드 옵션을 post로 지정*/ async function rest_create(url, data) { try { let res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) return await res.json(); } catch(e) { console.log(e); } } /* PUT */ async function rest_modify(url, data){ try { let res = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) return await res.json(); } catch(e) { console.log(e); } } /* DELETE */ async function rest_delete(url) { try { let res = await fetch(url, { method: "DELETE" }); return await res.text(); } catch(e) { console.log(e); } }
'Spring' 카테고리의 다른 글
0926. 답글 처리(서버 + 클라이언트) (0) 2023.09.27 0922. 댓글처리 Ajax, REST API (0) 2023.09.22 0918. Spring Form Tag 라이브러리 (0) 2023.09.20 0915. 여행 페이지를 만들자 (2) 2023.09.15