동기 요청 a가 끝나면 b 시작 b 끝나면 c 시작
-> 동기 요청을 보내면 페이지 바뀔 때마다 새로고침 됨
비동기 요청 요청이 들어오면 바로 시작함 다른 요청이 끝날 때까지 기다ㅣ지 않음
-> 페이지 새로고침 되지 않음
main.html 에서 ajax/main.html 로 이동하게 만들기
common/main.html
a 태그 추가
<h3><a href="/ajax/main">TodoList - Ajax 버전</a></h3>
요청 받아줄 controller 생성
AjaxController
@Controller // 요청/응답을 제어 역할 명시 + Bean 등록
@Slf4j
@RequestMapping("ajax")
public class AjaxController {
@GetMapping("main") // /ajax/main GET 요청 매핑
public String ajaxMain() {
// 접두사 : classpath:templates/
// 접미사 : .html
return "ajax/main";
}
}
Asynchronous == 비동기 (에이젝스, 아작스)
synchronous == 동기
동기식은 요청을 보냈을 때 응답이 돌아오기 전까지는 아무것도 못함
비동기적으로 서버와 브라우저 간에 데이터를 교환하는 기술로 웹 페이지를 새로고침 하지 않고도 서버로부터 데이터를 받아와서 웹 페이지의 일부분을 업데이트할 수 있도록 해주는 기술
-> 기존에는 웹 페이지를 다시 로드할 때 전체 페이지를 다시 받아와야했기 때문에 사용자 경험이 좋지 않았음
-> Ajax를 사용하면 웹 페이지를 로드한 후에도 웹 페이지와 서버 간에 데이터를 주고 받을 수 있으므로 사용자 경험을 향상시킬 수 있다.
resources/templates/ajax/main.html
css, js 연결
<!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>
<script src="/js/ajax-main.js"></script>
</body>
</html>
static/css/ajax-main.css
#popupLayer{
width: 400px;
height: 400px;
border: 3px solid black;
border-radius: 15px;
background-color: white;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); /* 그림자 효과 추가 */
position: fixed; /* 브라우저에서 위치 고정 */
/* 브라우저 정가운데 배치 */
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
}
/* popup layer 행 */
.popup-row{
padding: 10px;
margin: 10px;
border-bottom: 3px solid grey;
/* 위치의 상대적 기준점 */
position : relative;
width: 90%;
}
/* popup layer 숨기기/보이기 */
.popup-hidden{ display: none !important; }
/* x 버튼 */
#popupClose{
/* 절대적인 위치 지정(기준 내에서 아무곳에나 배치 가능) */
position: absolute;
top: 0px; /* 위쪽으로 부터 0px 떨어진 위치 */
right: 10px; /* 오른쪽으로 부터 10px 떨어진 위치 */
font-size: 30px;
font-weight: bold;
user-select: none; /* 드래그 방지 */
cursor: pointer; /* 커서 손가락 모양 */
}
#popupClose:hover{ color :grey; }
#popupClose:active{ color : red; }
/* 버튼 영역 */
.btn-container{
flex-grow: 1; /* 중심축 방향으로 팽창 */
display: flex;
justify-content: flex-end;
align-items: flex-end;
margin: 15px;
}
.btn-container > button{ margin: 5px;}
/* ----------------------------------------------------- */
/* 수정 팝업 레이어 */
#updateLayer{
width: 400px;
height: 250px;
border: 3px solid black;
border-radius: 15px;
background-color: white;
position: fixed; /* 브라우저에서 위치 고정 */
/* 브라우저 정가운데 배치 */
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
align-items: center;
}
#updateTitle{ width: 300px; }
main.html
<h3 id="todoHeader">
전체 Todo 개수 : <span id="totalCount">0</span>개
/
완료된 Todo 개수 : <span id="completeCount">0</span>개
<button id="reloadBtn">새로고침</button>
</h3>
const totalCount = document.querySelector("#totalCount");
const completeCount = document.querySelector("#completeCount");
const reloadBtn = document.querySelector("reloadBtn");
비동기로 서버(DB)에서 전체 Todo 개수 조회하는 fetch() API 코드 작성
(fetch : 가지고 오다)
기본 GET 요청
fetch("요청주소") 비동기 요청 수행 -> Promise 객체 반환
응답을 가지고 돌아올 때 객체 하나를 반환해줌
=> Promise 객체 반환 (비동기요청 보냈을 때 받는 응답 형태)
첫번째 .then
.then 은 응답이 됐을 때 돌아온 객체명을 response 라고 하겠다
(매개변수형태) 이름은 어떻게 쓰든 상관 없음
response : 비동기 요청에 대한 응답이 담긴 객체
두번째 .then
매개변수 (result) == 첫번째 then 에서 반환된 Promise 객체의 PromiseResult 값
result 매개변수 == Controller 메서드에서 반환된 진짜 값
ajax-main.js
function getTotalCount() {
fetch("/ajax/totalCount")
.then(response => {
return response.text();
})
PromiseResult 값
.then(result => {
totalCount.innerText = result;
});
};
getTotalCount(); // 함수 호출
return response.text();
응답이 담긴 객체.text() : 응답 데이터를 문자열/숫자 형태로 변환한 결과를 가지는 Promise 객체 반환
단일 값으로 넘어오는 값은 .text() 로 두번째 .then한테 넘겨줄 수 있음
totalCount.innerText = result;
첫번째 then에서 text()로 변환해서 보내준 값을 result 로 받아서 #totalCount 요소의 내용을 result로 변경
AjaxController
@GetMapping("totalCount")
@ResponseBody
public int getTotalCount() {
// 전체 할 일 개수 조회 서비스 호출 및 응답
return service.getTotalCount();
}
동기요청할 때는 return 자리에 forward/redirect 주소 적음
비동기 요청은 return 자리에 돌려 받은 값 그대로 돌려주겠다고 @ResponseBody 적어서 그대로 돌려보냄
컨트롤러 메서드의 반환값을 HTTP 응답 본문에 직접 바인딩하는 역할임을 명시
-> 컨트롤러 메서드의 반환값을 비동기 요청했던 HTML/JS 파일 부분에 값을 돌려보내 것이다 명시
@ResponseBody 어노테이션 붙어있으면 forward/redirect 로 인식하지 않음
main.html
완료된 Todo 개수 : <span id="completeCount">0</span>개
ajax-main.js
function getCompleteCount() {
// fetch() : 비동기로 요청해서 결과 데이터 가져오기
fetch("/ajax/completeCount")
};
AjaxController
@ResponseBody
@GetMapping("completeCount")
public int getCompleteCount() {
return service.getCompleteCount();
}
service, xml sql 문 실행하고 다시 controller로 돌아온 후
.then(resp => resp.text())
.then(result => {
completeCount.innerText = result;
});
getCompleteCount(); // 함수 호출
main.html
<button id="reloadBtn">새로고침</button>
ajax-main.js
const reloadBtn = document.querySelector("reloadBtn");
ajax-main.js
reloadBtn.addEventListener("click", () => {
getTotalCount(); // 비동기로 전체 할 일 개수 조회
getCompleteCount(); // 비동기로 완료된 할 일 개수 조회
});
-> 새로고침 버튼 누르면 함수가 호출되면서 비동기로 서버 갔다옴