게시판 댓글을 RESTful URL로 수정
다음은 댓글 요청 URL을 RESTful URL에 부합하도록 수정한 결과다.
목록: GET /comments/23 --23은 게시글 고유번호-- 새글: POST /comments/23 수정: PUT /comments/23/38 --38은 댓글 고유번호-- 삭제: DELETE /comments/23/38
댓글을 에이잭스 프로그램으로 수정하려 한다.
BbsController와 같은 패키지에 CommentsController를 생성한다.
@RestController
@RequestMapping("comments")
public class CommentsController {
@Autowired
private BoardService boardService;
//TODO
}
@RestController는 @Controller와 @ResponseBody 어노테이션을 합쳐놓은 어노테이션이다. 클래스 선언에 @RestController 어노테이션을 쓰면, 메소드마다 @ResponseBody를 붙여 주지 않아도 된다.
@ResponseBody 어노테이션이 있는 메소드의 반환 값은 뷰를 해석하는 데 사용되는 게 아니라, 메시지 변환기를 거쳐 변환된 후 응답 바디에 바로 쓰인다.
메시지 변환기가 메소드 반환 값을 JSON 객체로 바꾸게 하려면, pom.xml에 다음 의존성을 추가한다.
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.0</version> </dependency>
목록
Comment 클래스에 editable 속성을 추가한다.
package net.java_school.board;
import java.util.Date;
public class Comment {
private int commentNo;
private int articleNo;
private String email;
private String name;
private String memo;
private Date regdate;
private boolean editable;
public int getCommentNo() {
return commentNo;
}
public void setCommentNo(int commentNo) {
this.commentNo = commentNo;
}
public int getArticleNo() {
return articleNo;
}
public void setArticleNo(int articleNo) {
this.articleNo = articleNo;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMemo() {
return memo;
}
public void setMemo(String memo) {
this.memo = memo;
}
public Date getRegdate() {
return regdate;
}
public void setRegdate(Date regdate) {
this.regdate = regdate;
}
public boolean isEditable() {
return editable;
}
public void setEditable(boolean editable) {
this.editable = editable;
}
}
다음 메소드를 댓글 컨트롤러에 추가한다.
@GetMapping("{articleNo}")
public List<Comment> getAllComments(@PathVariable(name="articleNo") Integer articleNo,
Principal principal, Authentication authentication) {
List<Comment> comments = boardService.getCommentList(articleNo);
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
boolean isAdmin = false;
for (GrantedAuthority authority : authorities) {
String role = authority.getAuthority();
if (role.equals("ROLE_ADMIN")) {
isAdmin = true;
break;
}
}
if (isAdmin) {
for (Comment comment : comments) {
comment.setEditable(true);
comment.setEmail(null);
}
} else {
String username = principal.getName();
for (Comment comment : comments) {
if (comment.getEmail().equals(username)) {
comment.setEditable(true);
}
}
for (Comment comment : comments) {
comment.setEmail(null);
}
}
return comments;
}
상세보기 페이지에서 GET /comments/23 요청을 서버로 전송하면,
[{"commentNo":100,,,,},{"commentNo":99,,,,},{"commentNo":98,,,,}]와 같은 JSON 객체가 상세보기 페이지에 전달된다.
다음 자바스크립트 함수를 상세보기 페이지에 추가한다.
function displayComments() {
var url = '/comments/' + ${articleNo};
$.getJSON(url, function (data) {
$('#all-comments').empty();
$.each(data, function (i, item) {
var creation = new Date(item.regdate);
var comments = '<div class="comments">'
+ '<span class="writer">' + item.name + '</span>'
+ '<span class="date">' + creation.toLocaleString() + '</span>';
if (item.editable === true) {
comments = comments
+ '<span class="modify-del">'
+ '<a href="#" class="comment-modify-link">'
+ '수정' + '</a> | '
+ '<a href="#" class="comment-delete-link" title="' + item.commentNo + '">'
+ '삭제' + '</a>'
+ '</span>';
}
comments = comments
+ '<div class="comment-memo">' + item.memo + '</div>'
+ '<form class="comment-form" action="/comments/' + ${articleNo } + '/'
+ item.commentNo + '" method="put" style="display: none;">'
+ '<div style="text-align: right;">'
+ '<a href="#" class="comment-modify-submit-link">'
+ '전송'
+ '</a> | <a href="#" class="comment-modify-cancel-link">'
+ '취소' + '</a>'
+ '</div>'
+ '<div>'
+ '<textarea class="comment-textarea" name="memo" rows="7" cols="50">'
+ item.memo + '</textarea>'
+ '</div>'
+ '</form>'
+ '</div>';
$('#all-comments').append(comments);
console.log(item);
});
});
}
페이지가 처음 로드될 때
새 댓글을 작성했을 때
댓글이 수정될 때
댓글이 삭제될 때
displayComments() 함수가 실행돼야 한다.
댓글 관련 HTML 코드
<sf:form id="addCommentForm" action="/comments/${articleNo }" method="post" style="margin: 10px 0;">
<div id="addComment">
<textarea id="addComment-ta" name="memo" rows="7" cols="50"></textarea>
</div>
<div style="text-align: right;">
<input type="submit" value="댓글쓰기" />
</div>
</sf:form>
<!-- comments begin -->
<div id="all-comments">
</div>
<!-- comments end -->
<sf:form .. 처럼 쓰려면, 상세보기 페이지에 다음 선언이 필요하다.
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
div id="all-comments" 콘텐츠에 댓글 목록이 갱신된다.
아래를 참고해 페이지가 로드될 때와 댓글이 생성됐을 때 댓글 목록이 갱신되게 한다.
$(document).ready(function () {
displayComments();
//..omit..
$("#addCommentForm").submit(function (event) {
event.preventDefault();
var $form = $(this);
var memo = $('#addComment-ta').val();
memo = $.trim(memo);
if (memo.length === 0) {
$('#addComment-ta').val('');
return false;
}
var dataToBeSent = $form.serialize();
var url = $form.attr("action");
var posting = $.post(url, dataToBeSent);
posting.done(function () {
displayComments();
$('#addComment-ta').val('');
});
});
});
displayComments() 함수가 실행될 때마다, div id="all-comments" 콘텐츠에 동적으로 댓글 HTML 코드가 생성된다. 동적으로 생성되는 댓글 HTML 코드에 수정과 삭제 링크가 있다면 DOM 프로그래밍으로 각각의 핸들러를 등록한다. --페이지가 로드된 후 동적으로 생성되는 HTML 코드에 DOM 프로그래밍은 $(document).on() 함수에서 해야 한다--
$(document).on('click', '#all-comments', function (e) {
if ($(e.target).is('.comment-modify-link')) {
e.preventDefault();
var $form = $(e.target).parent().parent().find('.comment-form');
var $div = $(e.target).parent().parent().find('.comment-memo');
if ($form.is(':hidden') === true) {
$form.show();
$div.hide();
} else {
$form.hide();
$div.show();
}
} else if ($(e.target).is('.comment-modify-cancel-link')) {
e.preventDefault();
var $form = $(e.target).parent().parent().parent().find('.comment-form');
var $div = $(e.target).parent().parent().parent().find('.comment-memo');
if ($form.is(':hidden') === true) {
$form.show();
$div.hide();
} else {
$form.hide();
$div.show();
}
} else if ($(e.target).is('.comment-modify-submit-link')) {
e.preventDefault();
var $form = $(e.target).parent().parent().parent().find('.comment-form');
var $textarea = $(e.target).parent().parent().find('.comment-textarea');
var memo = $textarea.val();
$('#modifyCommentForm input[name*=memo]').val(memo);
var dataToBeSent = $('#modifyCommentForm').serialize();
var url = $form.attr("action");
$.ajax({
url: url,
type: 'PUT',
data: dataToBeSent,
success: function () {
displayComments();
},
error: function () {
alert('error!');
}
});
} else if ($(e.target).is('.comment-delete-link')) {
e.preventDefault();
var chk = confirm('정말로 삭제하겠습니까?');
if (chk === false) {
return;
}
var $commentNo = $(e.target).attr('title');
var url = $('#deleteCommentForm').attr('action');
url += $commentNo;
var dataToBeSent = $('#deleteCommentForm').serialize();
$.ajax({
url: url,
type: 'POST',
data: dataToBeSent,
success: function () {
displayComments();
},
error:function(request,status,error){
console.log("code:"
+ request.status
+ "\n" + "message:"
+ request.responseText
+ "\n" + "error:"
+ error);
}
});
}
});
댓글을 댓글을 삭제할 때 DELETE 요청이 아니라 모두 POST 요청인 것에 주목하라. --이유는 뒤에 다룬다-- 여기까지 작성하면 상세보기 페이지에서 댓글을 볼 수 있다.
새글
다음 새 댓글 쓰기 메소드를 댓글 컨트롤러에 추가한다.
@PostMapping("{articleNo}")
public void addComment(@PathVariable(name="articleNo") Integer articleNo,
@RequestParam(name="memo") String memo, Principal principal) {
Comment comment = new Comment();
comment.setMemo(memo);
comment.setArticleNo(articleNo);
comment.setEmail(principal.getName());
boardService.addComment(comment);
}
이젠 상세보기 페이지에서 댓글을 추가할 수 있다.
수정
다음 수정 메소드를 댓글 컨트롤러에 추가한다.
@PutMapping("{articleNo}/{commentNo}")
public void updateComment(@PathVariable(name="articleNo") Integer articleNo,
@PathVariable(name="commentNo") Integer commentNo,
@RequestParam(name="memo") String memo, Principal principal) {
Comment comment = boardService.getComment(commentNo);
comment.setMemo(memo);
boardService.modifyComment(comment);
}
이젠 댓글을 수정할 수 있다.
삭제
다음 삭제 메소드를 댓글 컨트롤러에 추가한다.
@DeleteMapping("{articleNo}/{commentNo}")
public void deleteComment(@PathVariable(name="articleNo") Integer articleNo, @PathVariable(name="commentNo") Integer commentNo) {
Comment comment = boardService.getComment(commentNo);
boardService.removeComment(comment);
}
스프링 MVC와 스프링 시큐리티를 사용하는 환경에서 DELETE 요청은 Request method 'DELETE' not supported 에러를 발생시킨다. 스프링 MVC만 사용하는 환경에선 DELETE 요청이 정상 동작한다.
스프링은 POST 요청을 PUT이나 DELETE 요청으로 매핑할 수 있다. 사실 이 기능은 브라우저가 PUT이나 DELETE 요청을 지원하지 않을 때 사용한다. 댓글 삭제를 위해 이 기능을 사용해 보자. --이때 댓글 수정 역시 수정해야 한다--
web.xml에 다음 필터를 추가한다.
<filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
추가한 필터는 _method 파라미터 값을 판단하여, 요청을 핸들러에 전달한다.
상세보기 페이지에서 댓글 수정과 삭제 폼을 다음과 같이 수정한다.
<sf:form id="modifyCommentForm" method="put"> <input type="hidden" name="memo" /> </sf:form>
<sf:form id="deleteCommentForm" action="/comments/${articleNo }/" method="delete">
</sf:form>
댓글 수정과 관련된 ajax 전송 코드에서 type을 PUT 에서 POST로 수정한다.
} else if ($(e.target).is('.comment-modify-submit-link')) {
e.preventDefault();
var $form = $(e.target).parent().parent().parent().find('.comment-form');
var $textarea = $(e.target).parent().parent().find('.comment-textarea');
var memo = $textarea.val();
$('#modifyCommentForm input[name*=memo]').val(memo);
var dataToBeSent = $('#modifyCommentForm').serialize();
var url = $form.attr("action");
$.ajax({
url: url,
type: 'POST',
data: dataToBeSent,
success: function () {
displayComments();
},
error: function () {
alert('error!');
}
});
} else if ($(e.target).is('.comment-delete-link')) {
