학습 목표
-JSON API 및 AJAX를 활용해 답변 추가/삭제 구현
학습 목차
5-1. AJAX를 활용해 답변 추가 기능 구현
5-2. AJAX를 활용해 답변 추가 기능 구현2
5-3. AJAX를 활용해 답변 삭제 기능 구현
5-4. 질문 목록에 답변 수 보여주기 기능 추가
5-4. 중복 제거 및 리팩토링
5-5. JSON API 추가 및 리팩토링
footer.html에 사용하려는 js 파일들이 나와있습니다.
그 중 작업하려는 파일은 scripts.js 입니다.
JQuery를 이용해서 답변하기를 클릭했을 때 서버로 바로 넘어가지 않도록 구현합니다.
<!--show.html-->
<form class="answer-write" method="post" action="/questions/{{id}}/answers"> <!--1.-->
<div class="form-group" style="padding:14px;">
<textarea class="form-control" placeholder="Update your status" name="contents"></textarea>
</div>
<input type="submit" class="btn btn-success pull-right" value="답변하기"/>
<div class="clearfix" />
</form>
1. class 이름을 "answer-write"으로 변경합니다.
<!--show.html-->
<form class="answer-write" method="post" action="/api/questions/{{id}}/answers"> <!--1.-->
<div class="form-group" style="padding:14px;">
<textarea class="form-control" placeholder="Update your status" name="contents"></textarea>
</div>
<input type="submit" class="btn btn-success pull-right" value="답변하기"/>
<div class="clearfix" />
</form>
1. action의 주소 앞에 "/api"를 추가하여 응답을 JSON이나 데이터만 준다는 것을 나타내줍니다.
//scripts.js
$(".answer-writer input[type=submit]").click(addAnswer); //1.
function addAnswer(e) {
e.preventDefault(); //2.
console.log("click me"); //3.
var queryString=$(".answer-writer").serialize(); //4.
console.log("query : "+queryString);
var url=$(".answer-write").attr("action"); //5.
console.log("url : "+url);
$.ajax({ //6.
type : 'post',
url : url,
data : queryString,
dataType : 'json',
error : onError,
success : onSuccess});
}
function onError() {
}
function onSuccess() {
}
1. html의 ".answer-wirter input[type=submit]" 태그에 click 이라는 이벤트가 발생하면 addAnswer라는 function을 호출합니다.
2. 서버로 답변이 넘어가지 않도록 막아줍니다.
3. 개발자 도구 콘솔 창에 해당 문구가 출력됩니다.
4. 값을 잘 전달하는지 확인하기 위해 작성해줍니다.
5. action이라는 값의 url을 가져옵니다.
6. 해당 url로 데이터를 보내면 데이터베이스에 답변 데이터가 저장됩니다.
그 사이에 에러가 발생하면 onError를 호출합니다.
정상적으로 답변이 저장되어 성공하면 onSuccess를 호출합니다.
//ApiAnswerController.java
@RestController //5.
@RequestMapping("/api/questions/{questionId}/answers") //1.
public class AnswerController {
@Autowired
private QuestionRepository questionRepository;
@Autowired
private AnswerRepository answerRepository;
@PostMapping("")
public Answer create(@PathVariable Long questionId, String contents, HttpSession session) { //2.
if(!HttpSessionUtils.isLoginUser(session)) { //3.
return null;
}
User loginUser=HttpSessionUtils.getUserFromSession(session);
Question question=questionRepository.findById(id).get();
Answer answer=new Answer(loginUser, question, contents);
return answerRepository.save(answer); //4.
}
}
AnswerController의 이름을 ApiAnswerController로 바꿔줍니다.
1. RequestMapping의 url 앞에 "/api"를 추가합니다.
2. 응답할 데이터는 Answer 데이터베이스에 저장되어 있는 답변 정보를 클라이언트로 받고 싶은 것이므로, 데이터베이스에 저장되기 전에는 데이터베이스의 아이디가 null인 상태입니다. 그 부분을 받아오기 위해 create 메소드의 타입을 String이 아닌 Answer로 바꿔줍니다.
3. 현재 사용자 정보가 null일 때는 null을 반환합니다.
4. Answer로 전달한 메소드의 인자는 똑같이 Answer입니다. 따라서 리턴 값도 데이터베이스에 저장된 Answer가 되어야 합니다.
5. Spring MVC가 컨트롤러에 해당하는 메소드를 바로 알아서 JSON으로 변환해주지 않습니다.
따라서 @RestController로 어노테이션을 바꿔줘야 합니다.
@RestController: JSON으로 변환하도록 인식시켜주는 컨트롤러입니다.
//scripts.js
$(".answer-writer input[type=submit]").click(addAnswer);
function addAnswer(e) {
e.preventDefault();
console.log("click me");
var queryString=$(".answer-writer").serialize();
console.log("query : "+queryString);
var url=$(".answer-write").attr("action");
console.log("url : "+url);
$.ajax({
type : 'post',
url : url,
data : queryString,
dataType : 'json',
error : onError,
success : onSuccess});
}
function onError() {
}
function onSuccess(data, status) { //1.
console.log(data);
}
1. 성공적으로 완료될 때 받아오는 정보는 answer 데이터(data)와 status 입니다.
//ApiUserController.java
@RestController //1.
@RequestMapping("/api/users")
public class ApiUserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/{id}") //2.
public User show(@PathVariable Long id) {
return userRepository.findById(id).get();
}
}
사용자의 정보를 조회하는 클래스입니다.
1. JSON과 같은 api를 개발하기 위해 @RestController를 추가합니다.
2. id값을 통해 해당하는 사용자 정보를 조회할 수 있도록 하는 메소드를 만듭니다.
//User.java
@Entity
public class User {
@Id
@GeneratedValue
@JsonProperty //1.
private Long id;
@Column(nullable=false, length=20)
@JsonProperty //1.
private String userId;
@JsonIgnore //2.
private String password;
@JsonProperty //1.
private String name;
@JsonProperty //1.
private String email;
public boolean matchId(Long newId) {
if(newId==null) {
return false;
}
return newId.equals(id);
}
public void setUserId(String userId) {
this.userId=userId;
}
public String getUserId() {
return userId;
}
public boolean matchPassword(String newPassword) {
if(newPassword==null) {
return false;
}
return newPassword.equals(password);
}
public void setPassword(String password) {
this.password=password;
}
public void setName(String name) {
this.name=name;
}
public void setEmail(String email) {
this.email=email;
}
public void update(User newUser) {
this.password=newUser.password;
this.name=newUser.name;
this.email=newUser.email;
}
@Override
public int hashCode() {
final int prime=31;
int result=1;
result=prime * result + ((id==null) ? 0: id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(obj==null)
return false;
if(getClass() != obj.getClass())
return false;
Question other=(Question) obj;
if(id==null) {
if(other.id!=null)
return false;
} else if(!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return "User [userId=" + userId + ", password=" + password + ", name=" + name, "email=" + email +"]";
}
}
1. @JsonProperty: JSON으로 변환하겠다는 의미
2. @JsonIgnore: JSON을 사용하지 않겠다고 명시적으로 표시해줌(없어도 된다)
//Answer.java
@Entity
public class Answer {
@Id
@GeneratedValue
@JsonProperty //1.
private Long id;
@ManyToOne
@JoinColumn(foreignKey=@ForeignKey(name="fk_answer_writer"))
@JsonProperty //1.
private User writer;
@ManyToOne
@JoinColumn(foreignKey=@ForeignKey(name="fk_answer_to_question"))
@JsonProperty //1.
private Question question;
@Lob
@JsonProperty //1.
private String contents;
private LocalDateTime createDate;
public Answer() {
}
public Answer(User writer, String contents) {
this.writer=writer;
this.contents=contents;
}
@Override
public int hashCode() {
final int prime=31;
int result=1;
result=prime * result + ((id==null) ? 0: id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(obj==null)
return false;
if(getClass() != obj.getClass())
return false;
Question other=(Question) obj;
if(id==null) {
if(other.id!=null)
return false;
} else if(!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return "Answer [id="+id+", writer="+writer+", contents="+contents+", createDate="+createDate+"]";
}
}
//Question.java
@Entity
public class Question {
@Id
@GeneratedValue
@JsonProperty //1.
private Long id;
@ManyToOne
@JoinColumn(foreignKey=@ForeignKey(name="fk_question_writer"))
@JsonProperty //1.
private User writer;
@JsonProperty //1.
private String title;
@JsonProperty //1.
@Lob
private String contents;
private LocalDateTime createDate;
@OneToMany(mappedBy="question")
@OrderBy("id ASC")
private List<Answer> answers;
public Question() {}
public Question(String writer, String title, String contents) {
super();
this.writer=writer;
this.title=title;
this.contents=contents;
this.createDate=LocalDateTime.now();
}
public String getFormattedCreateDate() {
if(createDate==null) {
return "";
}
return createDate.format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss"));
}
public void update(String title, String contents) {
this.title=title;
this.contents=contents;
}
public boolean isSameWriter(User loginUser) {
return this.writer.equals(loginUser);
}
@Override
public int hashCode() {
final int prime=31;
int result=1;
result=prime * result + ((id==null) ? 0: id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(obj==null)
return false;
if(getClass() != obj.getClass())
return false;
Question other=(Question) obj;
if(id==null) {
if(other.id!=null)
return false;
} else if(!id.equals(other.id))
return false;
return true;
}
1. @JsonProperty: JSON으로 변환하겠다는 의미
<!--show.html-->
<script type="text/template" id="answerTemplate">
<article class="article">
<div class="article-header">
<div class="article-header-thumb">
<img src="https://graph.facebook.com/v2.3/1324855987/picture" class="article-author-thumb" alt="">
</div>
<div class="article-header-text">
<a href="#" class="article-author-name">{0}</a>
<div class="article-header-time">{1}</div>
</div>
</div>
<div class="article-doc comment-doc">
{2}
</div>
<div class="article-util">
<ul class="article-util-list">
<li>
<a class="link-modify-article" href="/api/qna/updateAnswer/{3}">수정</a>
</li>
<li>
<a class="link-delete-article" href="/api/questions/{3}/answers/{4}">삭제</a>
</li>
</ul>
</div>
</article>
</script>
show.html파일의 body 태그 끝에 해당 script를 복사해서 넣어줍니다.
//scripts.js
$(".answer-writer input[type=submit]").click(addAnswer);
function addAnswer(e) {
e.preventDefault();
console.log("click me");
var queryString=$(".answer-writer").serialize();
console.log("query : "+queryString);
var url=$(".answer-write").attr("action");
console.log("url : "+url);
$.ajax({
type : 'post',
url : url,
data : queryString,
dataType : 'json',
error : onError,
success : onSuccess});
}
function onError() {
}
function onSuccess(data, status) {
console.log(data);
var answerTemplate=$("#answerTemplate").html(); //2.
var template=answerTemplate.format(data.writer.userId, data.formattedCreateDate, data.contents, data.id, data.id); //3.
$(".qna-comment-slipp-articles").prepend(template); //4.
$("textaria[name=contents]").val(""); //5.
}
String.prototype.format=function() { //1.
var args=arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
? args[number]
:match
;
});
}
1. 템플릿 엔진 역할을 합니다.
단순히 복사해서 붙여넣어 줍니다.
2. html 파일에서 id가 answerTemplate인 부분을 통째로 읽어옵니다.
3. html 파일에서 id가 answerTemplate인 부분의 값을 전달하는 코드입니다.
format은 아래 코드의 내용을 말합니다.
첫번째 인자({0}): 작성자의 아이디
두번째 인자({1}): 글이 작성된 시간
세번째 인자({2}): 글의 내용
네번째 인자({3}): 글의 아이디
다섯번째 인자({4}): 글의 아이디
4. template을 html의 ".qna-comment-slipp-articles" 앞부분에 추가해줍니다.
5. 답변이 달리고 답변을 입력하는 칸은 초기화시켜줍니다.
<!--show.html-->
<script type="text/template" id="answerTemplate">
<article class="article">
<div class="article-header">
<div class="article-header-thumb">
<img src="https://graph.facebook.com/v2.3/1324855987/picture" class="article-author-thumb" alt="">
</div>
<div class="article-header-text">
<a href="#" class="article-author-name">{0}</a>
<div class="article-header-time">{1}</div>
</div>
</div>
<div class="article-doc comment-doc">
{2}
</div>
<div class="article-util">
<ul class="article-util-list">
<li>
<a class="link-modify-article" href="/api/qna/updateAnswer/{3}">수정</a>
</li>
<li>
<a class="link-delete-article" href="/api/questions/{3}/answers/{4}">삭제</a> <!--1.-->
</li>
</ul>
</div>
</article>
</script>
1. 삭제 부분 클래스 이름을 "link-delete-article"로 변경합니다.
{3}: questions의 아이디
{4}: answers의 아이디
//scripts.js
$(".answer-writer input[type=submit]").click(addAnswer);
function addAnswer(e) {
e.preventDefault();
console.log("click me");
var queryString=$(".answer-writer").serialize();
console.log("query : "+queryString);
var url=$(".answer-write").attr("action");
console.log("url : "+url);
$.ajax({
type : 'post',
url : url,
data : queryString,
dataType : 'json',
error : onError,
success : onSuccess});
}
function onError() {
}
function onSuccess(data, status) {
console.log(data);
var answerTemplate=$("#answerTemplate").html();
var template=answerTemplate.format(data.writer.userId, data.formattedCreateDate, data.contents, data.question.id, data.id); //1.
$(".qna-comment-slipp-articles").prepend(template);
$("textaria[name=contents]").val("");
}
$("a.link-delete-article").click(deleteAnswer); //2.
function deleteAnswer(e) { //2.
e.preventDefualt(); //3.
var url=$(this).attr("href");
console.log("url : "+url);
$.ajax({
type : 'delete',
url : url,
dataType : 'json',
error : function(xhr, status) {
console.log("error");
}
success : function(data, status) {
console.log("success");
}
});
}
String.prototype.format=function() {
var args=arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
? args[number]
:match
;
});
}
'Spring > 게시판 만들기 프로젝트' 카테고리의 다른 글
[Spring Boot] #4_3 게시판 만들기 (0) | 2020.12.23 |
---|---|
[Spring Boot] #4_2 게시판 만들기 (0) | 2020.12.22 |
[Spring Boot] #4_1 게시판 만들기 (0) | 2020.12.22 |
[Spring Boot] #3_2 게시판 만들기 (0) | 2020.12.21 |
[Spring Boot] #3_1 게시판 만들기 (0) | 2020.12.21 |