【问题标题】:Why does my selected/highlighted input text not trigger the input event all the time?为什么我选择/突出显示的输入文本不会一直触发输入事件?
【发布时间】:2024-05-16 07:40:02
【问题描述】:

我创建了一个具有以下行为的 4 针字段:当一个字段被填充时,焦点转移到下一个字段(光标切换到下一个输入字段)。删除文本值时,该输入字段中的文本将被删除,并且光标会移动到其前面的 pin 字段。

问题是有时当一个带有文本的字段被高亮显示时(例如删除后),并且键入另一个键来覆盖以前的值(如果发生错误),input 事件不会被触发,将光标留在该字段中(光标不会移动到下一个引脚字段)。这显然不会带来很好的用户体验,我想让它在突出显示的文本被覆盖时始终切换 pin 字段。

相关代码部分贴在下面。

data() {
  return {
    focusIndex: 1,
    inputOne: '',
    inputTwo: '',
    inputThree: '',
    inputFour: '',
    numberOfPinFields: 4,
  };
},

mounted() {
// this highlights the first pin field
  this.$refs[this.focusIndex].focus();
},

methods: {
  handlePinFieldDeletion() {
    if (this.focusIndex > 1) {
      this.focusIndex -= 1;
    }
  },
  
  handlePinFieldInput(value, maxNumberOfFields) {
    this.$nextTick(() => {
     // after the focus index from the input field watch below is updated, increase the focusIndex value
       if (value.length === 1 && this.focusIndex < maxNumberOfFields) {
          this.focusIndex += 1;
        }
      });
    },
  ensurePinFieldHasOneInput(value) {
    return value.slice(0, 1);
  },
  highlightOnFocus(e, focusIndex) {
    // highlight the text
    e.target.select();
    // set the new focus index
    this.focusIndex = focusIndex;
  },
 }
 
 watch: {
  focusIndex(newValue) {
    this.$refs[newValue].focus();
  },
  inputOne(newValue) {
    // set focus index of first otp input field to 1 when it changes.
    // This will help with situations where the user doesn't use the input fields in numerical order
    this.focusIndex = 1;
    this.inputOne = this.ensurePinFieldHasOneInput(newValue);
  },

  inputTwo(newValue) {
  // set focus index of first otp input field to 2 when it changes.
  // This will help with situations where the user doesn't use the input fields in numerical order
    this.focusIndex = 2;
    this.inputTwo = this.ensurePinFieldHasOneInput(newValue);
  },

  inputThree(newValue) {
  // set focus index of first otp input field to 3 when it changes.
  // This will help with situations where the user doesn't use the input fields in numerical order
    this.focusIndex = 3;
    this.inputThree = this.ensurePinFieldHasOneInput(newValue);
  },
  inputFour(newValue) {
    // set focus index of first otp input field to 4 when it changes.
    // This will help with situations where the user doesn't use the input fields in numerical order
    this.focusIndex = 4;
    this.inputFour = this.ensurePinFieldHasOneInput(newValue);
  },
  },
<form>
  ...
  <q-input
    type="password"
    input-class="text-center"
    maxlength="1"
    @keyup.delete="handlePinFieldDeletion"
    @input="handlePinFieldInput($event, numberOfPinFields)"
    v-number-only
    @focus="highlightOnFocus($event, 1)"
    borderless
    ref="1"
    v-model="inputOne"
  />
  <q-input
        type="password"
        input-class="text-center"
        maxlength="1"
        v-number-only
        @focus="highlightOnFocus($event, 2)"
        @keyup.delete="handlePinFieldDeletion"
        @input="handlePinFieldInput($event, numberOfPinFields)"
        borderless
        ref="2"
        v-model="inputTwo"
      />
      <q-input
        type="password"
        input-class="text-center"
        maxlength="1"
        v-number-only
        @focus="highlightOnFocus($event, 3)"
        @keyup.delete="handlePinFieldDeletion"
        @input="handlePinFieldInput($event, numberOfPinFields)"
        borderless
        ref="3"
        v-model="inputThree"
      />
      <q-input
        type="password"
        input-class="text-center"
        v-number-only
        @focus="highlightOnFocus($event, 4)"
        maxlength="1"
        @keyup.delete="handlePinFieldDeletion"
        @input="handlePinFieldInput($event, numberOfPinFields)"
        borderless
        ref="4"
        v-model="inputFour"
      />
  ...
</form>

【问题讨论】:

  • 我发现代码缺少 numberOfPinFields!
  • 嗨,@Abuabdellah。我忘了包括它,但我已经更新了代码。
  • 愿那些遵循指导的人平安,@Tony。看我的回答。

标签: javascript vue.js dom-events quasar-framework


【解决方案1】:

我做了一些测试,当使用相同值覆盖时,我在 watch 中看到错误,即字段 1 突出显示 5 并再次更新 5。

代码:

 watch: {
  focusIndex(newValue) {
    this.$refs[newValue].focus();
  },
  inputOne(newValue) {
    this.$q.notify({
        message: 'inputOne changed',
        caption: 'moments ago',
        color: 'secondary'
      })

【讨论】:

  • 感谢您的回答。不过,我已经想通了,我发布了我的解决方案。
  • 我发现您的解决方案类似于我提到的原因。反正现在似乎已经解决了。
【解决方案2】:

maxLength 属性不允许在输入字段中包含最大数量的值时触发 input 事件。

但是,导致 pin 字段在突出显示的值被覆盖后有时会起作用,而在其他时候不起作用的原因是,当用户输入与以前相同的值时(即覆盖的值与之前的值),则不会触发input事件,而一旦使用不同的值覆盖之前的值,就会触发input事件。

我通过反复试验发现了这一点。我只能假设这是默认浏览器行为,因为我无法在其他任何地方确认。

为了解决这个问题,我删除了maxLength 属性并重构了代码,viola! 成功了。重构后的代码如下所示。

<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>

【讨论】: