Reputation: 1962
I have a text field component for numeric inputs. Basically I'm just wrapping v-text-field but in preparation for implementing it myself. It looks like this.
<template>
<v-text-field v-model.number = "content" />
</template>
<script>
export default {
name: 'NumericTextField',
props: [ 'value' ],
computed: {
content: {
get () { return this.value },
set (v) { this.$emit('input', f) },
},
}
}
</script>
This has generated user feedback that it's annoying when the text field has the string "10.2" in it and then backspace over the '2', then decimal place is automatically delete. I would like to change this behavior so that "10." remains in the text field. I'd also like to understand this from first principles since I'm relatively new to Vue.
So I tried this as a first past, and it's the most instructive of the things I've tried.
<template>
<v-text-field v-model="content" />
</template>
<script>
export default {
name: 'NumericTextField',
props: [ 'value' ],
computed: {
content: {
get () { return this.value },
set (v) {
console.log(v)
try {
const f = parseFloat(v)
console.log(f)
this.$emit('input', f)
} catch (err) {
console.log(err)
}
},
},
}
}
</script>
I read that v-model.number is based on parseFloat so I figured something like this must be happening. So it does fix the issue where the decimal place is automatically deleted. But... it doesn't even auto delete extra letters. So if I were to type "10.2A" the 'A' remains even though I see a console log with "10.2" printed out. Furthermore, there's an even worse misfeature. When I move to the start of the string and change it to "B10.2" it's immediately replaced with "NaN".
So I'd love to know a bunch of things. Why is the body of the text body immediately reactive when I change to a NaN but not immediately reactive when I type "10.2A"? Relatedly, how did I inadvertently get rid of the auto delete decimal place? I haven't even gotten to that part yet. So I'm misunderstanding data flow in Vue.
Lastly, how can I most simply provide a text box that's going to evaluate to a number for putting into my data model but not have the annoying auto delete of decimal places? The existing functionality doesn't auto delete trailing letters so I'm guessing the auto delete of decimal places was a deliberate feature that my users don't like.
Upvotes: 3
Views: 4456
Reputation: 19248
I'm not 100% sure of any of this, but consider how v-model
works on components. It basically is doing this:
<v-text-field
v-bind:value="content"
v-on:input="content = $event.target.value"
/>
And consider how the .number
modifier works. It runs the input through parseFloat
, but if parseFloat
doesn't work, it leaves it as is.
So with that understanding, I would expect the following:
When you type in "10.2" and then hit backspace, "10."
would be emitted via the input
event, parseFloat("10.")
would transform it to 10
, v-on:input="content = $event.target.value"
would assign it to content
, and v-bind:value="content"
would cause the input to display "10". So then, this is the expected behavior.
When you type in "10.2" and then hit "A", "10.2A"
would be emitted via the input
event, parseFloat("10.2A")
would transform it to 10.2
, v-on:input="content = $event.target.value"
would assign it to content
, and v-bind:value="content"
would cause the input to display "10.2". It looks like it's failing at that very last step of causing the input to display "10.2", because the state of content
is correctly being set to 10.2
. If you use <input type="text" v-model.number="content" />
instead of <v-text-field v-model.number="content" />
, once you blur, the text field successfully gets updated to "10.2". So it seems that the reason why <v-text-field>
doesn't is due to how Vuetify is handling the v-bind:value="content"
part.
When you type in "10.2" and then enter "B", in the beginning, "B10.2"
would be emitted via the input
event, parseFloat("B10.2")
would return NaN
, and thus the .number
modifier would leave it as is, v-on:input="content = $event.target.value"
would assign "B10.2"
to content
, and v-bind:value="content"
would cause the input to display "B10.2". I agree that it doesn't seem right for parseFloat("10.2A")
to return 10.2
but parseFloat("B10.2")
to return "B10.2"
.
Lastly, how can I most simply provide a text box that's going to evaluate to a number for putting into my data model but not have the annoying auto delete of decimal places?
Given that the default behavior is weird, I think you're going to have to write your own custom logic for transforming the user's input. Eg. so that "10.2A" and "B10.2" both get transformed to 10.2
(or are left as is), and so that decimals are handled like you want. Something like this (CodePen):
<template>
<div id="app">
<input
v-bind:value="content"
v-on:input="handleInputEvent($event)"
/>
<p>{{ content }}</p>
</div>
</template>
<script>
export default {
data() {
return {
content: 0,
};
},
methods: {
handleInputEvent(e) {
this.content = this.transform(e.target.value);
setTimeout(() => this.$forceUpdate(), 500);
},
transform(val) {
val = this.trimLeadingChars(val);
val = this.trimTrailingChars(val);
// continue your custom logic here
return val;
},
trimLeadingChars(val) {
if (!val) {
return "";
}
for (let i = 0; i < val.length; i++) {
if (!isNaN(val[i])) {
return val.slice(i);
}
}
return val;
},
trimTrailingChars(val) {
if (!val) {
return "";
}
for (let i = val.length - 1; i >= 0; i--) {
if (!isNaN(Number(val[i]))) {
return val.slice(0,i+1);
}
}
return val;
},
},
};
</script>
The $forceUpdate
seems to be necessary if you want the input field to actually change. However, it only seems to work on <input>
, not <v-text-field>
. Which is consistent with what we saw in the second bullet point. You can customize your <input>
to make it appear and behave like <v-text-field>
though.
I put it inside of a setTimeout
so the user sees "I tried to type this but it got deleted" rather than "I'm typing characters but they're not appearing" because the former does a better job of indicating "What you tried to type is invalid".
Alternatively, you may want to do the transform on the blur event rather than as they type.
Upvotes: 3