포폴에 들어갈 이메일 폼을 알아보던 중 Formspree라는 무료 api를 발견, 이를 적용해본다.
문제는, 내가 data나 JSON쪽은 지금 이걸 알아보면서 처음 배운거라, 하나 하나 찾아가면서 짜집기 + 주석 정리를 해 보았다.
나중을 위해 기록.
<div class="email-form-list">
<form id="contact-form" action="https://formspree.io/f/xblglpvl" method="POST">
<div class="contact-form-wrapper">
<label for="email" class="label_hidden" tabindex="-1">이메일:</label>
<input type="email" name="email" id="email" required placeholder="Your email">
<label for="message" class="label_hidden" tabindex="-1">메시지:</label>
<textarea name="message" id="message" required placeholder="Let's connect! Send me a message."></textarea>
<button type="submit" id="submit-btn">전송</button>
</div>
<div class="contact-form-text -success">
<p class="text_balance point_font">뉴스레터에 가입해 주셔서 감사합니다. 이메일 주소로 전송된 링크를 클릭하여 구독을 확인해 주세요.</p>
<p class="text_balance point_font">최신 뉴스, 프로모션, 이벤트를 알려드리게 되어 기쁩니다. 즐거운 읽기와 쇼핑 되세요!</p>
</div>
</form>
</div>
<style>
#contact-form {
display: flex;
flex-direction: column;
width: 100%;
max-width: 320px;
position: relative;
}
#contact-form input,
#contact-form textarea {
width: 100%;
padding: 10px;
margin: 5px 0;
border: 1px solid #ddd;
border-radius: 5px;
background: white;
}
#contact-form textarea {
height: 200px;
border-radius: 10px;
resize: none;
}
#contact-form button {
background-color: var(--point3-clr);
color: var(--body-bg-clr);
border: none;
padding: 10px;
cursor: pointer;
border-radius: 5px;
transition: color .3s ease, background-color .3s ease;
width: 100%;
}
.contact-form-text{
position: absolute;
top: 0;
left: 50%;
transition: opacity .5s ease-out;
transform: translateX(-50%);
font-size: calc(21px * var(--size));
}
#contact-form.is-submitted .contact-form-wrapper{
opacity: 0;
pointer-events: none;
transition: none;
}
.contact-form-text.-success{
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-column-gap: var(--grid-gutter);
}
#contact-form:not(.is-submitted) .contact-form-text.-success{
opacity: 0;
pointer-events: none;
transition: none;
}
.contact-form-text.-success p{
letter-spacing: .65px;
text-shadow: 0px 0px 1px rgba(255, 255, 255, 0.3);
}
</style>
<script>
function formBtnFunction() {
let theBtn = document.getElementById("submit-btn");
let emailInput = document.getElementById("email");
let messageInput = document.getElementById("message");
let form = document.getElementById("contact-form");
form.addEventListener("submit", function (e) {
e.preventDefault(); // 1. html 기본 폼 제출을 막는다 => Formspree에서 필수,
theBtn.disabled = true;
emailInput.disabled = true;
messageInput.disabled = true;
// Formspree API로 JSON 데이터 전송
let formData = {
email: emailInput.value,
message: messageInput.value,
};
fetch(form.action, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify(formData),
})
.then(response => {
if (response.ok) {
// 성공적으로 전송됨
setTimeout(function () {
form.classList.add("is-submitted");
setTimeout(function () {
// 입력값 초기화
emailInput.value = "";
messageInput.value = "";
// Placeholder 원래대로 복구
emailInput.placeholder = "Your email";
messageInput.placeholder = "Let's connect! Send me a message.";
// 입력 필드 & 버튼 다시 활성화
emailInput.disabled = false;
messageInput.disabled = false;
theBtn.disabled = false;
form.classList.remove("is-submitted");
}, 5000);
}, 3000);
} else {
alert("전송에 실패했습니다. 다시 시도해주세요.");
emailInput.disabled = false;
messageInput.disabled = false;
theBtn.disabled = false;
}
})
.catch(error => {
alert("에러가 발생했습니다. 네트워크를 확인해주세요.");
emailInput.disabled = false;
messageInput.disabled = false;
theBtn.disabled = false;
});
});
}
</script>
하나 하나 파헤쳐보자.
formspree는 JSON 형식을 요구한다. 그래서, javascript로 form에 관해 만질거면, 일단 html form 데이터 보내는 기본 형식을 event.preventDefault();로 막아야 한다.
formspree는 서버다. 즉, 그 서버에 보낼 데이터값을 만들어야 한다.
fetch()는 자바스크립트에서 서버와 응답을 주고 받을 때 사용하는 함수다.
마찬가지로, response 또한 서버에서 오는 응답 객체이다.
자바스크립트 data 객체는 말 그대로 자바스크립트 객체다.
문제는 formspree는 JSON data가 필요하기 때문에, 이걸 JSON.springify로 변환해줘야 한다.
참고로,
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
이 부분은, 보낼 데이터가 json이라는 것을 알려주면서, 응답 데이터를 json으로 달라고 요청하는 것이다.
response.ok는 서버에서 받은 응답 값이 true임을 판별하는 자바스크립트 기능이다.
여기서 else와 catch가 다른데,
else문은 말 그대로, 서버에서 받은 응답값이 오류일 때 else문이 적용되는 것이고,
catch error는 네트워크 문제일 확률이 높다.
이를 간단하게 주석으로 정리해 보았으며, 아래에 써 놓겠다.
<script>
function formBtnFunction() {
let theBtn = document.getElementById("submit-btn");
let emailInput = document.getElementById("email");
let messageInput = document.getElementById("message");
let form = document.getElementById("contact-form");
form.addEventListener("submit", function (e) {
e.preventDefault(); // 1. html 기본 폼 제출을 막는다 => Formspree에서 필수,
theBtn.disabled = true;
emailInput.disabled = true;
messageInput.disabled = true; // 중복 제출 방지
// Formspree API로 JSON 데이터 만들기
let formData = {
email: emailInput.value,
message: messageInput.value,
};
fetch(form.action, { //fetch()는 JavaScript에서 서버와 데이터를 주고받을 때 사용하는 함수, 웹사이트에서 서버로 요청을 보내고, 서버에서 응답을 받아오는 역할
method: "POST", //form.action의 method를 가져오고,
headers: {
"Content-Type": "application/json", //formspree에서 필수, 보낼 데이터가 json이라는 걸 알려줌
"Accept": "application/json", // formspree에서 필수, 받을 데이터를 json으로 보내달라고 요쳥하는 것.
},
body: JSON.stringify(formData), // formData는 자바스크립트 객체임. 그러나 서버는 JSON 문자열이 필요해서, formData를 JSON.stringify로 변환한 것. method가 post고 우리가 데이터를 보내는 거기 때문에, body: 가 필수적이다.
// 만약 여기서 body가 없으면, 서버는 요청은 확인하지만, 뭘 보내는 지 인식하지 못함.
})
.then(response => { // response는 서버에서 오는 응답 객체, 이 응답 객체에는 응답 상태, 응답 본문(JSON 데이터), 헤더 정보 등이 들어 있음.
if (response.ok) { // fetch API에서 제공하는 기본 속성, respons.ok는 서버 응답이 성공했는지(true/false)를 확인하는 속성.
// 즉, 성공적으로 적용되었을 경우,
setTimeout(function () {
form.classList.add("is-submitted");
setTimeout(function () {
// 입력값 초기화
emailInput.value = "";
messageInput.value = "";
// Placeholder 원래대로 복구
emailInput.placeholder = "Your email";
messageInput.placeholder = "Let's connect! Send me a message.";
// 입력 필드 & 버튼 다시 활성화
emailInput.disabled = false;
messageInput.disabled = false;
theBtn.disabled = false;
form.classList.remove("is-submitted");
}, 5000);
}, 3000);
} else { // 서버가 '응답을 보냈지만 실패한 경우, 예시로 오류코드 400, 401, 500'
alert("전송에 실패했습니다. 다시 시도해주세요.");
emailInput.disabled = false;
messageInput.disabled = false;
theBtn.disabled = false;
}
})
.catch(error => { // 이쪽 에러는 네트워크 쪽 오류일 확률이 높음, 인터넷 끊김, 서버 다운, fetch()오류
alert("에러가 발생했습니다. 네트워크를 확인해주세요.");
emailInput.disabled = false;
messageInput.disabled = false;
theBtn.disabled = false;
});
});
}
</script>