(18) 기능구현 - 앵커

앵커 구현하기

답글을 작성하거나 수정한 후에 항상 페이지 상단으로 스크롤이 이동된다.

때문에 작성한 답변을 확인하려면 다시 스크롤을 내려서 확인해야 하기 때문에 매우 번거롭다.

이 문제는 답변을 추천한 경우에도 동일하게 발생한다.

HTML의 앵커 태그를 사용하여 이 문제를 해결해보자.


시작하기 전에

개요 : 질문과 답변을 할 수 있는 게시판 서비스를 스프링부트를 통해 만들어 본다.

학습사이트 : https://wikidocs.net/book/7601

예제 코드 : https://github.com/pahkey/sbb


답변 앵커


답변 앵커 추가


  • question_detail.html 수정
<!-- 경로 : sbb/src/main/resources/templates/question_detail.html -->

<html layout:decorate="~{layout}">

	(... 생략 ...)
        
    <!-- 답변 반복 시작 -->
    <div class="card my-3" th:each="answer : ${question.answerList}">
    	<a th:id="|answer_${answer.id}|"></a> <!-- 앵커태그 -->
        <div class="card-body">
            
		(... 생략 ...)

</html>

답변 반복 부분 상단에 앵커 태그를 추가


답변 redirect


  • AnswerService.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerService.java

(... 생략 ...)

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;
    }

    (... 생략 ...)

}

컨트롤러에서 답변이 등록된 위치로 이동시켜주기 위해 답변 객체를 void에서 변경


  • AnswerController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java
package com.mysite.sbb.answer;

(... 생략 ...)

@RequestMapping("/answer") // URL 프리픽스
@RequiredArgsConstructor
@Controller
public class AnswerController {

    // 변수 지정
    private final QuestionService questionService;
    private final AnswerService answerService;
    private final UserService userService;

    // 답변 (POST) 처리
    @PreAuthorize("isAuthenticated()")
    @PostMapping("/create/{id}") // post요청만 받아들일 경우에 사용하는 에너테이션. (value=) 생략가능
    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());
    }
    
    (... 생략 ...)
    
    // 답변 수정 (POST) 처리
    @PreAuthorize("isAuthenticated()")
    @PostMapping("/modify/{id}")
    public String answerModify(
    		@Valid AnswerForm answerForm,
    		BindingResult bindingResult,
            @PathVariable("id") Integer id,
            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());
    }
    
    (... 생략 ...)
    
    // 추천 URL 매핑 (GET)
    @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());
        
        if(answer.getVoter().contains(siteUser) == true) {
        	this.answerService.votedel(answer, siteUser);
        }
        else {
        	this.answerService.vote(answer, siteUser);
        }
        
        // 답변 추천 후 앵커로 돌아감
        return String.format("redirect:/question/detail/%s#answer_%s", 
        		answer.getQuestion().getId(), 
        		answer.getId());
    }

}

생성, 수정, 추천 후 각 앵커로 return해준다.


SBB테스트

1

주소창에 앵커가 표시되고, 자동으로 스크롤되어 이동된다.

자동 스크롤이다보니 Ajax를 통해 구현하는게 더 깔끔할거 같다.






Leave a comment