# 댓글 수정하기
## 미션
---
기존 댓글 내용을

수정하시오.

## 개념
---
#### ⭐️ 진행 흐름
사용자가 댓글 수정을 클릭한다. JS는 클릭 이벤트를 감지하여, 수정 폼을 보여준다.

사용자는 댓글을 수정하고 완료 버튼을 클릭한다. 수정 데이터는 Ajax로 보내지고, 정상 작동 시 해당 페이지는 리로드(reload) 된다.

## 튜토리얼
---
#### ⭐️ 뷰 페이지
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) 기존 댓글 목록

9) 수정 내용 작성

10) 댓글 수정 확인

## 훈련하기
---
- 매 클릭 시 텍스트가 변경되는 토글(toggle) 기능을 구현하시오. "수정"을 클릭하면 "취소"로 바뀌고, 반대로 "취소"가 클릭 되면 "수정"이 되게 할 것. (구글링: "javascript button text toggle")

- 댓글 생성 및 수정 시각을 나타내시오.
- 최신 댓글이 제일 상단에 나오게 하시오. (구글링: "JpaRepository 정렬하기")
- `comments/_list.mustache`의 코드 중, 히든 인풋의 id들은 정당한가?
## 면접 준비
---
- 웹/서비스/리파지터리 레이어란?
- 컨트롤러, 서비스, 리파지터리, 모델, 뷰, DTO, Entity는 어느 레이어에 속하나?
- Ajax란 무엇? 왜 사용? 직접 구글링한 정보로 답해볼 것.