본문 바로가기
백엔드/웹 개발

블로그 화면 구성하기 - 타임리프4 (블로그 글 삭제 기능)

by study_yeon 2023. 8. 10.

2023.08.08 ~ 08.10 수업

교재 : 스프링부트3 백엔드 개발자 되기

◎ 블로그 화면 구성하기  

- 템플릿 엔진(타임리프) 이용
- 글 목록, 글 내용, 수정 화면으로 구성

 

2023.08.09 - [백엔드/웹 개발] - 블로그 화면 구성하기 - 타임리프3 (블로그 글 뷰 구현하기)

 

블로그 화면 구성하기 - 타임리프3 (블로그 글 뷰 구현하기)

2023.08.07 수업 교재 : 스프링부트3 백엔드 개발자 되기 ☆ 데이터베이스 오라클과 연결하기 1. build.gradle 설정 - 오라클 사용(기존에 설정해 둔 주석 열기) - h2 주석 닫기 runtimeOnly 'com.oracle.database.jdb

dustj0824.tistory.com

이어서

 

▶ 블로그 글 삭제하기

- 컨트롤러에서 삭제 요청

- /api/articles/${id} 로 요청이 온 글 삭제하기 구현

-> service의 deleteById() 가 사용됨

 

0. API 관련 클래스 작성

0-1) BlogApiController 작성

package net.choongang.ewha.hithymeleaf.controller;

@RequiredArgsConstructor
@RestController
public class BlogApiController {

    private final BlogService blogService;

    // 1. addArticle() : 글을 생성하고 생성된 블로그 글 반환 
    @PostMapping("/api/articles")
    public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
        Article savedArticle = blogService.save(request);

        return ResponseEntity.status(HttpStatus.CREATED)
                .body(savedArticle);
    }

    // 2. findAllArticles() : 글 목록 조회하고 응답
    @GetMapping("/api/articles")
    public ResponseEntity<List<ArticleResponse>> findAllArticles() {
        List<ArticleResponse> articles = blogService.findAll()
                .stream()
                .map(ArticleResponse::new)
                .toList();

        return ResponseEntity.ok()
                .body(articles);
    }
    
    // 3. findArticle() : 지정 글 조회하고 응답 (상세보기 기능에 해당)
    @GetMapping("/api/articles/{id}")
    public ResponseEntity<ArticleResponse> findArticle(@PathVariable long id) {
        Article article = blogService.findById(id);

        return ResponseEntity.ok()
                .body(new ArticleResponse(article));
    }

    // 4. deleteArticle() : 지정 글 삭제(응답은 필요 없음)
    @DeleteMapping("/api/articles/{id}")
    public ResponseEntity<Void> deleteArticle(@PathVariable long id) {
        blogService.deleteById(id);

        return ResponseEntity.ok()
                .build();
    }

    // 5. updateArticle() : 지정 글 수정하고 응답
    @PutMapping("/api/articles/{id}")
    public ResponseEntity<Article> updateArticle(@PathVariable long id,
                                                 @RequestBody UpdateArticleRequest request) {
        Article updatedArticle = blogService.update(id, request);

        return ResponseEntity.ok()
                .body(updatedArticle);
    }

}

0-2) ArticleResponse 작성

- 요청에 응답할 DTO 

- 글을 응답해준다(제목, 내용으로 구성됨)

- 삭제에서는 필요 없는 부분이지만 컨트롤러를 만들 때 글 조회 메소드를 처리하기 위해 만들어주어야 함

package net.choongang.ewha.hithymeleaf.dto;

@Getter
// Article엔티티의 응답용 객체로 임시저장용이므로 데이터 변수구조는 같고 메소드는 생성자만 있으면 됨
// 임시객체이므로 VO객체가 아니라 DTO객체
public class ArticleResponse {

    private final String title;
    private final String content;

    public ArticleResponse(Article article) {
        this.title = article.getTitle();
        this.content = article.getContent();
    }
}

1. 삭제 기능 코드 작성하기

 

1-1. HTML 수정하기

- 삭제버튼에 id 값 주기

- 블로그 글 id 추가

- article.js 임포트 하기

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>블로그 글</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/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">
		<div class="row">
			<div class="col-lg-8"> 
				<article>
					<!-- 블로그 글 id 추가 -->
					<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" class="btn btn-primary btn-sm">수정</button>
					<button type="button" id="delete-btn" class="btn btn-secondary btn-sm">삭제</button>
				</article>
			</div>
		</div>
	</div>
	<!-- article.js 파일 추가 -->
	<script src="/js/article.js"></script>
</body>
</html>

<input type=“hidden”>
사용자에게는 보이지 않는 숨겨진 입력 필드를 정의
숨겨진 입력 필드는 폼 제출 시에 사용자가 변경해서는 안 되는 데이터를 함께 보낼 때 사용
ex) 데이터베이스 테이블의 Primary Key 값
이 Primary Key 값을 통해 데이터베이스에서 해당 레코드를 식별하여 정보를 알맞게 삭제/수정


1-2 자바스크립트 사용

-> crud중에 create update delete 실행 시 에러검사나 실행 기능 검사를 목적

 

경로 : HiThymeleaf/src/main/resources/static 

폴더 : js

파일명 : article.js

- 람다함수 사용

- HTML에서 id로 설정한 엘리먼트를 찾아(getElementById) 그 엘리먼트에서 클릭 이벤트가 발생하면 fetch()를 통해 DELETE요청을 보내는 역할

- deleteButton 객체에 'click' 이벤트를 addEventListener()로 등록. 이때 fetch()를 사용하여 비동기 처리할 콜백함수 정의 

// 삭제버튼이 눌렸으면 그 삭제버튼의 이벤트 핸들러 등록
// 삭제버튼의 html태그 얻어오기
const deleteButton = document.getElementById('delete-btn');
// deleteButton이 있으면 이벤트 등록
if(deleteButton) {
	deleteButton.addEventListener('click', event => {
		let id = document.getElementById('article-id').value;
		fetch(`/api/articles/${id}`, {
			method: 'DELETE'
		})
		.then(()=> {
			alert('삭제가 완료되었습니다.');
			location.replace('/articles'); // 리스트 화면으로 이동
		});
	});
}

1) 정식함수
function 함수명(매개변수..) {
   return;
}

2) 익명함수
- 함수명이 없는 함수
- 콜백함수 정의에 사용
- 로컬변수에 값으로 대입
- 인라인 익명함수

3) 람다함수(화살표 함수)
- 익명함수를 화살표를 사용하여 간단하게 만듦
- 기본적으로 리턴값이 있음(마지막 계산 값)

 

fetch() : HTTP response 객체를 래핑한 Promise 객체를 반환한다. 따라서 프로미스의 후속 처리 메서드인 then을 사용

- 가져오고자 하는 리소스의 경로를 나타내는 하나의 인수를 가짐

then() :  서버응답결과를 처리한다. fetch()가 잘 완료되면 연이어 실행된다. 응답결과를 처리할 함수가 정의됨

alert() : then()이 실행되는 시점에 웹 브라우저 화면으로 삭제가 완려되었음을 알려주는 경고창

location.replace() : 기존 주소 덮어쓰기(전으로 돌리기 불가). 실행 시 사용자의 웹 브라우저 화면을 현재 주소를 기반해 옮겨줌

-> assign()을 사용하면 새롭게 이동하는 것이므로 히스토리가 남아 이전으로 돌아갈 수 있는 위험 발생 

 

* fetch :
자바스크립트 API 라이브러리
주로 비동기 방식으로 ajax의 단점(불편하고 복잡한 응답처리와 연속된 callback 호출 등)을 해결하기위해 나온 Rest방식(UI가 없다)에 적합하다

 

fetch(`/api/articles/${id}`, {  // 요청정보 : 없음
	method: 'DELETE'            // 요청메소드 : delete
})  // 실제 요청하는 주소는 경로변수를 이용한 방법으로 데이터 전송

- 요청정보 : post,  put으로 요청하는 경우 폼데이터

 


★ 기능 추가해보기

* 구현하고자 하는 기능 

- 삭제버튼을 누르면 '삭제를 하시겠습니까 ?' 라는 확인 문구 출력하여

확인을 누르면 글 삭제, 취소를 누르면 글이 삭제되지 않는 기능

- 자바스크립트의 confirm() 메서드 활용

const deleteButton = document.getElementById('delete-btn');
// deleteButton이 있으면 이벤트 등록
if(deleteButton) {
	deleteButton.addEventListener('click', event => {
		console.log('삭제 버튼 작동')
		let id = document.getElementById('article-id').value;
		
		if(confirm(`${id}` + "를 삭제 하시겠습니까?")) {
			fetch(`/api/articles/${id}`, {
			method: 'DELETE'
			})
			.then(()=> {
				alert('삭제가 완료되었습니다.');
				location.replace('/articles');
			});	
		} else {
			alert('취소 되었습니다.');
		}

	});
}