-
0926. 답글 처리(서버 + 클라이언트)Spring 2023. 9. 27. 17:42
보호발행인데 왜 보이지..
🌱
서버
- REST API 컨트롤러 만들어서 @RestController 사용
- RequestBody : 모델객체 만들 때 application/json 파일 복원
클라이언트
- Ajax : $.ajax()
promise 객체 / fetch()
async/await
📍서버 측 설계
비버 열어서
comment에 대한 foreign key 를 cno로 등록하기
필수요소 : cno, content, writer
tbl_reply 답글 테이블 생성
tbl_reply.sql
drop table if exists tbl_reply; create table tbl_reply ( no integer auto_increment primary key, cno integer not null, -- comment의 no! foreign key content varchar(1000) not null, writer varchar(50) not null, reg_date datetime default now(), update_date datetime default now(), constraint fk_reply_comment foreign key(cno) references tbl_comment(no) ); select * from tbl_reply;
답글 입력해 보기
alias 작업 전에 *로 테스트
select c.*, r.* from tbl_comment c left join tbl_reply r on c.no = r.cno where bno = 36;
순서 정렬
오름차순은 그냥 쓰면 default니까 r.no
이제 각각 별칭 붙여주기
/* 실제 sql 문 */ select c.no, c.bno, c.content c_content, c.writer c_writer, c.reg_date c_reg_date, c.update_date c_update_date, r.no r_no, r.content r_content, r.writer r_writer, r.reg_date r_reg_date, r.update_date r_update_date from tbl_comment c left join tbl_reply r on c.no = r.cno where bno = 36;
sql문
더보기drop table if exists tbl_reply; create table tbl_reply ( no integer auto_increment primary key, cno integer not null, -- comment의 no! foreign key content varchar(1000) not null, writer varchar(50) not null, reg_date datetime default now(), update_date datetime default now(), constraint fk_reply_comment foreign key(cno) references tbl_comment(no) ); insert into tbl_reply(cno, writer, content) values (4, 'admin', '답글'), (4, 'admin', '답글2'), (4, 'quokka', '답글3'); insert into tbl_reply(cno, writer, content) values (2, 'admin', '답글4'), (1, 'admin', '답글5'); /* 테스트 */ select c.*, r.* from tbl_comment c left join tbl_reply r on c.no = r.cno where bno = 36 -- 댓글은 내림차순, 답글은 오름차순(순서대로 밑에 생기게) order by c.no desc, r.no; /* 실제 sql 문 */ select c.no, c.bno, c.content c_content, c.writer c_writer, c.reg_date c_reg_date, c.update_date c_update_date, r.no r_no, r.content r_content, r.writer r_writer, r.reg_date r_reg_date, r.update_date r_update_date from tbl_comment c left join tbl_reply r on c.no = r.cno where bno = 36; select * from tbl_reply;
1. Domain 작업
ReplyVO
* CommentVO : ReplyVO = 1 : N 관계
=> 여러 개의 답글을 한 댓글이 담아야 하니, CommentVO에 property가 List<ReplyVO>로 추가되어야 한다.
package org.galapagos.domain; import java.util.Date; import lombok.Data; @Data public class ReplyVO { private Long no; private Long cno; // Comment의 no private String writer; private String content; private Date regDate; private Date updateDate; }
CommentVO
2. XML 수정
CommentMapper.xml
원본
1) collection의 자식태그로 정의하기
* collection은 java에서 List이므로 JSON에서는 배열[]처리해야 한다.
2) 비버의 select 조인문 가져와서 넣기
CommentMapper.xml 코드 ⬇️
더보기<?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="org.galapagos.mapper.CommentMapper"> <resultMap id="CommentMap" type="CommentVO"> <id property="no" column="no" /> <result property="bno" column="bno" /> <result property="content" column="c_content" /> <result property="writer" column="c_writer" /> <result property="regDate" column="c_reg_date" /> <result property="updateDate" column="c_update_date" /> <!-- collection의 자식태그로 정의 --> <!-- collection은 java에서 List이므로 JSON에서는 배열[]처리해야 한다. --> <collection property="replyList" ofType="org.galapagos.domain.ReplyVO"> <id property="no" column="r_no" /> <result property="cno" column ="cno"/> <result property="content" column="r_content" /> <result property="writer" column="r_writer" /> <result property="regDate" column="r_reg_date" /> <result property="updateDate" column="r_update_date" /> </collection> </resultMap> <insert id="create"> insert into tbl_comment (bno, writer, content) values(#{bno}, #{writer}, #{content}) <!-- insert로 인해 property랑 column에 auto_increment 된 값이 무엇인지 확인용 --> <selectKey resultType="Long" keyProperty="no" keyColumn="no" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> </insert> <update id="update"> update tbl_comment set content = #{content}, update_date = now() where no = #{no} </update> <delete id="delete"> delete from tbl_comment where no = #{no} </delete> <!-- SQL에서 select 조인문 가져오기 --> <select id="readAll" resultMap="CommentMap"> select c.no, c.bno, c.content c_content, c.writer c_writer, c.reg_date c_reg_date, c.update_date c_update_date, r.no r_no, r.cno, r.content r_content, r.writer r_writer, r.reg_date r_reg_date, r.update_date r_update_date from tbl_comment c left join tbl_reply r on c.no = r.cno where bno = #{bno} </select> <select id="get" resultType="CommentVO"> select c.no, c.bno, c.content c_content, c.writer c_writer, c.reg_date c_reg_date, c.update_date c_update_date, r.no r_no, r.cno, r.content r_content, r.writer r_writer, r.reg_date r_reg_date, r.update_date r_update_date from tbl_comment c left join tbl_reply r on c.no = r.cno where c.no = #{no} </select> </mapper>
talend로 가서 테스트 해 보기
답글 안 달아놓은 곳엔 cno : null로 나옴
3. ReplyMapper 인터페이스
package org.galapagos.mapper; import org.galapagos.domain.ReplyVO; public interface ReplyMapper { ReplyVO get(Long no); void create(ReplyVO vo); void update(ReplyVO vo); void delete(Long no); }
4. ReplyMapper.xml
<?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="org.galapagos.mapper.ReplyMapper"> <select id="get" resultType="ReplyVO"> select * from tbl_reply where no = #{no} </select> <insert id="create"> <selectKey resultType="Long" keyProperty="no" keyColumn="no" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into tbl_reply (cno, writer, content) values(#{cno}, #{writer}, #{content}) </insert> <update id="update"> update tbl_reply set content = #{content}, update_date = now() where no = #{no} </update> <delete id="delete"> delete from tbl_reply where no = #{no} </delete> </mapper>
5. ReplyController
@RestController 로 REST에 있는 body를 자동으로 등록해 준다.
package org.galapagos.controller; import org.galapagos.domain.ReplyVO; import org.galapagos.mapper.ReplyMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; 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.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/board/{bno}/reply") public class ReplyController { @Autowired private ReplyMapper mapper; @PostMapping("") public ReplyVO create(@RequestBody ReplyVO vo) { mapper.create(vo); return mapper.get(vo.getNo()); } @PutMapping("/{no}") public ReplyVO update(@PathVariable Long no, @RequestBody ReplyVO vo) { System.out.println("==> " + vo); mapper.update(vo); return mapper.get(vo.getNo()); } @DeleteMapping("/{no}") public String delete(@PathVariable Long no) { mapper.delete(no); return "OK"; } }
📍클라이언트 측 설계
- 화면 처리 -
- 본인 댓글에 본인이 답글 못 달게 하기
- 댓글에 답글이 있으면 들여쓰기로 보이게 하기❗
1. Comment.js 기존 코드 수정
const replyAddable = ` <button class="btn btn-light btn-sm reply-add-show-btn"> <i class="fa-solid fa-pen-to-square"></i> 답글 </button> `;
답글에도 로그인 사용자 조건 추가 (헷갈리니 이전에 있던 행까지 두 줄 같이 코드 첨부)
${writer && (writer == comment.writer) ? commentUpdatable : ''} ${writer && (writer != comment.writer) ? replyAddable : ''}
⭐들여쓰기 부분 margin left
el태그 위치 변경 (commentEl => createCommentTemplate() 로)
reply 답글이 있다면 화면에 나오게 콘솔로 찍어보기
2. 답글 화면
- 답글 입력칸 화면 보이기 -
reply.js 생성
function createReplyEditTemplate(reply) { return ` <div class="bg-light p-2 rounded reply-edit-block" data-no = "${reply.no}" data-cno="${reply.cno}" data-writer="${reply.writer}"> <div>${reply.no ? '' : ' 답글 작성'}</div> <textarea class="form-control mb-1 reply-editor"> ${reply.content || '' } </textarea> <div class="text-end"> <button class="btn btn-light btn-sm py-1 ${reply.no ? 'reply-update' : 'reply-add-btn'} "> <i class="fa-solid fa-check"></i> 확인 </button> <button class="btn btn-light btn-sm py-1 ${reply.no ? 'reply-update-cancel' : 'reply-add-cancel-btn'} "> <i class="fa-solid fa-undo"></i> 최소 </button> </div> </div> `; } function showReplyAdd(el, writer) { const commentEl = el.closest('.comment'); const cno = commentEl.data("no"); const reply = { cno, writer }; const template = createReplyEditTemplate(reply); commentEl.find('.reply-list').append($(template)); commentEl.find('.btn-group').hide(); commentEl.find('.reply-editor').focus(); }
board의 get.jsp 수정
reply.js 만들었으니 스크립트 처리 + url 전역 상수
get.jsp 하단에 "답글 버튼" 이벤트 핸들링
$(this)가 답글버튼
(jquery객체로 해서 로그인 사용자정보를 같이 전달~)
- 답글 등록 버튼 만들기 -
역시 등록하면 작성칸 사라지면서 처리해야 함
reply.js
답글 버튼 그룹에 들어갈 항목준비
윗부분에 추가해 주자(comment.js와 비슷하게 - 관리용이)
//답글 버튼 관리 const replyUpdatable = ` <button class="btn btn-light btn-sm reply-update-show-btn"> <i class="fa-solid fa-pen-to-square"></i> 수정 </button> <button class="btn btn-light btn-sm reply-delete-btn"> <i class="fa-solid fa-times"></i> 삭제 </button> `;
답글 생성(추가)을 위한 template
class 명과 매개변수가 다르다.
//답글 생성 function createReplyTemplate(reply, writer) { return ` <div class="reply my-3" data-no="${reply.no}" data-writer="${reply.writer}"> <div class="reply-title my-2 d-flex justify-content-between"> <div class="reply-head"> <strong class="reply-writer"> <img src="/security/avatar/sm/${reply.writer}" class="avatar-sm">${reply.writer} </strong> <span class="text-muted ml-3 reply-date"> ${moment(reply.regDate).format('YYYY-MM-DD hh:mm')} </span> </div> <div class="btn-group"> ${writer && (writer == reply.writer) ? replyUpdatable : ''} </div> </div> <div class="reply-body"> <div class="reply-content">${ reply.content || '' }</div> </div> </div> `; }
3. 답글 생성(추가) 기능 만들기
async 문에서 el : 답글 작성 "확인"버튼임
const content = replyeBlock.find에서 .reply-editor << textarea 부분임
DB 처리 안 해서 새로고침 하면 사라짐
reply.js
// 답글 추가 async function addReply(el, writer) { //el이 답글 작성하고 누르는 "확인"버튼 console.log('reply 추가'); // cno 추출, writer 추출 const commentEl = el.closest('.comment'); const replyBlock = commentEl.find('.reply-edit-block'); const cno = parseInt(commentEl.data("no")); const content =replyBlock.find('.reply-editor').val(); let reply = { cno, writer, content }; // REPLY POST API 호출 reply = await rest_create(REPLY_URL, reply); console.log(reply); const replyEl = $(createReplyTemplate(reply, writer)); commentEl.find('.reply-list').append(replyEl); commentEl.find('.reply-edit-block').remove(); commentEl.find('.btn-group').show(); }
get.jsp 에서 답글 작성 "확인" 버튼 이벤트 핸들링
// 답글 추가해서 작성 후 "확인" 버튼 $('.comment-list').on('click', '.reply-add-btn', function(e){ addReply($(this), writer); });
reply-add-btn = 답글 추가 확인 버튼 = $(this)
4. 답글 추가 "취소" 및 이벤트핸들링
reply.js
// 답글 취소 function cancelReply(e) { const commentEl = $(this).closest('.comment'); commentEl.find('.reply-edit-block').remove(); commentEl.find('.btn-group').show(); }
get.jsp
// 답글 "취소" $('.comment-list').on('click', '.reply-add-cancel-btn', cancelReply);
❤️+) 답글 등록(+수정)할 때는 writer 정보가 필요해서 매개변수(지역변수)로 받아야 하므로 함수를 호출까지 해야 하고
삭제는 필요없어서 이름만 설정하면 된다. (밑에 코드 참고해서 비교)
// 답글 추가해서 작성 후 "확인" 버튼 $('.comment-list').on('click', '.reply-add-btn', function(e){ addReply($(this), writer); });
5. 추가된 답글들도 화면에 다 보여줘야하니 답글 목록 만들기
댓글 목록 불러올 때 같이 불러와야 하니까 그 안에 넣어주면 된다.
Comment.js
(위에서 console로 테스트해 본 위치)
let replyEl = $(createReplyTemplate(reply, writer)); replyListEl.append(replyEl);
- 답글 수정 화면 보여주기 -
수정요청에 필요한 부분 : no, content
reply.js
// 답글 수정 화면 보여주기 function showUpdateReply(el) { const replyEl = el.closest('.reply'); const no = replyEl.data("no"); const content = replyEl.find('.reply-content').html(); const reply = { no, content }; const editor = $(createReplyEditTemplate(reply)); replyEl.find('.reply-content').hide(); replyEl.find('.reply-body').append(editor); }
답글 수정한 화면 이벤트 핸들링
get.jsp
// 답글 수정 화면 보이기 $('.comment-list').on('click', '.reply-update-show-btn', function(e) { showUpdateReply($(this)); });
하고 보니 수정버튼 누르면 답글입력칸(+확인,취소버튼)만 나와야 하는데 댓글수정삭제버튼이 안 숨겨짐
처리하기
replyEl.find('.btn-group').hide();
- 답글 수정한 내용 등록되게 처리 -
reply .js
// 답글 수정한 것 등록 처리 async function updateReply(el) { if(!confirm('답글을 수정할까요?')) return; const replyEl = el.closest('.reply'); const replyContent = replyEl.find('.reply-content'); const content = replyEl.find('.reply-editor').val(); const no = replyEl.data("no"); let reply = { no, content }; reply = await rest_modify(REPLY_URL + no, reply); replyContent.html(content); replyContent.show(); replyEl.find('.reply-edit-block').remove(); }
get.jsp 수정 등록 처리
// 답글 수정 등록 $('.comment-list').on('click', '.reply-update', function(e) { updateReply($(this)); });
reply.js remove 다음 줄에 추가
replyEl.find('.btn-group').show();
- 답글 수정 취소 화면 -
reply.js
// 답글 수정 화면 취소 function cancelReplyUpdate() { const replyEl = $(this).closest('.reply'); replyEl.find('.reply-content').show(); //취소니까 원래 화면 복원한 것 replyEl.find('.reply-edit-block').remove(); }
get.jsp
// 답글 수정 취소 $('.comment-list').on('click', '.reply-update-cancel', cancelReplyUpdate);
6. 답글 삭제
reply.js
// 답글 삭제 async function deleteReply(e) { if(!confirm('답글을 삭제할까요?')) return; const replyEl = $(this).closest('.reply'); const no = parseInt(replyEl.data("no")); await rest_delete(REPLY_URL + no); replyEl.remove(); }
get.jsp
// 답글 삭제 $('.comment-list').on('click', '.reply-delete-btn', deleteReply);
[소스코드]
Comment.js ⬇️
더보기//같이 보여줄 답글 버튼 const replyAddable = ` <button class="btn btn-light btn-sm reply-add-show-btn"> <i class="fa-solid fa-pen-to-square"></i> 답글 </button> `; //답글 버튼 구성 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) { console.log(comment, comment.writer, comment.content) console.log(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 ms-3 comment-date"> ${moment(comment.regDate).format('YYYY-MM-DD hh:mm')} </span> </div> <div class="btn-group"> ${writer && (writer == comment.writer) ? commentUpdatable : ''} ${writer && (writer != comment.writer) ? replyAddable : ''} </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); let replyListEl = commentEl.find('.reply-list'); // 답글 목록 처리 for(let reply of comment.replyList) { let replyEl = $(createReplyTemplate(reply, writer)); replyListEl.append(replyEl); }; } } // 댓글 입력 가이드 이벤트 처리 async function createComment(bno, writer) { const content = $('.new-comment-content').val(); console.log(content); 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-end"> <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 }; comment = await rest_modify(COMMENT_URL + comment.no, comment); console.log('수정', comment); const contentEl = commentEl.find('.comment-content') editContentEl.remove(); 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"); await rest_delete(COMMENT_URL + no); // api 호출 comment.remove(); }
reply.js ⬇️
더보기//답글 버튼 관리 const replyUpdatable = ` <button class="btn btn-light btn-sm reply-update-show-btn"> <i class="fa-solid fa-pen-to-square"></i> 수정 </button> <button class="btn btn-light btn-sm reply-delete-btn"> <i class="fa-solid fa-times"></i> 삭제 </button> `; //답글 생성 function createReplyTemplate(reply, writer) { return ` <div class="reply my-3" data-no="${reply.no}" data-writer="${reply.writer}"> <div class="reply-title my-2 d-flex justify-content-between"> <div class="reply-head"> <strong class="reply-writer"> <img src="/security/avatar/sm/${reply.writer}" class="avatar-sm">${reply.writer} </strong> <span class="text-muted ml-3 reply-date"> ${moment(reply.regDate).format('YYYY-MM-DD hh:mm')} </span> </div> <div class="btn-group"> ${writer && (writer == reply.writer) ? replyUpdatable : ''} </div> </div> <div class="reply-body"> <div class="reply-content">${ reply.content || '' }</div> </div> </div> `; } //답글 입력칸 생성 function createReplyEditTemplate(reply) { return ` <div class="bg-light p-2 rounded reply-edit-block" data-no = "${reply.no}" data-cno="${reply.cno}" data-writer="${reply.writer}"> <div>${reply.no ? '' : ' 답글 작성'}</div> <textarea class="form-control mb-1 reply-editor"> ${reply.content || '' } </textarea> <div class="text-end"> <button class="btn btn-light btn-sm py-1 ${reply.no ? 'reply-update' : 'reply-add-btn'} "> <i class="fa-solid fa-check"></i> 확인 </button> <button class="btn btn-light btn-sm py-1 ${reply.no ? 'reply-update-cancel' : 'reply-add-cancel-btn'} "> <i class="fa-solid fa-undo"></i> 최소 </button> </div> </div> `; } //답글 화면에 보여주기 function showReplyAdd(el, writer) { const commentEl = el.closest('.comment'); const cno = commentEl.data("no"); const reply = { cno, writer }; const template = createReplyEditTemplate(reply); commentEl.find('.reply-list').append($(template)); commentEl.find('.btn-group').hide(); commentEl.find('.reply-editor').focus(); } // 답글 추가 async function addReply(el, writer) { //el이 답글 작성하고 누르는 "확인"버튼 console.log('reply 추가'); // cno 추출, writer 추출 const commentEl = el.closest('.comment'); const replyBlock = commentEl.find('.reply-edit-block'); const cno = parseInt(commentEl.data("no")); const content =replyBlock.find('.reply-editor').val(); let reply = { cno, writer, content }; // REPLY POST API 호출 reply = await rest_create(REPLY_URL, reply); console.log(reply); const replyEl = $(createReplyTemplate(reply, writer)); commentEl.find('.reply-list').append(replyEl); commentEl.find('.reply-edit-block').remove(); commentEl.find('.btn-group').show(); } // 답글 수정 화면 보여주기 function showUpdateReply(el) { const replyEl = el.closest('.reply'); const no = replyEl.data("no"); const content = replyEl.find('.reply-content').html(); const reply = { no, content }; const editor = $(createReplyEditTemplate(reply)); replyEl.find('.reply-content').hide(); replyEl.find('.btn-group').hide(); replyEl.find('.reply-body').append(editor); } // 답글 수정한 것 등록 처리 async function updateReply(el) { if(!confirm('답글을 수정할까요?')) return; const replyEl = el.closest('.reply'); const replyContent = replyEl.find('.reply-content'); const content = replyEl.find('.reply-editor').val(); const no = replyEl.data("no"); let reply = { no, content }; reply = await rest_modify(REPLY_URL + no, reply); replyContent.html(content); replyContent.show(); replyEl.find('.reply-edit-block').remove(); replyEl.find('.btn-group').show(); } // 답글 수정 화면 취소 function cancelReplyUpdate() { const replyEl = $(this).closest('.reply'); replyEl.find('.reply-content').show(); //취소니까 원래 화면 복원한 것 replyEl.find('.reply-edit-block').remove(); } // 답글 취소 function cancelReply(e) { const commentEl = $(this).closest('.comment'); commentEl.find('.reply-edit-block').remove(); commentEl.find('.btn-group').show(); } // 답글 삭제 async function deleteReply(e) { if(!confirm('답글을 삭제할까요?')) return; const replyEl = $(this).closest('.reply'); const no = parseInt(replyEl.data("no")); await rest_delete(REPLY_URL + no); replyEl.remove(); }
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 src="/resources/js/reply.js"></script> <script> //댓글, 답글 기본 URL 상수 - 전역 상수 const COMMENT_URL = '/api/board/${param.bno}/comment/'; const REPLY_URL = '/api/board/${param.bno}/reply/'; $(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); /////// 답글 버튼 이벤트 핸들링 // 답글 추가버튼 인터페이스 보이기 $('.comment-list').on('click', '.reply-add-show-btn', function(e) { showReplyAdd($(this), writer); }); // 답글 추가해서 작성 후 "확인" 버튼 $('.comment-list').on('click', '.reply-add-btn', function(e){ addReply($(this), writer); }); // 답글 수정 화면 보이기 $('.comment-list').on('click', '.reply-update-show-btn', function(e) { showUpdateReply($(this)); }); // 답글 수정 등록 $('.comment-list').on('click', '.reply-update', function(e) { updateReply($(this)); }); // 답글 수정 취소 $('.comment-list').on('click', '.reply-update-cancel', cancelReplyUpdate); // 답글 "취소" $('.comment-list').on('click', '.reply-add-cancel-btn', cancelReply); // 답글 삭제 $('.comment-list').on('click', '.reply-delete-btn', deleteReply); }); </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"%>
'Spring' 카테고리의 다른 글
0925. 댓글 화면 출력(댓글 작성, 수정-확인 및 취소, 삭제) (0) 2023.09.25 0922. 댓글처리 Ajax, REST API (0) 2023.09.22 0918. Spring Form Tag 라이브러리 (0) 2023.09.20 0915. 여행 페이지를 만들자 (2) 2023.09.15