- HTML 구조에 맞게 수정하면 템플릿 파일들의 body 엘리먼트 바깥 부분(head 엘리먼트 등)은 모두 같은 내용으로 중복된다.
layout.html
<!doctype html>
<html lang="ko" xmlns:layout="">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
<link rel="stylesheet" type="text/css" th:href="@{/style.css}">
<title>Hello, sbb!</title>
</head>
<body>
<th:block layout:fragment="content"></th:block>
</body>
</html>
- layout.html템플릿은 모든 템플릿이 상속해야 하는 템플릿으로 html문서의 기본 틀이된다.
- <th:block layout:fragment="content"></th:block>영역에 해당되는 부분만 작성해도 표준 HTML문서가 된다.
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}은 layout.html파일을 의미
- 부모 템플릿의 위 부분을 자식 템플릿의 내용으로 바꾸기 위해
<div layout:fragment="content" class="container my-3">
(... 생략 ...)
</div>
- 위와 같이 바꾸면 부모 템플릿의 th:block엘리먼트의 내용이 자식 템플릿의 div엘리먼트의 내용으로 교체
질문 등록
<a th:href="@{/question/create}" class="btn-primary">질문 등록하기</a>
- question_list에 위 코드를 추가
- a태그지만 부트스트랩 btn사용
@GetMapping("/crate")
public String questionCreate(){
return "question_form";
}
- questioncontroller에 create메핑 question_form템플릿에 렌더링
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form th:action="@{/question/create}" method="post">
<div class="mb-3">
<label for="subject" class="form-label">제목</label>
<input type="text" name="subject" class="form-control">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea name="content" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="저장하기" class="btn btn-primary my-2">
</form>
</div>
</html>
Post전송 할 수 있도록 수정
@PostMapping("/create")
public String questionCreate(@RequestParam String subject, @RequestParam String content) {
// TODO 질문을 저장한다.
return "redirect:/question/list"; // 질문 저장후 질문목록으로 이동
}
서비스
public Question getQuestion(Integer id){
Optional<Question> question = this.questionRepository.findById(id);
if(question.isPresent()){
return question.get();
}
else{
throw new DataNotFoundException("question not fund");
}
- 컨트롤러에서 이 서비스를 사용할 수 있도록 수정
- 등록시 비어 있어도 등록이 가능한데 이걸 체크하는 방법 구현
Spring Boot Vaildation
- 화면에서 전달받은 입력값을 검증하려면 이 라이브러리가 필요
폼 클래스
- 화면에서 전달되는 입력 값을 검증 위해서 필요
@Getter
@Setter
public class QuestionForm {
@NotEmpty(message="제목은 필수항목입니다.")
@Size(max=200)
private String subject;
@NotEmpty(message="내용은 필수항목입니다.")
private String content;
}
- @NotEmpty는 빈 문자열 안된다
- @Size는 바이트 크기 제한
컨트롤러
@PostMapping("/question/create")
public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "question_form";
}
this.questionService.create(questionForm.getSubject(), questionForm.getContent());
return "redirect:/question/list";
}
- queestionCreate 매개변수를 subject, content대신 QuestionForm객체로 변경
- QuestionForm의 subject, content속성이 자동으로 바인딩
- Vaild는 QuestionForm의 @NotEmpty와 @Size설정 검증
- BindingResult는 @Vaild인해 검증이 수행된 결과
- BindingResult는 @Vaild바로 뒤에 위치
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form th:action="@{/question/create}" th:object="${questionForm}" method="post">
<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
<div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
</div>
<div class="mb-3">
<label for="subject" class="form-label">제목</label>
<input type="text" name="subject" class="form-control">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea name="content" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="저장하기" class="btn btn-primary my-2">
</form>
</div>
</html>
- #fields.allErrors() 실패한 오류 메시지 구할 수 있다
- alert alert-danger 부트스트랩 클래스로 오류 붉은색
- th:object 사용하여 폼의 속성들이 QuestionForm의 속성들로 구성된다는 점을 타임리프 엔진에 알림
@GetMapping("/create")
public String questionCreate(QuestionForm questionForm) {
return "question_form";
- get메핑에서 매개변수로 QuestionForm 객체 추가 필요
오류 발생이 입력 내용 유지
<input type="text" name="subject" class="form-control">
->
<input type="text" th:field="*{subject}" class="form-control">