충분히 쌓여가는
글 작성 view 구현 본문
src>java>com.enough>project>dto>ArticleListViewResponse.java 생성
@Getter
public class ArticleListViewResponse {
private final Long id;
private final String title;
private final String content;
public ArticleListViewResponse(Article article) {
this.id = article.getId();
this.title = article.getTitle();
this.content = article.getContent();
}
}
src>java>com.enough>project>controller>BlogViewController.java 생성
@RequiredArgsConstructor
@Controller
public class BlogViewController {
private final BlogService blogService;
@GetMapping("/articles")
public String getArticles(Model model) {
List<ArticleListViewResponse> articles = blogService.findAll()
.stream()
.map(ArticleListViewResponse::new)
.toList();
model.addAttribute("articles", articles);
return "articleList";
}
}
src>main>resources>templates>articleList.html 생성
<!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">글 목록</h1>
<h4 class="mb-3">게시글 목록 페이지</h4>
</div>
<div class="container">
<button type="button" id="create-btn"
th:onclick="|location.href='@{/new-article}'|"
class="btn btn-secondary btn-sm mb-3">글 등록</button>
<div class="row-6" th:each="item : ${articles}">
<div class="card">
<div class="card-header" th:text="${item.id}">
</div>
<div class="card-body">
<h5 class="card-title" th:text="${item.title}"></h5>
<p class="card-text" th:text="${item.content}"></p>
<a th:href="@{/articles/{id}(id=${item.id})}" class="btn btn-primary">보러가기</a>
</div>
</div>
<br>
</div>
</div>
<script src="/article.js"></script>
</body>
엔터티에 생성/수정 시간 추가
src>main>java>domiain>Article.java 에 코드 추가
public class Article {
// ...
@CreatedDate
@Column(name = "created_at")
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// ...
}
src>main>java>com.enough.project>ProjectApplication.java에 @EnableJpaAuditing 추가
@EnableJpaAuditing
@SpringBootApplication
public class ProjectApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectApplication.class, args);
}
}
컨트롤러 메서드 작성
src>main>java>com.enough.project>dto>ArticleViewResponse.java 생성
@NoArgsConstructor
@Getter
public class ArticleViewResponse {
private Long id;
private String title;
private String content;
private LocalDateTime createdAt;
public ArticleViewResponse(Article article) {
this.id = article.getId();
this.title = article.getTitle();
this.content = article.getContent();
this.createdAt = article.getCreatedAt();
}
}
src>main>java>com.enough.project>controller>BlogViewController.java에 getArticle() 메서드 추가
@RequiredArgsConstructor
@Controller
public class BlogViewController {
private final BlogService blogService;
@GetMapping("/articles")
public String getArticles(Model model) {
List<ArticleListViewResponse> articles = blogService.findAll()
.stream()
.map(ArticleListViewResponse::new)
.toList();
model.addAttribute("articles", articles);
return "articleList";
}
@GetMapping("/articles/{id}")
public String getArticle(@PathVariable Long id, Model model) {
Article article = blogService.findById(id);
model.addAttribute("article", new ArticleViewResponse(article));
return "article";
}
}
src>main>resources>templates>article.html 생성
<!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">글</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">
<h1 class="fw-bolder mb-1" th:text="${article.title}"></h1>
<div class="text-muted fst-italic mb-2" th:text="|Posted on ${#temporals.format(article.createdAt, 'yyyy-MM-dd HH:mm')}|"></div>
</header>
<section class="mb-5">
<p class="fs-5 mb-4" th:text="${article.content}"></p>
</section>
<button type="button" id="modify-btn"
th:onclick="|location.href='@{/new-article?id={articleId}(articleId=${article.id})}'|"
class="btn btn-primary btn-sm">수정</button>
<button type="button" id="delete-btn"
class="btn btn-secondary btn-sm">삭제</button>
</article>
</div>
</div>
</div>
<script src="/article.js"></script>
</body>
글 삭제 기능 추가
src>main>resources>static에 article.js 생성
const deleteButton = document.getElementById('delete-btn');
if (deleteButton) {
deleteButton.addEventListener('click', event => {
let id = document.getElementById('article-id').value;
fetch(`/api/articles/${id}`, {
method: 'DELETE'
})
.then(() => {
alert('삭제 완료');
location.replace('/articles');
});
});
}
생성/수정 기능
src>java>com.enough>project>controller>BlogViewController.java에 newArticle() 메서드 추가
@RequiredArgsConstructor
@Controller
public class BlogViewController {
private final BlogService blogService;
@GetMapping("/articles")
public String getArticles(Model model) {
List<ArticleListViewResponse> articles = blogService.findAll()
.stream()
.map(ArticleListViewResponse::new)
.toList();
model.addAttribute("articles", articles);
return "articleList";
}
@GetMapping("/articles/{id}")
public String getArticle(@PathVariable Long id, Model model) {
Article article = blogService.findById(id);
model.addAttribute("article", new ArticleViewResponse(article));
return "article";
}
@GetMapping("/new-article")
public String newArticle(@RequestParam(required = false) Long id, Model model) {
if (id == null) {
model.addAttribute("article", new ArticleViewResponse());
} else {
Article article = blogService.findById(id);
model.addAttribute("article", new ArticleViewResponse(article));
}
return "newArticle";
}
}
생성/수정 view 만들기
src>main>resources>templates>newArticle.html 생성
<!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">글</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>
<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="/article.js"></script>
</body>
수정 api
src>main>resources>static에 article.js에 코드 추가
// 생략 ...
const modifyButton = document.getElementById('modify-btn');
if (modifyButton) {
modifyButton.addEventListener('click', event => {
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}`);
});
});
}
생성 api
src>main>resources>static에 article.js에 코드 추가
// 생략 ...
const createButton = document.getElementById('create-btn');
if (createButton) {
createButton.addEventListener('click', event => {
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');
});
});
}
서버 실행(테스트)
http://localhost:8080/articles
글 등록 선택(http://localhost:8080/new-article)
제목: 게시글 1
내용: 내용 1
입력
보러가기 선택
수정 선택
제목: 게시글 수정 11
내용: 내용 수정 11
삭제 클릭
http://localhost:8080/articles로 돌아옴
그 외 html 파일 생성/수정/삭제/읽기에 맞게 수정하기~
'Spring > project' 카테고리의 다른 글
게시판 기능(CRUD) (0) | 2023.12.25 |
---|---|
스프링 시큐리티를 사용한 인증, 인가 (0) | 2023.12.25 |
게시판 리스트 페이지 생성 (0) | 2023.12.24 |
thymeleaf를 사용하여 레이아웃 적용 (0) | 2023.12.24 |
프로젝트 생성 및 기본 index 페이지 (0) | 2023.12.24 |