08_Spring_240408(월)_60일차(0) - Spring Boot - TodoList - Ajax

soowagger·2024년 4월 8일

8_Spring

목록 보기
9/38

💡 동기 / 비동기

  • 동기 : A, B, C / A가 다 끝나고 B 시작, B가 끝나고 C 시작...
  • 비동기 : A, B, C / A가 끝나든 말든, B요청이 오면 시작, B가 끝나든 말든 C요청이 오면 C도 시작...

1. 할 일 개수 / 완료된 할 일 개수

AjaxController

// @Autowired
// - 등록된 Bean 중 같은 타입 또는 상속관계인 Bean을
// 	 해당 필드에 의존성 주입(DI)
@Autowired
private TodoService service;


@GetMapping("main") // /ajax/main GET 요청 매핑
public String ajaxMain() {
	return "ajax/main";
}


// 전체 Todo 개수 조회
// forward / redirect
@ResponseBody // 값 그대로 돌려 보낼거야!
@GetMapping("totalCount")
public int getTotalCount() {
	
	// 전체 할 일 개수 조회 서비스 호출 및 응답
	int totalCount = service.getTotalCount();
	
	return totalCount;
	
}

@ResponseBody
@GetMapping("completeCount")
public int getCompleteCount() {
	return service.getCompleteCount();
} 

ajax-main.js

// 전체 Todo 개수 조회 및 출력하는 함수 정의
function getTotalCount() {
   
    // 비동기로 서버(DB)에서 전체 Todo 개수 조회하는
    // fetch() API 코드 작성
    // (fetch : 가지고 오다.)

    fetch("/ajax/totalCount") // 비동기 요청 수행 -> Promise 객체 반환
    .then( response =>  {
        // response 매개변수 : 비동기 요청에 대한 응답이 담긴 객체
        
        console.log("response : ", response);
        // response.test() : 응답 데이터를 문자열/숫자 형태로 변환한
        //                   결과를 가지는 Promise 객체
        return response.text();
    })
    // 두 번째 then의 매개변수 (result)
    // == 첫 번째 then에서 반환된 Promise 객체의 PromiseResult 값
    .then(result => {
        // result 매개변수 == Controller 메서드에서 반환된 값
        console.log("result : ", result);

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

// completeCount 값 비동기 통신으로 얻어와서 화면 출력
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(); // 비동기로 완료된 할 일 개수 조회
});

2. 할 일 추가

Ajax 컨트롤러

 * @RequestBody
 * - 비동기 요청(Ajax) 시 전달되는 데이터 중
 *   body 부분에 포함된 요청 데이터를
 *   알맞은 Java 객체 타입으로 바인딩하는 어노테이션
 * 
 * 
 * [HttpMessageConverter]
 * Spring에서 비동기 통신 시
 * - 전달되는 데이터의 자료형
 * - 응답하는 데이터의 자료형
 * 위 두가지를 알맞은 형태로 가공(변환) 해주는 객체
 * 
 * - 문자열, 숫자 <-> Text
 * - DTO <-> JSON
 * - Map <-> JSON
 * 
 * (참고) 
 * HttpMessageConverter가 동작하기 위해서는
 * Jackson-data-bind 라이브러리가 필요한데,
 * Spring Boot 모듈에 내장되어 있음
 * (Jackson : 자바에서 JSON 다루는 방법 제공하는 라이브러리)

@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());
}

Ajax JS

// 할 일 추가 버튼 클릭 시 동작
addBtn.addEventListener("click", () => {
    
    // 비동기로 할 일 추가하는 fetch() 코드 작성
    // - 요청 주소 : "/ajax/add"
    // - 데이터 전달 방식(method) : "POST" 방식

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

    fetch("/ajax/add", {
        // Key : Value
        method : "POST", // POST 방식 요청
        headers : {"Content-Type" : "application/json"}, // 요청 데이터 형식을 JSON으로 지정
        body : JSON.stringify(param) // param 객체를 JSON 으로 변환
    })
    .then( resp => resp.text()) // 반환된 값을 text로 변환
    .then( temp => { // 첫번째 then에서 반환된 값 중 변환된 text를 temp에 저장

        if(temp > 0) { // 성공
            alert("추가 성공!");
            
            // 추가 성공한 제목, 내용 지우기
            todoTilte.value = "";
            todoContent.value = "";

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

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

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

3. 비동기 할 일 목록 조회

컨트롤러

@ResponseBody
@GetMapping("selectList")
public List<Todo> selectList() {
	List<Todo> todoList = service.selectList();
	
	return todoList;
	
	// List(Java 전용 타입)를 반환
	// -> JS가 인식할 수 없기 때문에
	// --> HttpMessageConverter가
	// 		JSON 형태로 변환하여 반환
	// -> [{}, {}, {}] JSONArray
}

JS

// 비동기로 할 일 목록을 조회하는 함수
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.String(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();

                        // 할 일 상세 조회 비동기 요청
                        // e.target.href : 클릭된 a태그의 href 속성값
                        selectTodo(e.target.href);
                    });

                    continue;
                }

                td.innerText = todo[key];
                tr.append(td);
            }

            // tbody의 자식으로 tr(한 행) 추가
            tbody.append(tr);
        }
    })
}

4. 할 일 상세 조회

컨트롤러

@ResponseBody
@GetMapping("detail")
public Todo selectTodo(@RequestParam("todoNo") int todoNo) {
	
	// return 자료형 : Todo
	// -> HttpMessageConverter가 String(JSON)형태로 변환해서 반환
	return service.todoDetail(todoNo);
	
}

JS

// 비동기(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);

        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");
        
    });
}

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

// popup layer의 X 버튼 (#popupClose) 클릭 시 닫기
popupClose.addEventListener("click", () => {
    // 숨기는 class를 추가
    popupLayer.classList.add("popup-hidden");
    
});

5. 할 일 상세 조회 - 삭제

컨트롤러

@ResponseBody
@DeleteMapping("delete") // Delete, Put 방식 요청 처리(비동기 요청만 가능!) - REST API(Ajax)
// REST API = RESTFUL 자원 중심
public int todoDelete(@RequestBody int todoNo) {
	
	return service.todoDelete(todoNo);
}

JS

deleteBtn.addEventListener("click", () => {

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


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

    // 비동기 DELETE 방식 요청
    fetch("/ajax/delete", {
        method : "DELETE", // DELETE 방식 요청 -> @DeleteMapping 처리
        
        // 데이터 하나를 전달해도 application/json 작성
        headers : {"Content-type" : "application/json"},
        body : todoNo // todoNo 값을 body에 담아서 전달
                    // -> RequsetBody로 꺼냄
    })
    .then(resp => resp.text()) 
    .then(result => {

        if(result > 0) {
            alert("삭제 되었습니다.");

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

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

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

profile

0개의 댓글