백엔드/Spring Boot

7. 블로그 화면 구성하기 (5) - 수정 / 생성 기능 추가

두개의 문 2023. 8. 10. 09:22

5.  수정 / 생성 뷰 컨트롤러 작성하기 

 

 - 보통 블로그 글 수정과 생성은 같은 화면에서 진행됨 

 - 수정 / 생성에 따라 버튼 다르고, 기존 값의 유무가 다름 

    → URL의 설계와 흐름 또한 다름 

  

 ❶ 이미 생성한 글을 수정할 때
                   /new-article?id=123                                123 id를 가진 
  사용자   ------------------------->  뷰 컨트롤러 ------------------------->  뷰
                  ( id가 123인 글 수정 )                                  엔티티 조회 후 모델에 추가 
 ❷ 새 글을 생성할 때 
                            /new-article 
   사용자   ------------------------->  뷰 컨트롤러 ------------------------->  뷰
                                (생성)

  • 쿼리 파라미터

       : HTTP 요청에서 URL의 끝에 '?'로 시작하는 키 값으로 이루어진 문자열이며, '&'로 구분함 

 

  • 글을 생성할 때는 URL에 별도 쿼리 파라미터가 없음 

       하지만, 수정할 때는 URL에 ?id=123과 같이 수정할 글의 id를 쿼리 파라미터에 추가하여 요청함 

       → 쿼리 파라미터가 있는 경우 컨트롤러 메서드는 수정을 해야 하므로,

          엔티티를 조회해 기존 글 데이터를 모델에 넣어 화면에 보여줘야 함 ( 물론 새 글일 경우, 화면에 보여줄 필요 없음 )

       → 또한, 파라미터의 id 여부에 따라 [수정] 또는 [생성] 버튼을 보여줘야 함 

 

 

 

1) 수정 화면을 보여주기 위한 컨트롤러 메서드를 추가하기 

 

▪︎ BlogViewController.java

  - newArticle( ) 메서드 추가 

	@GetMapping("/new-article")
	public String newArticle(@RequestParam(required = false) Long id, Model model) {
		
		if( id == null ) {	// 쿼리문자열이 없는 경우 (/new-article) : id가 없으면 생성 
			// 원래 수정이라면, 조회할 글이 있으므로 그 글의 데이터를 
			// DTO 객체 ArticleViewResponse에 저장함 
			model.addAttribute("article", new ArticleViewResponse());
		} else {
			// * 수정할 글이 있으면, else 블럭 실행 
			// 쿼리스트링이 있다는 뜻 : '/new-article/?id=100'
			// 1) 이 쿼리스트링의 id를 Article 테이블에서 검색 
			// 2) 검색한 테이블의 내용을 Article 객체 VO 객체에 저장 
			// 3) VO 객체의 데이터를 수정할 수 있는 DTO객체 ArticleViewResponse에 저장함 
			// 4) 이 DTO를 웹 브라우저에게 응답 끝 
			
			Article article = blogService.findById(id);
			model.addAttribute("article", new ArticleViewResponse());
		}
		
		return "newArticle";	// template/newArticle.html
	}

 

 • 쿼리 파라미터로 넘어온 id값은 newArticle( ) 메서드의 Long 타입 id 인자에 매핑함 

 • id가 있으면 수정, 없으면 생성 

   - id가 없는 경우, 기본 생성자를 이용해 빈 ArticleViewResponse 객체를 생성 

   - id가 있으면, 기존 값을 가져오는 findById() 메서드 호출 

 

 

 

2. 수정 / 생성 뷰 만들기 

 

 

1) 컨트롤러 메서드에서 반환하는 newArticle.html 구현 

 

▪︎ newArticle.html ( resource/templates )

  - 수정 / 생성 뷰 만들기 

<!DOCTYPE html>
<html xmlns:th="http://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 mt-5">
	<div class="row">
		<div class="col-lg-8">
			<article>
				<!--  수정글일 경우, 서버로 수정 요청 시 
					  hidden 값으로 글 번호(id)를 요청값으로 추가  -->
				<!-- 아이디 정보 저장 -->
				<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>

<!--  타임리프는 동적기능 : resources/templates/ -->
<!--  		  정적기능 : resources/static/ 
		      js, css, images, 동영상 
		      /js 경로 -> resources/static/js/
-->
<script src="/js/article.js"></script>
</body>
</html>

 

• 수정할 때는 id가 필요

  - input 엘리멘트의 type을 'hidden'으로 설정하여 엘리멘트를 숨김 

 

• th:value : 글의 id를 저장 

• th:if : id가 있을 때 [수정] 버튼, 없을 때 [등록] 버튼이 나타나도록 함 

 

 

 

2) 실제 수정, 생성 기능을 위한 API를 구현하기 

 

▪︎ article.js ( static 디렉터리 )에 다음 코드 추가 

// * 수정 기능 
// ❶ id가 modify-btn인 엘리멘트 조회 
const modifyButton = document.getElementById('modify-btn');
if(modifyButton){
	// ❷ 클릭 이벤트가 감지되면 수정 API 요청 
	modifyButton.addEventListener('click', event => {
		// ?key1=value1&key2=value2 
		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({
				// JSON : 자바스크립트의 JSON parser  
				// stringify : 객체(실행코드) -> 문자열 구조로 변환 
				title: document.getElementById('title').value, 
				content: document.getElementById('content').value 
			})
		})
			.then(() => {
				alert('수정이 완료되었습니다.');
				location.replace(`/articles/${id}`);
			});
	});
}

• id가 modify-btn인 엘리멘트를 찾고, 그 엘리멘트에서 클릭 이벤트가 발생하면 id가 title, content인 엘리멘트의 값을 가져와 fetch( ) 메서드를 통해 수정 API로 /api/articles/ PUT 요청을 보냄 

 

• 요청을 보낼 때

  - headers : 요청 형식을 지정

  - body : HTML에 입력한 데이터를 JSON 형식으로 바꿔서 보냄 

 

• 요청이 완료되면, then( ) 메서드로 마무리 작업을 함 

 

 

 

2) article.html 파일을 열어 [수정] 버튼에 id값과 클릭 이벤트를 추가 

 

▪︎ article.html

<!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 mt-5">
	<div class="row">
		<div class="col-lg-8">
			<article>
				<!--  블로그 글 id 추가  -->
				<input type="hidden" id="article-id" th:value="${article.id}">
				<!-- header 영역  -->
				<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>
				<!--  [삭제] 버튼에 id 추가  -->
				<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>

 

 

3) 실행 테스트하기 

- 웹 브라우저에 'localhost/articles/1'에 접속해 [수정]버튼을 눌러보자 

- 수정 뷰가 출력되는 것을 확인하고, 제목 및 내용을 수정해보자

 

 

 - 수정할 제목 및 내용을 입력 후, [수정] 버튼 클릭 시 수정된 글이 출력됨 

 


6. 생성 기능 마무리하기 

 

1) 생성 기능 작성하기 

 

① [ 등록 ] 버튼을 누르면, 입력 칸에 있는 데이터를 가져와 게시글 생성 API에 글 생성 관련 요청을 보내주는 코드를 추가 

▪︎ article.js ( /resources/static/js 디렉터리 )

// * 등록 기능 
// ❶ id가 create-btn인 엘리멘트 조회  
const createButton = document.getElementById('create-btn');

if(createButton){
	// ❷ 클릭 이벤트가 감지되면 생성 API 요청 
	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");
		});
	});
}

• id가 create-btn인 엘리멘트를 조회 

  → 그 엘리멘트에서 클릭 이벤트 발생 시,

      id가 title, content인 엘리멘트의 값을 fetch( ) 메서드를 통해 생성 API로 '/api/articles/' POST 요청을 보내줌 

 

 

② articleList.html에 idrk 'create-btn'인 [생성] 버튼을 추가 

▪︎ articleList.html

/* 생략 */ 
<!--  컨테이너 영역 : 컨텐츠 -->
	<div class="container">
		<!-- 글 등록 버튼 추가  -->
		<button type="button" id="create-btn"
			th:onclick="|location.href='@{ /new-article }'|"
			class="btn btn-secondary btn-sm mb-3">글 등록</button>
/* 생략 */

 

③ 실행 테스트하기 

 - 웹 브라우저에 'localhost/articles'에 접속한 뒤, [등록] 버튼을 눌러 제대로 기능이 동작하는지 확인 

 

 

 

 

 


7장 마무리 

 

▪︎ 템플릿 엔진인 타임리프를 사용해서 게시글 리스트 뷰, 상세 뷰, 삭제 기능, 수정 기능, 생성 기능을 추가 

▪︎ 템플릿 엔진 

  - 데이터를 넘겨받아 HTML에 데이터를 넣어 동적인 웹 페이지를 만들어주는 도구 

 

▪︎ @Controller 

  : 반환값으로 뷰를 찾아 보여주는 애너테이션