먼저, 질문 상세 화면에 답변을 입력하기 위한 텍스트 창과 답변등록 버튼을 생성한다.
경로: \src\main\resources\templates\question_detail.html
<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>
<form th:action="@{|/answer/create/${question.id}|}" method="post">
<textarea name="content" id="content" rows="15"></textarea>
<input type="submit" value="답변등록">
</form>
경로: \src\main\java\com\gdsc\webboard\answer\AnswerController.java
(... 생략 ...)
@RequestMapping("/answer") //url 프리픽스를 '/answer'로 고정
@RequiredArgsConstructor
@Controller
public class AnswerController {
private final QuestionService questionService;
@PostMapping("/create/{id}") //'/answer/create/{id}'와 같은 URL 요청시 createAnswer 메서드가 호출되도록 @PostMapping으로 매핑
public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam String content) {
Question question = this.questionService.getQuestion(id);
// TODO: 답변을 저장한다.
return String.format("redirect:/question/detail/%s", id);
}
}
@RequestParam String content
: @RequestParam을 통해 textarea의 내용, 즉 content를 불러옴경로: \src\main\java\com\gdsc\webboard\answer\AnswerController.java
(... 생략 ...)
@RequiredArgsConstructor
@Service
public class AnswerService {
private final AnswerRepository answerRepository;
public void create(Question question, String content) {
Answer answer = new Answer(); //답변 객체 생성
answer.setContent(content);
answer.setCreateDate(LocalDateTime.now());
answer.setQuestion(question);
this.answerRepository.save(answer);
}
(... 생략 ...)
public class AnswerController {
private final QuestionService questionService;
private final AnswerService answerService;
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam String content) {
Question question = this.questionService.getQuestion(id);
this.answerService.create(question, content);
return String.format("redirect:/question/detail/%s", id);
}
}
경로: \src\main\resources\templates\question_detail.html
<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>
<h5 th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<div>
<ul>
<li th:each="answer : ${question.answerList}" th:text="${answer.content}"></li>
</ul>
</div>
<form th:action="@{|/answer/create/${question.id}|}" method="post">
<textarea name="content" id="content" rows="15"></textarea>
<input type="submit" value="답변등록">
</form>
#lists.size
: 타임리프가 제공하는 유틸리티, 객체의 길이를 반환th: each
: Question객체의 answerList를 반복 조회하여 각 answer의 content를전 트위터(현 엑스)에서 만든 오픈소스 웹 프레임워크로 디자이너 도움 없이 그럴듯한 UI를 만들 수 있게 도와줌
지금까지 작성한 질문 목록, 질문 상세 템플릿은 표준 HTML 구조가 아니다. 어떤 웹 브라우저를 사용하더라도 웹 페이지가 동일하게 보이고 정상적으로 작동하게 하려면 반드시 웹 표준을 지키는 HTML문서를 작성해야 한다.
먼저 표준 HTML 구조의 기본 틀이 되는 layout.html
템플릿을 다음처럼 작성하자.
경로: \src\main\resources\templates\layout.html
<!doctype html>
<html lang="ko">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
<!-- sbb CSS -->
<link rel="stylesheet" type="text/css" th:href="@{/style.css}">
<title>Hello, sbb!</title>
</head>
<body>
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
<th:block layout:fragment="content"></th:block>
<!-- 기본 템플릿 안에 삽입될 내용 End -->
</body>
</html>
layout.html
템플릿은 모든 템플릿이 상속해야 하는 템플릿으로 표준 HTML 문서의 기본 틀이 됨<th:block layout:fragment="content"></th:block>
부분이 바로 layout.html
을 상속한 템플릿에서 개별적으로 구현해야 하는 영역경로: \src\main\resources\templates\question_list.html
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container my-3">
<table class="table">
<thead class="table-dark">
<tr>
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
<tr th:each="question, loop : ${questionList}">
<td th:text="${loop.count}"></td>
<td>
<a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
</td>
<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
</tr>
</tbody>
</table>
</div>
</html>
Layout:decorate
를 통해 템플릿의 레이아웃으로 사용할 템플릿 지정#temporals.format(날짜객체, 날짜포맷)
: 날짜객체를 날짜포맷에 맞게 변환class="container my-3"
, class="table"
, class="table-dark
등은 부트스트랩 스타일시트에 정의되어 있는 클래스