ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 0925. 댓글 화면 출력(댓글 작성, 수정-확인 및 취소, 삭제)
    Spring 2023. 9. 25. 18:55

    🌱댓글 화면 출력 

    댓글이 화면상에 보이게 할 것이다.
     
    [구현할 기능]

    • 댓글 목록보기
    • 새 댓글 작성(작성자 아닌 회원만)
    • 댓글 수정(수정 확인 + 수정 취소)
    • 댓글 삭제

     
    * JSON 
    JS Object 를 JSON 문자열로 바꾸는 것 : 직렬화
    JSON 문자열을 JS Object 로 바꾸는 것 : 역직렬화 parse( ) - 문자열을 해석해서 처리하는 역할


    📍rest.js 작업

    댓글 등록
     
    ⭐method, headers, body를 잘 봐야 한다.

    fetch 함수

     
    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);

    *사진상 오류는 admin 작성자인 게시글에 admin으로 댓글달려고 해서 안 됨 원래 (하드코딩이라 가능)


    댓글 수정
     
    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.js

    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>
    	`;
    }

    삼항연산자 파트
    로그인 조건 + 본인 글인지 + 버튼 보여주기

    아까 나온 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 : 새 댓글을 목록보기의 제일 앞에 추가

     
    예시

    (붙여서 나열하니까 사이즈가 안 줄여짐;; 이미지가 너무 크다)

    좌 : 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));
    	}
    }

    출력화면 

    admin으로 로그인한 상태


    📍댓글 생성

    (댓글 목록 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 의 이벤트 핸들러 작동 방식

    🖤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(); //로그인한 유저에 맞춰서 해당 댓글의 수정 및 삭제 버튼 보여주기
    		
    }

     
     

    ✅댓글 수정(취소) 버튼

    css 스타일은 자주 하니까 show로 해도 된다.(none은 hide)

    // 댓글 수정 취소
    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
Designed by Tistory.