Reputation: 3802
I'm using Vue.js and would like to create a input field component that only takes positive integers (0 is fine too). I would like to avoid using regex to keep it human readable :)
My consuming parent component passes in the current value and uses the shorthand sync directive
<template>
<div>
<form>
<positive-integer-input :value.sync="currentValue" />
</form>
</div>
</template>
<script>
import PositiveIntegerInput from "../components/PositiveIntegerInput.vue";
export default {
components: {
"positive-integer-input": PositiveIntegerInput
},
data() {
return {
currentValue: 0
};
}
};
</script>
The input component itself is just a basic text input (for better CSS styling)
<template>
<input :id="_uid" type="text" :value="value" @input="onInput" />
</template>
<script>
export default {
props: {
value: {
type: Number,
required: true
}
},
methods: {
onInput({ data }) {
const inputNumber = Number(data);
if (!Number.isInteger(inputNumber) || inputNumber < 0) {
const input = document.getElementById(this._uid);
input.value = this.value;
} else {
this.$emit("update:value", inputNumber);
}
}
}
};
</script>
Whenever the input changes I'm validating the incoming input for positive integers. If that validation fails I want to reset the current input field content (maybe there is a more userfriendly solution?)
The input field itself only takes one digit or multiple identical digits. So only these are possible:
but not:
Does someone know how to fix the component?
Thanks in advance
Upvotes: 1
Views: 344
Reputation: 3802
For the sake of completeness I tried to improve Skirtles answer. So the component consumer remains the same, only the input component changes a little bit. Now it's possible to leave the input empty but the value won't trigger an update then. Also the input field adds some visual styles based on the validation.
<template>
<input
type="text"
:value="value"
@input="onInput"
:class="[inputValueIsValid ? 'valid-input' : 'invalid-input']"
/>
</template>
<script>
export default {
props: {
value: {
type: Number,
required: true
}
},
data() {
return {
digitsOnlyRegex: /^\d*$/,
inputValueIsValid: false
};
},
mounted() {
this.validateInputValue(this.value);
},
methods: {
onInput(inputEvent) {
const currentInputValue = inputEvent.target.value;
this.validateInputValue(currentInputValue);
if (this.inputValueIsValid) {
const inputNumber = parseInt(currentInputValue, 10);
this.$emit("update:value", inputNumber);
}
},
validateInputValue(currentInputValue) {
const isNotEmpty = currentInputValue.toString().length > 0;
const containsDigitsOnly = this.digitsOnlyRegex.test(currentInputValue);
this.inputValueIsValid = isNotEmpty && containsDigitsOnly;
}
}
};
</script>
<style scoped>
.valid-input {
background: green;
}
.invalid-input {
background: red;
}
</style>
Upvotes: 0
Reputation: 29122
I'm still not entirely sure I understand the requirements but I think this is somewhere close to what you're looking for:
const PositiveIntegerInput = {
template: `
<input
ref="input"
type="text"
:value="value"
@input="onInput"
>
`,
props: {
value: {
type: Number,
required: true
}
},
methods: {
onInput(ev) {
const value = ev.target.value;
const inputNumber = parseInt(value, 10);
if (inputNumber >= 0 && String(inputNumber) === value) {
this.$emit('update:value', inputNumber);
} else {
// Reset the input if validation failed
this.$refs.input.value = this.value;
}
}
}
};
new Vue({
el: '#app',
components: {
PositiveIntegerInput
},
data () {
return {
currentValue: 0
}
}
})
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
<div>
<form>
<positive-integer-input :value.sync="currentValue" />
</form>
</div>
</div>
I've used $refs
instead of document.getElementById
to perform the reset.
To check the input is an integer I've simply converted it to an integer using parseInt
, then converted it back to a string and compared that to the original string.
There are several edge cases that could cause problems. A particularly obvious one is that the input can never be empty, which makes changing an existing value quite difficult. However, as far as I can tell, this does meet the requirements given in the question.
Update:
A few notes on number parsing.
Using Number(value)
will return NaN
if it can't parse the entire string. There are some quirks with using Number
too. e.g. Try Number('123e4')
, Number('0x123')
or Number('0b11')
.
Using parseInt(value, 10)
will parse as many characters as it can and then stop. parseInt
also has some quirks but by explicitly specifying a radix of 10
they mostly go away.
If you try to parse '53....eeöööööö'
using parseInt
you'll get 53
. This is an integer, so it'll pass a Number.isInteger
test.
Personally I'd like to have used a RegExp to ensure the string only contains digits 0-9 before doing any further manipulation. That quickly helps to eliminate a whole load of possible edge cases such that they wouldn't need any further consideration. Something like /^\d*$/.test(value)
, or the converse !/\D/.test(value)
.
Upvotes: 1
Reputation: 2477
Your problem is here:
const input = document.getElementById(this._uid);
input.value = this.value;
Use v-model
instead or try to replace code by this:
<template>
<input :id="_uid" type="text" :value="inputValue" @input="onInput($event.target.value, $event)" />
</template>
<script>
export default {
data(){
return inputValue: 0,
},
props: {
value: {
type: Number,
required: true
}
},
created(){
this.inputValue = this.value;
},
methods: {
onInput(value, event) {
const inputNumber = Number(value);
if (!Number.isInteger(inputNumber) || inputNumber < 0) {
this.inputValue = this.value;
} else {
this.$emit("update:value", inputNumber);
}
}
}
};
</script>
Upvotes: 0