Published 2022. 8. 5. 11:23
이전글보기
https://aamoos.tistory.com/680
목표
- 리스트에서 글 제목을 누르면 상세로 가고, 수정시 validation 체크, 수정 기능을 개발해보겠습니다.
list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="layout/default_layout">
<div layout:fragment="content" class="content">
<form th:action th:object="${form}" method="get">
<nav class="container">
<br>
<div class="input-group">
<input type="text" name="searchVal" th:value="${searchVal}" class="form-control" placeholder="제목을 입력해주세요.">
<button type="submit" class="btn btn-secondary">검색</button>
</div>
<br>
<table class="table table-hover">
<colgroup>
<col width="2%" />
<col width="5%" />
<col width="20%" />
<col width="5%" />
<col width="5%" />
<col width="5%" />
</colgroup>
<thead>
<tr>
<th>
<label class="checkbox-inline">
<input type="checkbox" id="allCheckBox" onclick="allChecked()">
</label>
</th>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>날짜</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
<tr th:each="list, index : ${list}">
<td>
<label class="checkbox-inline">
<input type="checkbox" name="chk" class="chk"onclick="chkClicked()" th:value="${list.id}">
</label>
<td th:text="${totalCount - (size * number) - index.index}"></td>
<td><a th:text="${list.title}" th:href="@{/update/{boardId}(boardId=${list.id})}"></a></td>
<td th:text="${list.username}"></td>
<td th:text="${#temporals.format(list.regDate, 'yyyy-MM-dd')}"></td>
<td th:text="${list.viewCount}"></td>
</tr>
</tbody>
</table>
<br>
<div class="d-flex justify-content-end">
<a href='javascript:boardDelete();' class="btn btn-danger">글삭제</a>
<a href="/write" class="btn btn-primary">글쓰기</a>
</div>
<br>
<nav class="container d-flex align-items-center justify-content-center" aria-label="Page navigation example"
th:with="start=${(list.number/maxPage)*maxPage + 1},
end=(${(list.totalPages == 0) ? 1 : (start + (maxPage - 1) < list.totalPages ? start + (maxPage - 1) : list.totalPages)})">
<ul class="pagination">
<li th:if="${start > 1}" class="page-item">
<a th:href="@{/?(page=0, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">««</span>
</a>
</li>
<li th:if="${start > 1}" class="page-item">
<a th:href="@{/?(page=${start - maxPage-1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li th:each="page: ${#numbers.sequence(start, end)}" class="page-item" th:classappend="${list.number+1 == page} ? active">
<a th:href="@{/?(page=${page-1}, searchVal=${searchVal})}" th:text="${page}" class="page-link" href="#">1</a>
</li>
<li th:if="${end < list.totalPages}" class="page-item">
<a th:href="@{/?(page=${start + maxPage -1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
<li th:if="${end < list.totalPages}" class="page-item">
<a th:href="@{/?(page=${list.totalPages-1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">»»</span>
</a>
</li>
</ul>
</nav>
</nav>
</form>
</div>
</html>
<script>
//체크박스 전체 선택 클릭 이벤트
function allChecked(target){
//전체 체크박스 버튼
const checkbox = document.getElementById('allCheckBox');
//전체 체크박스 버튼 체크 여부
const is_checked = checkbox.checked;
//전체 체크박스 제외한 모든 체크박스
if(is_checked){
//체크박스 전체 체크
chkAllChecked()
}
else{
//체크박스 전체 해제
chkAllUnChecked()
}
}
//자식 체크박스 클릭 이벤트
function chkClicked(){
//체크박스 전체개수
const allCount = document.querySelectorAll(".chk").length;
//체크된 체크박스 전체개수
const query = 'input[name="chk"]:checked'
const selectedElements = document.querySelectorAll(query)
const selectedElementsCnt = selectedElements.length;
//체크박스 전체개수와 체크된 체크박스 전체개수가 같으면 전체 체크박스 체크
if(allCount == selectedElementsCnt){
document.getElementById('allCheckBox').checked = true;
}
//같지않으면 전체 체크박스 해제
else{
document.getElementById('allCheckBox').checked = false;
}
}
//체크박스 전체 체크
function chkAllChecked(){
document.querySelectorAll(".chk").forEach(function(v, i) {
v.checked = true;
});
}
//체크박스 전체 체크 해제
function chkAllUnChecked(){
document.querySelectorAll(".chk").forEach(function(v, i) {
v.checked = false;
});
}
//글삭제
function boardDelete(){
//체크박스 체크된 항목
const query = 'input[name="chk"]:checked'
const selectedElements = document.querySelectorAll(query)
//체크박스 체크된 항목의 개수
const selectedElementsCnt = selectedElements.length;
if(selectedElementsCnt == 0){
alert("삭제할 항목을 선택해주세요.");
return false;
}
else{
if (confirm("정말로 삭제하시겠습니까?")) {
//배열생성
const arr = new Array(selectedElementsCnt);
document.querySelectorAll('input[name="chk"]:checked').forEach(function(v, i) {
arr[i] = v.value;
});
const form = document.createElement('form');
form.setAttribute('method', 'post'); //Post 메소드 적용
form.setAttribute('action', '/delete');
var input1 = document.createElement('input');
input1.setAttribute("type", "hidden");
input1.setAttribute("name", "boardIds");
input1.setAttribute("value", arr);
form.appendChild(input1);
console.log(form);
document.body.appendChild(form);
form.submit();
}
}
}
</script>
- 제목 클릭시 /update/{id} url로 이동합니다.
update.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/default_layout}">
<head layout:fragment="css">
<style>
.fieldError {
border-color: #bd2130;
}
.form-group p{
color: red;
}
</style>
</head>
<div layout:fragment="content" class="content">
<form th:action="@{/update/ + *{id}}" th:object="${boardDto}" method="post">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" th:value="*{id}" />
<article>
<div class="container" role="main">
<div class="form-group">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" name="title" th:value="*{title}" placeholder="제목을 입력해 주세요" th:class="${#fields.hasErrors('title')}? 'form-control fieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Incorrect date</p>
</div>
<br>
<div class="mb-3">
<label for="reg_id">작성자</label>
<input type="text" class="form-control" id="reg_id" name="regId" value="관리자" readonly>
</div>
<br>
<div class="mb-3">
<label for="content">내용</label>
<textarea class="form-control" rows="5" id="content" name="content" th:text="*{content}" placeholder="내용을 입력해 주세요"></textarea>
</div>
<br>
<br>
<div>
<button type="submit" class="btn btn-sm btn-primary" id="btnSave">수정</button>
<button onclick="location.href='/'" type="button" class="btn btn-sm btn-primary" id="btnList">목록</button>
</div>
</div>
</article>
</form>
</div>
</html>
<script>
</script>
BoardController.java
package jpa.board.controller;
import jpa.board.dto.BoardDto;
import jpa.board.entity.Board;
import jpa.board.repository.BoardRepository;
import jpa.board.repository.CustomBoardRepository;
import jpa.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.function.LongToIntFunction;
/**
* packageName : jpa.board.controller
* fileName : BoardController
* author : 김재성
* date : 2022-08-01
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2022-08-01 김재성 최초 생성
*/
@Controller
@RequiredArgsConstructor
public class BoardController {
private final CustomBoardRepository customBoardRepository;
private final BoardService boardService;
/**
* @methodName : list
* @date : 2022-08-02 오후 2:07
* @author : 김재성
* @Description: 게시판 목록화면
**/
@GetMapping("/")
public String list(String searchVal, Pageable pageable, Model model){
Page<BoardDto> results = customBoardRepository.selectBoardList(searchVal, pageable);
model.addAttribute("list", results);
model.addAttribute("maxPage", 5);
model.addAttribute("searchVal", searchVal);
pageModelPut(results, model);
return "board/list";
}
/**
* @methodName : pageModelPut
* @date : 2022-08-02 오후 4:36
* @author : 김재성
* @Description: pagenation 관련 값 model 넣기
**/
private void pageModelPut(Page<BoardDto> results, Model model){
model.addAttribute("totalCount", results.getTotalElements());
model.addAttribute("size", results.getPageable().getPageSize());
model.addAttribute("number", results.getPageable().getPageNumber());
}
/**
* @methodName : write
* @date : 2022-08-02 오후 2:07
* @author : 김재성
* @Description: 게시판 글쓰기화면
**/
@GetMapping("/write")
public String write(Model model){
model.addAttribute("boardDto", new BoardDto());
return "board/write";
}
/**
* @methodName : save
* @date : 2022-08-03 오후 2:15
* @author : 김재성
* @Description: 게시판 글 등록
**/
@PostMapping("/write")
public String save(@Valid BoardDto boardDto, BindingResult result){
//유효성검사 걸릴시
if(result.hasErrors()){
return "board/write";
}
boardService.saveBoard(boardDto);
return "redirect:/";
}
/**
* @methodName : update
* @date : 2022-08-02 오후 2:07
* @author : 김재성
* @Description: 게시판 수정화면
**/
@GetMapping("/update/{boardId}")
public String detail(@PathVariable Long boardId, Model model){
Board board = boardService.selectBoardDetail(boardId);
BoardDto boardDto = new BoardDto();
boardDto.setId(boardId);
boardDto.setTitle(board.getTitle());
boardDto.setContent(board.getContent());
model.addAttribute("boardDto", boardDto);
return "board/update";
}
/**
* @methodName : update
* @date : 2022-08-05 오전 11:20
* @author : 김재성
* @Description: 게시판 수정
**/
@PutMapping("/update/{boardId}")
public String update(@Valid BoardDto boardDto, BindingResult result){
//유효성검사 걸릴시
if(result.hasErrors()){
return "board/update";
}
boardService.saveBoard(boardDto);
return "redirect:/";
}
/**
* @methodName : delete
* @date : 2022-08-05 오전 11:20
* @author : 김재성
* @Description: 게시판 목록 체크박스 선택 삭제
**/
@PostMapping("/delete")
public String delete(@RequestParam List<String> boardIds){
for(int i=0; i<boardIds.size(); i++){
Long id = Long.valueOf(boardIds.get(i));
boardService.deleteBoard(id);
}
return "redirect:/";
}
}
- /update{id} api를 타면 validation 처리를 한후 이상이없으면 저장하는 방식입니다.
put 방식 설정법
https://aamoos.tistory.com/682
boardService.java
package jpa.board.service;
import jpa.board.dto.BoardDto;
import jpa.board.entity.Board;
import jpa.board.entity.Member;
import jpa.board.repository.BoardRepository;
import jpa.board.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* packageName : jpa.board.service
* fileName : BoardService
* author : 김재성
* date : 2022-08-03
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2022-08-03 김재성 최초 생성
*/
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
private final MemberRepository memberRepository;
/**
* @methodName : selectBoard
* @date : 2022-08-03 오후 5:50
* @author : 김재성
* @Description: 상세조회
**/
public Board selectBoardDetail(Long id){
return boardRepository.findById(id).get();
}
/**
* @methodName : saveBoard
* @date : 2022-08-03 오후 2:13
* @author : 김재성
* @Description: 글등록
**/
@Transactional
public Board saveBoard(BoardDto boardDto){
List<Member> memberList = memberRepository.findAll();
Member member = memberList.get(0);
Board board = null;
//insert
if(boardDto.getId() == null){
board = boardDto.toEntity(member);
boardRepository.save(board);
}
//update
else{
board = boardRepository.findById(boardDto.getId()).get();
board.update(boardDto.getTitle(), boardDto.getContent());
}
return board;
}
/**
* @methodName : deleteBoard
* @date : 2022-08-03 오후 2:14
* @author : 김재성
* @Description: 글 삭제
**/
@Transactional
public Board deleteBoard(Long id){
Board board = boardRepository.findById(id).get();
//플래그값이 Y이면 논리삭제
board.delete("Y");
return board;
}
}
결과화면
- 수정전 게시물
- 필수값이 없는경우
- 수정후
- 게시글이 정상적으로 수정되었습니다. 다음장에는 조회수 업데이트를 개발해보겠습니다.
다음글 보기
https://aamoos.tistory.com/683