2

Based on Is there specific number input component in Vuetify? I'm trying to create a numeric input.

The input and output value is unknown so it could be undefined or null because one might want to clear the field so it should not respond with 0.

The input component should not have "up"/"down" buttons if possible.

If the user passes in a flag isAcceptingFloatingPointNumbers = false this input should only accept integer values ( it should not be possible to type floats )

Reproduction link

<template>
  <v-app>
    <v-main>
      <v-text-field 
        type="number"
        label="number input" 
        :clearable="true"
        :model-value="num"
        @update:modelValue="num = $event"
      />
    </v-main>
  </v-app>
</template>

<script setup lang="ts">
import { ref, watch, Ref } from 'vue'

const num: Ref<unknown> = ref(undefined)

watch(num, () => console.log(num.value))
</script>

How can I make sure the user can only type integer values if the flag isAcceptingFloatingPointNumbers returns false? The only thing coming to my mind is to append a custom rule like

v => Number.isInteger(v) || 'Must be integer'

but AFAIK this rule would trigger even if the value could be undefined. Is there a way to prevent the user input instead?


Based on yoduh's answer I tried this ( reproduction link )

NumberField.vue

<template>
  <v-text-field 
    type="number"
    label="number input" 
    :clearable="true"
    :model-value="num"
    @update:modelValue="emit('update:modelValue', $event)"
    @keypress="filterInput"
  />
</template>

<script setup lang="ts">
const props = defineProps<{
  num: unknown;
  isAcceptingFloatingPointNumbers: boolean;
}>();

const emit = defineEmits<{
  (e: "update:modelValue", newValue: unknown): void;
}>();

function filterInput(inputEvent) {
  if(props.isAcceptingFloatingPointNumbers.value) {
    return true;
  }
  
  const inputAsString = inputEvent.target.value.toString() + inputEvent.key.toString();
  const inputValue = Number(inputAsString);
  
  if(!Number.isInteger(inputValue)) {
    inputEvent.preventDefault();
  }
  
  return true;
}
</script>

I'm consuming the component like so

<template>
  <number-field :num="num" :isAcceptingFloatingPointNumbers="false" @update:model-value="num = $event" />
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import NumberField from "./NumberField.vue";

const num: Ref<unknown> = ref(undefined);
  
watch(num, () => console.log(num.value));
</script>

The problem is that my filter function is wrong. It's still possible to type "12.4" because the filter ignores "12." and then converts "12.4" to 124.

Does someone got any ideas how to fix this?

4 Answers 4

3
+100

Since an integer is made only of digits, you can test only if each pressed key is a digit, no need to check the whole input value.

function filterInput(inputEvent) {
  
  if(props.isAcceptingFloatingPointNumbers.value) {
    return true;
  }

  if(!inputEvent.target.value.length && inputEvent.key === '-'){
    return true;
  }
  
  if(!Number.isInteger(Number(inputEvent.key))) {
    // Of course, you can choose any other method to check if the key 
    // pressed was a number key, for ex. check if the event.keyCode is 
    // in range 48-57.
    inputEvent.preventDefault();
  }
  
  return true;
}

Concerning the arrows, it is not a Vuetify specific element, but elements added by the browser to inputs of type number. You can disable them like this.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks for your reply, that's a pretty nice solution. But support for negative integers is missing :)
ah, you're right. You can allow "-" key if it is the first key pressed, then follow the integer test afterwards, like in my updated answer.
2

As per my understanding you have below requirments :

  • To prevent the user input based on the isAcceptingFloatingPointNumbers flag value (Only accept integers if flag is false else field should accept the floating numbers).
  • No up/down arrows in the input field.
  • Input field should accept the 0 value.

If my above understandings are correct, You can simply achieve this requirement by normal text field and on every keyup event, You can replace the input value with an empty string if it's not matched with passed valid regEx.

Live Demo :

const { ref } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();

let options = {
    setup: function () {
    let num = ref('');
    let isAcceptingFloatingPointNumbers = ref(false);
    
    const validateInput = () => {
      const numbersRegEx = !isAcceptingFloatingPointNumbers.value ? /[^-\d]/g : /[^-\d.]/g;
      num.value = num.value.replace(numbersRegEx, '');
    }

    return {
      num,
      validateInput
    };
  }
};

let app = Vue
.createApp(options)
.use(vuetify)
.mount('#app');
<script src="https://unpkg.com/vue@next/dist/vue.global.js"></script>
<script src="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.css"/>

<div id="app">
  <v-text-field label="Numper Input" v-model="num" v-on:keyup="validateInput"></v-text-field>
</div>

2 Comments

thanks for your reply but unfortunately the solution doesn't support negative integers :(
@baitendbidz we can allow by updating the regex, I updated my answer. It is accepting the negative numbers now.
1

I think the best way would be to create a custom filter function that runs on keypress. With your own custom filter you can also remove the type="number" since it's no longer necessary and will remove the up/down arrows on the input.

<v-text-field 
        label="number input" 
        :clearable="true"
        :model-value="num"
        @update:modelValue="num = $event"
        @keypress="filter(event)"
      />
const filter = (e) => {
  e = (e) ? e : window.event;
  const input = e.target.value.toString() + e.key.toString();

  if (!/^[0-9]*$/.test(input)) {
    e.preventDefault();
  } else {
    return true;
  }
}

updated sandbox

1 Comment

thanks for your answer. I think sticking to type="number" is better because then you won't have to check for float values. So filter only needs to take care for integer values ( based on the boolean prop )
1

As per your comment on @yoduh's answer, if you want to stick with type="number" (good to reduce the step to validate the non-numeric characters), then hide the arrows using following CSS-

/* 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;
}

Logic 1-

On the keyup event, check if isAcceptingFloatingPointNumbers is false and the typed input is not an integer, empty the input field's value. To check if the input value is an integer or not-

  1. You can use a regex pattern, /^-?[0-9]+$/.test(num).
  2. You can use the JS method Number.isInteger(num).

Though, in the second method the input value will always be of type string (why?). To resolve this, use the built-in Vue.js directive v-model.number to recast the input value's type to a number.

Demo-

const { ref } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();

let options = {
  setup: function() {
    let num = ref(null);
    let error = ref('');
    let isAcceptingFloatingPointNumbers = ref(false);
    const validateInput = () => {
      // If floats not allowed and input is not a integer, clean it.
      if (
        !isAcceptingFloatingPointNumbers.value &&
        !Number.isInteger(num.value)
      ) {
        num.value = null;
        error.value = "Only integers are allowed."
      } else {
        error.value = '';
      }
    };
    return {
      num,
      error,
      validateInput,
    };
  },
};

let app = Vue.createApp(options)
  .use(vuetify)
  .mount("#app");
.error {
  color: red;
}
<script src="https://unpkg.com/vue@next/dist/vue.global.js"></script>
<script src="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.css"/>
<div id="app">
  <v-text-field 
    type="number"
    label="number input" 
    :clearable="true"
    v-model.number="num"
    @keyup="validateInput"
    >
  </v-text-field>
  <label class="error">{{ error }}</label>
</div>

The only glitch here is if the user types 123. and stops typing then the dot will be visible because of the type="number" but if you use this value, it will always be decoded as 123.
If you want to restrict the typing of the dot, detect the key on the keypress event and prevent further execution.

EDIT------------------

Logic 2

If a user tries to input the float number, you can return the integer part of that floating-point number by removing the fractional digits using Math.trunc(num) method.

Demo-

const { ref } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();

let options = {
  setup: function() {
    let num = ref(null);
    let error = ref('');
    let isAcceptingFloatingPointNumbers = ref(false);
    const validateInput = () => {
      if (!isAcceptingFloatingPointNumbers.value && !Number.isInteger(num.value)) {
        error.value = "Only integer is allowed.";
        // Keep only integer part.
        num.value = Math.trunc(num.value);
      } else {
        error.value = ''
      }

    };
    return {
      num,
      error,
      validateInput,
    };
  },
};

let app = Vue.createApp(options)
  .use(vuetify)
  .mount("#app");
.error {
  color: red;
}
<script src="https://unpkg.com/vue@next/dist/vue.global.js"></script>
<script src="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.css"/>
<div id="app">
  <v-text-field 
    type="number"
    label="number input" 
    :clearable="true"
    v-model.number="num"
    @keyup="validateInput"
    >
  </v-text-field>
  <label class="error">{{ error }}</label>
</div>

3 Comments

thanks for your reply. I think it's a good solution ( and I don't care for the dot ) but clearing the field if the input is invalid is not what I'm looking for :(
If the input is invalid, what do you want to perform? Or do you want to restrict typing float numbers?
I added one more logic, see if its helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.