[Vuejs]-Why does my selected/highlighted input text not trigger the input event all the time?

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>

Leave a comment