이제 SBB가 점점 완성 되어가고 있다. 이번에는 더 많은 기능을 추가하기 전에 발견된 몇 가지 문제점을 해결하려고 한다.
발견된 문제점은 답글을 작성하거나 수정한 후에 항상 페이지 상단으로 스크롤이 이동되기 때문에 본인이 작성한 답변을 확인하려면 다시 스크롤을 내려서 확인해야 한다는 점이다. 이 문제는 답변을 추천한 경우에도 동일하게 발생한다.
Ajax와 같은 비동기 통신 기술을 사용하여 이 문제를 해결할 수도 있지만 여기서는 보다 쉬운 방법으로 이 문제를 해결해 보자.
HTML에는 URL 호출시 원하는 위치로 이동시켜 주는 앵커(anchor) 태그가 있다. 예를 들어 HTML 중간에 라는 앵커 태그가 있다면 이 HTML을 호출하는 URL 뒤에 #test 라고 붙여주면 해당 페이지가 호출되면서 해당 앵커로 스크롤이 이동된다.
먼저 답변 작성, 수정시에 이동해야할 앵커 태그를 질문 상세 템플릿에 추가하자.

답변이 반복되어 표시되는 th:each 바로 다음에 와 같이 앵커 태그를 추가했다. 앵커 태그의 id 속성은 유일한 값이어야 하므로 answer_{{ answer.id }}와 같이 답변 id를 사용했다.
이제 답변을 등록하거나 수정할 때 위에서 지정한 앵커 태그로 이동하도록 코드를 수정하자. 다음은 답변 등록 또는 답변 수정을 한 뒤 사용했던 기존 코드의 일부이다.
return String.format("redirect:/question/detail/%s#answer_%s",
answer.getQuestion().getId(), answer.getId());
리다이렉트되는 질문 상세 URL에 #answer_2와 같은 앵커를 추가한 것이다. 이러한 방법으로 답변 작성, 수정, 추천의 리다이렉트 부분을 변경하자.
컨트롤러에서 답변이 등록된 위치로 이동하기 위해서는 답변 객체가 반드시 필요하다. 따라서 다음과 같이 AnswerService를 먼저 수정해야 한다.
(... 생략 ...)
public class AnswerService {
(... 생략 ...)
public Answer create(Question question, String content, SiteUser author) {
Answer answer = new Answer();
answer.setContent(content);
answer.setCreateDate(LocalDateTime.now());
answer.setQuestion(question);
answer.setAuthor(author);
this.answerRepository.save(answer);
return answer;
}
(... 생략 ...)
}
그리고 컨트롤러의 리다이렉트 코드들을 다음과 같이 수정하자.
(... 생략 ...)
public class AnswerController {
(... 생략 ...)
@PreAuthorize("isAuthenticated()")
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
Question question = this.questionService.getQuestion(id);
SiteUser siteUser = this.userService.getUser(principal.getName());
if (bindingResult.hasErrors()) {
model.addAttribute("question", question);
return "question_detail";
}
Answer answer = this.answerService.create(question,
answerForm.getContent(), siteUser);
return String.format("redirect:/question/detail/%s#answer_%s",
answer.getQuestion().getId(), answer.getId());
}
(... 생략 ...)
@PreAuthorize("isAuthenticated()")
@PostMapping("/modify/{id}")
public String answerModify(@Valid AnswerForm answerForm, @PathVariable("id") Integer id,
BindingResult bindingResult, Principal principal) {
if (bindingResult.hasErrors()) {
return "answer_form";
}
Answer answer = this.answerService.getAnswer(id);
if (!answer.getAuthor().getUsername().equals(principal.getName())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
this.answerService.modify(answer, answerForm.getContent());
return String.format("redirect:/question/detail/%s#answer_%s",
answer.getQuestion().getId(), answer.getId());
}
(... 생략 ...)
@PreAuthorize("isAuthenticated()")
@GetMapping("/vote/{id}")
public String answerVote(Principal principal, @PathVariable("id") Integer id) {
Answer answer = this.answerService.getAnswer(id);
SiteUser siteUser = this.userService.getUser(principal.getName());
this.answerService.vote(answer, siteUser);
return String.format("redirect:/question/detail/%s#answer_%s",
answer.getQuestion().getId(), answer.getId());
}
}