
<form>은 기본적으로 submit이 발생하면:
브라우저가 HTML 기본 유효성 검사를 수행하고
통과하면 서버로 요청을 보내면서 페이지 이동/새로고침이 발생한다.
그런데 우리는 “페이지 이동 없이 서버 요청만” 하고 싶을 때가 많다(= AJAX).
이때 사용하는 방식이:
<form onsubmit="return addBookmarkRequest();">
return true → 기본 submit 동작 수행(페이지 이동)
return false → 기본 submit 동작 취소(페이지 유지)
-> 즉 AJAX만 하고 싶으면 return false로 기본 submit을 막는다.
브라우저가 기본 제공하는 검증 기능:
minlength: 최소 길이
maxlength: 최대 길이
required: 필수 입력
min, max: 숫자 범위
url: URL 형식 검증
아래 흐름은 브라우저(HTML+JS) ↔ Spring 서버(Controller) 관점으로 나눠서 정리한다.
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<form onsubmit="return addBookmarkRequest();">
<label>즐겨찾기 이름 : </label><input type="text" name="name" minlength="2" maxlength="100" required><br>
<label>즐겨찾기 URL : </label><input type="url" name="url" required><br>
<input type="submit"><br>
</form>
<button onclick="getBookmarkListRequest();">즐겨찾기 목록 가져오기</button>
<ol id="bookmark-list">
<!-- 여기에 즐겨찾기 목록이 나옵니다. -->
</ol>
<script>
function addBookmarkRequest() {
const name = document.querySelector('input[name=name]').value;
const url = document.querySelector('input[name=url]').value;
const requestObject = {name: name, url: url};
const requestJson = JSON.stringify(requestObject);
function onReadyStateChange(event) {
const currentAjaxRequest = event.currentTarget;
if (currentAjaxRequest.readyState === XMLHttpRequest.DONE) {
if (currentAjaxRequest.status === 200) {
alert("즐겨찾기가 등록되었습니다.");
} else {
console.error('request failed');
}
}
}
const ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = onReadyStateChange;
ajaxRequest.open('POST', '/bookmark');
ajaxRequest.setRequestHeader('Content-Type', 'application/json');
ajaxRequest.send(requestJson);
return false;
}
function getBookmarkListRequest() {
function onReadyStateChange(event) {
const currentAjaxRequest = event.currentTarget;
if (currentAjaxRequest.readyState === XMLHttpRequest.DONE) {
if (currentAjaxRequest.status === 200) {
const bookmarkListDom = document.querySelector('#bookmark-list');
bookmarkListDom.innerHTML = '';
const bookmarks = JSON.parse(currentAjaxRequest.responseText);
bookmarks.forEach(bookmark => {
const liNode = document.createElement('li');
const textNode = document.createTextNode(bookmark.name + ' - ' + bookmark.url);
liNode.appendChild(textNode);
bookmarkListDom.appendChild(liNode);
});
} else {
console.error('request failed');
}
}
}
const ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = onReadyStateChange;
ajaxRequest.open('GET', '/bookmarks');
ajaxRequest.send();
}
</script>
</body>
</html>
입력 폼(이름, URL)
submit 버튼
“즐겨찾기 목록 가져오기” 버튼
<ol id="bookmark-list"> 영역
<script> 안의 함수(addBookmarkRequest, getBookmarkListRequest)가 등록된다.-> 이 시점엔 아직 서버 요청이 자동으로 나가지 않는다.
(버튼 클릭/submit 이벤트가 있어야 호출됨)
1) 사용자가 submit 버튼 클릭
2) 브라우저가 HTML 유효성 검사 먼저 수행
예를 들어:
name: minlength="2", maxlength="100", required
url: type="url", required
검증 실패 → 브라우저가 안내 띄우고 제출 중단
검증 성공 → 다음 단계 진행
3) onsubmit 핸들러 실행
<form onsubmit="return addBookmarkRequest();">
→ addBookmarkRequest() 호출
여기서 핵심: 기본 submit(페이지 이동/새로고침)을 막고 AJAX만 수행하게 함
addBookmarkRequest() 부분에 대해서는 어제 올린 것에 상세하게 설명되어 있으니 스킵하겠습니다.
4) 서버(Spring)에서 처리 흐름
요청: POST /bookmark + JSON body
컨트롤러:
@RequestMapping(method = RequestMethod.POST, path = "/bookmark")
public String registerBookmark(@RequestBody Bookmark bookmark) {
bookmarks.add(bookmark);
return "registered";
}
서버 내부 흐름:
/bookmark POST 라우팅 매칭
@RequestBody가 JSON을 Bookmark 객체로 변환(역직렬화)
bookmarks 리스트에 저장(메모리)
"registered" 문자열 응답 반환 (보통 200 OK)
중요한 특징: DB 저장이 아니라 서버 메모리(List) 저장이라서
서버 재시작하면 데이터가 사라질 수 있음.
5) 브라우저가 응답을 받으면 콜백 실행
if (currentAjaxRequest.readyState === XMLHttpRequest.DONE) {
if (currentAjaxRequest.status === 200) {
alert("즐겨찾기가 등록되었습니다.");
}
}
readyState === DONE일 때가 “응답까지 끝난 시점”
status === 200이면 성공 처리(alert)
여기까지가 “등록 완료” 흐름
1) 사용자가 버튼 클릭
<button onclick="getBookmarkListRequest();">즐겨찾기 목록 가져오기</button>
→ getBookmarkListRequest() 실행
2) getBookmarkListRequest() 내부(JS) 흐름
(1) XHR 생성 + 콜백 등록
const ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = onReadyStateChange;
(2) GET 요청 설정 + 전송
ajaxRequest.open('GET', '/bookmarks');
ajaxRequest.send();
3) 서버(Spring)에서 처리 흐름
컨트롤러:
@RequestMapping(method = RequestMethod.GET, path = "/bookmarks")
public List<Bookmark> getBookmarks() {
return bookmarks;
}
서버 내부 흐름:
/bookmarks GET 매핑
bookmarks 리스트 반환
스프링이 List를 JSON 배열로 자동 변환(직렬화)해서 응답
응답 예시(JSON 문자열):
[
{"name":"구글","url":"https://google.com"},
{"name":"네이버","url":"https://naver.com"}
]
4) 브라우저가 응답 받으면 화면 렌더링
(1) 목록 영역 초기화
const bookmarkListDom = document.querySelector('#bookmark-list');
bookmarkListDom.innerHTML = '';
(2) responseText(JSON 문자열) → JS 배열로 파싱
const bookmarks = JSON.parse(currentAjaxRequest.responseText);
(3) DOM에 li 추가
bookmarks.forEach(bookmark => {
const liNode = document.createElement('li');
const textNode = document.createTextNode(bookmark.name + ' - ' + bookmark.url);
liNode.appendChild(textNode);
bookmarkListDom.appendChild(liNode);
});
여기까지가 “목록 조회 후 화면 표시” 흐름
등록(POST): submit → (HTML 유효성 검사) → JS가 JSON 만들어 전송 → 서버가 List에 저장 → 응답 → alert
조회(GET): 버튼 클릭 → 서버에서 List를 JSON으로 반환 → JS가 JSON.parse → <ol>에 <li>로 렌더링
어제 이해를 제대로 못해서 조금 더 자세히 알기 위해 비슷한 내용이지만 빼먹은 부분 한번 더 정리함