[javascript] 숫자만 입력하면 자동 하이픈 연결로 포매팅되는 전화번호 처리 기능 만들기

전화번호, 또는 휴대폰 번호는 입력 오류가 가장 빈번하게 발생하는 입력 필드다보니 입력한 자리수 체크와 숫자 체크, 그리고 하이픈 입력 여부를 체크해서 후처리를 하는 스크립트가 필수입니다.

코딩 스킬이 발전하기 전에는 전화 번호 입력을 국번, 4자리 숫자, 4자리 숫자로 입력 필드를 구분해서 입력을 받는 방식을 사용했습니다.

이런 입력 오류를 차단하기 위해 지역 번호(또는 휴대폰 국번)은 콤보리스트로 선택하도록 하고, 전화 번호는 3~4자리 숫자만 입력했는지 체크를 해서 그나마 입력 오류를 줄이는 방법을 사용합니다.

<select name="area">
    <option value="02">02</option>
    <option value="051">051</option>
    <option value="053">053</option>
    <option value="032">032</option>
    <option value="062">062</option>
    <option value="042">042</option>
    <option value="052">052</option>
    <option value="044">044</option>
    <option value="031">031</option>
    <option value="033">033</option>
    <option value="043">043</option>
    <option value="041">041</option>
    <option value="063">063</option>
    <option value="061">061</option>
    <option value="054">054</option>
    <option value="055">055</option>
    <option value="064">064</option>
    </select>-
<input type="text" name="main" size="4" maxlength="4">-
<input type="text" name="sub" size="4" maxlength="4">

이런 방식을 더는 사용하지 않으므로 대충 이렇게 만들어 쓰기도 했다 정도만 알면 됩니다.

코딩 기법이 발전하면서 이런 필드 구분보다는 하나의 필드에 입력한 전화번호를 인식해서 자동으로 하이픈도 추가해주고, 맞는 전화번호인지 체크도하는 방식을 주로 사용합니다.

전화번호 입력을 받는 필드는 다음과 같은 규칙을 가지게 됩니다.

  • 입력 필드는 텍스트 입력 필드로 최대 13자리까지만 입력이 가능
  • 숫자와 하이픈만 입력 가능
  • 지역번호는 0으로 시작
  • 지역번호(식별번호)는 2자리(서울 02), 또는 3자리 숫자(서울 외 지역 번호, 휴대폰 번호)
  • 국번은 3~4자리 숫자
  • 개별번호는 4자리 숫자
  • 지역번호, 국번, 개별번호 사이에는 하이픈이 있거나, 또는 없을 수 있음

정규표현식으로 일치하는 전화번호가 있는지를 찾으려면 다음과 같이 표현할 수 있습니다.

0[1-6][0-5]?-?\d{3,4}-?\d{4}

이 정규표현식은 현재의 유선 전화 지역번호와 휴대폰 번호 010만을 인식할 수 있고 전체 번호를 매칭하기 때문에 구 전화번호, 구 휴대폰 번호, 또는 번호 일부를 매칭하려면 다른 정규표현식을 사용해야 합니다.

지역번호 길이와 숫자 범위

0으로 시작하는 세자리 지역 번호는 "[1-6][0-5]" 숫자 범위만을 가질 수 있습니다. 정밀하게 체크하면 10~65 사이에 올 수 없는 숫자까지 체크를 할 수 있지만, 나중에 유지보수 이슈가 생길 수 있기 때문에 넘어갑니다.

지역 번호는 예외가 한가지 있습니다. 서울 지역 번호인 "02"만 두 자리입니다. 나머지 유선 지역 번호, 휴대폰 번호는 모두 0으로 시작하고 두 자리 숫자로 구성됩니다.

숫자를 제외한 나머지 문자 모두 삭제

입력 필드에 입력된 문자열에서 하이픈을 포함해 숫자가 아닌 것은 모두 삭제해서 숫자만 남깁니다. 정규표현식은 "\D"로 숫자가 아닌 문자를 모두 매칭합니다.

입력 필드에서 얻는 문자열은 pn 변수로 사용합니다.

pn=pn.replace(/\D/g, '')

입력 필드에 실시간 입력 체크 이벤트 핸들러 등록

입력필드는 최대 입력 길이를 13이하로 제한해야 합니다.

<input name="phonenumber" id="phonenumber" type="text" length="13" maxlength="13">

이벤트 핸들러를 사용해서 키 입력이 있을 때마다 formatNumber() 함수로 숫자만 남겨서 포매팅 한 결과를 입력 필드에 반영합니다.

window.addEventListener('DOMContentLoaded',function(){
    document.getElementById('phonenumber').addEventListener('keyup',formatNumber);
})

전화번호 영역별 실시간 체크

전화번호 지역번호, 국번, 개별 번호 순으로 순차적으로 왼쪽에서부터 입력 문자열을 잘라서 포매팅을 합니다.

지역번호, 국번, 식별번호를 저장하는 pnn 배열에 언은 결과를 저장한 후 "{pnn[0]}-{pnn[1]}-{pnn[2]}" 포맷으로 입력 필드를 갱신합니다.

function formatNumber(e){
    if(e.target?.name == 'phonenumber'){
        let pn = e.target.value.replace(/\D/g, '') //입력필드 값 얻기
        let pnn = ['','',''] //분리한 전화번호 저장
        let area = pn.match(/^0[1-6][0-5]?|02|0/) //지역번호 매칭
        pnn[0] = area?.length ? area[0] : ''
        if(pnn[0] != ''){
            pnn[1] = pn.substring(pnn[0].length,pnn[0].length+4) //국번
            pnn[2] = pn.substring(pnn[0].length+pnn[1].length,pnn[0].length+pnn[1].length+4) //식별번호
            if(pnn[1].length === 4 && pnn[2].length === 3){ //국번 4자리, 식별번호 3자리면 3-4로 포맷 변환
                pnn2=((pnn[1]+pnn[2]).split('')).slice(0,3).join('')
                pnn3=((pnn[1]+pnn[2]).split('')).slice(3).join('')
            }
        }
        e.target.value=pnn[0]+(pnn[1]!=''?'-':'')+pnn[1]+(pnn[2]!=''?'-':'')+pnn[2] //입력필드 갱신
    }
}

예외적으로 국번이 4자리, 식별 번호가 3자리로 끝나는 경우, 국번 3자리, 식별 번호 4자리가 되도록 조건문으로 포맷 변환 재처리를 합니다.

국번과 식별번호는 값이 없으면 하이픈이 표시되지 않도록 해야 합니다. 그렇지 않으면 입력 필드에서 백스페이스 키로 하이픈을 지울 수 없게 됩니다.

전화번호 입력 길이로 실시간 체크

앞서의 방법은 실시간으로 입력 오류를 수정해서 지역번호 패턴의 정확성까지 체크를 합니다.

구조적으로 조금 더 단순하고 유지보수가 쉬운 장점이 있습니다.

전화번호는 최소 길이가 1자리부터 11자리(01012345678)가 될 수 있습니다. 따라서 문자열 길이별로 같은 패턴의 정규표현식으로 처리할 수 있는 종류를 구분해서 작성하면 됩니다.

// 길이별 정규표현식
function getPattern(len, areaLen) {
    let pattern = '', regex = null;
    if(len < 4){
        pattern=areaLen==2?'(\\d{2})()()':'(\\d{3})()()'
    }else if(len < 7){
        pattern='(\\d{'+areaLen+'})(\\d{'+(len-areaLen)+'})()'
    }else if(len == 7){
        pattern=areaLen==2?'(\\d{2})(\\d{4})(\\d{1})':'(\\d{3})(\\d{4})()'
    }else if(len < 11){
        pattern='(\\d{'+areaLen+'})(\\d{4})(\\d{'+(len-4-areaLen)+'})'
    }else{
        pattern='(\\d{3})(\\d{4})(\\d{4})'
    }
    console.log(pattern)
    if(pattern != '')
        regex = new RegExp(pattern, 'g')
    return regex
}

// 입력필드 문자열 포매팅
function formatNumber(e) {
    let pn = e.target.value.replace(/\D/g, '') //입력필드 값 얻기
    let len = pn.length;
    let areaLen = pn.substring(0,2)=='02'?2:3
    const regex = getPattern(len, areaLen); // 입력된 문자열 길이와 패턴에 맞는 정규표현식 얻기
    e.target.value=regex ? pn.replace(regex, '$1'+(len-areaLen>0?'-':'')+'$2'+(len-areaLen>4?'-':'')+'$3') : pn; // 포매팅된 문자열 입력 필트에 적용
}

입력 문자열 길이가 9인 경우 국번을 3자리로 할지 4자리로 할지를 예외처리를 해야 하는데 여기서는 그냥 4자리 한가지로만 처리를 했습니다.

이벤트 핸들링을 할 때 실시간으로 매칭을 하지 않고 입력을 완료한 시점에 한번만 처리되도록 하려면 "keyup" 이벤트 대신 "blur" 이벤트를 사용하면 됩니다.