如何使用Vuetify创建一个数值整数输入?

huangapple go评论78阅读模式
英文:

How to create a numeric integer input using Vuetify?

问题

根据 https://stackoverflow.com/questions/53589108/is-there-specific-number-input-component-in-vuetify,我正在尝试创建一个数字输入框。

输入和输出的值是 unknown,因此它可以是 undefinednull,因为有人可能想要清除字段,所以它不应该返回 0

如果用户传入标志 isAcceptingFloatingPointNumbers = false,则此输入框应仅接受整数值(不应该可以输入浮点数)。

演示链接

<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>

如何确保如果标志 isAcceptingFloatingPointNumbers 返回 false,用户只能输入整数值?我能想到的唯一方法是附加一个自定义规则,例如

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

但据我所知,即使值可以是 undefined,这个规则也会触发。是否有办法阻止用户输入而不触发规则?


根据 yoduh 的答案,我尝试了以下内容(演示链接

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>

我像这样使用组件

<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>

问题是我的过滤函数有问题。仍然可以输入 "12.4",因为过滤器忽略了 "12.",然后将 "12.4" 转换为 124。

有人有任何想要修复这个问题的想法吗?

英文:

Based on https://stackoverflow.com/questions/53589108/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

&lt;template&gt;
  &lt;v-app&gt;
    &lt;v-main&gt;
      &lt;v-text-field 
        type=&quot;number&quot;
        label=&quot;number input&quot; 
        :clearable=&quot;true&quot;
        :model-value=&quot;num&quot;
        @update:modelValue=&quot;num = $event&quot;
      /&gt;
    &lt;/v-main&gt;
  &lt;/v-app&gt;
&lt;/template&gt;

&lt;script setup lang=&quot;ts&quot;&gt;
import { ref, watch, Ref } from &#39;vue&#39;

const num: Ref&lt;unknown&gt; = ref(undefined)

watch(num, () =&gt; console.log(num.value))
&lt;/script&gt;

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 =&gt; Number.isInteger(v) || &#39;Must be integer&#39;

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

&lt;template&gt;
  &lt;v-text-field 
    type=&quot;number&quot;
    label=&quot;number input&quot; 
    :clearable=&quot;true&quot;
    :model-value=&quot;num&quot;
    @update:modelValue=&quot;emit(&#39;update:modelValue&#39;, $event)&quot;
    @keypress=&quot;filterInput&quot;
  /&gt;
&lt;/template&gt;

&lt;script setup lang=&quot;ts&quot;&gt;
const props = defineProps&lt;{
  num: unknown;
  isAcceptingFloatingPointNumbers: boolean;
}&gt;();

const emit = defineEmits&lt;{
  (e: &quot;update:modelValue&quot;, newValue: unknown): void;
}&gt;();

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;
}
&lt;/script&gt;

I'm consuming the component like so

&lt;template&gt;
  &lt;number-field :num=&quot;num&quot; :isAcceptingFloatingPointNumbers=&quot;false&quot; @update:model-value=&quot;num = $event&quot; /&gt;
&lt;/template&gt;

&lt;script setup lang=&quot;ts&quot;&gt;
import { ref, watch } from &#39;vue&#39;
import NumberField from &quot;./NumberField.vue&quot;;

const num: Ref&lt;unknown&gt; = ref(undefined);
  
watch(num, () =&gt; console.log(num.value));
&lt;/script&gt;

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?

答案1

得分: 3

由于整数仅由数字组成,您只需测试每个按下的键是否为数字,无需检查整个输入值。

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

  if (!inputEvent.target.value.length && inputEvent.key === '-') {
    return true;
  }
  
  if (!Number.isInteger(Number(inputEvent.key))) {
    // 当然,您可以选择任何其他方法来检查按下的键是否为数字键,例如检查事件的 keyCode 是否在范围 48-57 内。
    inputEvent.preventDefault();
  }
  
  return true;
}

关于箭头键,这不是特定于 Vuetify 的元素,而是浏览器添加到类型为数字的输入框中的元素。您可以像这样禁用它们:链接

英文:

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 &amp;&amp; inputEvent.key === &#39;-&#39;){
    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.

答案2

得分: 2

根据我的理解,您有以下要求:

  • 根据 isAcceptingFloatingPointNumbers 标志值来阻止用户输入(如果标志为 false,则只接受整数,否则字段应接受浮点数)。
  • 输入字段中不允许使用上/下箭头。
  • 输入字段应接受值 0

如果我的上述理解是正确的,您可以通过普通文本字段来实现此要求,并在每次 keyup 事件上,如果输入值与传递的有效 regEx 不匹配,可以将输入值替换为空字符串。

演示如下:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->
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');

<!-- language: lang-html -->
<script src="https://unpkg.com/vue@next/dist/vue.global.js"></script>
<script src="https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.css"/>

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

<!-- end snippet -->

希望这能满足您的需求。

英文:

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 :

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

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

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

    return {
      num,
      validateInput
    };
  }
};

let app = Vue
.createApp(options)
.use(vuetify)
.mount(&#39;#app&#39;);

<!-- language: lang-html -->

&lt;script src=&quot;https://unpkg.com/vue@next/dist/vue.global.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.js&quot;&gt;&lt;/script&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.css&quot;/&gt;

&lt;div id=&quot;app&quot;&gt;
  &lt;v-text-field label=&quot;Numper Input&quot; v-model=&quot;num&quot; v-on:keyup=&quot;validateInput&quot;&gt;&lt;/v-text-field&gt;
&lt;/div&gt;

<!-- end snippet -->

答案3

得分: 1

我认为最好的方法是创建一个自定义的过滤函数,该函数在按键时运行。使用自定义的过滤函数,您还可以删除 `type=&quot;number&quot;`,因为它不再必要,将会删除输入框上的上/下箭头。

```html
&lt;v-text-field 
        label=&quot;数字输入&quot; 
        :clearable=&quot;true&quot;
        :model-value=&quot;num&quot;
        @update:modelValue=&quot;num = $event&quot;
        @keypress=&quot;filter(event)&quot;
      /&gt;
const filter = (e) =&gt; {
  e = (e) ? e : window.event;
  const input = e.target.value.toString() + e.key.toString();

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

更新的沙盒


<details>
<summary>英文:</summary>

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=&quot;number&quot;` since it&#39;s no longer necessary and will remove the up/down arrows on the input.

```html
&lt;v-text-field 
        label=&quot;number input&quot; 
        :clearable=&quot;true&quot;
        :model-value=&quot;num&quot;
        @update:modelValue=&quot;num = $event&quot;
        @keypress=&quot;filter(event)&quot;
      /&gt;
const filter = (e) =&gt; {
  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

答案4

得分: 1

根据您对@yoduh答案的评论,如果您想继续使用type="number"(以减少验证非数字字符的步骤),然后可以使用以下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;
}

逻辑1-

keyup事件中,检查isAcceptingFloatingPointNumbers是否为false,且输入的内容不是整数时,清空输入字段的值。 要检查输入值是否为整数,可以采取以下两种方法之一:

  1. 您可以使用正则表达式模式,/^-?[0-9]+$/.test(num)
  2. 您可以使用JS方法Number.isInteger(num)

然而,在第二种方法中,输入值将始终是字符串类型(为什么?)。为了解决这个问题,可以使用内置的Vue.js指令v-model.number来将输入值的类型重新转换为数字类型。

演示-

<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>

这里唯一的问题是,如果用户输入123.并停止输入,由于type="number",点号dot将可见,但如果您使用这个值,它将始终解码为123
如果要限制输入dot,请在keypress事件上检测该键并阻止进一步执行。

编辑------------------

逻辑2

如果用户尝试输入浮点数,您可以通过使用Math.trunc(num)方法删除小数部分来返回该浮点数的整数部分。

演示-

<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>
英文:

As per your comment on @yoduh's answer, if you want to stick with type=&quot;number&quot; (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-

<!-- begin snippet: js hide: true console: false babel: false -->

<!-- language: lang-js -->

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

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

let app = Vue.createApp(options)
  .use(vuetify)
  .mount(&quot;#app&quot;);

<!-- language: lang-css -->

.error {
  color: red;
}

<!-- language: lang-html -->

&lt;script src=&quot;https://unpkg.com/vue@next/dist/vue.global.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.js&quot;&gt;&lt;/script&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.css&quot;/&gt;
&lt;div id=&quot;app&quot;&gt;
  &lt;v-text-field 
    type=&quot;number&quot;
    label=&quot;number input&quot; 
    :clearable=&quot;true&quot;
    v-model.number=&quot;num&quot;
    @keyup=&quot;validateInput&quot;
    &gt;
  &lt;/v-text-field&gt;
  &lt;label class=&quot;error&quot;&gt;{{ error }}&lt;/label&gt;
&lt;/div&gt;

<!-- end snippet -->

The only glitch here is if the user types 123. and stops typing then the dot will be visible because of the type=&quot;number&quot; 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-

<!-- begin snippet: js hide: true console: false babel: false -->

<!-- language: lang-js -->

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

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

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

let app = Vue.createApp(options)
  .use(vuetify)
  .mount(&quot;#app&quot;);

<!-- language: lang-css -->

.error {
  color: red;
}

<!-- language: lang-html -->

&lt;script src=&quot;https://unpkg.com/vue@next/dist/vue.global.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.js&quot;&gt;&lt;/script&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/@vuetify/nightly@3.1.1/dist/vuetify.css&quot;/&gt;
&lt;div id=&quot;app&quot;&gt;
  &lt;v-text-field 
    type=&quot;number&quot;
    label=&quot;number input&quot; 
    :clearable=&quot;true&quot;
    v-model.number=&quot;num&quot;
    @keyup=&quot;validateInput&quot;
    &gt;
  &lt;/v-text-field&gt;
  &lt;label class=&quot;error&quot;&gt;{{ error }}&lt;/label&gt;
&lt;/div&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年1月9日 18:27:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/75055910.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定