이전 포스팅에서 이어집니다!
🍀 블로그 화면 구성하기 (1)
// 삭제 기능
const deleteButton = document.getElementById('delete-btn');
if(deleteButton){
deleteButton.addEventListener('click', e => {
let id = document.getElementById('article-id').value;
fetch(`/api/articles/${id}`, {
method: 'DELETE'
})
.then(() => {
alert('삭제가 완료되었습니다.');
location.replace('/articles');
});
});
}
(생략)
<article>
<input type="hidden" id="article-id" th:value="${article.id}">
(생략)
<!-- [삭제] 버튼에 id 추가 -->
<button type="button" class="btn btn-secondary btn-sm" id="delete-btn">삭제</button>
</article>
</div>
</div>
</div>
<script src="/js/article.js"></script>
</body>
</html>
삭제 버튼을 클릭했을 때 기능이 제대로 작동하는지 테스트해 본다! (과정 생략)
삭제하고 목록으로 돌아왔을 때 방금 삭제한 글이 목록에서 사라져야 성공이다.
(생략)
@GetMapping("/new-article")
// id 키를 가진 쿼리 파라미터의 값을 id 변수에 매핑 (id는 없을 수도 있음)
public String newArticle(@RequestParam(required = false) Long id, Model model){
if(id == null){ // id가 없으면 생성
model.addAttribute("article", new ArticleViewResponse());
} else{ // id가 없으면 수정
Article article = blogService.findById(id);
model.addAttribute("article", new ArticleViewResponse(article));
}
return "newArticle";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>블로그 글</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
</head>
<body>
<div class="p-5 mb-5 text-center</> bg-light">
<h1 class="mb-3">My Blog</h1>
<h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>
<div class="container mt-5">
<div class="row">
<div class="col-lg-8">
<article>
<!-- 아이디 정보 저장 -->
<input type="hidden" id="article-id" th:value="${article.id}">
<header class="mb-4">
<input type="text" class="form-control" placeholder="제목" id="title" th:value="${article.title}">
</header>
<section class="mb-5">
<textarea class="form-control h-25" rows="10" placeholder="내용" id="content" th:text="${article.content}"></textarea>
</section>
<!-- id가 있을 때는 [수정] 버튼을, 없을 때는 [등록] 버튼이 보이게 함 -->
<button th:if="${article.id} != null" type="button" id="modify-btn" class="btn btn-primary btn-sm">수정</button>
<button th:if="${article.id} == null" type="button" id="create-btn" class="btn btn-primary btn-sm">등록</button>
</article>
</div>
</div>
</div>
<script src="/js/article.js"></script>
</body>
</html>
(생략)
// 수정 기능
// 1. id가 modify-btn인 엘리먼트 조회
const modifyButton = document.getElementById('modify-btn');
if(modifyButton){
// 클릭 이벤트가 감지되면 수정 API 요청
modifyButton.addEventListener('click', e=>{
let params = new URLSearchParams(location.search);
let id = params.get('id');
fetch(`/api/articles/${id}`, {
method: 'PUT',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: document.getElementById('title').value,
content: document.getElementById('content').value
})
})
.then(()=>{
alert('수정이 완료되었습니다.');
location.replace(`/articles/${id}`);
})
})
}
(생략)
<button type="button" class="btn btn-primary btn-sm" id="modify-btn" th:onclick="|location.href='@{/new-article?id={articleId}(articleId=${article.id})}'|">수정</button>
(생략)
테스트 완료!
(생략)
// 생성 기능
const createButton = document.getElementById('create-btn');
if(createButton){
createButton.addEventListener('click', e=>{
fetch("/api/articles", {
method: "POST",
headers: {
"Content-Type" : "application/json"
},
body: JSON.stringify({
title: document.getElementById("title").value,
content: document.getElementById("content").value
}),
})
.then(()=>{
alert("등록이 완료되었습니다.");
location.replace("/articles");
})
})
}
(생략)
<button type="button" id="create-btn" th:onclick="|location.href='@{/new-article}'|" class="btn btn-secondary btn-sm mb-3">글 등록</button>
(생략)
실행 테스트까지 완료한다!
이렇게 타임리프를 사용해 CRUD 뷰와 기능을 구현해 보았다. 전반적인 구조나 순서는 똑같으나 뷰 구현 시 th:
를 붙이는 것이 아직은 낯설지만.. 익숙해질 때까지 계속 연습해 보자.
다음 포스팅에서는 스프링 시큐리티로 로그인/로그아웃, 회원 가입을 구현해 볼 것이다. 😀