1. 유효성 검사
1) 구현해야 하는 내용
- 이름이 빈 값인 경우 경고 메시지 출력. 채워졌을 경우 성공 표시
- 아이디가 5글자 미만인 경우 경고 메시지 출력. 조건이 일치할 경우 성공 표시
- 비밀번호가 빈 값인 경우 경고 메시지 출력. 채워졌을 경우 성공 표시
- 비밀번호 확인 입력시 비밀번호와 비밀번호 확인의 내용이 일치하지 않는 경우 경고 메시지 출력. 일치할 경우 성공 표시
- 모든 조건이 일치하는 경우에만 회원가입 버튼 활성화
- 회원가입 버튼 클릭시 가입 완료 메시지 띄우기
2) 구현한 코드
📚 HTML
<main>
<div class="card">
<div class="sign-up">
<h1>Sign Up</h1>
<div class="input-box">
<fieldset>
<label for="userName">이름</label>
<input type="text" id="userName" placeholder="이름" />
<p class="failure-message hide">이름은 비워둘 수 없습니다</p>
</fieldset>
<fieldset>
<label for="userId">아이디</label>
<input type="text" id="userId" placeholder="아이디" />
<p class="failure-message hide">
아이디는 다섯 글자 이상이어야 합니다
</p>
</fieldset>
<fieldset>
<label for="password">비밀번호</label>
<input type="password" id="password" placeholder="비밀번호" />
<p class="failure-message hide">비밀번호는 비워둘 수 없습니다</p>
</fieldset>
<fieldset>
<label for="passwordRetype">비밀번호 확인</label>
<input
type="password"
id="passwordRetype"
placeholder="비밀번호 확인"
/>
<p class="failure-message hide">비밀번호가 일치하지 않습니다</p>
</fieldset>
</div>
<fieldset class="btn-box">
<button type="button" id="btnSignUp" class="disabled" disabled>
회원가입
</button>
</fieldset>
</div>
<div class="complete">
<div class="check"></div>
<h2>이름님, 환영합니다!</h2>
<p>회원가입이 완료되었습니다.</p>
</div>
</div>
</main>
📝 JS 파일 각 요소들 변수에 할당
// **인풋
let elInputUserName = document.querySelector('#userName')
let elInputUserId = document.querySelector('#userId')
let elInputPassword = document.querySelector('#password')
let elInputPasswordRetype = document.querySelector('#passwordRetype')
let elInputPhone = document.querySelector('#phone')
// **메시지
let elNameFailMsg = document.querySelector('#userName + p')
let elIdFailMsg = document.querySelector('#userId + p')
let elPwFailMsg = document.querySelector('#password + p')
let elPwRetypeFailMsg = document.querySelector('#passwordRetype + p')
let elPhoneMsg = document.querySelector('#phone + .message')
// **버튼
let BtnSignUp = document.querySelector('#btnSignUp')
// **박스
let elInputBox = document.querySelector('.input-box')
(1) 이름, 비밀번호 빈값인 경우 확인 및 메시지 출력
// 이름, 비밀번호 - 빈값이 아닌지
function isNotEmpty(value) {
return value !== ''
}
// 이름
elInputUserName.onkeyup = function () {
if (isNotEmpty(elInputUserName.value)) {
elNameFailMsg.classList.add('hide')
this.classList.add('success')
this.classList.remove('failure')
} else {
elNameFailMsg.classList.remove('hide')
this.classList.remove('success')
this.classList.add('failure')
}
}
// 비밀번호
elInputPassword.onkeyup = function () {
if (isNotEmpty(elInputPassword.value)) {
elPwFailMsg.classList.add('hide')
this.classList.add('success')
this.classList.remove('failure')
} else {
elPwFailMsg.classList.remove('hide')
this.classList.remove('success')
this.classList.add('failure')
}
}
input 요소의 값이 빈 문자열인지를 확인하기 위해 isNotEmpty 함수를 만들었다.
isNotEmpty는 매개변수로 value를 받아 value가 빈 문자열이 아닌지 판단한다.
이름 인풋과 비밀번호 인풋에는 .onkeyup 이벤트를 걸어준다.
✅ 키보드 이벤트
- keydown : 키가 눌렸을때 이벤트 발생
- keypress : 키를 누르고 있을때 이벤트 발생. 단 문자를 출력할수있는 키가 눌러졌을때만 실행된다.
- keyup : 키가 눌렀다가 떼어졌을때 이벤트 발생
키를 한번 눌렀다 뗄 경우, keydown - keypress - keyup 순서로 이벤트가 발생된다.
이벤트 발생시 실행하는 코드의 내용은 다음과 같다.
isNotEmpty가 true라면(값이 빈문자열이 아니라면) - 실패 메시지 숨김 / 인풋에 success 클래스 추가, failure 클래스 삭제
isNotEmpty가 false라면(값이 빈문자열이라면) - 실패 메시지 출력 / 인풋에 success 클래스 삭제, failure 클래스 추가
이 메시지들과 성공 조건들은 다른 이벤트 실행 코드들과 같으므로 아래 내용부터 설명을 생략한다.
(2) 아이디 5글자 미만 조건 확인
// 아이디 판단함수 - 아이디가 5글자 이상인지
function isMoreThan5Length(id) {
return id.length >= 5
}
// 아이디
elInputUserId.onkeyup = function () {
// 조건문은 이벤트 핸들러 안에
if (isMoreThan5Length(elInputUserId.value)) {
// id가 5글자 이상이면 실패 메시지 숨김
elIdFailMsg.classList.add('hide')
this.classList.add('success')
this.classList.remove('failure')
} else {
// id가 5글자 미만이면 실패 메시지 출력
elIdFailMsg.classList.remove('hide')
this.classList.remove('success')
this.classList.add('failure')
}
}
id의 길이가 5 이상인지 확인하기 위해 isMoreThan5Length 함수를 만들었다.
매개변수로 id를 받아 id.length가 5 이상인지 판별한다.
아이디의 인풋에 onkeyup 이벤트를 걸어 키가 눌러질때 이벤트가 발생하도록 한다.
(3) 비밀번호 확인 인풋 조건 확인
// 비밀번호 판단함수 - 비밀번호가 비밀번호 확인과 같은지
function isMatch(pw1, pw2) {
if (pw1 !== '' && pw2 !== '') {
return pw1 === pw2
}
}
// 비밀번호 확인
elInputPasswordRetype.onkeyup = function () {
if (isMatch(elInputPassword.value, elInputPasswordRetype.value)) {
// 비밀번호와 확인이 일치하면 - 실패메시지 숨김
elPwRetypeFailMsg.classList.add('hide')
this.classList.add('success')
this.classList.remove('failure')
} else {
// 비밀번호와 확인이 일치하지 않으면 - 실패메시지 출력
elPwRetypeFailMsg.classList.remove('hide')
this.classList.remove('success')
this.classList.add('failure')
}
}
비밀번호 인풋의 값과 비밀번호 확인 인풋의 값이 같은지 확인하기 위해 isMatch 함수를 만들었다.
매개변수로 pw1, pw2를 받아 각각 빈 문자열이 아닐때, pw1과 pw2가 같은지 확인한다.
비밀번호 확인 인풋에 onkeyup이벤트를 걸어 키가 눌러질때 이벤트가 발생하도록 한다.
(4) 회원가입 버튼 활성화
// 가입버튼 활성화 - 모든 조건 완료시에만
elInputBox.addEventListener('keyup', function () {
// 이름과 아이디와 비밀번호의 조건이 모두 만족하는지
if (
isNotEmpty(elInputUserName.value) &&
isMoreThan5Length(elInputUserId.value) &&
isMatch(elInputPassword.value, elInputPasswordRetype.value)
) {
// 회원가입 버튼 활성화
BtnSignUp.classList.remove('disabled')
BtnSignUp.removeAttribute('disabled', '')
} else {
// 회원가입버튼 비활성화
BtnSignUp.classList.add('disabled')
BtnSignUp.setAttribute('disabled', '')
}
})
회원가입 버튼은 모든 조건이 true로 일치했을때만 활성화 상태가 되어야 한다. 조건 중의 하나라도 false일 경우에는 비활성화 상태로 보여져야 한다.
그렇다면 회원가입 비활성화/활성화 전환을 위한 이벤트는 모든 인풋에 keyup 이벤트가 발생할때 계속 모든 조건이 true인지 판단해야 한다. 만약 한번만 조건을 판단하게 된다면, 버튼은 계속 비활성화 상태일 것이다.
그렇다면 모든 인풋의 keyup 이벤트에 모든 조건이 true인지 판단하는 조건문을 추가해야할까?
그렇게 할수도 있겠지만 나는 검색 후 이벤트 버블링과 이벤트 위임을 활용하기로 했다.
이벤트 버블링(event bubbling)은 한 요소에 이벤트가 발생했을때, 해당 요소에 할당된 핸들러가 동작하고 그 다음으로 부모 요소의 핸들러가 동작하고, 그것이 가장 최상단의 조상요소를 만날때까지 이 과정이 반복되며 요소 각각에 할당된 핸들러가 동작하는 것이다. 이렇게 이벤트가 제일 깊은 곳에 있는 요소에서 시작해 부모 요소를 향해 올라가며 이벤트가 발생하는 모습이 마치 물거품 같기 때문에 이를 이벤트 버블링이라고 한다.
이벤트 위임(event delegation)은 캡쳐링과 버블링을 활용한 이벤트 핸들링 패턴이다.
주로 비슷한 방식으로 여러 요소를 다뤄야 할때 사용하는데, 이벤트 위임을 사용하면 요소마다 핸들러를 할당하는 것이 아닌 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러개의 요소를 한꺼번에 다룰 수 있다.
그래서 나는 이 모든 인풋들을 싸고있는 박스요소 .input-box에 이벤트 핸들러를 할당했다.
따라서 키가 눌러지면 이벤트 버블링으로 박스요소에 이벤트가 전달될 것이고 input-box에 이벤트 핸들러를 하나 할당하는 것 만으로도 모든 인풋에 이벤트가 발생할때 해당 조건을 판단하게 할수있다!
버튼의 disable 속성을 변경하기 위해 setAttribute와 removeAttribute 메서드를 사용했다.
(5) 가입 완료 메시지 출력
CSS
main {
display: flex;
flex-direction: column;
align-items: center;
width: 400px;
height: 600px;
perspective: 1100px;
}
.card {
width: 100%;
height: 100%;
position: relative;
transition: 0.4s;
transform-style: preserve-3d;
}
.card > div {
background: rgba(255, 255, 255, 0.5);
padding: 30px;
border-radius: 30px;
position: absolute;
backface-visibility: hidden;
width: 100%;
height: 100%;
box-shadow: 10px 10px 15px 5px rgba(0, 0, 0, 0.2);
}
.complete {
transform: rotateY(180deg);
}
.flip {
transform: rotateY(180deg);
}
JS
// 가입 버튼 클릭시
BtnSignUp.onclick = function () {
document.querySelector('.card').classList.add('flip')
document.querySelector('h2').innerHTML =
elInputUserName.value + '님,' + '<br>' + '환영합니다!'
}
회원가입 버튼을 클릭하면 .card에 .flip 클래스가 추가되고, h2의 내용이 변경된다.
이름 인풋의 value를 사용해 사용자가 입력한 이름이 가입인사 메시지에 나타나도록 했다.
transform의 rotateY 속성을 사용해 3D처럼 카드가 뒤집어지는 효과를 만들었다.
처음에 했을때는 제대로 뒤집어질때도 있고 그렇지 않아서 왜 그런지 궁금했는데 요소를 한번 card로 싸고 나니까 괜찮아졌다. 검색해보니 모든 면을 감싼 요소 자체에 효과를 주면 원근감 효과 때문에 입체적으로 뒤집힐 때 해당 요소가 예상범위를 벗어나기 때문에 변하는 상태가 끊기는것이라고 한다.
(6) 리팩토링
각 인풋의 onkeyup 이벤트가 실행될때의 내용이 모두 비슷하다고 생각했다.
비슷하다고 생각한 이유는 아래와 같다.
- if 문으로 조건 판단
- true 일 경우 - 메시지 숨김 / 해당 인풋 요소에 특정 클래스 추가 및 삭제
- false 일 경우 - 메시지 출력 / 해당 인풋 요소에 특정 클래스 추가 및 삭제
사용되는 요소들과 조건을 판단하는 함수는 다르지만 구조가 비슷해서 이부분을 따로 함수로 빼면 코드가 더 간단해 질 것 같았다. 그래서 msgToggle 이라는 함수를 만들었다.
하지만 막상 작성해보려고 하니 문제가 생겼다...
다른 함수들은 모두 매개변수가 3개 필요했다.
- 조건 판단에 사용할 함수
- 조건 판단에 사용되고 성공실패클래스가 붙을 인풋
- 출력될 실패메시지
이렇게 3개만 필요했다.
하지만 비밀번호 확인 이벤트는 매개변수가 4개 필요했다🤦♀️
- 조건 판단에 사용할 함수
- 조건 판단에 사용되고 성공실패클래스가 붙을 인풋(비밀번호 확인 인풋)
- 조건 판단에 사용될 인풋(비밀번호 인풋)
- 출력될 실패메시지
진짜.. 이때는 어떻게 해야하는지 고민이 필요했다.
다른 함수를 다시 만들어야 하나? -> 내용은 똑같은데 함수를 두개 만든다면 그게 더 비효율적일것같다
근데 매개변수 개수가 다른데 어떡함?? -> 그러게...
그래서 다시 생각해 본 다음 함수를 수정했다.
매개변수가 3개일 경우 실행할 코드와 매개변수가 4개일 경우 실행할 코드를 구분했다.
들어올수도 있는 매개변수 input2는 undefined로 기본값을 주고 이 값이 undefined 일때만 매개변수가 3개일 경우의 코드를 실행하도록 작성했다.
🤔 msgToggle (1차)
function msgToggle(fn, input, input2 = undefined, elMsg) {
if (input2 === undefined) {
// 두번째 인풋이 매개변수로 들어오지 않았다면
if (fn(input.value)) {
elMsg.classList.add('hide')
input.classList.add('success')
input.classList.remove('failure')
} else {
elMsg.classList.remove('hide')
input.classList.remove('success')
input.classList.add('failure')
}
} else {
// 두번째 인풋이 매개변수로 들어온다면 = 비밀번호 확인의 경우라면
if (fn(input.value, input2.value)) {
elMsg.classList.add('hide')
input.classList.add('success')
input.classList.remove('failure')
} else {
elMsg.classList.remove('hide')
input.classList.remove('success')
input.classList.add('failure')
}
}
}
그런데 위와 같이 함수를 작성하고 테스트를 해보니 아예 동작이 되지 않았다.
이렇게 요소의 classList를 불러오지 못한다는 에러가 발생했다.
다시 잘 생각해보니 이것은 매개변수를 입력하는 부분에서 잘못된거였다.
다른 함수들은 모두 (조건판단함수, 사용할 인풋, 출력할 메시지) 로 매개변수를 넘겨주었지만
비밀번호 확인의 경우 (조건판단함수, 비밀번호인풋, 비밀번호 확인 인풋, 출력할 메시지) 로 매개변수를 넘겨주어서
msgToggle에서 매개변수를 받아올때도 (조건판단함수, 인풋1, 인풋2 = undefined, 메시지) 형태로 내용을 작성했다.
하지만 이렇게 되면 아이디나 이름등의 이벤트가 실행되었을때는 내가 3번째 매개변수에 기본값을 undefined로 주었기 때문에 계속 요소를 읽어오지 못한다는 에러가 발생한 것이다.
그래서 매개변수의 순서를 변경했다.
🙆♀️ msgToggle (수정)
function msgToggle(fn, input, elMsg, input2 = undefined) {
if (input2 === undefined) {
// 두번째 인풋이 매개변수로 들어오지 않았다면
if (fn(input.value)) {
elMsg.classList.add('hide')
input.classList.add('success')
input.classList.remove('failure')
} else {
elMsg.classList.remove('hide')
input.classList.remove('success')
input.classList.add('failure')
}
} else {
// 두번째 인풋이 매개변수로 들어온다면 = 비밀번호 확인의 경우라면
if (fn(input.value, input2.value)) {
elMsg.classList.add('hide')
input.classList.add('success')
input.classList.remove('failure')
} else {
elMsg.classList.remove('hide')
input.classList.remove('success')
input.classList.add('failure')
}
}
}
// 이름
elInputUserName.addEventListener('keyup', function () {
return msgToggle(isNotEmpty, elInputUserName, elNameFailMsg)
})
// 아이디
elInputUserId.addEventListener('keyup', function () {
return msgToggle(isMoreThan5Length, elInputUserId, elIdFailMsg)
})
// 비밀번호
elInputPassword.addEventListener('keyup', function () {
return msgToggle(isNotEmpty, elInputPassword, elPwFailMsg)
})
// 비밀번호 확인
elInputPasswordRetype.addEventListener('keyup', function () {
return msgToggle(
isMatch,
elInputPasswordRetype,
elPwRetypeFailMsg,
elInputPassword
)
})
이렇게 하면 매개변수의 순서에도 문제가 없다.
다시 테스트 해보니 문제 없이 동작한다!
이 이벤트 리스너들은 함수를 리턴하게 되는데 그러면 이건 클로저라고 할수있는걸까?
아직 클로저를 다 이해하지 못해서 확실히는 잘 모르겠다.
다음 수업때 질문해볼까 싶다.
나름대로 코드를 간결하게 리팩토링 해보았는데 좋은 방향일지... 그것도 좀 궁금하다.
2. 이벤트 객체
사용자 입력을 트리거로 발생한 이벤트 정보를 담은 객체.
- event 객체에 출력되는 내용
: 이벤트의 종류, 이벤트가 발생한 요소, 요소의 위치
- event.target이 담고있는 값
: 이벤트가 발생한 html 요소
***************
유효성 검사를 직접 만들어보았다!
나름대로 리팩토링도 해보고 에러가 발생한 부분도 스스로 해결할 수있어서 뿌듯했다.
다음번에는 전화번호를 입력하는 것도 해보고싶다. 아마 정규식을 사용해야 하는것 같아서, 처음에는 정규식 없이 해결해보려고 했지만 에러가 계속 발생하고 시간이 많이 걸리는 것 같다.
다음번에는 정규식에 대해 공부해보려고 한다.
'study > TIL' 카테고리의 다른 글
23.01.13 - 클래스와 인스턴스, 객체 지향 프로그래밍, 프로토타입과 클래스 (0) | 2023.01.13 |
---|---|
23.01.12 - 고차 함수, filter, map, reduce (0) | 2023.01.12 |
23.01.05 - DOM, DOM으로 CRUD (0) | 2023.01.05 |
23.01.04 - 복습 및 추가 보완 (0) | 2023.01.04 |
23.01.03 - 클로저, spread/rest 문법, 구조 분해 할당 (0) | 2023.01.03 |