Notice
Recent Posts
Recent Comments
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

SYDev

[스프링 부트 3 백엔드 개발자 되기] Chapter 7. 블로그 화면 구성하기 본문

대딩코딩/웹개발 스터디

[스프링 부트 3 백엔드 개발자 되기] Chapter 7. 블로그 화면 구성하기

시데브 2024. 5. 1. 12:14

7.1. 사전 지식: 타임리프

  • 템플릿 엔진: 스프링 서버에서 데이터를 받아 우리가 보는 웹 페이지, HTML 상에 그 데이터를 넣어 보여주는 도구. 

템플릿 엔진 개념 잡기

<h1 text = ${이름}>
<p text = ${나이}>

 

-> 서버에서 이름, 나이라는 키로 데이터를 템플릿 엔진에 넘겨주고, 템플릿 엔진은 이를 받아 HTML 값에 적용

  • 대표적으로 JSP, 타임리프, 프리마커 등이 있다.
  • 스프링은 타임리프를 권장한다.

 

타임리프 표현식과 문법

타임리프 표현식

표현식 설명
${...} 변수의 값 표현식
#{...} 속성 파일 값 표현식
@{...} URL 표현식
*{...} 선택한 변수의 표현식. th:object에서 선택한 객체에 접근

 

타임리프 문법

표현식 설명 예제
th:text 텍스트를 표현할 때 사용 th:text = ${person.name}
th:each 컬렉션을 반복할 때 사용 th:each = "person: ${persons}"
th:if 조건이 true인 때만 표시 th:if= "${person.age} >= 20"

 

타임리프 사용을 위한 의존성 추가

  • build.gradle 파일에 implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 추가

 

타임리프 문법 익히기용 컨트롤러 작성

ExampleController.java

package me.shinsunyoung.springbootdeveloper.controller;

import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.time.LocalDate;
import java.util.List;

@Controller
public class ExampleController {

    @GetMapping("/thymeleaf/example")
    public String thymeleafExample(Model model) {   //뷰로 데이터를 넘겨주는 모델 객체
        Person examplePerson = new Person();
        examplePerson = new Person();
        examplePerson.setId(1L);
        examplePerson.setName("홍길동");
        examplePerson.setAge(11);
        examplePerson.setHobbies(List.of("운동", "독서"));

        model.addAttribute("person", examplePerson);    //Person 객체 저장
        model.addAttribute("today", LocalDate.now());

        return "example";
    }

    @Setter
    @Getter
    class Person {
        private Long id;
        private String name;
        private int age;
        private List<String> hobbies;
    }
}

 

-> Model 객체는 HTML 쪽으로 값을 넘겨주는 객체.

-> addAttribute() 메서드로 모델에 값을 저장 

-> "person"이라는 키에 사람 정보, "today"라는 키에 날짜 정보 저장

-> thymeleafExample이 반환하는 값은 "example" -> 이 값은 클래스에 붙은 애너테이션이 @Controller이므로 뷰의 이름을 반환하는 것 -> resource/templates 디렉터리에서 example.html을 찾은 다음 웹 브라우저에 해당 파일을 보여준다.

 

모델의 역할 살펴보기

  • 모델에는 "person", "today" 이렇게 두 키를 가진 데이터가 들어있다.
  • 컨트롤러는 이렇게 모델을 통해 데이터를 설정하고, 모델은 뷰로 이 데이터를 전달해 키에 맞는 데이터를 뷰에서 조회할 수 있게 한다.
  • 모델은 컨트롤러와 뷰의 중간다이 역할

 

뷰 작성하기

  • src/main/resources/templates 디렉터리에 example.html 파일 생성

example.html

<!DOCTYPE html>
<html xmlns:th = "http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>타임리프 익히기</h1>
<p th:text = "${#temporals.format(today, 'yyyy-MM-dd')}"></p>
<div th:object = "${person}">
    <p th:text = "|이름 : *{name}|"></p>
    <p th:text = "|나이 : *{age}|"></p>
    <p>취미</p>
    <ul th:each = "hobby : *{hobbies}">
        <li th:text = "${hobby}"></li>
        <span th:if = "${hobby == '운동'}">(대표 취미)</span>
    </ul>
</div>
<a th:href = "@{/api/articles/{id}(id = ${person.id})}">글 보기</a>
</body>
</html>

 

 

7.2. 블로그 글 목록 뷰 구현하기

컨트롤러 메서드 작성하기

  • 6장에서는 컨트롤러 메서드가 데이터를 직렬화한 JSON 문자열을 반환
  • 뷰 컨트롤러 메서드는 뷰의 이름을 반환하고, 모델 객체에 값을 담는다.

1단계

  • 뷰에게 데이터를 전달하기 위한 객체 생성

ArticleListViewResponse.java

package me.shinsunyoung.springbootdeveloper.dto;

import me.shinsunyoung.springbootdeveloper.domain.Article;

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();
    }
}

 

 

2단계

  • /articles GET 요청을 처리할 코드 작성 -> 여기서는 블로그 글 전체 리스트를 담은 뷰를 반환

BlogViewController.java

package me.shinsunyoung.springbootdeveloper.controller;

import lombok.RequiredArgsConstructor;
import me.shinsunyoung.springbootdeveloper.dto.ArticleListViewResponse;
import me.shinsunyoung.springbootdeveloper.service.BlogService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@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";   //articleList.html라는 뷰 조회
    }
}

-> addAttribute() 메서드를 사용해 모델에 값을 저장

-> articles 키에 글 리스트를 저장

-> 반환값인 articleList는 resource/templates/articleList.html을 찾도록 뷰의 이름을 명시

 

HTML 뷰 만들고 테스트

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">My Blog</h1>
    <h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>

<div class="container">
    <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 href="#" class="btn btn-primary">보러 가기</a>
            </div>
        </div>
        <br>
    </div>
</div>
</body>

-> th:each로 "articles" 키에 담긴 데이터 개수만큼 반복

-> th:text는 반복 대상 객체의 id, "text"를 출력 -> 화면을 쉽고 간편하게 만들 수 있는 부트스트랩 사용

 

 

7.3. 블로그 글 뷰 구현하기

  • 블로그 화면 상의 [보러 가기] 버튼을 누르면 블로그 글이 보이도록 블로그 글 뷰를 구현
  • 엔티티에 생성 시간, 수정 시간을 추가하고, 컨트롤러 메서드를 만든 다음 HTML 뷰를 만들고 확인하는 과정으로 개발

엔티티에 생성, 수정 시간 추가하기

1단계

  • 엔티티에 생성 시간과 수정 시간을 추가해 글이 언제 생성되었는지 뷰에서 확인

Article.java

package me.shinsunyoung.springbootdeveloper.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
@Entity //엔티티로 지정
@Getter //getId(), getTitle()같은 게터 메스드들을 대치
@NoArgsConstructor(access = AccessLevel.PROTECTED)  //기본 생성자 대치
public class Article {

	...
    
    @CreatedDate    //엔티티가 생성될 때 생성 시간 저장
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @LastModifiedDate   //엔티티가 수정될 때 수정 시간 저장
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}

-> @CreatedDate: 엔티티가 생성될 때 생성 시간을 created_at 컬럼에 저장

-> @LastModifiedDate: 엔티티가 수정될 때 마지막으로 수정된 시간을 updated_at 컬럼에 저장

-> @EntityListeners(AuditingEntityListener.class): 엔티티의 생성 및 수정 시간을 자동으로 감시하고 기록

 

2단계

  • 엔티티를 생성하면 생성 시간과 수정 시간이 자동으로 저장되지만, 스프링 부트를 실행할 대마다 SQL문으로 데이터를 넣는 import.sql파일은 created_at과 update_at을 바꾸지 않는다.
  • 최초 파일 생서엥도 이 값을 수정하도록 import.sql파일을 수정해 실행할 때마다 create_at, update_at이 바뀌도록 한다.

import.sql

INSERT INTO article (title, content, created_at, updated_at) VALUES ('제목 1', '내용 1', NOW(), NOW())
INSERT INTO article (title, content, created_at, updated_at) VALUES ('제목 2', '내용 2', NOW(), NOW())
INSERT INTO article (title, content, created_at, updated_at) VALUES ('제목 3', '내용 3', NOW(), NOW())

 

3단계

  • SpinrgBootDeveloperApplication.java 파일을 열어 엔티티의 created_at, updated_at을 자동으로 업데이트하기 위한 Annotation 추가

SpringBootDeveloperApplication.java

package me.shinsunyoung.springbootdeveloper;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing  //creaeted_at, updated_at 자동 업데이트
@SpringBootApplication
public class SpringBootDeveloperApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDeveloperApplication.class, args);
    }
}

 

컨트롤러 메서드 작성하기

1단계 

  • 뷰에서 사용할 DTO를 만들기 위해 dto 디렉터리에 ArticleViewResponse.java 생성한 뒤 클래스 구현

ArticleViewResponse.java

package me.shinsunyoung.springbootdeveloper.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;
import me.shinsunyoung.springbootdeveloper.domain.Article;

import java.time.LocalDateTime;

@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();
    }
}

 

2단계

  • 블로그 글을 반환할 컨트롤러의 메서드 작성

BlogViewController.java

package me.shinsunyoung.springbootdeveloper.controller;

import lombok.RequiredArgsConstructor;
import me.shinsunyoung.springbootdeveloper.domain.Article;
import me.shinsunyoung.springbootdeveloper.dto.ArticleListViewResponse;
import me.shinsunyoung.springbootdeveloper.service.BlogService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

@RequiredArgsConstructor
@Controller
public class BlogViewController {

    private final BlogService blogService;

	...
    
    @GetMapping("/articles/{id}")
    public String getArticle(@PathVariable Long id, Model model) {
        Article article = blogService.findById(id);
        model.addAttribute("article", new ArticleViewResponse(article));

        return "article";
    }
}

-> getArticle() 메서드는 인자 id에 URL로 넘어온 값을 받아 findById()메서드로 넘겨 글을 조회하고, 화면에서 사용할 모델에 데이터를 저장한 다음, 보여줄 템플릿 이름을 반환

 

HTML 뷰 만들기

1단계

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">My Blog</h1>
    <h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>

<div class="container mt-5">
    <div class="row">
        <div class="col-lg-8">
            <article>
                <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"
                        class="btn btn-secondary btn-sm">삭제</button>
            </article>
        </div>
    </div>
</div>
</body>

 

2단계

articleList.html

...

<div class="container">
    <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>
</body>

-> 보러 가기 버튼만 수정

-> @{...}을 사용해 보러 가기를 눌렀을 때, 주소창의 값을 /articles/{item.id}로 변경해 글 상세 화면으로 이동

 

 

7.4. 삭제 기능 추가하기

  • 글 상세화면에서 [삭제] 버튼을 눌러 글을 삭제
  • 삭제 API로 요청을 보낼 코드를 작성

삭제 기능 코드 작성하기

1단계

  • src/main/resources/static/js 디렉터리르 생성하고, article.js 파일 생성

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');
        });
    });
}

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

-> fetch() 메서드에 이어지는 then() 메서드는 fetch()가 잘 완료되면 연이어 실행되는 메서드.

-> alert() 메서드는 then() 메서드가 실행되는 시점에 웹 브라우저 화면으로 삭제가 완료되었음을 알리는 팝업을 띄워주는 메서드

-> location.replace()메서드는 실행 시 사용자의 웹 브라우저 화면을 현재 주소를 기반해 옮겨주는 역할

 

2단계

  • [삭제] 버튼을 눌렀을 때 삭제하도록 button 엘리먼트에 delete-btn이라는 아이디 값 추가하고 앞서 작성한 article.js가 이 화면에서 동작하도록 임포트

article.html

<!DOCTYPE html>
	...

<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 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>

<script src = "/js/article.js"></script> <!-- article.js 파일 추가 -->
</body>

 

 

 

 

7.5. 수정/생성 기능 추가하기

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

  • 수정: 사용자: /new-article?id=123(id가 123인 글 수정) -> 뷰 컨트롤러: 123 id를 가진 엔티티 조회 후 모델에 추가 -> 뷰
  • 생성: 사용자:/new-article(생성) -> 뷰 컨트롤러 -> 뷰

BlogViewController.java

package me.shinsunyoung.springbootdeveloper.controller;

import lombok.RequiredArgsConstructor;
import me.shinsunyoung.springbootdeveloper.domain.Article;
import me.shinsunyoung.springbootdeveloper.dto.ArticleListViewResponse;
import me.shinsunyoung.springbootdeveloper.dto.ArticleViewResponse;
import me.shinsunyoung.springbootdeveloper.service.BlogService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@RequiredArgsConstructor
@Controller
public class BlogViewController {

    private final BlogService blogService;

	...

    @GetMapping("/new-article")
    //id 키를 가진 쿼리 파라미터의 값을 id 변수에 매핑
    public String newArticle(@RequestParam(required = false) Long id, Model model) {
        if (id == null) {   //id가 없으면 생성
            model.addAttribute("article", new ArticleViewResponse());
        } else {
            Article article = blogService.findById(id);
            model.addAttribute("article", new ArticleViewResponse(article));
        }

        return "newArticle";
    }
}

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

-> id가 있으면 수정, 없으면 생성이므로 id가 없는 경우 기본 생성자를 이용해 빈 ArticleViewResponse 객체를 만들고, id가 있으면 기존 값을 가져오는 findById() 메서드 호출

 

수정/생성 뷰 만들기

1단계

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

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">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>
                <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>

 

2단계

  • 실제 수정, 생성 기능을 위한 API 구현

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');
        });
    });
}

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": "applications/json",
            },
            body: JSON.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 요청을 보낸다.

-> 요청을 보낼 때는 headersd에 요청 형식을 지정하고, body에 HTML에 입력한 데이터를 JSON 형식으로 바꿔 보낸다.

-> 요청이 완료되면 then() 메서드로 마무리 작업

 

3단계

  • 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://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>
				...
                <section class="mb-5">
                    <p class="fs-5 mb-4" th:text="${article.content}"></p>
                </section>
                <!-- 수정 버튼에 id 추가 -->
                <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 = "/js/article.js"></script> 
</body>

 

 

 

7.6. 생성 기능 마무리하기

생성 기능 작성하기

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

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");
        });
    });
}

-> id가 create-btn인 엘리먼트 -> 그 엘리먼트에서 클릭 이벤트가 발생하면 id가 title, content인 엘리먼트의 값을 가져와 fetch() 메서드를 통해 생성 API로 /api/articles/ POST 요청을 보내준다.

 

2단계

  • articleList.html 파일을 수정해 id가 create-btn인 [생성] 버튼 추가

articleList.html

<!DOCTYPE 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>
    
    <div class="row-6" th:each="item : ${articles}">
        
        ...

    </div>
</div>
</body>

 


참고자료

  • 스프링 부트 3 백엔드 개발자 되기, 신선영, 골든래빗, 2023.05.12