Notice
Recent Posts
Recent Comments
«   2024/09   »
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
Archives
Today
Total
관리 메뉴

충분히 쌓여가는

글 작성 view 구현 본문

Spring/project

글 작성 view 구현

빌드이너프 2023. 12. 25. 16:49

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 파일 생성/수정/삭제/읽기에 맞게 수정하기~