ajax 정의
예시
43.ajax.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Javascript 실습</title>
<!-- 구글 웹 폰트를 사용을 위한 CDN -->
<!-- 아이콘 사용을 위한 Font Awesome 6 CDN -->
<!-- 내가 만든 CSS 파일을 불러오는 코드 -->
<link rel="stylesheet" type="text/css" href="./css/reset.css">
<link rel="stylesheet" type="text/css" href="./css/commons.css">
<link rel="stylesheet" type="text/css" href="./css/test.css">
<style>
</style>
<!-- jquery cdn -->
<!-- Javascript 작성 공간 -->
<script>
//목표 : 버튼을 누르면 43.ajax.txt를 비동기통신으로 불러와서 내용을 출력
$(function(){
$(".load-btn").click(function(){
// $.ajax({옵션});
$.ajax({
// url:"./43.ajax.txt", //불러올 대상의 주소
url:"http://localhost:8080/hello",
success:function(response){
$(".display").text(response);
}
});
});
});
</script>
</head>
<body>
<button class="load-btn">불러오기</button>
<hr>
<div class="display"></div>
</body>
</html>
위의 실행코드
![](https://blog.kakaocdn.net/dn/oD8pN/btstrRtosol/4Ep8aglKKI1rNJe86FhX3K/img.png)
CORS
- Cross Origin Resource Sharing, 교차 출처 자원 공유 정책
- 다른 서버에 있는 프로젝트는 비동기 통신이 원칙적으로 금지되어 있다.
- 백엔드에서 허용 가능한 프론트엔드를 지정해야 한다.
- 이것을 CORS 설정이라고 한다.
백엔드에서 ajax
spring11ajax 프로젝트를 생성 후 spring11.rest 패키지 생성
DummyRestController 생성
package com.kh.spring11.rest;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//CORS를 해제하기 위한 설정(Annotation)
//@CrossOrigin //전부다 허용(위험!)
@CrossOrigin(origins = {"http://127.0.0.1:5501"}) //vscode 홈페이지 주소
@RestController //@Controller + @responseBody
public class DummyRestController {
@RequestMapping("/hello")
public String hello() {
return "hello spring ajax";
}
}
같은 프로젝트에서는 CrossOrigin이 필요 없다.
DummyRestController 생성
package com.kh.spring11.rest;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//CORS를 해제하기 위한 설정(Annotation)
//@CrossOrigin //전부다 허용(위험!)
@CrossOrigin(origins = {"http://127.0.0.1:5501"}) //vscode 홈페이지 주소
@RestController //@Controller + @responseBody
public class DummyRestController {
@RequestMapping("/hello")
public String hello() {
return "hello spring ajax";
}
//Rest Controller에서는 내가 전해줄 데이터가 반환형이 된다.
//- 자동으로 Spring에서 JSON형태로 변환하여 반환
//- 변환을 담당하는 라이브러리 jackson-databind
@RequestMapping("/lotto")
public Set<Integer> lotto() {
Random r = new Random();
Set<Integer> set = new TreeSet<>();
while(set.size() < 6) {
int n = r.nextInt(45) + 1;
set.add(n);
}
return set;
}
}
비주얼스튜디오 코드로 실행
아이디 중복 검사
44.id-check.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Javascript 실습</title>
<!-- 구글 웹 폰트를 사용을 위한 CDN -->
<!-- 아이콘 사용을 위한 Font Awesome 6 CDN -->
<!-- 내가 만든 CSS 파일을 불러오는 코드 -->
<link rel="stylesheet" type="text/css" href="./css/reset.css">
<link rel="stylesheet" type="text/css" href="./css/commons.css">
<link rel="stylesheet" type="text/css" href="./css/test.css">
<style>
</style>
<!-- jquery cdn -->
<!-- Javascript 작성 공간 -->
<script>
$(function(){
//목표 : 아이디의 입력이 완료되면 중복검사를 요청하여 결과를 출력
$("[name=memberId]").blur(function(){
var id = $(this).val(); //this = 입력창
$.ajax({ //이 객체에서 지정한대로 비동기통신 발생 시켜주겠다
url:"http://localhost:8080/idCheck",
method:"post", //전송방식
data:{//전송할 때 날아갈 데이터
memberId : id
},
success:function(response){
//아이디가 있으면 "Y", 없으면 "N"이 반환된다.
// $(".display").text(response);
if(response == "Y") {
$(".display").text("이미 사용중인 아이디입니다.");
}
else if(response == "N") {
$(".display").text("사용 가능한 아이디입니다.");
}
}
});
});
});
</script>
</head>
<body>
<div class="container w-500">
<div class="row">
<h1>아이디 검사</h1>
</div>
<div class="row">
<input type="text" name="memberId" class="form-input w-100">
</div>
<div class="row display"></div>
</div>
</body>
</html>
springhome progect 에서 memberDto, memberMapper, memberDao, memberDaoImpl 가져오기
DummyRestController
@Autowired
private MemberDao memberDao;
@PostMapping("/idCheck")
public String idCheck(@RequestParam String memberId) {
MemberDto memberDto = memberDao.selectOne(memberId);
if(memberDto != null) { //아이디가 있으면
return "Y";
}
else { //아이디가 없으면
return "N";
}
}
}
닉네임 중복 검사 기능
MemberRestController
@PostMapping("/nicknameCheck")
public String nicknameCheck(@RequestParam String memberNickname) {
MemberDto memberDto =
memberDao.selectOneByMemberNickname(memberNickname);
if(memberDto == null) {
return "Y";
}
else {
return "N";
}
}
join.js id부분에 중복 검사 코드 추가
$(function(){
//상태 객체
var status = {
memberId:false,
memberPw:false,
memberPwCheck:false,
memberNickname:false,
memberContact:false,
memberBirth:false,
memberEmail:false,
memberAddress:false,
ok:function(){
return this.memberId && this.memberPw
&& this.memberPwCheck && this.memberNickname
&& this.memberContact && this.memberBirth
&& this.memberEmail && this.memberAddress;
},
};
$("[name=memberId]").blur(function(e){
var regex = /^[a-z][a-z0-9]{4,19}$/;
var isValid = regex.test($(e.target).val());
$(e.target).removeClass("success fail fail2");
if(isValid) {//형식이 유효하다면
$.ajax({
url:"http://localhost:8080/rest/member/idCheck",
method:"post",
// data : {memberId : e.target.value },
data : { memberId : $(e.target).val() },
success : function(response){
if(response == "Y") {//사용가능
$(e.target).addClass("success");
status.memberId = true;
}
else {//사용불가(중복)
$(e.target).addClass("fail2");
status.memberId = false;
}
},
error : function(){
alert("서버와의 통신이 원활하지 않습니다");
},
});
}
else {//형식이 유효하지 않다면(1차실패)
$(e.target).addClass("fail");
status.memberId = false;
}
});
$("[name=memberPw]").blur(function(){
var regex = /^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$])[A-Za-z0-9!@#$]{8,16}$/;
var isValid = regex.test($(this).val());
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberPw = isValid;
});
$("#password-check").blur(function(){
var pw1 = $("[name=memberPw]").val();
var pw2 = $(this).val();
$(this).removeClass("success fail fail2");
if(pw1.length == 0) {
$(this).addClass("fail2");
status.memberPwCheck = false;
}
else if(pw1 == pw2) {
$(this).addClass("success");
status.memberPwCheck = true;
}
else {
$(this).addClass("fail");
status.memberPwCheck = false;
}
});
$("[name=memberNickname]").blur(function(){
var regex = /^[ㄱ-ㅎㅏ-ㅣ가-힣0-9]{2,10}$/;
var isValid = regex.test($(this).val());
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberNickname = isValid;
});
$("[name=memberEmail]").blur(function(){
var regex = /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
var email = $(this).val();
var isValid = email.length == 0 || regex.test(email);
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberEmail = isValid;
});
$("[name=memberContact]").blur(function(){
var regex = /^010[1-9][0-9]{7}$/;
var contact = $(this).val();
var isValid = contact.length == 0 || regex.test(contact);
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberContact = isValid;
});
$("[name=memberBirth]").blur(function(){
var regex = /^(19[0-9]{2}|20[0-9]{2})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
var birth = $(this).val();
var isValid = birth.length == 0 || regex.test(birth);
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberBirth = isValid;
});
$("[name=memberPost],[name=memberAddr1],[name=memberAddr2]").blur(function(){
//this 사용 불가(확실히 누군지 알 수 없음)
var post = $("[name=memberPost]").val();
var addr1 = $("[name=memberAddr1]").val();
var addr2 = $("[name=memberAddr2]").val();
var isBlank = post.length == 0 && addr1.length == 0 && addr2.length == 0;
var isFill = post.length > 0 && addr1.length > 0 && addr2.length > 0;
var isValid = isBlank || isFill;
$("[name=memberPost],[name=memberAddr1],[name=memberAddr2]").removeClass("success fail");
$("[name=memberPost],[name=memberAddr1],[name=memberAddr2]").addClass(isValid ? "success" : "fail");
status.memberAddress = isValid;
});
//페이지 이탈 방지
//- window에 beforeunload 이벤트 설정
$(window).on("beforeunload", function(){
return false;
});
//- form 전송할 때는 beforeunload 이벤트를 제거
$(".join-form").submit(function(e){
$(".form-input").blur();
if(!status.ok()) {
e.preventDefault();
//return false;
}
else {
$(window).off("beforeunload");
}
});
});
42.join.html을 실행
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Javascript 실습</title>
<!-- css 파일을 불러오는 코드 -->
<!-- 아이콘 사용을 위한 Font Awesome 6 CDN -->
<!-- 구글 웹 폰트 사용을 위한 CDN -->
<!-- 내가 만든 CSS 파일-->
<link rel="stylesheet" type="text/css" href="./css/reset.css">
<link rel="stylesheet" type="text/css" href="./css/commons.css">
<!-- <link rel="stylesheet" type="text/css" href="./css/test.css"> -->
<style>
/*
진행바 디자인
*/
.progressbar {
width: 100%;
height: 5px;
}
.progressbar > .guage {
width: 0%;
height: 100%;
background: rgb(238,174,202);
background: radial-gradient(circle, rgba(238,174,202,1) 0%, rgba(148,187,233,1) 100%);
transition: width 0.1s ease-out;
}
</style>
<!-- jquery cdn -->
<script src="multipage.js"></script>
<script src="join.js"></script>
<!-- daum 우편 API cdn -->
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<!-- javascript 작성 공간 -->
<script>
$(function(){
//최초 게이지 계산
refreshProgressbar();
//이전이나 다음버튼을 누르면 진행상황을 파악하여 게이지 계산
$(".btn-prev, .btn-next").click(function(){
refreshProgressbar();
});
function refreshProgressbar() {
//page중에 보여지는 태그를 찾아서 계산
//- 전체 페이지 수 + 보여지는 페이지 번호
var count = 0;
var index = 0;
$(".page").each(function(idx, el){
//if(현재 태그가 표시중이라면) {
if($(this).is(":visible")) {
index = idx+1;
}
count++;
});
var percent = index * 100 / count;
$(".progressbar > .guage").css("width", percent+"%");
}
});
$(function(){
//검색버튼, 우편번호 입력창, 기본주소 입력창을 클릭하면 검색 실행
$(".post-search").click(function(){
new daum.Postcode({
oncomplete: function(data) {
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
// document.querySelector("[name=memberPost]").value = data.zonecode; //js
$("[name=memberPost]").val(data.zonecode);
// document.querySelector("[name=memberAddr1]").value = addr; //js
$("[name=memberAddr1]").val(addr);
// 커서를 상세주소 필드로 이동한다.
// document.querySelector("[name=memberAddr2]").focus(); //트리거 //js
$("[name=memberAddr2]").focus();
}
}).open();
});
});
</script>
</head>
<body>
<form class="join-form" action="" method="post" autocomplete="off">
<div class="container w-600">
<div class="row">
<h1>회원 정보 입력</h1>
</div>
<!-- 전체 진행단계를 알 수 있는 게이지 출력 -->
<div class="row">
<div class="progressbar">
<div class="guage"></div>
</div>
</div>
<!-- 1단계 : 아이디+비밀번호+확인 -->
<div class="row page">
<div class="row">
<h2>1단계 : 아이디/비밀번호</h2>
</div>
<div class="row left">
<label>아이디 <i class="fa-solid fa-asterisk red"></i></label>
<input type="text" name="memberId" placeholder="영문 소문자, 숫자 5~20자"
class="form-input w-100">
<div class="success-feedback">멋진 아이디입니다!</div>
<div class="fail-feedback">아이디를 형식에 맞게 입력하세요</div>
<div class="fail2-feedback">이미 사용중인 아이디입니다</div>
</div>
<div class="row left">
<label>비밀번호 <i class="fa-solid fa-asterisk red"></i></label>
<input type="password" name="memberPw"
placeholder="영문,숫자,특수문자 반드시 1개 이상 포함"
class="form-input w-100">
<div class="success-feedback">올바른 비밀번호 형식입니다</div>
<div class="fail-feedback">잘못된 비밀번호 형식입니다</div>
</div>
<div class="row left">
<label>비밀번호 확인 <i class="fa-solid fa-asterisk red"></i></label>
<input type="password" id="password-check"
placeholder="비밀번호 한 번 더 입력" class="form-input w-100">
<div class="success-feedback">비밀번호가 일치합니다</div>
<div class="fail-feedback">비밀번호가 일치하지 않습니다</div>
<div class="fail2-feedback">비밀번호를 먼저 작성하세요</div>
</div>
<div class="row right">
<button type="button" class="btn btn-prev">
<i class="fa-solid fa-arrow-left"></i>
</button>
<button type="button" class="btn btn-next">
<i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- 2단계 : 닉네임 -->
<div class="row page">
<div class="row">
<h2>2단계 : 닉네임</h2>
</div>
<div class="row left">
<label>닉네임</label>
<input type="text" name="memberNickname"
placeholder="한글 또는 숫자 2~10자" class="form-input w-100">
<div class="success-feedback">멋진 닉네임입니다!</div>
<div class="fail-feedback">닉네임 형식이 올바르지 않습니다</div>
<div class="fail2-feedback">닉네임이 이미 사용중입니다</div>
</div>
<div class="row right">
<button type="button" class="btn btn-prev">
<i class="fa-solid fa-arrow-left"></i>
</button>
<button type="button" class="btn btn-next">
<i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- 3단계 : 이메일+연락처 -->
<div class="row page">
<div class="row">
<h2>3단계 : 연락 가능한 정보</h2>
</div>
<div class="row left">
<label>이메일</label>
<input type="text" name="memberEmail"
placeholder="test@kh.com" class="form-input w-100">
<div class="fail-feedback">이메일 형식이 잘못되었습니다</div>
</div>
<div class="row left">
<label>연락처</label>
<input type="tel" name="memberContact" placeholder="010XXXXXXXX (- 없이)"
class="form-input w-100">
<div class="fail-feedback">전화번호 형식이 올바르지 않습니다</div>
</div>
<div class="row right">
<button type="button" class="btn btn-prev">
<i class="fa-solid fa-arrow-left"></i>
</button>
<button type="button" class="btn btn-next">
<i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- 4단계 : 생년월일-->
<div class="row page">
<div class="row">
<h2>4단계 : 생년월일</h2>
</div>
<div class="row left">
<label>생년월일</label>
<input type="date" name="memberBirth" class="form-input w-100">
<div class="fail-feedback">잘못된 날짜를 선택하셨습니다</div>
</div>
<div class="row right">
<button type="button" class="btn btn-prev">
<i class="fa-solid fa-arrow-left"></i>
</button>
<button type="button" class="btn btn-next">
<i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
<!-- 5단계 : 주소 -->
<div class="row page">
<div class="row">
<h2>5단계 : 주소</h2>
</div>
<div class="row left">
<label style="display: block;">주소</label>
<input type="text" name="memberPost" placeholder="우편번호"
class="form-input" size="6" maxlength="6" readonly>
<button type="button" class="btn post-search">
<i class="fa-solid fa-magnifying-glass"></i>
</button>
<input type="text" name="memberAddr1" placeholder="기본주소"
class="form-input w-100 mt-10 post-search" readonly>
<input type="text" name="memberAddr2" placeholder="상세주소"
class="form-input w-100 mt-10 post-search">
<div class="fail-feedback">모든 주소를 작성해주세요</div>
</div>
<div class="row right">
<button type="button" class="btn btn-prev">
<i class="fa-solid fa-arrow-left"></i>
</button>
<button type="button" class="btn btn-next">
<i class="fa-solid fa-arrow-right"></i>
</button>
<button type="submit" class="btn btn-positive">가입하기</button>
</div>
</div>
</div>
</form>
</body>
</html>
비주얼스튜디오 코드 실행
join.js nickname 부분에 중복 검사 코드 추가
$(function(){
//상태 객체
var status = {
memberId:false,
memberPw:false,
memberPwCheck:false,
memberNickname:false,
memberContact:false,
memberBirth:false,
memberEmail:false,
memberAddress:false,
ok:function(){
return this.memberId && this.memberPw
&& this.memberPwCheck && this.memberNickname
&& this.memberContact && this.memberBirth
&& this.memberEmail && this.memberAddress;
},
};
$("[name=memberId]").blur(function(e){
var regex = /^[a-z][a-z0-9]{4,19}$/;
var isValid = regex.test($(e.target).val());
if(isValid) {//형식이 유효하다면
$.ajax({
url:"http://localhost:8080/rest/member/idCheck",
method:"post",
// data : {memberId : e.target.value },
data : { memberId : $(e.target).val() },
success : function(response){
$(e.target).removeClass("success fail fail2");
if(response == "Y") {//사용가능
$(e.target).addClass("success");
status.memberId = true;
}
else {//사용불가(중복)
$(e.target).addClass("fail2");
status.memberId = false;
}
},
error : function(){
alert("서버와의 통신이 원활하지 않습니다");
},
});
}
else {//형식이 유효하지 않다면(1차실패)
$(e.target).removeClass("success fail fail2");
$(e.target).addClass("fail");
status.memberId = false;
}
});
$("[name=memberPw]").blur(function(){
var regex = /^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$])[A-Za-z0-9!@#$]{8,16}$/;
var isValid = regex.test($(this).val());
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberPw = isValid;
});
$("#password-check").blur(function(){
var pw1 = $("[name=memberPw]").val();
var pw2 = $(this).val();
$(this).removeClass("success fail fail2");
if(pw1.length == 0) {
$(this).addClass("fail2");
status.memberPwCheck = false;
}
else if(pw1 == pw2) {
$(this).addClass("success");
status.memberPwCheck = true;
}
else {
$(this).addClass("fail");
status.memberPwCheck = false;
}
});
$("[name=memberNickname]").blur(function(e){
var regex = /^[ㄱ-ㅎㅏ-ㅣ가-힣0-9]{2,10}$/;
var isValid = regex.test($(e.target).val());
if(isValid) {//형식 통과
$.ajax({
url:"http://localhost:8080/rest/member/nicknameCheck",
method:"post",
// data:{ memberNickname : e.target.value },//JS
data : { memberNickname : $(e.target).val() },//jQuery
success : function(response){
$(e.target).removeClass("success fail fail2");
if(response == "Y") {//사용 가능한 닉네임
$(e.target).addClass("success");
status.memberNickname = true;
}
else {//이미 사용중인 닉네임
$(e.target).addClass("fail2");
status.memberNickname = false;
}
},
error : function(){
alert("서버와의 통신이 원활하지 않습니다");
},
});
}
else {//형식 오류
$(e.target).removeClass("success fail fail2");
$(e.target).addClass("fail");
status.memberNickname = false;
}
});
$("[name=memberEmail]").blur(function(){
var regex = /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
var email = $(this).val();
var isValid = email.length == 0 || regex.test(email);
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberEmail = isValid;
});
$("[name=memberContact]").blur(function(){
var regex = /^010[1-9][0-9]{7}$/;
var contact = $(this).val();
var isValid = contact.length == 0 || regex.test(contact);
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberContact = isValid;
});
$("[name=memberBirth]").blur(function(){
var regex = /^(19[0-9]{2}|20[0-9]{2})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
var birth = $(this).val();
var isValid = birth.length == 0 || regex.test(birth);
$(this).removeClass("success fail");
$(this).addClass(isValid ? "success" : "fail");
status.memberBirth = isValid;
});
$("[name=memberPost],[name=memberAddr1],[name=memberAddr2]").blur(function(){
//this 사용 불가(확실히 누군지 알 수 없음)
var post = $("[name=memberPost]").val();
var addr1 = $("[name=memberAddr1]").val();
var addr2 = $("[name=memberAddr2]").val();
var isBlank = post.length == 0 && addr1.length == 0 && addr2.length == 0;
var isFill = post.length > 0 && addr1.length > 0 && addr2.length > 0;
var isValid = isBlank || isFill;
$("[name=memberPost],[name=memberAddr1],[name=memberAddr2]").removeClass("success fail");
$("[name=memberPost],[name=memberAddr1],[name=memberAddr2]").addClass(isValid ? "success" : "fail");
status.memberAddress = isValid;
});
//페이지 이탈 방지
//- window에 beforeunload 이벤트 설정
$(window).on("beforeunload", function(){
return false;
});
//- form 전송할 때는 beforeunload 이벤트를 제거
$(".join-form").submit(function(e){
$(".form-input").blur();
if(!status.ok()) {
e.preventDefault();
//return false;
}
else {
$(window).off("beforeunload");
}
});
});