항해99/개발일지

20220126 개발일지 #게시판 만들기(원페이지)

paran21 2022. 1. 26. 13:07

다른 분들의 도움으로 원페이지 게시판 만들기도 성공했다.

게시글 1개 조회하기가 구현이 잘 안되서 계속 문제였는데, 우선 어제 팀원분이 모달로 띄우셨다고 해서 참고해서 만들었다.

게시글 목록을 불러오는 fuction 안에 모달을 포함해서 모달 실행 시 상세페이지가 모달로 보이는 형태다.

이렇게 하면 controller에서 게시글 1개를 불러오기 위한 GetMapping을 별도로 만들지 않아도 되고, html에서도 목록 불러오기 function 하나로 해결할 수 있다.

그런데 모달의 특성인지, 이렇게 하면 게시글의 제목, 작성자, 작성일이 테이블안에 제대로 들어가지 않았다.

 

오늘 스터디하고나서 show/hide로 하신 분의 코드를 보았다.

처음에 내가 생각했던 것처럼 목록 불러오기에서 onclick(혹은 모달 실행)으로 getOneBoard를 실행하고, 매개변수로 id를 전달 + getOneBoard에서 ajax로 해당 게시글 불러오기였다.

계속 js문제인거 같아서 여기서 수정을 했었는데, 여기 문제가 아니라 controller문제였던 것 같다.

모달 대신 카드로 게시글을 불러오고 show/hide 기능을 포함해서 완성했다.

 

결과적으로 다중페이지/원페이지 구현을 모두 다 구현해서 뿌듯하다.

게시글 상세보기도 헤매기는 했지만 코드만 놓고 보면 미니프로젝트때보다 간단하게 구현한 것 같다.

 

github: https://github.com/paran22/board_onepage

 

1. 구조

전체적인 구조는 다음과 같다.

Spring 기본강의 3주차를 보고 게시글에 맞게 일부만 변경해서 만들었다.

수정하기/삭제 기능은 포함하지 않았다.

Service없이 controller에서 모든 로직을 구현하는 형태이다.

2. 코드

#Entity, repository, Dto, application

@NoArgsConstructor
@Getter
@Entity
public class Board extends Timestamped {
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String contents;

    public Board(String title, String username, String contents) {
        this.title = title;
        this.username = username;
        this.contents = contents;
    }

    public Board(BoardRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.username = requestDto.getUsername();
        this.contents = requestDto.getContents();
    }

}
public interface BoardRepository extends JpaRepository<Board, Long> {
    //createdAt을 기준으로 내림차순(desc, 최신순) 정렬
    List<Board> findAllByOrderByCreatedAtDesc();

}
@Getter
public class BoardRequestDto {
    private String title;
    private String username;
    private String contents;
    }
@Getter
@MappedSuperclass //Entity가 자동으로 칼럼으로 인식
@EntityListeners(AuditingEntityListener.class) //생성/변경시간을 자동으로 업데이트
public abstract class Timestamped {

    @CreatedDate
    private LocalDateTime createdAt;
@EnableJpaAuditing
@SpringBootApplication
public class Project1Application {

    public static void main(String[] args) {
        SpringApplication.run(Project1Application.class, args);
    }

}

#Controller

게시글 상세보기에서 repo의 findById함수를 사용했는데 orElseThrow까지 해주고 나서야 Board로 반환할 수 있었다.

게시들 등록하기는 Dto를 사용하지만 게시글 목록보기, 상세보기는 Board를 그대로 가져온다.

@RequiredArgsConstructor
@RestController
public class BoardController {

    private final BoardRepository boardRepository;

    //게시글 등록하기
    @PostMapping("/boards")
    public Board createBoard(@RequestBody BoardRequestDto requestDto) {
        Board board = new Board(requestDto);
        return boardRepository.save(board);
    }

    //게시글 보기
    @GetMapping("/boards")
    public List<Board> getBoard() {
        return boardRepository.findAllByOrderByCreatedAtDesc();
    }

    //게시글 상세보기
    //findById는 반드시 orElseThrow를 해야 Board로 저장되는 것 같다.
    @GetMapping("/boards/{id}")
    public Board getBoardOne(@PathVariable Long id){
        Board board = boardRepository.findById(id).orElseThrow(
                () -> new NullPointerException("아이디가 존재하지 않습니다.")
        );
        return board;
    }

}

#index.html

게시글 목록보기에서 보기버튼을 만들고, onclick시에 getOneBord({$id})를 실행한다.

getOneBoard는 card empty, show 하고 ajax로 게시글 데이터를 불러와서 card에 보여준다.

그리고 card에 닫기 버튼을 만들어서 hide하였다.

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- Webpage Title -->
    <title>항해 블로그</title>

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <!-- Font Awesome CSS -->
    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Stylish&display=swap" rel="stylesheet">

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
            integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
            crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
            integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
            crossorigin="anonymous"></script>



    <script>
        $(document).ready(function () {
            getBoards();
        })

        //게시글 목록보기
        function getBoards() {
            $("#boardList").empty();
            $.ajax({
                type: "GET",
                url: `/boards`,
                success: function (response) {
                    for (let i = 0; i < response.length; i++) {
                        let board = response[i];
                        let id = board.id;
                        let title = board.title;
                        let contents = board.contents;
                        let username = board.username;
                        let createdAt = board.createdAt;
                        let temp_html = `<tr>
                                            <th scope="col">${title}
                                            <button type="button" class="btn btn-primary" id="writeBtn"
                                                    onclick="getOneBoard(${id})">보기
                                              </button></th>
                                            <th scope="col">${username}</th>
                                            <th scope="col">${createdAt}</th>
                                        </tr>`
                        $('#boardList').append(temp_html);
                    }
                }
            })
        }

        //게시글 한개 보기
        //게시글 목록보기에서 버튼 onclick시 함수 실행되고 id값을 받아온다.
        function getOneBoard(id) {
            $('#boardOne').empty();
            $('#boardOne').show();
            $.ajax({
                type: "GET",
                url: `/boards/${id}`,
                success: function (response) {
                    let board = response;
                    let title = board.title;
                    let contents = board.contents;
                    let temp_html = `<div class="card" style="width: 18rem;">
                                      <div class="card-body">
                                        <h5 class="card-title">${title}</h5>
                                        <p class="card-text">${contents}</p>
                                        <a onclick="closeCard()" class="btn btn-primary">닫기</a>
                                      </div>
                                    </div>`
                    $('#boardOne').append(temp_html);
                }
            })
        }

        //게시글 카드 닫기
        function closeCard() {
            $('#boardOne').hide();
        }


        //게시글 등록하기
        function postBoard() {
            let title = $('#title').val();
            let username = $('#username').val();
            let contents = $('#contents').val();
            let data = {'title': title, 'username': username, 'contents': contents}
            $.ajax({
                type: "POST",
                url: "/boards",
                contentType: "application/json",
                data: JSON.stringify(data),
                success: function () {
                    alert('게시글이 성공적으로 작성되었습니다.');
                    $("#exampleModal").modal('dispose')
                    window.location.reload()
                }
            })
        }
    </script>

</head>
<body>
<div class="container" style="margin-top:30px">
    <div class="row">
        <div class="col-sm-12">
            <h2>항해 블로그</h2>
            <button type="button" class="btn btn-primary" id="writeBtn"
                    data-toggle="modal" data-target="#exampleModal">글쓰기
            </button>
        </div>
    </div>

    <div id="boardOne" class="card" style="width: 18rem;">
    </div>

    <div id="card" class="card" style="width: 18rem;">
    </div>
    <table style="white-space: normal" class="table">
        <thead>
        <tr>
            <th scope="col">제목</th>
            <th scope="col">작성자명</th>
            <th scope="col">작성날짜</th>
        </tr>
        </thead>
        <tbody id="boardList">
        </tbody>
    </table>
</div>

<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
     aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">게시글 작성하기</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <form>
                    <div class="form-group">
                        <label for="recipient-name" class="col-form-label">제목</label>
                        <input type="text" class="form-control" id="title">
                    </div>
                    <div class="form-group">
                        <label for="recipient-name" class="col-form-label">작성자</label>
                        <input type="text" class="form-control" id="username">
                    </div>
                    <div class="form-group">
                        <label for="message-text" class="col-form-label">내용</label>
                        <textarea class="form-control" id="contents"></textarea>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
                <button type="button" class="btn btn-primary" onclick="postBoard()">저장하기</button>
            </div>
        </div>
    </div>
</div>


</body>