안녕하세요.
저번 글에서 리스트를 구현하였습니다. 이번에는 게시글의 상세정보를 볼 수 있도록 상세페이지를 구현해보겠습니다.
먼저 PostController에 상세페이지 메서드를 추가해주세요!
@GetMapping("/post/view.do")
public String openPostView(@RequestParam final Long id, Model model) {
PostResponse post = postService.findPostById(id);
model.addAttribute("post", post);
return "post/view";
}
파라미터로 받은 Long id는 PostMapper의 findById 쿼리의 WHERE 조건으로 사용되는 게시글 번호 (PK)입니다.
post는 PostService의 findPostById()의 실행 결과(특정 게시글의 상세정보)를 담은 게시글 응답 객체입니다. 화면(HTML)에서는 "${post.변수명}"으로 응답 객체의 각 데이터에 접근합니다.
다음으로 리턴 해준 post/view html을 작성해보겠습니다.
write.html, list.html과 동일한 경로에 view.html을 추가하고, 코드를 입력해줄게요.
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="layout/basic">
<th:block layout:fragment="title">
<title>상세 페이지</title>
</th:block>
<th:block layout:fragment="content">
<div class="page_tits">
<h3>게시판 관리</h3>
<p class="path"><strong>현재 위치 :</strong> <span>게시판 관리</span> <span>리스트형</span> <span>상세정보</span></p>
</div>
<div class="content">
<section>
<table class="tb tb_row">
<colgroup>
<col style="width:10%;" /><col style="width:23%;" /><col style="width:10%;" /><col style="width:23%;" />
</colgroup>
<tbody>
<tr>
<th scope="row">등록일</th>
<td th:text="${#temporals.format( post.createdDate, 'yyyy-MM-dd HH:mm' )}"></td>
</tr>
<tr>
<th scope="row">제목</th>
<td>[[ ${post.title} ]]</td>
<th scope="row">조회</th>
<td colspan="3">[[ ${post.viewCnt} ]]</td>
</tr>
<tr>
<th scope="row">이름</th>
<td colspan="3">[[ ${post.writer} ]]</td>
</tr>
<tr>
<th scope="row">내용</th>
<td colspan="3">[[ ${post.content} ]]</td>
</tr>
</tbody>
</table>
<p class="btn_set">
<a th:href="@{/post/write.do( id=${post.id} )}" class="btns btn_bdr4 btn_mid">수정</a>
<button type="button" class="btns btn_bdr1 btn_mid">삭제</button>
<a th:href="@{/post/list.do}" class="btns btn_bdr3 btn_mid">뒤로</a>
</p>
</section>
</div> <!--/* .content */-->
</th:block>
</html>
layout=fragment="content"는 그동안의 html과 같이 실제 컨텐츠가 들어가는 부분입니다.
openPostView()에서 화면(HTML)으로 전달한 post 객체를 이용해서 필요한 게시글 정보를 텍스트 형식으로 출력합니다.
수정 버튼 -> "/post/write.do"
삭제 버튼 -> "/post/delete.do"
뒤로 버튼 -> "/post/list.do"
이제 애플리케이션을 실행시켜 결과를 확인해 보겠습니다. (http://localhost:8080/post/list.do)
이렇게 화면이 뜹니다.
다시보니 이 리스트에 제대로 되지 않은 부분이 있네요..
현재 번호에 번호대신 제목이 뜨고 하나씩 앞으로 밀려있습니다.. 이부분을 고쳐보겠습니다!
list.html 의 코드를
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="layout/basic">
<th:block layout:fragment="title">
<title>리스트 페이지</title>
</th:block>
<th:block layout:fragment="content">
<div class="page_tits">
<h3>게시판 관리</h3>
<p class="path"><strong>현재 위치 :</strong> <span>게시판 관리</span> <span>리스트형</span> <span>리스트</span></p>
</div>
<div class="content">
<section>
<!--/* 검색 */-->
<div class="search_box">
<form id="searchForm" onsubmit="return false;" autocomplete="off">
<div class="sch_group fl">
<select title="검색 유형 선택">
<option value="">전체 검색</option>
<option value="">제목</option>
<option value="">내용</option>
</select>
<input type="text" placeholder="키워드를 입력해 주세요." title="키워드 입력"/>
<button type="button" class="bt_search"><i class="fas fa-search"></i><span class="skip_info">검색</span></button>
</div>
</form>
</div>
<!--/* 리스트 */-->
<table class="tb tb_col">
<colgroup>
<col style="width:50px;"/><col style="width:7.5%;"/><col style="width:auto;"/><col style="width:10%;"/><col style="width:15%;"/><col style="width:7.5%;"/>
</colgroup>
<thead>
<tr>
<th scope="col"><input type="checkbox"/></th>
<th scope="col">번호</th>
<th scope="col">제목</th>
<th scope="col">작성자</th>
<th scope="col">등록일</th>
<th scope="col">조회</th>
</tr>
</thead>
<tbody>
<tr th:if="${not #lists.isEmpty( posts )}" th:each="row, status : ${posts}">
<td><input type="checkbox"/></td>
<td th:text="${status.size - status.index}"></td>
<td class="tl"><a th:href="@{/post/view.do( id=${row.id} )}" th:text="${row.title}"></a></td>
<td th:text="${row.writer}"></td>
<td th:text="${#temporals.format( row.createdDate, 'yyyy-MM-dd HH:mm' )}"></td>
<td th:text="${row.viewCnt}"></td>
</tr>
<tr th:unless="${not #lists.isEmpty( posts )}">
<td colspan="5">
<div class="no_data_msg">검색된 결과가 없습니다.</div>
</td>
</tr>
</tbody>
</table>
<!--/* 페이지네이션 */-->
<div class="paging">
<a href="#" class="page_bt first">첫 페이지</a><a href="#" class="page_bt prev">이전 페이지 그룹</a>
<p><span class="on">1</span><a href="#">2</a><a href="#">3</a><a href="#">4</a><a href="#">5</a><a href="#">6</a><a href="#">7</a><a href="#">8</a><a href="#">9</a><a href="#">10</a></p>
<a href="#" class="page_bt next">다음 페이지 그룹</a><a href="#" class="page_bt last">마지막 페이지</a>
</div>
<!--/* 버튼 */-->
<p class="btn_set tr">
<a th:href="@{/post/write.do}" class="btns btn_st3 btn_mid">글쓰기</a>
</p>
</section>
</div> <!--/* .content */-->
</th:block>
</html>
이렇게 고쳐주시면
정상적으로 됩니다!!
1번 게시글 제목을 클릭해보겠습니다.
그러면 이렇게 게시글의 상세페이지를 볼 수 있습니다.
상세 페이지로 들어가게 되면 위의 주소가
/post/view.do?id=2 이런식으로 변경됩니다.
이렇게 파라미터 정보가 key=value 형식으로 이루어진 것을 쿼리 스트링 이라고 합니다.
쿼리 스트링의 첫 번째 파라미터는 '?'로 시작하고, 두 번째 파라미터부터는 '&'로 구분합니다.
쿼리스트링으로 연결된 URL은 HTTP 요청 메서드 중 GET 방식임을 의미합니다.
이번에는 상세페이지에서 글을 수정할 수 있도록 해보겠습니다.
write.html 파일을 수정해줍니다.
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="layout/basic">
<th:block layout:fragment="title">
<title>글작성 페이지</title>
</th:block>
<th:block layout:fragment="content">
<div class="page_tits">
<h3>게시판</h3>
<p class="path"><strong>글 작성</strong></p>
</div>
<div class="content">
<section>
<form id="saveForm" method="post" autocomplete="off">
<!--/* 게시글 수정인 경우, 서버로 전달할 게시글 번호 (PK) */-->
<input type="hidden" id="id" name="id" th:if="${post != null}" th:value="${post.id}" />
<!--/* 서버로 전달할 공지글 여부 */-->
<input type="hidden" id="noticeYn" name="noticeYn" />
<table class="tb tb_row">
<colgroup>
<col style="width:15%;" /><col style="width:35%;" /><col style="width:15%;" /><col style="width:35%;" />
</colgroup>
<tbody>
<tr>
<th scope="row">등록일</th>
<td colspan="3"><input type="text" id="createdDate" name="createdDate" readonly /></td>
</tr>
<tr>
<th>제목 <span class="es">필수 입력</span></th>
<td colspan="3"><input type="text" id="title" name="title" maxlength="50" placeholder="제목을 입력해 주세요." /></td>
</tr>
<tr>
<th>이름 <span class="es">필수 입력</span></th>
<td colspan="3"><input type="text" id="writer" name="writer" maxlength="10" placeholder="이름을 입력해 주세요." /></td>
</tr>
<tr>
<th>내용 <span class="es">필수 입력</span></th>
<td colspan="3"><textarea id="content" name="content" cols="50" rows="10" placeholder="내용을 입력해 주세요."></textarea></td>
</tr>
</tbody>
</table>
</form>
<p class="btn_set">
<button type="button" id="saveBtn" onclick="savePost();" class="btns btn_st3 btn_mid">저장</button>
<a th:href="@{/post/list.do}" class="btns btn_bdr3 btn_mid">뒤로</a>
</p>
</section>
</div> <!--/* .content */-->
</th:block>
<th:block layout:fragment="script">
<script th:inline="javascript">
/*<![CDATA[*/
window.onload = () => {
initCreatedDate();
}
// 등록일 초기화
function initCreatedDate() {
document.getElementById('createdDate').value = dayjs().format('YYYY-MM-DD');
}
// 게시글 저장(수정)
function savePost() {
const form = document.getElementById('saveForm');
const fields = [form.title, form.writer, form.content];
const fieldNames = ['제목', '이름', '내용'];
for (let i = 0, len = fields.length; i < len; i++) {
isValid(fields[i], fieldNames[i]);
}
document.getElementById('saveBtn').disabled = true;
form.action = [[ ${post == null} ]] ? '/post/save.do' : '/post/update.do';
form.submit();
}
window.onload = () => {
renderPostInfo();
}
// 게시글 상세정보 렌더링
function renderPostInfo() {
const post = [[ ${post} ]];
if ( !post ) {
initCreatedDate();
return false;
}
const form = document.getElementById('saveForm');
const fields = ['id', 'title', 'content', 'writer', 'noticeYn'];
form.isNotice.checked = post.noticeYn;
form.createdDate.value = dayjs(post.createdDate).format('YYYY-MM-DD HH:mm');
fields.forEach(field => {
form[field].value = post[field];
})
}
// 등록일 초기화
function initCreatedDate() {
document.getElementById('createdDate').value = dayjs().format('YYYY-MM-DD');
}
// 게시글 저장(수정)
function savePost() {
const form = document.getElementById('saveForm');
const fields = [form.title, form.writer, form.content];
const fieldNames = ['제목', '이름', '내용'];
for (let i = 0, len = fields.length; i < len; i++) {
isValid(fields[i], fieldNames[i]);
}
document.getElementById('saveBtn').disabled = true;
form.noticeYn.value = form.isNotice.checked;
form.action = [[ ${post == null} ]] ? '/post/save.do' : '/post/update.do';
form.submit();
}
/*]]>*/
</script>
</th:block>
</html>
window.onload() 는 자바스크립트의 이벤트로, 페이지가 로딩되는 시점에 단 한번 실행되는 코드입니다.
게시글 상세정보는 상세 페이지에 접속했을 때 한 번만 보여주면 되기 때문에 여러번 호출하지 않도록 합니다.
rederPostInfo()는 게시글 상세정보를 화면에 렌더링 하는 역할을 하는 함수입니다.
신규 게시글 작성은 post 객체를 화면으로 전달하지 않기 때문에 오늘 날짜를 렌더링 한 후 로직을 종료하고, 기존 게시글을 수정하는 경우에만 메인 로직이 실행됩니다.
메인로직이 실행되면, saveForm에서 fields에 선언한 필드명에 해당되는 필드를 찾아, 기존 게시글 정보를 렌더링 합니다.
이제 상세페이지로 가서 수정 버튼을 클릭하게 되면
이렇게 정상적으로 수정 페이지가 뜹니다!
게시글 수정 기능은 미리 구현해 두었기 때문에 서비스의 메서드만 연결해주면 됩니다.
PostController에 메서드를 추가해줍니다.
@PostMapping("/post/update.do")
public String updatePost(final PostRequest params) {
postService.updatePost(params);
return "redirect:/post/list.do";
}
}
이제 결과를 확인해보겠습니다.
이렇게 수정해주고 저장버튼을 눌러주면
이렇게 수정된 것을 확인할 수 있습니다!
상세페이지를 들어가도
잘 업데이트 된 것을 확인할 수 있습니다.
끗!
[참고 사이트]
'IT > 게시판 프로젝트' 카테고리의 다른 글
git 연동하기 (1) | 2023.05.09 |
---|---|
springboot 게시판 만들기 8 (게시글 삭제 기능) (0) | 2023.04.14 |
springboot 게시판 만들기 6 (게시글 리스트 조회 기능) (0) | 2023.04.14 |
springboot 게시판 만들기 5 (게시글 등록 기능) (0) | 2023.04.14 |
springboot 게시판 만들기 4 (게시판 CRUD 처리) (0) | 2023.04.07 |