헬로월드예이
148
2021-10-16 20:21:05
7
260

js파일을 html에 사용할 때


제가 지금 회원가입 폼을 만들고 있는데  자바스크립트 파일을 따로 만들어서 함수를 관리하고 있습니다.

회원가입시 공백인데 회원가입버튼을 누르면 class에 warning을 추가하는 nullCheck.js가 먼저 실행되고

유효성검사에 맞지 않으면 alert를 띄우는 signinCheck.js이 그다음에 실행되도록하는게 제 의도입니다.


궁금한 점은 

(제가 만든 파일 내용은 질문아래 있습니다. )


1.  둘다 아래의 변수를 공유하고 있는데, 모든 파일에서 변수선언을 하면 에러가 나잖아요. 그래서 먼저 실행되는 nullCheck에만 변수선언을 했는데 이게 맞나요?

const yesBtn = document.querySelector('.btn-yes');

const id = document.getElementById('id');
const pw = document.getElementById('pw');
const name = document.getElementById('name');
const email = document.getElementById('email');
const tel = document.getElementById('tel');


2. 진행순서가 nullCheck이후 signinCheck가 아니라 둘이 동시에 작동하고 있습니다. 게다가 엇박으로.. 비밀번호 유효성검사를 하면서 이름 공백체크를 하네요. 이부분을 해결하려면 nullCheck이랑 signinCheck를 합쳐서 같은 addEventListener에 넣는 방법밖에 없나요? 


3. 자바스크립트 파일에 제이쿼리를 쓰지 않는게 좋다고 해서 에이젝스는 html에 코딩했는데요. 이렇게 하는게 맞나요?


아래는 제가 작업한 파일들입니다. 



1. memberForm.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/css/style.css">
    <title>회원가입</title>
    <script src="https://kit.fontawesome.com/d1b79b9b3f.js" crossorigin="anonymous"></script>

    <script src="/js/navBtn.js" defer></script>
    <script src="/js/admin_menu.js" defer></script>
    <script src="/js/showPw.js"defer ></script>
    <script src="/js/boxClose.js" defer></script>
    <script src="/js/nullCheck.js" defer></script>
    <script src="/js/signInCheck.js" defer></script>


</head>
<body>
<div id="container">
    <div id="navbar">
        <div id="nav_logo">
            <i class="fas fa-desktop"></i><a href="/main">개발자1</a>
        </div>
        <ul class="nav_menu">
            <li><a href="/loginform">로그인</a></li>
            <li>
                <a href="https://bellasimi.github.io/">
                    <i class="fas fa-blog fa-2x"></i>
                </a>
            </li>
            <li>
                <a href="https://github.com/bellasimi">
                    <i class="fab fa-github fa-2x"></i>
                </a>
            </li>
        </ul>
        <div id="nav_Btn">
            <a href="#"><i class="fas fa-bars fa-2x"></i></a>
        </div>
    </div>

    <div class="signin-form">
        <h1>회원가입</h1>
        <form action="/memberSave" method="post" id="signinForm">
            <div class="in-area">
                <input type="text" name="id" id="id" autocomplete="off" required>
                <label for="id">ID</label>

            </div>
                <button type="button" id="checkIdBtn" >중복체크</button>
            <div class="in-area">
                <input type="password" name="pw" id="pw" autocomplete="off" required>
                <label for="pw">PASSWORD</label>
                <i class="fas fa-eye" id="showPwBtn"></i>
            </div>
            <div class="in-area">
                <input type="text" name="name" id="name" autocomplete="off" required>
                <label for="name">이름</label>
            </div>

            <div class="in-area" >
                <input type="text" name="email" id="email" autocomplete="off" required>
                <label for="email">EMAIL</label>
            </div>

            <div class="in-area">
                <input type="text" name="tel" id="tel" autocomplete="off" required >
                <label for="tel" class="telLabel">전화번호</label>

                <h3>전화번호는 - 없이 연속으로 입력해주세요!</h3>
            </div>
            <div class="btn-area">
                <button type="button" class="btn-yes">회원가입</button>
                <button type="reset" class="btn-no">취소</button>
            </div>

        </form>

    </div>
    <div id="messageBox" th:if="${not #strings.isEmpty(message)}" th:text="${message}">
        <button type="button" id="closeMsgBtn">
            <i class="fas fa-times fa-2x"></i>
        </button>
    </div>
</div>
<script  src="https://code.jquery.com/jquery-latest.min.js"></script>
<script>
    $(document).ready(function(){

        checkIdBtn.addEventListener('click',()=>{
          if(id.value==""){
                alert("id는 영문숫자혼합 4-12자로 입력해주세요!");
            }
            else{
                $.ajax({
                    type : "post",
                    async : "false",
                    url: "/checkId",
                    data: {
                        id: id.value

                    },
                    success : function(data, textStatus) {
                        console.log("ajax성공시: "+data.trim());
                        alert(data.trim());
                    },
                    error : function(data, textStatus) {
                        alert("에러가 발생했습니다!"+data);
                    }

                });//ajax*/

            }//조건문 끝



        });//클릭이벤트

    });//ready

</script>
</body>

</html>

2. nullCheck.js


const yesBtn = document.querySelector('.btn-yes');

const id = document.getElementById('id');
const pw = document.getElementById('pw');
const name = document.getElementById('name');
const email = document.getElementById('email');
const tel = document.getElementById('tel');

yesBtn.addEventListener('click',()=>{
    if(id.value == ""){
        id.nextElementSibling.classList.add('warning');
        id.focus();
        setTimeout(function() {
            id.nextElementSibling.classList.remove('warning');
        },1500);
        return false;
    }
    if(pw.value == ""){
        pw.nextElementSibling.classList.add('warning');
        pw.focus();
        setTimeout(function() {
            pw.nextElementSibling.classList.remove('warning');
        },1500);
        return false;
    }
    if(name.value == ""){
        name.nextElementSibling.classList.add('warning');
        setTimeout(function() {
            name.nextElementSibling.classList.remove('warning');
        },1500);
        name.focus();
        return false;
    }
    if(email.value == ""){
        email.nextElementSibling.classList.add('warning');
        setTimeout(function() {
            email.nextElementSibling.classList.remove('warning');
        },1500);
        email.focus();
        return false;
    }
    if(tel.value == ""){
        tel.nextElementSibling.classList.add('warning');
        tel.focus();
        setTimeout(function() {
            tel.nextElementSibling.classList.remove('warning');
        },1500);
        return false;
    }

});//yesBtn 클릭 함수


3. signInCheck.js



const form = document.getElementById('signinForm');

const idReg = /^[a-zA-Z0-9]{4,12}$/;
const pwReg = /\d{4,8}/;
const emailReg = /^[a-zA-Z0-9]([-_\.]?[a-zA-Z0-9])*@[a-zA-Z0-9]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
const telReg =/\d{9,11}/;

yesBtn.addEventListener('click',()=>{
    if(!idReg.test(id.value)){
        alert("id는 영문숫자혼합 4-12자로 입력해주세요!");
        id.select();
        id.focus();
        return false;
    }
    if(!pwReg.test(pw.value)){
        alert("비밀번호는 숫자 4-8자로 입력해주세요!");
        console.log(pw.value)
        pw.select();
        pw.focus();
        return false;
    }
    if(!emailReg.test(email.value)){
        alert("이메일은 www@www.www형식으로 입력해주세요!");
        email.select();
        email.focus();
        return false;
    }
    if(!telReg.test(tel.value)){
        alert("전화번호는 -를 빼고 입력해주세요!");
        tel.select();
        tel.focus();
        return false;
    }

        form.submit();
});

긴 글 읽어주셔서 감사합니다. 😢 더 잘하고 싶은데 코딩실력이 마음을 따라주지 못하네요. 답변해주시면 더 열심히 공부하겠습니다. 좋은 주말 보내세요 😎

0
  • 답변 7

  • siva6
    5k
    2021-10-16 21:23:11

    1. 정답은 없습니다만,
    js파일을 분리하는 목적이 다른 js파일, html과도 독립적인 분리를 원한다면, html의 class, id를 직접 코딩하는 것은 피하는게 좋을 것 같습니다.
    그리고, js파일들이 서로 동일한 global scope의 변수를 공유하다면, 오류의 가능성도 늘어나게 됩니다.
    가능한 각 js가 독립적인 scope를 가지는게 좋습니다.

    2. click event를 함께 등록하셨는데요.
    저라면 submit을 하는 쪽에서  nullCheck쪽의 함수를 호출하는 형태로 구현할 것 같습니다.

    3. jQuery는 많이 쓰였던 좋은 라이브러리입니다. 
    js파일 내에서 jQuery를 쓰지 않는게 좋다는 말의 의도는 모르겠지만,
    jQuery에 의존적인 코드를 쓰지 않는거였다 하더라도, 현재 jQuery를 페이지에서 쓰고 있는데, 제외할 필요가 있을지 모르겠네요.

  • 헬로월드예이
    148
    2021-10-16 23:48:28

    @siva6 😉 답변감사합니다. 

    js의 파일이 현재 동일한 변수를 공유하는데 그럼 2번 답변처럼 submit하는 쪽에 변수선언을 전부 하고 다른 스크립트엔 함수만 쓴 뒤 submit 하는 쪽에서 호출하면 되겠군요.

    3번은 저도 이유를 몰라서 궁금했습니다. 자바스크립트파일엔 자바스크립트만 들어가는게 유의미하단 내용의 글이었는데.. 좀 더 찾아봐야겠네요. 

    덕분에 생각정리가 됐어요!! 

  • rtome
    30
    2021-10-17 00:00:15

    아마 처음 제가 AJAX와 JS를 배웠을 때 헷갈린 거랑

    똑같이 헷갈리고 계신거 같아서 답변 드립니다.


     AJAX는 JQUERY에 종속적인 기술이 아닙니다.

    보통 AJAX 예제 코드가 JQUERY로 되어 있는 게 많고

    JQUERY가 AJAX를 잘 지원하고 있기 때문에

    처음 배울 때 헷갈리게 되는 같습니다.


    그리고 JQUERY를 JS에서 사용하지 않는 게 좋다는 것의 의도는

    JS 표준 ES가 JQUERY를 과거와 달리 대체할 수 있을 정도로 발전했기에

    가능한 JS를 사용하면 좋다는 것이지,

    무조건적으로 JQUERY를 사용하지 말라는 의미는 아닌 것 같습니다.

    지금도 JQUERY는 많은 페이지에서 쓰이는 라이브러리니까요.


    다만, 같은 의미의 코드를 하나는 JS, 하나는 JQUERY로 만들어

    통일성과 가독성을 떨어트리는 건 지양해야겠죠.


  • yeori
    2k
    2021-10-17 00:30:19

    nullCheck.js 가 하는 일(공백입력)은 signInCheck.js 에서 하는 값을 검증하는 일에 해당합니다.

    1. 공백 문자 안됨 - 공통 규칙

    2. 입력값의 맥락에 맞는 형식이어야 함 - 하위 규칙

    그러니까 nullCheck.js 에서하는 공백 검증은 signInCheck.js로 흡수시켜도 될듯 합니다.

    1. 중복제거

    nullCheck.js에서 하고 있는 일이 곰곰히 살펴보면 대상만 다를뿐이지(id, pw, name, email 등) 행위는 완전히 똑같습니다.

        if([입력필드].value == ""){
            [입력필드].nextElementSibling.classList.add('warning');
            [입력필드].focus();
            setTimeout(function() {
                id.nextElementSibling.classList.remove('warning');
            },1500);
            return false;
        }

    * [입력필드] 만 그때그때 다름

    아래와 같이 signInCheck.js 안에 하나의 함수를 만듭니다.

    // signInCheck.js
    /**
     * 공백이면 true, 공백이 아니면 false 반환
     */
    const notEmpty = (elem) => {
      const {value} = elem
      const isEmpty = value.trim() === ""
      if(isEmpty){
        elem.nextElementSibling.classList.add('warning');
        elem.focus();
        setTimeout(function() {
          id.nextElementSibling.classList.remove('warning');
        },1500)
      }
      return isEmpty
    }

    * true를 반환하면 오류로 간주함

    정규 표현식 왼쪽에 공백 확인하는 함수 notEmpty 를 같이 사용합니다.

    yesBtn.addEventListener('click',()=>{
        if(notEmpty(id) || !idReg.test(id.value)){
            ....
            return false;
        }
        if(notEmpty(pw) || !pwReg.test(pw.value)){
            ....
            return false;
        }
        if(notEmpty(email) || !emailReg.test(email.value)){
            ....
            return false;
        }
        if(notEmpty(tel) || !telReg.test(tel.value)){
            ....
            return false;
        }
    
         alert('성공')
    });

    * notEmpty가 true를 반환하면 공백이라는 뜻


    이제 nullCheck.js 에서 변수를 선언하는 부분을 signInCheck.js로 옮긴 후 nullCheck.js 파일을 없앱니다.


    const form = document.getElementById('signinForm');
    
    // 다 가져옴
    const yesBtn = document.querySelector('.btn-yes');
    
    const id = document.getElementById('id');
    const pw = document.getElementById('pw');
    const name = document.getElementById('name');
    const email = document.getElementById('email');
    const tel = document.getElementById('tel');
    // 다 가져옴
    
    
    const idReg = /^[a-zA-Z0-9]{4,12}$/;
    const pwReg = /\d{4,8}/;
    const emailReg = /^[a-zA-Z0-9]([-_\.]?[a-zA-Z0-9])*@[a-zA-Z0-9]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
    const telReg =/\d{9,11}/;
    
    const notEmpty = (elem) => {
        ....
    }
    yesBtn.addEventListener('click',()=>{
        if(notEmpty(id) || !idReg.test(id.value)){
            ....
            return false;
        }
        if(notEmpty(pw) || !pwReg.test(pw.value)){
            ....
            return false;
        }
        if(notEmpty(email) || !emailReg.test(email.value)){
            ....
            return false;
        }
        if(notEmpty(tel) || !telReg.test(tel.value)){
            ....
            return false;
        }
    
         alert('성공')
    });


    2. 더 줄일 수 있음

    공백 확인과 정규식으로 입력값 형식을 검증하는 코드 역시 판에 박은듯 똑같습니다(대상만 다름)

    id, pw, name 등의 폼을 배열로 담아서 루프를 돌며 처리하고 싶습니다.(if 로 일일이 검사하고 싶지 않음)

    아래와 같이 input 필드들을 배열로 감싸도 되고...

    const id = document.getElementById('id');
    const pw = document.getElementById('pw');
    const name = document.getElementById('name');
    const email = document.getElementById('email');
    const tel = document.getElementById('tel');
    
    const formElems = [id, pw, name, email, tel]; // 여기다 모아놓음

    또는 querySelectorAll을 이용해서 한꺼번에 잡아낼 수도 있습니다.

    const formElems = document.querySelectorAll('#signinForm input[name]')

    * #signinForm 아래에 name 속성이 정의된 input 태그를 모두 잡아냄

    * 반환타입은 NodeList 입니다(얘를 루프로 돌리고 싶음)

    하위규칙들에 따라서(idReg, pwReg) 보여줄 메세지가 다른듯 합니다. input 들의 name attribute를 갖고 아래와 같이 규칙과 에러 메세지를 모아놓은 자료구조를 도입합니다.


    const idReg = ...
    const pwReg = ...
    const emailReg =...
    const telReg =...
    
    const regMap = {
      id: {rule: idReg, msg: 'id 오류'},
      pw: {rule: pwReg, msg: 'PW 오류'},
      name: {rule: /.*/, msg: 'name 오류'},
      email: {rule: emailReg, msg: 'email 오류'},
      tel: {rule: telReg, msg: 'tel 오류'}
    }

    각각의 input 마다 name 속성값을 이용해서('id', 'pw' 등) 정규표현식과 오류 메세지를 담은 객체를 바인딩 해놓습니다.

    이제 yesBtn.addEventListener('click', ..) 의 조건문을 걷어냄

    const idReg = ...
    const pwReg = ...
    const emailReg =...
    const telReg =...
    
    const regMap = {
      ....
    }
    const notEmpty = (elem) => {...}
    
    yesBtn.addEventListener('click',() => {
      const formElems = document.querySelectorAll('#signinForm input[name]')
      const failed = [] // 검증 실패를 여기다 담고 싶음
      for(let i = 0; i < formElems.length; i++) {
        const elem = formElems[i]
        const regex = regMap[elem.name] // name="id", name="pw"
        if(notEmpty(elem) || !regex.rule.test(elem.value)) {
          alert(regex.msg)
          elem.focus()
          elem.select()
          failed.push({elem, msg: regex.msg}) // 요렇게 담기
          break;
        }
      }
      if(failed.length === 0) {
         alert('성공')
      } else {
        showError(failed[0])
      }

    * formElems 에 input 들이 모여있음

    * const elem - 각각의 input

    * regMap[elem.name] - <input name="pw">인 경우 elem.name은 "pw"값. regMap.pw에 접근함, 이 안에는 정규표현식 rule과 화면에 보여줄 msg 를 담고 있습니다.

    * 검증 실패하면 failed 에 담아줌

    여기서는 하나라도 실패하면 중단시키게 구현했는데, 보통은 실패한 오류들을 다 보여주는게 좋고, 실패할때마다 바로바로 화면에 표시하는게 더 좋습니다(가입버튼 안눌러도).

    그리고 showError 함수를 만들어서 화면에 오류를 보여주는...? 일을 합니다(이런 부분은 나중에 수정할 일이 많기때문에 함수로 빼내야 일이 줄어듬)

    const showError = (failed) => {
      const {elem} = failed
      elem.nextElementSibling.classList.add('warning');
      setTimeout(function() {
        elem.nextElementSibling.classList.remove('warning');
      }, 3000);
    }
    
    yesBtn.addEventListener('click', () => {
      if (...) {
        ...
      else {
        showError(failed[0])
      }
    });
    

    * focus() select() 처럼 화면을 건드리는 코드도 showError로 모아두는게 좋음

    * notEmpty에서 화면을 건드리는 코드들(addClass(...))도 걷어내서 showError로 옮기는게 좋음.

    * notEmpty같은 함수는 "값에 문제가 있는지" 판단하는 일만 하는게 좋습니다.


    최종

    const form = document.getElementById('signinForm');
    const yesBtn = document.querySelector('.btn-yes');
    
    const idReg = /^[a-zA-Z0-9]{4,12}$/;
    const pwReg = /\d{4,8}/;
    const emailReg = /^[a-zA-Z0-9]([-_\.]?[a-zA-Z0-9])*@[a-zA-Z0-9]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
    const telReg =/\d{9,11}/;
    
    const regMap = {
      id: {rule: idReg, msg: 'id 오류'},
      pw: {rule: pwReg, msg: 'PW 오류'},
      name: {rule: /.*/, msg: 'name 오류'},
      email: {rule: emailReg, msg: 'email 오류'},
      tel: {rule: telReg, msg: 'tel 오류'}
    }
    
    const notEmpty = (elem) => {
      const {value} = elem
      const isEmpty = value.trim() === ""
      if(isEmpty){
        elem.nextElementSibling.classList.add('warning');
        elem.focus();
        setTimeout(function() {
          id.nextElementSibling.classList.remove('warning');
        }, 3000)
      }
      return isEmpty
    }
    const showError = (failed) => {
      const {elem} = failed
      elem.nextElementSibling.classList.add('warning');
      setTimeout(function() {
        elem.nextElementSibling.classList.remove('warning');
      }, 3000);
    }
    
    yesBtn.addEventListener('click',() => {
      const formElems = document.querySelectorAll('#signinForm input[name]')
      const failed = [] // 여기다 담고 싶음
      for(let i = 0; i < formElems.length; i++) {
        const elem = formElems[i]
        const regex = regMap[elem.name] // name="id", name="pw"
        if(notEmpty(elem) || !regex.rule.test(elem.value)) {
          alert(regex.msg)
          elem.focus()
          elem.select()
          failed.push({elem, msg: regex.msg})
          break;
        }
      }
      if(failed.length === 0) {
         alert('성공')
      } else {
        showError(failed[0])
      }
    });


  • 헬로월드예이
    148
    2021-10-17 20:27:08

    rtome 그렇네요. 전 ajax가 $.ajax{} 형태로만 존재하고 제이쿼리에서만 사용가능한 줄 알았어요. ajax는 그냥 xml과 자바스크립트간 비동기적 정보교환기능을 의미할 뿐이고, fetch()나 XMLHttpRequest()를 가지고 순수자바스크립트로 코딩이 가능했네요. 답변감사합니다. 덕분에 몰랐던 점 짚고 가요!

  • 헬로월드예이
    148
    2021-10-17 20:29:33 작성 2021-10-17 22:53:39 수정됨

    yeori 와... 답변 정말 큰 도움이 됐습니다. 사수가 생긴 기분이에요😮 감사합니다. 자바스크립트에서 맵을 쓸 생각을 안해봤는데 생각의 전환이 됐어요. 코드가 간결하고 경제적이네요. 근데 한가지 궁금한게 왜 함수들을 변수처리 하신건가요? 특별한 이유가 있을까요?

  • yeori
    2k
    2021-10-17 23:07:48

    실제로 가입 등 공통 코드를 만들다보면 굉장히 다양한 rule을 정의하게 됩니다.

    * 최소 길이 6글자 이상

    * 숫자로만 입력되어야 함

    * 소수점 안됨

    * 특수문자가 1개 이상

    * 소문자 1개 이상

    ....

    이러한 rule들을 1개 이상 조합해서 특정 필드에 대한 검증 규칙으로 적용하는 경우가 많고, notEmpty처럼 함수로 따로 빼내서 다른 곳에서 사용될때가 많습니다. 

  • 로그인을 하시면 답변을 등록할 수 있습니다.