다른 분들의 도움으로 원페이지 게시판 만들기도 성공했다.
게시글 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">×</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>
'항해99 > 개발일지' 카테고리의 다른 글
20220128 개발일지 (0) | 2022.01.29 |
---|---|
20220127 개발일지 #원페이지 게시판 controller/service 분리 (0) | 2022.01.27 |
20220125 개발일지 #서버시간 변경하기 (0) | 2022.01.25 |
20220124 개발일지 #게시판 만들기(멀티페이지) (0) | 2022.01.25 |
20220122 개발일지 (0) | 2022.01.23 |