0👍
I did some tests and I see the error in the watch when the overwrite with the same value i.e field 1 had 5 highlighted and updated by 5 again.
Code:
watch: {
focusIndex(newValue) {
this.$refs[newValue].focus();
},
inputOne(newValue) {
this.$q.notify({
message: 'inputOne changed',
caption: 'moments ago',
color: 'secondary'
})
0👍
The maxLength
attribute would not allow for an input
event to be triggered when the maximum number of values are contained in the input field.
However, what caused the pin field to work sometimes after the highlighted value is overwritten, and not to work other times, is that when the user inputs the same value as what was previously there (i.e the overwritten value is the same as the previous value), the input
event will not be triggered, while once a different value is used to overwrite the previous value, the input
event is triggered.
I discovered this via trial and error. I can only assume that this is the default browser behaviour, as I couldn’t confirm it anywhere else.
To fix this issue, I removed the maxLength
attribute and I refactored the code, and viola!, it works. The refactored code is shown below.
<script>
const generatePinInputs = (numberOfPinFields) => {
const objectOfPinFields = {};
// create an array of values of the number of pin fields
Array(numberOfPinFields).fill('').forEach((field, index) => {
objectOfPinFields[index + 1] = field;
});
return objectOfPinFields;
};
export default {
name: 'app-pin',
props: {
numberOfPinFields: {
type: Number,
default: 4,
},
},
mounted() {
// switched from quasar's q-input to input because of error thrown about v-model mutating prop value
this.$nextTick(() => {
// After all asynchronous actions (including dialog popup showing pin fields), focus on the first input
this.$refs[1][0].focus();
});
},
data() {
return {
pinInputs: generatePinInputs(this.numberOfPinFields),
};
},
computed: {
pinFilled() {
return Object.values(this.pinInputs).join('');
},
},
watch: {
pinFilled(newValue) {
// if all 4 pin fields have been completed, emit completed
if (newValue.length === this.numberOfPinFields) {
this.$emit('completed', newValue);
}
},
},
methods: {
handleInput(e, index) {
const { value } = e.target;
// if there is no value, do nothing - the deletion of a value also calls the input value
// this prevents an error when a value is deleted
if (!value) {
return;
}
// replace the data property with the latest value that is typed
this.pinInputs[index] = value.slice(-1);
// sometimes this is required by vue to override default browser behaviour
this.$refs[index][0].value = value.slice(-1);
if (index !== this.numberOfPinFields) {
this.$refs[index + 1][0].focus();
}
},
handleDelete(index) {
if (index !== 1) {
this.$refs[index - 1][0].focus();
}
},
},
};
</script>
<template>
<transition appear enter-active-class="animated slideInRight">
<form class="appPin">
<input
v-for="(index) in numberOfPinFields"
:key="index"
input-class="text-center"
@input="handleInput($event, index)"
@keyup.delete="handleDelete(index)"
v-number-only
type="password"
inputmode="numeric"
:ref="index"
v-model="pinInputs[index]"
/>
</form>
</transition>
</template>