[TIL: 4주차] 유효성 검사

palette·2021년 7월 22일
0

TIL👩‍💻

목록 보기
2/3
post-thumbnail

유효성 검사

4주 차에는 DOM 탐색 프로퍼티를 사용해 인풋 값이 일치하는지 검증하는 함수를 만들고, 이 함수를 회원가입 폼으로 구현하는 과제를 받았다.

아이디와 비밀번호가 올바른 형식인지 검증하고 두 비밀번호의 인풋 값이 일치하는지 확인하는 것 자체는 구현이 어렵지는 않았지만.
onkeyup 이벤트를 통해 함수가 활성화되다 보니 입력 중인 인풋 박스의 조건이 하나라도 변경될 경우, 이를 인식해 회원가입 버튼을 비활성 시켜야 해서 인풋 박스별로 조건을 세세하게 검증하는 일이 중요한 과제였다.

회원가입 폼 만들기


처음에는 회원 정보 입력을 순차적 과정으로 생각해서 약관 동의 체크박스를 따로 만들어
개인정보 취급방침에 동의했을 때, 박스 내부 모든 값의 형식이 올바르면 회원가입 버튼이 활성화되게 만드는 꼼수를 썼었다.
하지만 이 코드는 형식을 전부 올바르게 쓰고 약관 체크박스에 체크해서 회원가입 버튼이 활성화되면, 그 후에 아이디나 비밀번호를 올바르지 않은 형식으로 수정해도 여전히 버튼이 활성화된 상태로 남아있는 오류가 있었다.

그래서 꼼수 부리지 않고… 유효성 검증을 꼼꼼히 해서 조건에 맞게 오류 메시지와 버튼이 잘 활성화되도록 코드를 수정했다.

나의 코드

const elInputUsername = document.querySelector('#username')
const elFailureMessage = document.querySelector('.failure-message')
const elSuccessMessage = document.querySelector('.success-message')
const elInputPassword1 = document.querySelector('#password')
const elInputPassword2 = document.querySelector('#password-retype')
const elPasswordMatch = document.querySelector('.match-message')
const elPasswordMismatch = document.querySelector('.mismatch-message')
const elPasswordFailure = document.querySelector('.password-failure')
const signupBtn = document.querySelector('#signup')
const termBtn = document.querySelector('#terms')
let member = {};

elInputUsername.onkeyup = function(){
  if(isMoreThan4Length(elInputUsername.value)){
    if(onlyNumberAndEnglish(elInputUsername.value)){
      elSuccessMessage.classList.remove('hide');
      elFailureMessage.classList.add('hide');

      if(termBtn.checked &&
        strongPassword(elInputPassword1.value) && 
        isMatch(elInputPassword1.value, elInputPassword2.value)){
          signupBtn.removeAttribute('disabled');
      }
    } else {
      elSuccessMessage.classList.add('hide');
      elFailureMessage.classList.remove('hide');
      signupBtn.setAttribute('disabled', true);
    }
  } else {
    elSuccessMessage.classList.add('hide');
    elFailureMessage.classList.remove('hide');
    signupBtn.setAttribute('disabled', true);
  }
}

function isMoreThan4Length(value) {
  // TODO : 동영상 강의를 보고 이 함수를 완성하세요.
  return value.length >= 4;
}

// [유효성 검증 함수]: 영어 또는 숫자만 가능
function onlyNumberAndEnglish(str) {
  return /^[A-Za-z][A-Za-z0-9]*$/.test(str);
}

elInputPassword1.onkeyup = function(){
  if(strongPassword(elInputPassword1.value)){
    elPasswordFailure.classList.add('hide');

    if(termBtn.checked &&
      onlyNumberAndEnglish(elInputUsername.value) && 
      isMoreThan4Length(elInputUsername.value) &&  
      isMatch(elInputPassword1.value, elInputPassword2.value)){
        signupBtn.removeAttribute('disabled');
    }

    if(!isMatch(elInputPassword1.value, elInputPassword2.value)){
      elPasswordMatch.classList.add('hide');
      elPasswordMismatch.classList.remove('hide');
      signupBtn.setAttribute('disabled', true);
    } else {
      elPasswordMatch.classList.remove('hide');
      elPasswordMismatch.classList.add('hide');
    }

  } else {
    elPasswordFailure.classList.remove('hide');
    signupBtn.setAttribute('disabled', true);

    if(!isMatch(elInputPassword1.value, elInputPassword2.value)){
      elPasswordMatch.classList.add('hide');
      elPasswordMismatch.classList.remove('hide');
      signupBtn.setAttribute('disabled', true);
    } else {
      elPasswordMatch.classList.remove('hide');
      elPasswordMismatch.classList.add('hide');
    }
  }
}

elInputPassword2.onkeyup = function(){
  if(isMatch(elInputPassword1.value, elInputPassword2.value)){
    elPasswordMatch.classList.remove('hide');
    elPasswordMismatch.classList.add('hide');

    if(termBtn.checked &&
      onlyNumberAndEnglish(elInputUsername.value) && 
      isMoreThan4Length(elInputUsername.value) &&  
      strongPassword(elInputPassword1.value)){
        signupBtn.removeAttribute('disabled');
    }

  } else {
    elPasswordMatch.classList.add('hide');
    elPasswordMismatch.classList.remove('hide');
    signupBtn.setAttribute('disabled', true);
  }
}

function isMatch (password1, password2) {
  // TODO : 동영상 강의를 보고 이 함수를 완성하세요.
  return password1 === password2;
}

// [유효성 검증 함수]: 최소 8자 이상하면서, 알파벳과 숫자 및 특수문자(@$!%*#?&) 는 하나 이상 포함
function strongPassword(str) {
  return /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/.test(str);
}

function validate(){
  if(termBtn.checked){
    if(onlyNumberAndEnglish(elInputUsername.value) && 
    isMoreThan4Length(elInputUsername.value) && 
    strongPassword(elInputPassword1.value) && 
    isMatch(elInputPassword1.value, elInputPassword2.value)){
      signupBtn.removeAttribute('disabled');
    }
  } else {
    signupBtn.setAttribute('disabled', true);
  }
}

function signup(){
  alert("회원가입을 축하합니다!")
  member['ID'] = elInputUsername.value;
  member['Password'] = elInputPassword1.value;
  console.log(member);

  elInputUsername.value='';
  elInputPassword1.value='';
  elInputPassword2.value='';
  termBtn.checked = false;
  elSuccessMessage.classList.add('hide');
  elPasswordMatch.classList.add('hide');
  signupBtn.setAttribute('disabled', true);
}

결과

비밀번호 조건이 만족되지 않더라도, 두 비밀번호가 일치할 경우에는 비밀번호 일치 메시지가 뜨도록 구현했다.

조건이 전부 만족됐을 경우에만 회원가입 버튼이 활성화된다.

회원가입 버튼을 누르면,
1) 알림창에 "회원가입을 축하합니다!" 메시지가 출력되고,
2) 빈 객체에 id와 비밀번호를 각 속성에 저장한 후 회원 배열에 추가,
3) 회원가입 창을 비우도록 설정해줬다.

이 일련의 순서대로 일어나는 과정이지만, 한눈에 볼 수 있도록 이미지 한 장으로 합성했다✌️

지금은 회원 배열을 어떻게 활용할지 코드로는 잘 떠오르지 않지만, 나중에 서버 관리를 배우면 회원 배열에 일치하는 조건을 찾아 로그인하는 기능으로 활용할 수 있을 것 같다.

CSS로 디자인하기


이번 과제는 기능 구현 자체는 간단하다 보니, CSS 단위를 통일하고 반응형 웹을 만드는 데 집중해서 코드를 짜봤다.

#signup__form{
  margin: 10vh 0;
  width: 80vmin;
  height: 70vmin;
  background-color: white;
  border: 2px solid black;
  box-shadow: 25px 25px black;
  display: flex;
  justify-content: center;
  align-items: center;
}

요즘 회원가입 폼들은 컨테이너를 활용하는 디자인이 많은 것 같아, 그런 디자인들을 참고해서 꾸몄다.
여백이 느껴지지 않게 코드스테이츠의 그래픽이 들어간 배경 이미지를 넣어주고, 컨테이너는 배경색과 대비되는 화이트 컬러와 투명도 없는 검은 그림자를 사용해 강조해주었다.

반응형 웹은 폰트 사이즈에 em 단위를 쓰는 코드가 많길래 처음에는 폰트 단위를 전부 em으로 통일해서 짰는데, 컨테이너 자체의 사이즈를 제한해둬서 그런지 화면을 줄였을 때 텍스트가 컨테이너 밖으로 출력되는 현상을 확인했다.

일단 컨테이너 자체에 overflow-x축은 hidden, overflow-y축은 auto 속성을 줘서 페이지가 작아질 경우 스크롤로 세로축 공간을 확장시키면 해결될 줄 알았지만, 이번에는 세로축을 줄일 때 인풋 박스와 텍스트가 겹쳐 보이는 오류가 있었다.

처음에는 경고메시지에 overflow: hidden 속성을 줘서 해결하는 방법을 생각해봤다.

페이지 크기에 관계없이 오류 메시지가 표시될 때도 인풋 박스의 크기가 일정하게 유지된다는 점은 좋았지만, 오류 메시지가 출력되는 것은 그 메시지를 전달하기 위해서이기 때문에… 결국 기각.

오류 메시지가 디스플레이되면 다음 필드셋들이 조금씩 밑으로 내려가면 되는데, 마진으로 해결될 것 같은 문제가 아무리 마진을 건드려 봐도 해결되지 않았다…ㅠㅠ

폰트 단위들을 이리저리 바꿔보다 결국 해결…🎉

rem, em은 부모속성에 영향을 받는 단위라 x축, y축이 줄어들 때 유연하게 변하진 않아서 오류 메시지의 폰트 단위를 vmin, vmax로 바꿔줬다.
너비나 높이를 지정할 때 주로 썼던 단위라 폰트에도 적용되는 줄 몰랐는데, 딱 내가 원하던 대로 구현됐다! 😭👍

하지만 다른 속성들을 이리저리 수정해본 결과, 문제는 텍스트가 아니라 필드셋의 높이였던 것 같다. 퍼센트로 설정했던 필드셋을 vmax 단위로 바꿔주니 폰트 단위에 관계없이 원하던 대로 구현할 수 있었다.

최종 코드는 타이틀과 인풋 라벨은 가독성을 위해 rem, 오류 메시지, 약관, 버튼은 vmin, vmax로 통일해 페이지 사이즈에 따라 크기가 유연하게 변할 수 있도록 설정했다.

최종 결과물


button {
  width: 90%;
  height: 7vmin;
  margin-top: 3%;
  border: 0px;
  background-color: #401ac4;
  color: white;
  font-size: 2vmin;
  overflow: hidden;
  transition: all 0.25s ease;
}

button:hover{
  cursor: pointer;
}

button:hover:not(:disabled) {
  color: white;
  background: linear-gradient(to left, #743ad5, #401ac4);
  border: 1px solid white;
}

button:disabled {
  background-color: white;
  color: #401ac4;
  border: 1px solid #401ac4;
}

button:disabled:hover{
  animation: shake .2s;
}

버튼 비활성화 상태에서 클릭하면 버튼을 흔들리는 애니메이션과,
버튼 활성화 상태에서 마우스를 올리면 버튼이 그라데이션으로 변하는 호버 효과를 추가해서 최종 완성한 결과물✨

역시 난 눈에 바로바로 보이는 CSS가 좋다…
이번엔 바닐라 CSS로 구현했지만, 버튼 디자인 참고한 사이트에서 본 화려한 SCSS 이펙트가 궁금해져서 SCSS도 천천히 공부해보고 싶다!

profile
지속가능한 삶을 위해

0개의 댓글