스프링 부트, 확장!

준비중..

스프링 부트, 확장!

댓글 기능 및 소셜 로그인!

27 댓글 수정하기

# 댓글 수정하기 ## 미션 --- 기존 댓글 내용을 ![클라우드스터딩-스프링부트-댓글-목록-조회](https://i.imgur.com/nglzLsW.png) 수정하시오. ![클라우드스터딩-스프링부트-댓글-수정-작성](https://i.imgur.com/H8ff2nJ.png) ## 개념 --- #### ⭐️ 진행 흐름 사용자가 댓글 수정을 클릭한다. JS는 클릭 이벤트를 감지하여, 수정 폼을 보여준다. ![클라우드스터딩-스프링-부트-수정-폼-부트스트랩-collapse](https://i.imgur.com/lsQEv26.png) 사용자는 댓글을 수정하고 완료 버튼을 클릭한다. 수정 데이터는 Ajax로 보내지고, 정상 작동 시 해당 페이지는 리로드(reload) 된다. ![클라우드스터딩-스프링-부트-댓글-수정-과정](https://i.imgur.com/6E2oMcQ.png) ## 튜토리얼 --- #### ⭐️ 뷰 페이지 1) 리팩토링: "comments/_comments.mustache" ``` <div class="card" id="comments"> <div class="card-body"> <!-- 댓글 작성 창 --> {{>comments/_form}} <!-- 댓글 목록 --> {{>comments/_list}} </div> </div> <Script src="/js/app/comment.js"></script> ``` 2) 댓글 작성 폼 생성: "comments/_form.mustache" ``` <form> <div class="form-group"> <label>댓글 작성</label> <textarea class="form-control" id="comment-content" rows="3"></textarea> </div> <input type="hidden" id="comment-author" value="익명"> <button type="button" class="btn btn-primary" id="comment-create-btn">제출</button> </form> ``` 3) 댓글 목록 페이지 생성: "comments/_list.mustache" ``` <ul class="list-unstyled"> {{#comments}} <li class="media mt-4" id="comment-{{id}}"> <img src="https://api.adorable.io/avatars/64/{{author}}.png" class="mr-3" alt="avata"> <div class="media-body"> <!-- 댓글 --> <h5 class="mt-0 mb-1">{{author}} <small> <!-- 부트스트랩 collapse 활용, https://getbootstrap.com/docs/4.5/components/collapse/ --> <a href="#" class="comment-edit-btn" data-toggle="collapse" data-target=".multi-collapse-{{id}}">수정</a> </small> </h5> <!-- 보기 모드 --> <p class="collapse multi-collapse-{{id}} show">{{content}}</p> <!-- 수정 모드 --> <form class="collapse multi-collapse-{{id}}"> <div class="form-group"> <textarea class="form-control" id="comment-content" rows="3">{{content}}</textarea> </div> <input type="hidden" id="comment-id" value="{{id}}"> <input type="hidden" id="comment-author" value="{{author}}"> <button type="button" class="btn btn-info comment-update-btn">수정 완료</button> </form> </div> </li> {{/comments}} </ul> ``` #### ⭐️ JS 4) 댓글 수정 요청 보내기: "static/js/app/comment.js" ``` // 데이터 전송 객체 생성! var comment = { // 이벤트 등록 init: function() { var _this = this; // 생성 버튼 변수화 const createBtn = document.querySelector('#comment-create-btn'); // 생성 버튼 클릭 시, 수행할 메소드 연결! createBtn.addEventListener('click', function(){ _this.create(); }); // 수정 버튼 변수화 const updateBtns = document.querySelectorAll('.comment-update-btn'); // 모든 수정 버튼별, 이벤트 등록 updateBtns.forEach(function(item) { item.addEventListener('click', function() { // 클릭 이벤트 발생시, var form = this.closest('form'); // 클릭 이벤트가 발생한 버튼에 제일 가까운 폼을 찾고, _this.update(form); // 해당 폼으로, 업데이트 수행한다! }); }); }, // 댓글 등록 create: function() { // 데이터 var data = { author: document.querySelector('#comment-author').value, content: document.querySelector('#comment-content').value, }; // url에서 article의 id를 추출! var split = location.pathname.split('/'); var articleId = split[split.length - 1]; // Ajax 통신 // - https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch // - https://t.ly/Vrrz fetch('/api/comments/' + articleId, { // 요청을 보냄 method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }).then(function(response) { // 응답 처리 if (response.ok) { // 성공 alert('댓글이 등록되었습니다.'); window.location.reload(`/articles/${articleId}#comment`); } else { // 실패 alert('댓글 등록 실패..!'); } }); }, // 댓글 수정 update: function(form) { // 데이터 var data = { id: form.querySelector('#comment-id').value, author: form.querySelector('#comment-author').value, content: form.querySelector('#comment-content').value, }; // url에서 article의 id를 추출! var split = location.pathname.split('/'); var articleId = split[split.length - 1]; // 비동기 통신 fetch('/api/comments/' + data.id, { // 요청을 보냄 method: 'PUT', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }).then(function(response) { // 응답 처리 if (response.ok) { // 성공 alert('댓글이 수정되었습니다.'); } else { // 실패 alert('댓글 수정 실패..!'); } window.location.reload(true); // 페이지 리로드 }); } }; // 객체 초기화! comment.init(); ``` #### ⭐️ API 컨트롤러 5) 수정 요청 처리: "controller/CommentApiController" ``` ... @RestController public class CommentApiController { private final CommentService commentService; ... @PutMapping("/api/comments/{id}") public Long update(@PathVariable Long id, @RequestBody CommentForm form) { // 서비스 객체가 댓글 수정 Comment updated = commentService.update(id, form); return updated.getId(); } } ``` #### ⭐️ 서비스 6) 수정 작업 수행: "service/CommentService" ``` ... @Service public class CommentService { private final ArticleRepository articleRepository; private final CommentRepository commentRepository; ... @Transactional public Comment update(Long id, CommentForm form) { // 수정 댓글 폼을 엔티티로 변경 log.info("form: " + form.toString()); Comment edited = form.toEntity(); log.info("edited: " + form.toString()); // DB에서 기존 댓글을 가져옴 Comment target = commentRepository.findById(id) .orElseThrow( () -> new IllegalArgumentException("해당 댓글이 없습니다.") ); log.info("target: " + target.toString()); // 기존 댓글을 수정! target.rewrite(edited.getContent()); log.info("updated: " + target.toString()); return commentRepository.save(target); } } ``` #### ⭐️ 엔티티 7) 메소드 추가: "entity/Comment#rewrite" ``` ... public class Comment extends BaseTime { ... // 해당 댓글의 내용을 새것으로 갱신! public void rewrite(String content) { this.content = content; } } ``` #### ⭐️ 확인하기 8) 기존 댓글 목록 ![클라우드스터딩-스프링부트-댓글-목록-조회](https://i.imgur.com/nglzLsW.png) 9) 수정 내용 작성 ![클라우드스터딩-스프링부트-댓글-수정-작성](https://i.imgur.com/H8ff2nJ.png) 10) 댓글 수정 확인 ![클라우드스터딩-스프링부트-댓글-수정-확인](https://i.imgur.com/pJPyjLx.png) ## 훈련하기 --- - 매 클릭 시 텍스트가 변경되는 토글(toggle) 기능을 구현하시오. "수정"을 클릭하면 "취소"로 바뀌고, 반대로 "취소"가 클릭 되면 "수정"이 되게 할 것. (구글링: "javascript button text toggle") ![클라우드스터딩-스프링부트-JS-버튼-토글](https://i.imgur.com/qtjRUcZ.png) - 댓글 생성 및 수정 시각을 나타내시오. - 최신 댓글이 제일 상단에 나오게 하시오. (구글링: "JpaRepository 정렬하기") - `comments/_list.mustache`의 코드 중, 히든 인풋의 id들은 정당한가? ## 면접 준비 --- - 웹/서비스/리파지터리 레이어란? - 컨트롤러, 서비스, 리파지터리, 모델, 뷰, DTO, Entity는 어느 레이어에 속하나? - Ajax란 무엇? 왜 사용? 직접 구글링한 정보로 답해볼 것.