관리 메뉴

bright jazz music

guestbook : 10. guestbook 게시글 수정/삭제 처리(2) 본문

Framework/Spring

guestbook : 10. guestbook 게시글 수정/삭제 처리(2)

bright jazz music 2022. 7. 21. 23:08

수정 처리 시 고려할 사항

  • 수정  시 수정해야 하는 내용('제목', '내용', '글번호')이 전달되어야 한다.
  • 수정 후에는 목록 페이지로 이동하거나 조회 페이지로 이동해야 한다. 이 때 기존 페이지의 번호를 유지하는 것이 좋다.

 

1. 수정 절차 중 페이지 번호를 유지하도록 처리

  • 현재 modify.html에는 '/guestbook/read'로 이동할 때 페이지 번호(page)가 파라미터로 전달된다.
  • 이는 수정 페이지로 이동하는 경우에도 마찬가지이다.
  • 이를 이용해서 수정 완료된 후에도 동일한 정보를 유지할 수 있도록 page값을 <form>태그에 추가하여 전달한다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
    <th:block th:fragment="content">

        <h1 class="mt-4">GuestBook Modify Page!!</h1> <!-- 제목 변경 -->
        <form action="/guestbook/modify" method="post"> <!-- form태그로 감싸기  -->

            <!-- 페이지 번호: 수정 완료 후에도 동일한 정보를 유지하도록 하기 위해  추가!!!!--> 
            <input type="hidden" name="page" th:value="${requestDTO.page}">

            <div class="form-group">
                <label>Gno</label> <!--수정불가-->
                <input type="text" class="form-control" name="gno" th:value="${dto.gno}" readonly>
                <!--
                name 속성은 DTO의 프로퍼티와 같게 해줘야 한다. DTO에서 소문자로 선언했으므로
                여기서도 소문자 gno로 해야 한다. 대문자로 했다가 아래와 같은 에러를 만났다.
                아래의 태그들의 속성 값도 전부 소문자로 해줄 것.

                    java.lang.IllegalStateException:
                    Optional long parameter 'gno' is present but cannot be translated into a null value
                    due to being declared as a primitive type. Consider declaring it as object wrapper
                    for the corresponding primitive type.
                -->
            </div>

            <div class="form-group">
                <label>Title</label> <!--수정가능-->
                <input type="text" class="form-control" name="title" th:value="${dto.title}">
            </div>

            <div class="form-group">
                <label>Content</label> <!--수정가능-->
                <textarea area class="form-control" rows="5" name="content">[[${dto.content}]]</textarea>
            </div>

            <div class="form-group">
                <label>Writer</label> <!--수정불가-->
                <input type="text" class="form-control" name="writer" th:value="${dto.writer}" readonly>
            </div>

            <div class="form-group">
                <label>RegDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
                <input type="text" class="form-control"
                       th:value="${#temporals.format(dto.regDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
            </div>

            <div class="form-group">
                <label>ModDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
                <input type="text" class="form-control"
                       th:value="${#temporals.format(dto.modDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
            </div>
        </form> <!-- form태그로 감싸기 -->

        <!--수정/삭제는 form 태그의 action을 이용해서 처리할 수 있음. 이 부분은 추후 처리-->
        <!-- 버튼 구분을 위해 class 속성에 구분용 단어를 첨자하였다. -->
        <!--<button type="button" class="btn btn-primary">Modify</button>-->
        <button type="button" class="btn btn-primary modifyBtn">Modify</button>

        <!--<button type="button" class="btn btn-info">List</button>-->
        <button type="button" class="btn btn-info listBtn">List</button>

        <!--<button type="button" class="btn btn-danger">Remove</button>-->
        <button type="button" class="btn btn-danger removeBtn">Remove</button>

        <script th:inline="javascript">
            var actionForm = $("form"); //form 태그 객체

            $(".removeBtn").click(function(){
                //삭제 버튼 처리. 버튼을 누르면 form태그의 action 속성과 method 속성을 조정한다.
               actionForm
                    .attr("action", "/guestbook/remove")
                    .attr("method", "post");
                actionForm.submit();
            })
        </script>

    </th:block>
</th:block>

</html>

 

2. 컨트롤러 수정

  • GuestbookController.java에서는 Guestbook 자체의 수정과 페이징 관련 데이터 처리를 같이 처리해야 한다.
//GuestbookController.java

package com.example.guestbook.controller;

import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.entity.Guestbook;
import com.example.guestbook.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
@RequestMapping("/guestbook")
@Log4j2
@RequiredArgsConstructor //자동 주입을 위한 어노테이션
public class GuestbookController {

    private final GuestbookService service; // final로 선언

    @GetMapping("/")
    public String index(){
        return "redirect:/guestbook/list";
    }

    @GetMapping("/list")
    public String list(PageRequestDTO pageRequestDTO, Model model){

        log.info("list...................." + pageRequestDTO);

        // model에 result를 key로 담아서 list 페이지에 뿌려준다.
        model.addAttribute("result", service.getList(pageRequestDTO));

        return "/guestbook/list";
    }

    /*
        SpringDAta JPA를 이용하는 경우 @Pageable 어노테이션으로 Pageable 타입을 이용할 수도 있고,
        application.properties에 0이 아닌 1부터 페이지 번호를 시작하도록 받을 수 있도록 처리할 수도 있다.
        예제에서는 그냥 0부터 받는 방식을 사용하였다. 추후 검색조건 등과 같이 추가로 전달되어야 하는 데이터가
        많을 경우 더욱 복잡해질 수 있기 때문이다.
     */



    @GetMapping("/register")
    public void register(){
        log.info("register get...");
    }

    @PostMapping("/register")
    public String registerPost(GuestbookDTO dto, RedirectAttributes redirectAttributes){
        log.info("dto..." + dto);

        //새로 추가된 엔티티의 번호
        Long gno = service.register(dto);

        redirectAttributes.addFlashAttribute("msg", gno);
        return "redirect:/guestbook/list";
        /*
        * 등록 작업은 GET 방식에서는  화면을 보여주고 POST 방식에서는 처리 후에 목록페이지로 이동하도록 설계.
        * 이 때 RedirectAttributes를 이용해서 한 번만 화면에서 'msg'라는 이름의 변수를 사용할 수 있도록 처리.
        * addFlashAttribute()는 단 한 번만 데이터를 전달하는 용도로 사용함.
        * --> 브라우저에 전달되는 'msg'를 이용해서 화면 창에 모달 창을 보여주는 용도로 사용.
        * */
    }

    @GetMapping({"/read", "/modify"}) // modify도 추가하였다. 조회/수정을 위한 메서드
    public void read(long gno, @ModelAttribute("requestDTO") PageRequestDTO requestDTO, Model model){
        log.info("gno: " + gno);
        GuestbookDTO dto = service.read(gno);
        model.addAttribute("dto", dto);
    }

    @PostMapping("/remove") //삭제를 위한 메서드
    public String remove(long gno, RedirectAttributes redirectAttributes) {
        System.out.println("#######################################################################");
        log.info("gno: " + gno);

        service.remove(gno); //서비스 계층으로 전달

        redirectAttributes.addFlashAttribute("msg", gno); //모달창 사용할 목적
        return "redirect:/guestbook/list";
    }

    @PostMapping("/modify")
    public String modify(GuestbookDTO dto, @ModelAttribute("requestDTO") PageRequestDTO requestDTO
            , RedirectAttributes redirectAttributes){

        log.info("post modify.............");
        log.info("dto" + dto);

        service.modify(dto);

        redirectAttributes.addAttribute("page", requestDTO.getPage());
        redirectAttributes.addAttribute("gno", dto.getGno());

        return "redirect:/guestbook/read";

        /* 파라미터
        * GuestbookDTO : 수정해야 하는 글의 정보를 보유
        * PageRequestDTO : 기존 페이지 정보를 유지하기 위한 목적
        * RedirectAttributes : 처리 후 리다이렉트. 이 때 목록 페이지로 이동. 기존 페이지 정보도 유지!!
        * */
    }


}

 

 

3. 수정 화면에서의 이벤트 처리

컨트롤러를 호출하는 화면에서는 'Modify' 버튼의 이벤트 처리를 통해 작업

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
    <th:block th:fragment="content">

        <h1 class="mt-4">GuestBook Modify Page!!</h1> <!-- 제목 변경 -->
        <form action="/guestbook/modify" method="post"> <!-- form태그로 감싸기  -->

            <!-- 페이지 번호: 수정 완료 후에도 동일한 정보를 유지하도록 하기 위해  추가!!!!-->
            <input type="hidden" name="page" th:value="${requestDTO.page}">

            <div class="form-group">
                <label>Gno</label> <!--수정불가-->
                <input type="text" class="form-control" name="gno" th:value="${dto.gno}" readonly>
                <!--
                name 속성은 DTO의 프로퍼티와 같게 해줘야 한다. DTO에서 소문자로 선언했으므로
                여기서도 소문자 gno로 해야 한다. 대문자로 했다가 아래와 같은 에러를 만났다.
                아래의 태그들의 속성 값도 전부 소문자로 해줄 것.

                    java.lang.IllegalStateException:
                    Optional long parameter 'gno' is present but cannot be translated into a null value
                    due to being declared as a primitive type. Consider declaring it as object wrapper
                    for the corresponding primitive type.
                -->
            </div>

            <div class="form-group">
                <label>Title</label> <!--수정가능-->
                <input type="text" class="form-control" name="title" th:value="${dto.title}">
            </div>

            <div class="form-group">
                <label>Content</label> <!--수정가능-->
                <textarea area class="form-control" rows="5" name="content">[[${dto.content}]]</textarea>
            </div>

            <div class="form-group">
                <label>Writer</label> <!--수정불가-->
                <input type="text" class="form-control" name="writer" th:value="${dto.writer}" readonly>
            </div>

            <div class="form-group">
                <label>RegDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
                <input type="text" class="form-control"
                       th:value="${#temporals.format(dto.regDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
            </div>

            <div class="form-group">
                <label>ModDate</label> <!--수정불가, name 속성도 제거. 화면 수정 자체도 불가하고 jpa에서 자동처리 할 것이기 때문-->
                <input type="text" class="form-control"
                       th:value="${#temporals.format(dto.modDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
            </div>
        </form> <!-- form태그로 감싸기 -->

        <!--수정/삭제는 form 태그의 action을 이용해서 처리할 수 있음. 이 부분은 추후 처리-->
        <!-- 버튼 구분을 위해 class 속성에 구분용 단어를 첨자하였다. -->
        <!--<button type="button" class="btn btn-primary">Modify</button>-->
        <button type="button" class="btn btn-primary modifyBtn">Modify</button>

        <!--<button type="button" class="btn btn-info">List</button>-->
        <button type="button" class="btn btn-info listBtn">List</button>

        <!--<button type="button" class="btn btn-danger">Remove</button>-->
        <button type="button" class="btn btn-danger removeBtn">Remove</button>

        <script th:inline="javascript">
            var actionForm = $("form"); //form 태그 객체

            $(".removeBtn").click(function(){
                //삭제 버튼 처리. 버튼을 누르면 form태그의 action 속성과 method 속성을 조정한다.
               actionForm
                    .attr("action", "/guestbook/remove")
                    .attr("method", "post");
                actionForm.submit();
            })

            $(".modifyBtn").click(function (){
                //수정버튼 처리
                if(!confirm("수정하시겠습니까?")){
                    return;
                }
                actionForm
                    .attr("action", "/guestbook/modify")
                    .attr("method", "post")
                    .submit();
            });
        </script>

    </th:block>
</th:block>

</html>

 

 

 

 

적용 완료 화면

 

목록에서 수정할 게시글 클릭

 

수정 페이지로 이동. Modify버튼 클릭

 

 

수정 가능 페이지로 이동.

 

 

내용 수정 후 Modify 버튼 클릭

 

 

모달창 생성. Cancel을 누면 모달창만 사라진다.

 

 

Ok 버튼을 누르면 이와 같이 수정 내용이 반영된 조회 페이지로 이동한다.

Comments