81

I have a text box and only want to accept numbers and a period "." when using VueJS. Can anyone help with code? I'm new to Vue.

John Kary
  • 6,703
  • 1
  • 24
  • 24
Muh Ghazali Akbar
  • 1,169
  • 3
  • 13
  • 21
  • 7
    I know its an old question but it was top in Google results. So here is a solution to users with similar problem. I think all solutions here are complicated. The simplest thing to do is to add @input event and replace all non-digits and dot. `this.message.replace(/[^0-9.]/g,'');`. Here is a **[fiddle](http://jsfiddle.net/2c7h4qsm/1/)** to demonstrate. – Kalimah Dec 08 '18 at 10:05
  • 3
    @KalimahApps - allows for multiple dots = problem. – Jquestions Feb 06 '19 at 17:20
  • Seriously, don't read further do what @Kalimah suggests. I had it enabled in 15 secs – DKebler Jan 29 '20 at 23:31
  • @Kalimah I really like your solution. That's what I did firstly, but the problem is when I have multiple text fields which accept only numbers. How can I do it without repeating code? I made one function that worked for all fields using using `event.target.value = event.target.value.replace(/[^0-9]/g, "");` on "@input" event, but when I added v-model (because I need it for different reason), now it doesn't work... Please help. Thanks in advance! – Vasilije Bursac Aug 25 '20 at 20:06
  • 1
    @VasilijeBursac You can use this [codepen](https://codepen.io/aliso1990/pen/eYzdQXy). – Ali Sohrabi Oct 21 '20 at 07:32
  • @AliSohrabi Thank you very much for answer and help! – Vasilije Bursac Oct 22 '20 at 15:54

20 Answers20

111

You can write a Vue method and that method can be called on the keypress event. Check out this fiddle.

Update:

adding source code:

HTML

<div id="demo">
  <input v-model="message" @keypress="isNumber($event)">
</div>

Vue.js

var data = {
  message: 1234.34
}

var demo = new Vue({
  el: '#demo',
  data: data,
  methods: {
    isNumber: function(evt) {
      evt = (evt) ? evt : window.event;
      var charCode = (evt.which) ? evt.which : evt.keyCode;
      if ((charCode > 31 && (charCode < 48 || charCode > 57)) && charCode !== 46) {
        evt.preventDefault();;
      } else {
        return true;
      }
    }
  }
});
Ben
  • 60,438
  • 111
  • 314
  • 488
Deendayal Garg
  • 5,030
  • 2
  • 19
  • 33
51

You should change your input to type="number" to more accurately reflect your behaviour. You can then use the built-in Vue.js directive v-model.number.

Usage:

<input type="number" v-model.number="data.myNumberData"/>
JBeagle
  • 2,630
  • 2
  • 18
  • 20
  • 4
    Some browsers have `onScroll` for `type=number`, which increment/decrement values. – Danon Mar 26 '19 at 19:32
  • 7
    Using `type=number` is not always a good solution for numerical input as the browser will style the input box differently and add increment/decrement buttons. – nmg49 Jun 05 '19 at 17:34
  • 2
    this answer work but only one character (E) is allowing to enter. why any reason? – Sudhir K Gupta Jul 04 '19 at 09:43
  • 2
    @SudhirKGupta Scientific notations like 1.2e-1 which is 0.12 – Daniel Cheung Jul 31 '19 at 03:32
  • 2
    This still allows entries with multiple decimal points, such as 1.2.3.4.5 – SteveC Jul 31 '19 at 11:24
  • Use the `step` attribute if you don't want to allow decimal values. And concerning the letter E, [here is the link to a more complete explanation](https://stackoverflow.com/questions/31706611/why-does-the-html-input-with-type-number-allow-the-letter-e-to-be-entered-in#:~:text=HTML%20input%20number%20type%20allows,also%20be%20written%20as%202e5.) – nook Aug 31 '20 at 09:25
  • This also works for `v-text-field`, cool! – Valentin Grégoire Feb 05 '21 at 11:09
37

This was my solution. Most of the answers here have been deprecated. Additionally, input values always return a string, even if you key a number. So because of that, some of the solutions here did not work for me.

In my case I didn't want a decimal point, but I added that into the array for the purpose of this thread.

<b-form-input v-model.number="quantity" @keypress="isNumber($event)" type="number"></b-form-input>
isNumber (evt: KeyboardEvent): void {
    const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'];
    const keyPressed: string = evt.key;
    
    if (!keysAllowed.includes(keyPressed)) {
           evt.preventDefault()
    }
}
Anbuselvan Rocky
  • 606
  • 6
  • 22
Brian Kopp
  • 429
  • 4
  • 6
23

Short and easy understand.

HTML

 <input @keypress="onlyNumber" type="text">

VUE JS

onlyNumber ($event) {
   //console.log($event.keyCode); //keyCodes value
   let keyCode = ($event.keyCode ? $event.keyCode : $event.which);
   if ((keyCode < 48 || keyCode > 57) && keyCode !== 46) { // 46 is dot
      $event.preventDefault();
   }
}
Mengseng Oeng
  • 753
  • 2
  • 7
  • 19
15

A simple way to do this in one line:

IsNumber(event) {
  if (!/\d/.test(event.key) && event.key !== '.') return event.preventDefault();
}
  • 3
    I dont know about the performance (because of the regex), but this should the one and only answer if you are checking for numbers.... – yooouuri Aug 25 '20 at 14:03
  • 2
    For me works `if (!/^[0-9]+$/.test(event.key) || event.key === '.') return event.preventDefault();` – Tenarius Aug 28 '20 at 09:13
  • `allowDecimalChars(event: KeyboardEvent) { if (/[^.0-9]/.test(event.key)) { event.preventDefault() } }` – broc.seib Sep 26 '20 at 00:35
10

Here is a better way to handle the specific question asked (numbers and "dots" only) by setting v-restrict.number.decimal using the following directive. It also had some bonus code to support alpha only or alphanumeric. You could also only allow "dots" although I do not know why you would. It will not allow extra characters to "sneak through" if typing fast. It also supports copy/paste, delete, and some other keys users would expect to still work from an input:

Vue.directive('restrict', {
  bind (el, binding) {
    el.addEventListener('keydown', (e) => {
      // delete, backpsace, tab, escape, enter,
      let special = [46, 8, 9, 27, 13]
      if (binding.modifiers['decimal']) {
        // decimal(numpad), period
        special.push(110, 190)
      }
      // special from above
      if (special.indexOf(e.keyCode) !== -1 ||
        // Ctrl+A
        (e.keyCode === 65 && e.ctrlKey === true) ||
        // Ctrl+C
        (e.keyCode === 67 && e.ctrlKey === true) ||
        // Ctrl+X
        (e.keyCode === 88 && e.ctrlKey === true) ||
        // home, end, left, right
        (e.keyCode >= 35 && e.keyCode <= 39)) {
        return // allow
      }
      if ((binding.modifiers['alpha']) &&
        // a-z/A-Z
        (e.keyCode >= 65 && e.keyCode <= 90)) {
        return // allow
      }
      if ((binding.modifiers['number']) &&
        // number keys without shift
        ((!e.shiftKey && (e.keyCode >= 48 && e.keyCode <= 57)) ||
        // numpad number keys
        (e.keyCode >= 96 && e.keyCode <= 105))) {
        return // allow
      }
      // otherwise stop the keystroke
      e.preventDefault() // prevent
    }) // end addEventListener
  } // end bind
}) // end directive

To use:

<!-- number and decimal -->
<input
  v-model="test"
  v-ep-restrict.number.decimal
  ...
/>

<!-- alphanumeric (no decimal) -->
<input
  v-model="test2"
  v-ep-restrict.alpha.number
  ...
/>

<!-- alpha only -->
<input
  v-model="test3"
  v-ep-restrict.alpha
  ...
/>

This can be modified to serve as a base for just about any scenario and a good list of key codes is here

D Durham
  • 1,243
  • 2
  • 20
  • 42
8

I solved issue like yours via vue.js filters. First i created filter - let's say in filters.js file

export const JustDigits = () => {
  Vue.directive('digitsonly', (el, binding) => {
    if (/[\d\.]+/i.test(el.value)) {
      console.log('ok');
    } else {
      let newValue = el.value.replace(/[a-zA-Z]+/ig, '');
      el.value = newValue;
      console.log('should fix', newValue);
      binding.value = el.value;
    }
  });
};

Then in the component where this functionality is required i did:

import {
  JustDigits
} from './filters';

JustDigits();

And then you are able to use this directive in template:

 <input  v-model="myModel"
         v-digitsonly
         type="text"  
         maxlength="4"  class="form-control" id="myModel" name="my_model" />

Please note, that my regex may differ from what you need, feel free to modify it as well as this code line let newValue = el.value.replace(/[a-zA-Z]+/ig, ''); that removes characters from the string. I posted it just to show you one of the possible solutions vue.js provides to solve task like this.

flamedmg
  • 586
  • 3
  • 12
  • if you type more than one letter the second one sometimes goes through. – Yevgeniy Afanasyev May 28 '18 at 02:09
  • You can avoid the fast typing extra characters getting though by wrapping the value in a half second or so debounce (importing from lodash or unserscore) before checking it – D Durham Aug 29 '18 at 02:26
8

Building on previous solutions, to prevent multiple decimal positions, also pass the v-model to the function:

<input v-model="message" v-on:keypress="isNumber($event, message)">

and modify the isNumber method as follows:

isNumber(event, message) {
  if (!/\d/.test(event.key) &&  
    (event.key !== "." || /\./.test(message))  
  )  
    return event.preventDefault();  
}

To limit the number of digits after the decimal add the following line into the isNumber method:

 if (/\.\d{2}/.test(message)) return event.preventDefault();

The \d{2} limits the entry of two digits. Change this to \d{1} to limit to one.

As stated in other answers, this does not prevent the pasting of non-numeric data.

SteveC
  • 644
  • 7
  • 12
5

I needed my input to allow only digits, so no e symbol, plus, minus nor .. Vue seems funky and doesn't re-trigger @onkeypress for symbols like dot.

Here is my solution to this problem:

<input
  onkeypress="return event.key === 'Enter'
    || (Number(event.key) >= 0
    && Number(event.key) <= 9"
  type="number"
>

I am taking digits only, limiting from 0 to 9, but also I do want enable form submit on Enter, which would be excluded with the above approach - thus the enter.

Adam
  • 3,415
  • 4
  • 30
  • 49
4

You can handle this via simple html

<input type="number">

and in your app.css

/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* this is for Firefox */
input[type=number] {
  -moz-appearance: textfield;
}

Style code will remove ugly arrows from your number input field and yes it accepts dots

elvonkh
  • 107
  • 7
4

@Kalimah answer didn't work for me, but I liked the idea of using regex. In my case I needed to filter out any non-digit character, including dots. The below code worked for me, Vue 2.6.

<input type="text" v-model="variable" @input="cleanVariable" />
methods: {
    cleanVariable(event) {
        this.variable = event.target.value.replace(/[^0-9]/g, "");
}
netotz
  • 193
  • 1
  • 4
  • 12
3

Why not using an external mask lib like vue-the-mask or cleave.js?

For example, with vue-the-mask you can easily use theirs directive like this:

<input type="text" name="some-name" id="some-id" v-model="some.value" v-mask="'##.##.##.##.###'">
Pablo Souza
  • 863
  • 2
  • 11
  • 25
3

You can use this library https://www.npmjs.com/package/vue-input-only-number

import onlyInt, { onlyFloat } from 'vue-input-only-number';

Vue.use(onlyInt);
Vue.use(onlyFloat);

<input type="text" v-int>
<input type="text" v-float>
trueboroda
  • 2,650
  • 26
  • 24
3

There is input event which is more powerful/flexible than keypress, keydown or change that reacts on any type of change: program or user type such as key presses or paste events.

// Initial input state
let prevValue = ''
let prevSelectionStart = 0

function allowNumbersOnly(event) {
  const input = event.target
  let value = event.target.value

  // Check if value is number
  let isValid = +value == +value

  if (isValid) {
    // preserve input state
    prevValue = value
    prevSelectionStart = input.selectionStart
  } else {
    // restore previous valid input state.
    // we have to fire one more Input event in  order to reset cursor position.
    var resetEvent = new InputEvent('input')
    input.value = prevValue
    input.selectionStart = prevSelectionStart
    input.selectionEnd = prevSelectionStart
    input.dispatchEvent(resetEvent)
  }
}
<input type="text" oninput="allowNumbersOnly(event)">
Maksim Shamihulau
  • 1,219
  • 1
  • 15
  • 17
3

Just evaluate if is nan and now you can prevent default

<input @keypress="isNumber">

isNumber (val) {
  if (isNaN(Number(val.key))) {
    return val.preventDefault();
  }
}
Andres Felipe
  • 4,292
  • 1
  • 24
  • 41
3

The problem with type="number" and min="1" is that it allows typing or pasting the - sign, the e sign and the + sign.

The problem with the Math.abs(value) is that it replaces the whole typed in number with 0, which we don't want and is very frustrating.

The e.keyCode is very unreadable and deprecated.

The pure HTML method doesn't work in this case, you have to use JavaScript.

Remove the type="number" and do this:

function inputNumberAbs() {
  var input = document.getElementsByTagName("input")[0];
  var val = input.value;
  val = val.replace(/^0+|[^\d.]/g, '');
  input.value = val;
}
<input oninput="inputNumberAbs()">

Regex explanation:

^0+ removes all 0s from beginning

| OR operator

[^\d]. remove everything that is ^(NOT) a \d(NUMBER) or a .(PERIOD)

This prevents the user to type or paste any of the not defined characters, like e, -, +, and all other characters that are not numbers.

If you don't need decimal numbers just remove . from regex.

2

I cannot find the perfect solution because some work for input but not for copy&paste, some are the other way around. Below's solution works for me. It prevents negative numbers, typing "e", copy&paste "e" text.

I use mixin so I can be reused anywhere.

const numberOnlyMixin = {
    directives: {
    numericOnly: {
        bind(el, binding, vnode) {

        // console.log(el, binding);

        // this two prevent from copy&paste non-number text, including "e".
        // need to have both together to take effect.
        el.type = 'number';
        el.addEventListener('input', (e) => {
            // console.log('input', e);
            // console.log(el.validity);
            return el.validity.valid || (el.value = '');
        });

        // this prevents from typing non-number text, including "e".
        el.addEventListener('keypress', (e) => {
            let charCode = (e.which) ? e.which : e.keyCode;
            if ((charCode > 31 && (charCode < 48 || charCode > 57)) && charCode !== 46) {
            e.preventDefault();
            } else {
            return true;
            }
        });
        }
    }
    },
};

export {numberOnlyMixin}

In your component, add to your input.

<input v-model="myData" v-numericOnly />
Arst
  • 3,098
  • 1
  • 35
  • 42
  • 1
    POTENTIAL MEMORY LEAK! Is this solution production ready? I think you need to unbind event listeners when the element leaves the DOM. – Emeke Ajeh Feb 19 '20 at 09:34
  • I believe creating a directive is the correct approach, although this implementation is more complex than it should be. If you're concerned about memory leaks, you can remove the listener on the directive's `unbind` event. JavaScript seems to take care of that automatically though. – Parziphal Apr 04 '22 at 15:14
2
<v-text-field class='reqField' label="NUMBER" @keypress="isNumber($event)"></v-text-field>
methods: {
    isNumber: function(evt) {
        evt = (evt) ? evt : window.event;
        var charCode = (evt.which) ? evt.which : evt.keyCode;
        if (charCode > 31 && (charCode < 48 || charCode > 57) && (charCode != 9)) {
            evt.preventDefault();
        } else {
            return true;
        }
        `enter code here`
    },
}
1

I use watcheffect() and regex for this. This way, it works with copy-paste as well.

<template>
  <input type="text" v-model="data">
</template>

<script>
import { ref, watchEffect } from 'vue'

export default {
  setup() {
    const data = ref('')

    watchEffect(() => {
      data.value = data.value.replace(/[^\.0-9]/g, '')
    })

    return { data }
  }
}
</script>
nht.ctn
  • 11
  • 1
0

You can use the number type in it:

<input type="number" class="yourCssClass" placeholder="someText" id="someId" />

and then, add the CSS required to remove the up/down spinners:

/* Chrome, Safari, Edge, Opera */
  input::-webkit-outer-spin-button,
  input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }

  /* Firefox */
  input[type=number] {
    -moz-appearance: textfield;
  }
undertuga
  • 29
  • 1
  • 1
  • 5
  • This answer is insufficient. It doesn't handle `e` in Chromium-based browsers. Also, browsers such as Firefox don't block non numeric chars when input is type number. You can check this using MDN documentation while running Firefox: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number – derekbaker783 Feb 12 '22 at 12:20