Todo List - Ajax 버전

송지윤·2024년 4월 8일

Spring Framework

목록 보기
11/65

Ajax(Asynchronous JavaScript and XML)는 비동기적으로 서버와 브라우저 간에 데이터를 교환하는 기술로 웹 페이지를 새로고침하지 않고도 서버로부터 데이터를 받아와서 웹 페이지의 일부분을 업데이트할 수 있도록 해줌
-> 기존에는 웹 페이지를 다시 로드할 때 전체 페이지를 다시 받아와야했기 때문에 사용자 경험이 좋지 않았음
-> Ajax를 사용하면 웹 페이지를 로드한 후에도 웹 페이지와 서버 간에 데이터를 주고 받을 수 있으므로 사용자 경험을 향상시킬 수 있다.
Asynchronous : 비동기
synchronous : 동기

JavaScript 연결해서 사용

html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TodoList - Ajax</title>

    <link rel="stylesheet" href="/css/ajax-main.css">
</head>
<body>
    <h1>Todo List - Ajax 버전</h1>
    <pre>
        Ajax(Asynchronous JavaScript and XML)는 비동기적으로 서버와 브라우저 간에 데이터를 교환하는 기술로
        웹 페이지를 새로고침 하지 않고도 서버로부터 데이터를 받아와서 웹 페이지의 일부분을 업데이트할 수 있도록 해주는 기술
        -> 기존에는 웹 페이지를 다시 로드할 때 전체 페이지를 다시 받아와야했기 때문에 사용자 경험이 좋지 않았음
        -> Ajax를 사용하면 웹 페이지를 로드한 후에도 웹 페이지와 서버 간에 데이터를 주고 받을 수 있으므로 사용자 경험을
        향상시킬 수 있다.
        Asynchronous : 비동기
        synchronous : 동기
        에이젝스 아작스
    </pre>

    <hr>

    <!-- form 태그 제출은 무조건 동기식 -->
    <div>
        <h4>할 일 추가</h4>

        <div>
            제목 : <input type="text" id="todoTitle">
        </div>

        <div>
            <textarea id="todoContent"
                cols="50" rows="5" placeholder="상세 내용"></textarea>
        </div>

        <button id="addBtn">추가하기</button>
    </div>

    <h3 id="todoHeader">
        전체 Todo 개수 : <span id="totalCount">0</span>개
        /
        완료된 Todo 개수 : <span id="completeCount">0</span><button id="reloadBtn">새로고침</button>
    </h3>

    <table border="1" style="border-collapse: collapse;">
        <thead>
            <th>번호</th>
            <th>할 일 제목</th>
            <th>완료 여부</th>
            <th>등록 날짜</th>
        </thead>

        <tbody id="tbody">

        </tbody>
    </table>

    <!-- 
        할 일 상세 조회 시 출력되는 화면(모달창)
        popup layer : 현재 페이지 위에 새로운 레이어를 띄우는 것
     -->

    <!-- 처음에 숨겨 놓기 -->
    <div id="popupLayer" class="popup-hidden">
        <div class="popup-row">
            번호 : <span id="popupTodoNo"></span>
            |
            제목 : <span id="popupTodoTitle"></span>

            <span id="popupClose">&times;</span>
        </div>

        <div class="popup-row">
            완료 여부 : <span id="popupComplete"></span>
            |
            등록일 : <span id="popupRegDate"></span>
        </div>

        <div class="popup-row">
            [내용]
            <div id="popupTodoContent"></div>
        </div>

        <div class="btn-container">
            <button id="changeComplete">완료 여부 변경</button>
            <button id="updateView">수정</button>
            <button id="deleteBtn">삭제</button>
        </div>
    </div>
     
    <!-- 수정 팝업 레이어 (처음에 숨겨져 있음) -->
    <div id="updateLayer" class="popup-hidden">
        <div class="popup-row">
          제목 : <input type="text" id="updateTitle">
        </div>

        <div class="popup-row">
            <textarea id="updateContent" cols="50" rows="5"></textarea>
        </div>

        <div class="btn-container">
            <button id="updateBtn">수정</button>     
            <button id="updateCancel">취소</button>     
        </div>
    </div>


    <script src="/js/ajax-main.js"></script>
</body>
</html>
/* 요소 얻어와서 변수에 저장 */
const totalCount = document.querySelector("#totalCount");
const completeCount = document.querySelector("#completeCount");
const reloadBtn = document.querySelector("#reloadBtn");

// 할 일 추가 관련 요소
const todoTitle = document.querySelector("#todoTitle");
const todoContent = document.querySelector("#todoContent");
const addBtn = document.querySelector("#addBtn");

// 할 일 목록 조회 관련 요소
const tbody = document.querySelector("#tbody");

// 할 일 상세 조회 관련 요소
const popupLayer = document.querySelector("#popupLayer");
const popupTodoNo = document.querySelector("#popupTodoNo");
const popupTodoTitle = document.querySelector("#popupTodoTitle");
const popupComplete = document.querySelector("#popupComplete");
const popupRegDate = document.querySelector("#popupRegDate");
const popupTodoContent = document.querySelector("#popupTodoContent");
const popupClose = document.querySelector("#popupClose");

// 상세 조회 버튼
const deleteBtn = document.querySelector("#deleteBtn");
const updateView = document.querySelector("#updateView");
const changeComplete = document.querySelector("#changeComplete");

// 수정 레이어 버튼
const updateLayer = document.querySelector("#updateLayer");
const updateTitle = document.querySelector("#updateTitle");
const updateContent = document.querySelector("#updateContent");

const updateBtn = document.querySelector("#updateBtn");
const updateCancel = document.querySelector("#updateCancel");

// 전체 Todo 개수 조회 및 출력하는 함수 정의
function getTotalCount() {

    // 비동기로 서버(DB)에서 전체 Todo 개수 조회하는
    // fetch() API 코드 작성
    // (fetch : 가지고 오다) 기본 GET 요청

    fetch("/ajax/totalCount") // 비동기 요청 수행 -> Promise 객체 반환 (응답 형태)
    .then(response => {
        // response : 비동기 요청에 대한 응답이 담긴 객체 (매개변수명)

        console.log("response : ", response);
        // response.text() : 응답 데이터를 문자열/숫자 형태로 변환한
        //                   결과를 가지는 Promise 객체 반환 (두번째 then 한테 넘겨줌)
        return response.text();
    })
    // 두 번째 then의 매개변수 (result)
    // == 첫 번째 then에서 반환된 Promise 객체의 PromiseResult 값 (응답값)
    .then(result => {
        // result 매개변수 == Controller 메서드에서 반환된 값
        console.log("result : ", result);

        // #totalCount 요소의 내용을 result 변경
        totalCount.innerText = result;
    });
}

// completeCount 값 비동기 통신으로 얻어와서 화면 출력

// 완료된 Todo 개수
function getCompleteCount() {
    
    // fetch() : 비동기로 요청해서 결과 데이터 가져오기
    
    // 첫 번째 then의 response :
    // - 응답 결과, 요청 주소, 응답 데이터 등이 담겨있음
    
    // response.text() : 응답 데이터를 text 형태로 변환
    
    // 두 번째 then 의 result
    // - 첫 번째 then 에서 text로 변환된 응답 데이터
    fetch("/ajax/completeCount")
    .then(response => {
        return response.text();
    })
    .then(result => {
        // #completeCount 요소에 내용으로 result 값 출력
        completeCount.innerText = result;
    });
    
}

// 새로고침 버튼이 클릭 되었을 때
reloadBtn.addEventListener("click", () => {
    getTotalCount(); // 비동기로 전체 할 일 개수 조회
    getCompleteCount(); // 비동기로 완료된 할 일 개수 조회
});

// -----------------------------------------------------

// 할 일 추가 버튼 클릭 시 동작
addBtn.addEventListener("click", () => {

    // 비동기로 할 일 추가하는 fetch() 코드 작성
    // - 요청 주소 : "/ajax/add"
    // - 데이터 전달 방식(method) : "POST" 방식

    // 파라미터를 저장한 JS 객체
    const param = {
        // Key        : Value
        "todoTitle"   : todoTitle.value,
        "todoContent" : todoContent.value
    };

    /* javaScript 객체 형태를 java 에서 그대로 쓸 수 없음 json 사용할거임 */

    fetch("/ajax/add" , { // ajax 앞에 / 빼먹으면 안됨
        // 옵션에 대한 key, value 형태로 작성
        // key : value
        method : "POST", // POST 방식 요청
        headers : {"Content-Type" : "application/json"}, // 요청 데이터의 형식을 JSON으로 지정해서 보냄
        body : JSON.stringify(param) // param 객체를 JSON(string)으로 변환
    })
    .then(resp => resp.text()) // 반환된 값을 text로 변환
    .then(temp => { // 첫번째 then 에서 반환된 값 중 변환된 text를 temp에 저장

        if(temp > 0) { // 성공
            alert("추가 성공");

            // 추가 성공한 제목, 내용 지우기
            todoTitle.value = "";
            todoContent.value = "";

            // 할 일이 추가되었기 때문에 전체 Todo 개수 다시 조회
            getTotalCount();

            // 할 일 목록 다시 조회
            selectTodoList();

        } else { // 실패
            alert("추가 실패");
        }
    });
});

// --------------------------------------------------------------

// 비동기(ajax)로 할 일 상세 조회하는 함수
const selectTodo = (url) => {
    // 매개변수 url == "/ajax/detail?todoNo=10" 형태의 문자열

    // response.json() :
    // - 응답 데이터가 JSON인 경우
    //   이를 자동으로 Object 형태로 변환하는 메서드
    //   == JSON.parse(JSON 데이터)
    fetch(url)
    .then(resp => resp.json())
    .then(todo => {
        // 매개 변수 todo :
        // - 서버 응답(JSON)이 Object로 변환된 값

        // const todo = JSON.parse(result);
        // javaScript 객체 형태로 바꿔주는 애 parse string으로 넘어온 값을

        // console.log(todo);
        console.log(todo);

        // popup Layer에 조회된 값을 출력
        popupTodoNo.innerText = todo.todoNo;
        popupTodoTitle.innerText = todo.todoTitle;
        popupComplete.innerText = todo.complete;
        popupRegDate.innerText = todo.regDate;
        popupTodoContent.innerText = todo.todoContent;

        // popup layer 보이게 하기
        popupLayer.classList.remove("popup-hidden");

        // update Layer가 혹시라도 열려있으면 숨기기
        updateLayer.classList.add("popup-hidden");
    });
};

// -----------------------------------------------------

// popup layer의 x 버튼 (#popupClose) 클릭 시 닫기

popupClose.addEventListener("click", () => {
    popupLayer.classList.add("popup-hidden");
});

// 비동기로 할 일 목록을 조회하는 함수
const selectTodoList = () => {

    fetch("/ajax/selectList")
    .then(resp => resp.text()) // 응답 결과를 text로 변환
    .then(result => {
        console.log(result);
        console.log(typeof result); // 객체가 아닌 문자열 형태

        // 문자열은 가공은 할 수 있지만 너무 힘들다.
        // -> JSON.parse(JSON데이터) 이용

        // JSON.parse(JSON데이터) : string -> object
        // - string 형태의 JSON 데이터를 JS Object 타입으로 변환

        // JSON.stringify(JS Object) : object -> string
        // - JS Object 타입을 string 형태의 JSON 데이터로 변환
        const todoList = JSON.parse(result);

        console.log(todoList);

        // ------------------------------------------------------

        // 기존에 출력되어있던 할 일 목록을 모두 삭제
        tbody.innerHTML = "";

        // #tbody에 tr/td 요소를 생성해서 내용 추가
        for(let todo of todoList) { // 향상된 for문

            // tr 태그 생성
            const tr = document.createElement("tr");

            const arr = ['todoNo', 'todoTitle', 'complete', 'regDate'];

            for(let key of arr) {
                const td = document.createElement("td");

                // 제목인 경우
                if(key === 'todoTitle') {
                    const a = document.createElement("a"); // a 태그 생성
                    a.innerText = todo[key]; // 제목을 a 태그 내용으로 대입
                    a.href = "/ajax/detail?todoNo=" + todo.todoNo;
                    td.append(a);
                    tr.append(td);

                    // a 태그 클릭 시 기본 이벤트(페이지 이동) 막기
                    a.addEventListener("click", (e) => {
                        e.preventDefault(); // 기본 이벤트 막아주는 메서드
                        // 페이지 이동하는 게 동기 요청이라서 막고 비동기 요청으로
                        // 할 일 상세 조회 비동기 요청
                        selectTodo(e.target.href);
                        // e.target.href : 클릭된 a태그의 href 속성 값
                    });

                    continue;
                }

                td.innerText = todo[key];
                tr.append(td);
            }
            // tbody의 자식으로 tr(한 행) 추가
            tbody.append(tr);
        }
    });
};

// -------------------------------------------------

// 삭제 버튼 클릭 시
deleteBtn.addEventListener("click", () => {

    // 취소 클릭 시 아무것도 안함
    if( !confirm("정말 삭제하시겠습니까?") ) { return; }

    // 삭제할 할 일 번호 얻어오기
    const todoNo = popupTodoNo.innerText; // #popupTodoNo 내용 얻어오기

    // 비동기 DELETE 방식 요청 ajax REST API
    fetch("/ajax/delete", {
        method : "DELETE", // DELETE 방식 요청 -> @DeleteMapping 처리
        
        // 데이터 하나를 전달해도 application/json 작성
        headers : {"content-type" : "application/json"},
        body : todoNo // todoNo 값을 body에 담아서 전달
        //               -> @RequestBody로 꺼냄
    })
    .then(resp => resp.text())
    .then(result => {
        if(result > 0) { // 삭제 성공
            alert("삭제 되었습니다.");

            // 상세 조회 창 닫기
            popupLayer.classList.add("popup-hidden");

            // 전체, 완료된 할 일 개수 다시 조회
            // + 할 일 목록 다시 조회
            getTotalCount();
            getCompleteCount();
            selectTodoList();

        } else { // 삭제 실패
            alert("삭제 실패");
        }
    });
});

/* changeComplete.addEventListener("click", () => {
    let complete = popupComplete.innerText;
    const todoNo = popupTodoNo.innerText;

    console.log(complete);
    console.log(todoNo);

    complete = (complete === 'Y') ? 'N' : 'Y';
    
    popupComplete.innerHTML = complete;
    const param = {
        // Key     : Value
        "todoNo"   : todoNo,
        "complete" : complete
    };

    fetch("/ajax/changeComplete", {
        method : "PUT",

        headers : {"content-type" : "application/json"},
        body : JSON.stringify(param)
    })
    .then(resp => resp.text())
    .then(result => {
        if(result > 0) { // 수정 성공
            // alert("수정 완료");

            // popupLayer.classList.add("popup-hidden");

            // getTotalCount();
            getCompleteCount();
            selectTodoList();
        } else {
            alert("수정 실패");
        }
    });
}); */

// ------------------------------------------------------------------

// 완료 여부 변경 버튼 클릭 시
changeComplete.addEventListener("click", () => {

    // 변경할 할 일 번호, 완료 여부 (Y <-> N)
    const todoNo = popupTodoNo.innerText;
    const complete = popupComplete.innerText ===  'Y' ? 'N' : 'Y';

    // SQL 수행에 필요한 값을 객체로 묶음 (JS 객체 형태로)
    const obj = {"todoNo" : todoNo, "complete" : complete};

    // 비동기로 완료 여부 변경
    fetch("/ajax/changeComplete", {
        method: "PUT", // 변경 수정할 때 사용 (UPDATE)
        headers : {"Content-type" : "application/json"}, // 값을 하나만 보내더라도 꼭 써줘야함
        body : JSON.stringify(obj) // obj 라는 객체를 JSON 으로 변경해서 java로 넘겨줌 지금 obj 는 JS 객체
    })
    .then(resp => resp.text())
    .then(result => {

        if(result > 0) { // 성공

            // update 된 DB 데이터를 다시 조회해서 화면에 출력
            // -> 서버 부하가 큼

            // 서버 부하를 줄이기 위해 상세 조회에서 Y/N만 바꾸기
            popupComplete.innerText = complete;

            // getCompleteCount();
            // 서버 부하를 줄이기 위해 완료된 Todo 개수 +-1

            const count = Number(completeCount.innerText); // 넘어온 값이 String 이라서 Number로 형변환

            if(complete === 'Y') completeCount.innerText = count + 1;
            else completeCount.innerText = count - 1;

            // 서버 부하 줄이기 가능 -> 코드 조금 복잡
            selectTodoList();

        } else { // 실패
            alert("완료 여부 변경 실패");
        }
    });
});

// --------------------------------------------------------------------

// 상세 조회에서 수정 버튼 (#updateView) 클릭 시
updateView.addEventListener("click", () => {

    // 기존 팝업 레이어는 숨기고
    popupLayer.classList.add("popup-hidden");

    // 수정 레이어 보이게
    updateLayer.classList.remove("popup-hidden");

    // 수정 레이어 보일 때
    // 팝업 레이어에 작성된 제목, 내용을 얻어와 세팅
    updateTitle.value = popupTodoTitle.innerText;

    updateContent.value = popupTodoContent.innerHTML.replaceAll("<br>", "\n");
    // HTML 에서는 줄바꿈이 <br> 이고 textarea 에서는 줄바꿈이 \n 이라서
    // innerHTML 로 그냥 가져오면 줄바꿈 인식 못함 가져올 때 <br> 이면 \n 로 바꿈

    // updateContent.value = popupTodoContent.innerText;
    // HTML 화면에서 줄 바꿈이 <br>로 인식되고 있는데
    // textarea에서는 \n으로 바꿔줘야 줄 바꿈으로 인식된다.

    // 수정 레이어 -> 수정 버튼에 data-todo-no 속성 추가
    updateBtn.setAttribute("data-todo-no", popupTodoNo.innerText);
});

// -------------------------------------------------------------------------

// 수정 레이어에서 취소 버튼(#updateCancel)이 클릭되었을 때
updateCancel.addEventListener("click", () => {

    // 수정 레이어 숨기기
    updateLayer.classList.add("popup-hidden");

    // 팝업 레이어 보이기
    popupLayer.classList.remove("popup-hidden");
});

// -------------------------------------------------------------------------

updateBtn.addEventListener("click", e => {

    // 위에서 setAttribute 로 data-todo-no 속성을 추가해둠
    // e.target.dataset 안에 있는 todoNo 으로 가져옴

    // 서버로 전달해야되는 값을 객체로 묶어둠
    const obj = {
        "todoNo" : e.target.dataset.todoNo,
        "todoTitle" : updateTitle.value,
        "todoContent" : updateContent.value
    };

    // 비동기 요청
    fetch("/ajax/update", {
        method : "PUT",
        headers : {"Content-Type" : "application/json"},
        body : JSON.stringify(obj)
    })
    .then(resp => resp.text())
    .then(result => {

        if(result > 0) {
            alert("수정 성공");

            // 수정 레이어 숨기기
            updateLayer.classList.add("popup-hidden");
            
            // 목록 다시 조회
            selectTodoList();

            popupTodoTitle.innerText = updateTitle.value;

            popupTodoContent.innerHTML
                = updateContent.value.replaceAll("\n", "<br>");

            popupLayer.classList.remove("popup-hidden");

            // 수정 레이어에 있는 남은 흔적 제거
            updateTitle.value = "";
            updateContent.value = "";
            updateBtn.removeAttribute("data-todo-no"); // 속성 제거

        } else {
            alert("수정 실패");
        }
    });
});

selectTodoList();

getTotalCount(); // 함수 호출
getCompleteCount();

Controller

package edu.kh.todo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import edu.kh.todo.model.dto.Todo;
import edu.kh.todo.model.service.TodoService;
import lombok.extern.slf4j.Slf4j;

/* @ResponseBody
 * - 컨트롤러 메서드의 반환값을
 *   HTTP 응답 본문에 직접 바인딩하는 역할임을 명시
 *   
 * - 컨트롤러 메서드의 반환값을
 *   비동기 요청했던 HTML/JS 파일 부분에
 *   값을 돌려보낼 것이다 명시
 *   
 * - forward/redirect 로 인식 X
 * 
 * @RequestBody
 * - 비동기 요청(ajax) 시 전달되는 데이터 중
 *   body 부분에 포함된 요청 데이터를
 *   알맞은 Java 객체 타입으로 바인딩하는 어노테이션
 *   
 * - 비동기 요청 시 body 에 담긴 값을
 *   알맞은 타입으로 변환해서 매개변수에 저장
 *   
 * [HttpMessageConverter]
 * Spring 에서 비동기 통신 시
 * - 전달되는 데이터의 자료형
 * - 응답하는 데이터의 자료형
 * 위 두가지를 알맞은 형태로 가공(변환)해주는 객체
 * 
 * - 문자열, 숫자 <-> TEXT
 * - DTO <-> JSON
 * - Map <-> JSON
 * 
 * (참고)
 * HttpMessageConverter 가 동작하기 위해서는
 * Jackson-data-bind 라이브러리가 필요한데
 * Spring Boot 모듈에 내장되어 있음
 * (Jackson : 자바에서 JSON 다루는 방법을 제공하는 라이브러리)
 * (Spring Legacy 는 내장 X 직접 추가해줘야함)
 * */

@Controller // 요청/응답 제어 역할 명시 + Bean 등록
@RequestMapping("ajax")
@Slf4j
public class AjaxController {

	// @Autowired
	// - 등록된 Bean 중 같은 타입 또는 상속관계인 Bean 을
	//   해당 필드에 의존성 주입(DI)
	
	@Autowired
	private TodoService service;
	
	@GetMapping("main") // /ajax/main GET 요청 매핑
	public String ajaxMain() {
		
		// 접두사 : classpath:templates/
		// 접미사 : .html
		return "ajax/main";
	}
	
	// Spring Controller 메서드 return 자리는 응답 페이지 쪽 보여주는 자리
	// forward / redirect
	
	// 전체 Todo 개수 조회
	@ResponseBody // 값 그대로 호출한 곳에 돌려보내는 어노테이션
	@GetMapping("totalCount")
	public int getTotalCount() {
		// 전체 할 일 개수 조회 서비스 호출 및 응답
		int totalCount = service.getTotalCount();
		
		return totalCount;
	}
	
	@ResponseBody
	@GetMapping("completeCount")
	public int getCompleteCount() {
		
//		int completeCount = service.getCompleteCount();
		
//		return completeCount;
		return service.getCompleteCount();
	}
	
	@ResponseBody // 비동기 요청 결과로 값 자체를 반환
	@PostMapping("add")
	public int addTodo(
			// JSON 이 파라미터로 전달된 경우 아래 방법으로 얻어오기 불가능
			// @RequestParam("todoTitle") String todoTitle,
			// @RequestParam("todoContent") String todoContent
			
			@RequestBody Todo todo // 요청 body 에 담긴 값을 Todo 에 저장
			) {
		
		log.debug(todo.toString());
		return service.addTodo(todo.getTodoTitle(), todo.getTodoContent());
	}
	
	@ResponseBody
	@GetMapping("selectList")
	public List<Todo> selectList() {
		List<Todo> todoList = service.selectList();
		return todoList;
		// List(Java 전용 타입)를 반환
		// -> JS가 인식할 수 없기 때문에
		//    HttpMessageConverter 가
		//    JSON 형태로 반환하여 반환
		// -> [{}, {}, {}] JSONArray
	}
	
	@ResponseBody
	@GetMapping("detail")
	public Todo selectTodo(@RequestParam("todoNo") int todoNo) {
		
		// return 자료형 : Todo
		// -> HttpMessageConverter 가 String(JSON) 형태로 변환해서 반환
		return service.todoDetail(todoNo);
	}
	

	@ResponseBody
	@DeleteMapping("delete") // Delete 방식 요청 처리 (비동기 요청만 가능)
	public int todoDelete(@RequestBody int todoNo) {
		// GET/POST (동기/비동기)
		// DELETE/PUT (비동기)
		// REST API (AJAX) 자원 중심 언어간의 상호작용 수월하게 해주는 모바일이든 웹이든
		/* HTTP 메서드
		 * - GET : 자원 조회
		 * - POST : 자원 생성
		 * - PUT : 자원 업데이트
		 * - DELETE : 자원 삭제
		 * */
		
		return service.todoDelete(todoNo);
	}
	
	// 비동기 요청 return 에 작성하는 값이 비동기 요청 보낸 쪽으로 돌려주려면 어노테이션 추가
	// return 에 주소값이 아닌 객체 그대로 돌려줄 때
	// 완료 여부 변경
	@ResponseBody
	@PutMapping("changeComplete")
	public int changeComplete(@RequestBody Todo todo) {
		return service.changeComplete(todo);
	}
	
	// 할 일 수정
	@ResponseBody
	@PutMapping("update")
	public int todoUpdate(@RequestBody Todo todo) {
		return service.todoUpdate(todo);
	}
}

0개의 댓글