코드/Trouble Shoot

[JS] input 숫자만 입력하기

Yeah-Panda 2020. 3. 19. 13:58

input 폼에 숫자만 입력하기 위해
보통 HTML5 스펙에 있는 input type="number" 를 사용한다.

<input type="number">

다음의 문제가 발생한다

  • 타입은 number 지만 지수 표기법으로 인해 영문자 e 혹은 E 가 입력이 가능하다.
  • 한글이 입력된다.
  • 숫자가 아닌 값이 입력될때 input 의 value 가 null 이 아닌 아예 빈 값이 된다.
    ( input 에 보여지는 상태는 변화가 없다. )

KeyPress Event

한글 입력시 이벤트 자체가 발생하지 않음
그외 알파벳 및 숫자 이벤트 발생
현재 까지 입력된 키값중에 number 가 아닌 알파벳 혹은 문자 "e" 가 들어간 경우
target.value 가 빈 값이 된다. ( input  폼안에는 텍스트 존재 )

  • event.key: "1", "2", "a", "b" 등
  • event.code: "KeyE", "KeyA" 등
  • event.target.value: 없음

KeyDown Event (한글 입력시 이벤트 발생)

  • event.key:
    • 숫자/ 알파벳 : "1", "2", "a", "b", "c"
    • 한글 : "Process"
  • event.code:
    • 숫자일 경우 "Digit1" "Digit2" 의 형태
    • 그외 알파벳 "KeyK", "KeyA" 
  • event.target.value:
    • 한글일 경우 : 값 없음

Input Event (한글 입력시 이벤트 발생)

  • event.data: "한", "글", "1", "2", "abc"
  • event.target.value:
    • 숫자: "123"
    • 한글/알파벳: 값 없음

시도한 방법들

event 받아서 replace - 입력 후 리플레이스 되는 형태라서 그런지 한글은 특히 입력/삭제가 눈에 보인다.
삽질 오래하다가 아래의 방법을 찾음

https://jsfiddle.net/emkey08/zgvtjc51

 

Input filter showcase - JSFiddle - Code Playground

 

jsfiddle.net

기발함. 좋음. 잘됨.
이전 입력된 값을 저장했다가 숫자가 아닌 값이 들어오면 이전 값으로 돌려 놓는 형태인데
비슷한 방법을 생각했다가 잘 되지 않아서 관뒀었는데 조건문의 형태를 조금 다른것 같다.

// Restricts input for the given textbox to the given inputFilter.
function setInputFilter(textbox, inputFilter) {
  ["input", "keydown", "keyup", "mousedown", "mouseup", "select", "contextmenu", "drop"].forEach(function(event) {
    textbox.addEventListener(event, function() {
      if (inputFilter(this.value)) {
        this.oldValue = this.value;
        this.oldSelectionStart = this.selectionStart;
        this.oldSelectionEnd = this.selectionEnd;
      } else if (this.hasOwnProperty("oldValue")) {
        this.value = this.oldValue;
        this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
      } else {
        this.value = "";
      }
    });
  });
}


// Install input filters.
setInputFilter(document.getElementById("intTextBox"), function(value) {
  return /^-?\d*$/.test(value); });
setInputFilter(document.getElementById("uintTextBox"), function(value) {
  return /^\d*$/.test(value); });
setInputFilter(document.getElementById("intLimitTextBox"), function(value) {
  return /^\d*$/.test(value) && (value === "" || parseInt(value) <= 500); });
setInputFilter(document.getElementById("floatTextBox"), function(value) {
  return /^-?\d*[.,]?\d*$/.test(value); });
setInputFilter(document.getElementById("currencyTextBox"), function(value) {
  return /^-?\d*[.,]?\d{0,2}$/.test(value); });
setInputFilter(document.getElementById("latinTextBox"), function(value) {
  return /^[a-z]*$/i.test(value); });
setInputFilter(document.getElementById("hexTextBox"), function(value) {
  return /^[0-9a-f]*$/i.test(value); });

다만 아쉬운점은 얘는 input type="text" 다.
나의 케이스는 input 폼 옆에 숫자 증가/감소 버튼이 필요했다.
즉 input type="number" 여야 했다. 별도로 화살표 UI 를 만들수는 있었지만
위의 저 코드를 활용해보는게 더 빠를 듯하여 저 코드에서 input type="number"
로 바꾸니 대번에 제대로 동작 하지 않는다..... 한글도 입력되고..

조금 살펴보니 type="number" 일때 전달되는 이벤트 형태가 type="text" 와 좀 다른것 같아
이 부분을 방어처리하니 제대로 된다. ( 결론적으로 사용한 버전 )

import {DirectiveOptions} from 'vue';

interface HTMLInputElement2 extends HTMLInputElement{
    oldValue: string;
    oldSelectionStart: number | null;
    oldSelectionEnd: number | null;
}

export const InputNumber: DirectiveOptions = {
    bind(el) {
        const handler = (event: any) => {
            const target: HTMLInputElement2 = (event.target as HTMLInputElement2);
            const v: string = event.data || target.value;

            if (/^\d*$/.test(v)) {
                target.oldValue = target.value;
                target.oldSelectionStart = target.selectionStart;
                target.oldSelectionEnd = target.selectionEnd;
            }  else if (target.hasOwnProperty('oldValue')) {
                target.value = target.oldValue;
                if (target.type !== 'number') {
                    target.setSelectionRange(target.oldSelectionStart, target.oldSelectionEnd);
                }
            } else {
                target.value = '';
            }
        };

        el.addEventListener('input', handler);
    }
};

event.data || target.value 가 존재하는 이유가 type=number 일때 숫자가 아닌 다른 값을 입력하는 케이스에
대한 방어코드다.

또한 input number 타입의 경우 오른쪽에 증가/감소 버튼이 있는데 얘를 누르면
event.data 가 전달 되지 않는다. target.value 로 체크가 가능하다.

if (target.type !== 'number') 아래는 input number 타입이 아닌 애들도 쓸수 있게
범용적으로 하려고 놔두긴했지만 현재의 경우는 크게 필요는 없을 것 같다.