티스토리 뷰

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

1-1. 컨트롤러 메서드

1) DTO 구현 - ArticleListViewResponse

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

 

 

2) BlogViewController - getArticles() 메서드 구현하기

@RequiredArgsConstructor
@Controller
public class BlogViewController {

    private final BlogService blogService;

    @GetMapping("/articles")
    public String getArticles(Model model) {
        // 블로그 글 목록을 조회하고, 각 글을 ArticleListViewResponse 객체로 변환하여 리스트로 저장
        List<ArticleListViewResponse> articleList
                = blogService.findAll()
                .stream()
                .map(ArticleListViewResponse::new)
                .toList();

        // 블로그 글 목록을 뷰에서 사용할 수 있도록 모델에 추가
        model.addAttribute("articles", articleList); // 블로그 글 리스트 저장

        // "articleList" 뷰를 반환
        return "articleList"; // 뷰 조회
    }

}

 

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

 

1-3. 실행 테스트하기

실행하면 나오는 화면

 


2. 블로그 글 뷰 구현하기

2-1. 엔티티에 생성, 수정 시간 추가

1) Article.java

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 기본 생성자 protected로 만들기
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false)
    private Long id;

    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "content", nullable = false)
    private String content;


    // 생성, 수정시간 추가
    @CreatedDate // 엔티티가 생성될 때 생성시간 저장
    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @LastModifiedDate // 엔티티가 수정될 때 수정 시간 저장
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;



    @Builder // 빌더 패턴 객체 생성
    public Article(String author, String title, String content) {
        this.author = author;
        this.title = title;
        this.content = content;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

 

2) data.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) SpringBootApplication에 어노테이션 추가

@EnableJpaAuditing // 이거
@SpringBootApplication
public class SpringBootDeveloperApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDeveloperApplication.class, args);
    }
}

Article.java 에도 어노테이션 추가

@EntityListeners(AuditingEntityListener.class) // 이거
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 기본 생성자 protected로 만들기
public class Article { }

 

 

2-2. 컨트롤러 구현하기

1) DTO 추가 - ArticleViewResponse

@NoArgsConstructor
@Getter
public class ArticleViewResponse {

    private Long id;
    private String title;
    private String content;
    private LocalDateTime createdAt;
    private String author;

    public ArticleViewResponse(Article article) {
        this.id = article.getId();
        this.title = article.getTitle();
        this.content = article.getContent();
        this.createdAt = article.getCreatedAt();
        this.author = article.getAuthor();
    }
}

 

2) 컨트롤러에 메서드 구현 - getArticle()

@RequiredArgsConstructor
@Controller
public class BlogViewController {

    private final BlogService blogService;

    @GetMapping("/articles")
    public String getArticles(Model model) {
        // 블로그 글 목록을 조회하고, 각 글을 ArticleListViewResponse 객체로 변환하여 리스트로 저장
        List<ArticleListViewResponse> articleList
                = blogService.findAll()
                .stream()
                .map(ArticleListViewResponse::new)
                .toList();

        // 블로그 글 목록을 뷰에서 사용할 수 있도록 모델에 추가
        model.addAttribute("articles", articleList); // 블로그 글 리스트 저장

        // "articleList" 뷰를 반환
        return "articleList"; // 뷰 조회
    }



// 추가

    @GetMapping("/articles/{id}")
    public String getArticle(@PathVariable Long id, Model model) {
        // 요청된 ID에 해당하는 블로그 글을 블로그 서비스를 통해 조회
        Article article = blogService.findById(id);
        // 조회한 블로그 글을 "article"라는 이름으로 모델에 추가
        model.addAttribute("article", new ArticleViewResponse(article));

        return "article"; // "article" 뷰를 반환
    }

}

 

2-3. 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')} By ${article.author}|"></div>
          </header>
          <section class="mb-5">
            <p class="fs-5 mb-4" th:text="${article.content}"></p> <!-- 블로그 글의 내용을 출력 -->
          </section>
          <button type="button" id="modify-btn"
                  class="btn btn-primary btn-sm">수정</button>
          <button type="button" id="delete-btn"
                  class="btn btn-secondary btn-sm">삭제</button>
        </article>
      </div>
    </div>
  </div>

</body>
  • 템플릿 함수 중 ${#temporals.format()}은 날짜 형식을 yyyy-MM-dd HH:mm으로 포매팅한다.
  • 포매팅한 날짜 형식을 | | 기호와 함께 Posted on이라는 텍스트와 붙인 점도 주의할 내용.  
    그러면 블로그 결과물에 ‘2099-99-31 23:11 Posted on 같이 글을 게시한 시간 알림을 표시할 수 있다.

 

 

2) alticleList.html - 글 보러 가기 수정

=> href 속성을 th:href 속성으로 변경하고 URL 표현식 @{...} 을 사용해 [보러 가기] 버튼 클릭 시

   주소창의 값을 /articles/{item.id}로 변경해 글 상세 화면으로 이동하게 만들기

글 상세 부분은 글 리스트 화면에서 확인 가능해야....

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>블로그 글 목록</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
</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}"> <!--article 개수만큼 반복 -->
        <div class="card">
            <div class="card-header" th:text="${item.id}"> <!-- 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 class="btn btn-warning" th:href="@{/articles/{id}(id=${item.id})}">보러 가기</a>
            </div>
        </div>
        <br>
    </div>
    
</div>

</body>

 

2-4. 실행 테스트하기

 


3. 삭제 기능 추가하기

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

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

 

2) article.html 에 delete-btn 아이디값 추가

<!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">
            <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="delete-btn" <!--추가-->
                  class="btn btn-secondary btn-sm">삭제</button>
        </article>
      </div>
    </div>
  </div>

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

 

3-2. 실행 테스트하기

1

 

2

 


4. 수정, 생성 기능 추가하기

그림으로 설계 흐름 잡기

[그림]

 

  • 글을 생성할 때는 URL에 별도 쿼리 파라미터가 없다.
  • 하지만 수정할 때는 수정할 글의 id를 쿼리 파라미터에 추가해 요청한다.
  • 쿼리 파라미터란 HTTP  요청에서 URL의 끝에 '?' 로 시작하는 키 /값으로 이루어진 문자열. & 으로 구분.
  • 즉, 쿼리 파라미터가 있는 경우 컨트롤러 메서드는 수정을 해야 하므로 엔티티를 조회하여 기존 글 데이터를 모델에 넣어 화면에 보여줘야 한다.
  • 쿼리 파라미터가 없을 때에는 새 글이므로 화면에 아무 것도 보여줄 필요가 없다.
  • 또한 뷰에선 쿼리 파라미터의 id 여부에 따라 [수정]과 [생성] 중 적절한 버튼을 보여줘야 한다.

 

4-1. 수정 화면 컨트롤러 코드

1) BlogViewController

@RequiredArgsConstructor
@Controller
public class BlogViewController {

    private final BlogService blogService;

    @GetMapping("/articles")
    public String getArticles(Model model) {
        // 블로그 글 목록을 조회하고, 각 글을 ArticleListViewResponse 객체로 변환하여 리스트로 저장
        List<ArticleListViewResponse> articleList
                = blogService.findAll()
                .stream()
                .map(ArticleListViewResponse::new)
                .toList();

        // 블로그 글 목록을 뷰에서 사용할 수 있도록 모델에 추가
        model.addAttribute("articles", articleList); // 블로그 글 리스트 저장

        // "articleList" 뷰를 반환
        return "articleList"; // 뷰 조회
    }

    @GetMapping("/articles/{id}")
    public String getArticle(@PathVariable Long id, Model model) {
        // 요청된 ID에 해당하는 블로그 글을 블로그 서비스를 통해 조회
        Article article = blogService.findById(id);
        // 조회한 블로그 글을 "article"라는 이름으로 모델에 추가
        model.addAttribute("article", new ArticleViewResponse(article));

        return "article"; // "article" 뷰를 반환
    }
    
    
    //추가

    @GetMapping("/new-article")
    public String newArticle(@RequestParam(required = false) Long id, Model model) {
        if (id == null) {
            // 'id' 파라미터가 null인 경우,
            // 새로운 블로그 글 작성을 위한 빈 ArticleViewResponse 객체를 모델에 추가.
            model.addAttribute("article", new ArticleViewResponse());
        } else {
            // 'id' 파라미터가 존재하는 경우, 해당 ID에 해당하는 블로그 글을 조회하여 모델에 추가.
            Article article = blogService.findById(id);
            model.addAttribute("article", new ArticleViewResponse(article));
        }

        // 'newArticle' 뷰를 반환.
        return "newArticle";
    }
}

 

4-2. 수정/생성 뷰 만들기

1) newArticle.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>블로그 글</title>
  <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
</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 id="article-id" th:value="${article.id}" type="hidden">

        <header class="mb-4">
          <input class="form-control" id="title" placeholder="제목" th:value="${article.title}" type="text">
        </header>
        <section class="mb-5">
          <textarea class="form-control h-25" id="content" placeholder="내용" rows="10"
                    th:text="${article.content}"></textarea>
        </section>
        <!--id가 있을 때는 수정 버튼을, 없을 때는 등록 버튼이 보이게 함. -->
        <button class="btn btn-primary btn-sm" id="modify-btn" th:if="${article.id} != null" type="button">수정</button>
        <button class="btn btn-primary btn-sm" id="create-btn" th:if="${article.id} == null" type="button">등록</button>
      </article>
    </div>
  </div>
</div>

<script src="/js/article.js"></script>
</body>

 

2) 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}`);
            });
    });
}

 

3) article.html에 id값과 클릭 이벤트 추가하기

<!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">
            <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="/js/article.js"></script>
</body>

 

4-3. 실행 테스트하기

원상태 글

1

 

수정 제목, 내용을 입력하고 버튼 누르면

2

 

바뀐 것 확인 가능

3

 

 

4-4. 생성 기능 작성하기

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

// 생성 기능
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');
            });
    });
}

 

 

2) 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">
    
    	<!-- 추가 -->
        <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="/js/article.js"></script>
</body>

 

4-5. 실행 테스트하기

1

 

새로운 글 등록 화면

2

 

3

새로운 글 확인 가능

728x90
반응형
Comments
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
250x250