이전글보기

https://aamoos.tistory.com/680

 

[Spring Jpa] 11. 게시판 만들기 - 유효성 검증, @Valid 사용해보기

이전글 보기 https://aamoos.tistory.com/679 [Spring Jpa] 10. 게시판 만들기 - 게시판 글 등록하기 이전글 보기 https://aamoos.tistory.com/678 [Spring Jpa] 9. 게시판 만들기 - 체크박스 선택삭제 기능 만들..

aamoos.tistory.com

 

목표

- 리스트에서 글 제목을 누르면 상세로 가고, 수정시 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">&laquo;&laquo;</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">&laquo;</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">&raquo;</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">&raquo;&raquo;</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

 

[Spring] form 전송시 put, delete설정

- put이나 patch로 보낼경우 해당 에러가 발생하는 경우가 있습니다. - put이나, delete를 사용하려면 간단한 설정이 필요한대 그것을 알아보겠습니다. application.yml mvc: hidden-method: filter: enabled: t..

aamoos.tistory.com

 

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

 

[Spring Jpa] 13. 게시판 만들기 - 게시판 조회수 개발

이전글 보기 https://aamoos.tistory.com/681 [Spring Jpa] 12. 게시판 만들기 - 글 상세, 수정하기 이전글보기 https://aamoos.tistory.com/680 [Spring Jpa] 11. 게시판 만들기 - 유효성 검증, @Valid 사용해보..

aamoos.tistory.com

 

복사했습니다!