英文:
How to create a numeric integer input using Vuetify?
问题
根据 https://stackoverflow.com/questions/53589108/is-there-specific-number-input-component-in-vuetify,我正在尝试创建一个数字输入框。
输入和输出的值是 unknown
,因此它可以是 undefined
或 null
,因为有人可能想要清除字段,所以它不应该返回 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
,这个规则也会触发。是否有办法阻止用户输入而不触发规则?
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 )
<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?
答案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 && 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.
答案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 isfalse
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('');
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="Numper Input" v-model="num" v-on:keyup="validateInput"></v-text-field>
</div>
<!-- end snippet -->
答案3
得分: 1
我认为最好的方法是创建一个自定义的过滤函数,该函数在按键时运行。使用自定义的过滤函数,您还可以删除 `type="number"`,因为它不再必要,将会删除输入框上的上/下箭头。
```html
<v-text-field
label="数字输入"
: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;
}
}
<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="number"` since it's no longer necessary and will remove the up/down arrows on the input.
```html
<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;
}
}
答案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,且输入的内容不是整数时,清空输入字段的值。 要检查输入值是否为整数,可以采取以下两种方法之一:
- 您可以使用正则表达式模式,
/^-?[0-9]+$/.test(num)
。 - 您可以使用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="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-
- You can use a regex pattern,
/^-?[0-9]+$/.test(num)
. - 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('');
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");
<!-- language: lang-css -->
.error {
color: red;
}
<!-- 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
type="number"
label="number input"
:clearable="true"
v-model.number="num"
@keyup="validateInput"
>
</v-text-field>
<label class="error">{{ error }}</label>
</div>
<!-- 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="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-
<!-- 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('');
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");
<!-- language: lang-css -->
.error {
color: red;
}
<!-- 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
type="number"
label="number input"
:clearable="true"
v-model.number="num"
@keyup="validateInput"
>
</v-text-field>
<label class="error">{{ error }}</label>
</div>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论