[removed] Set cursor position when changing the va

2019-06-03 20:43发布

问题:

I am trying to reproduce in my app a UX similar to Microsoft Excel / Google Sheets when you type of formula and you have an autocomplete dropdown for the different formulas and variables you have at your disposal.

For that purpose, after having validated the autocomplete, I want to be able to control the position of the cursor.

For exemple, if I type =sum(variable1, variable2), on autocomplete of variable2, cursor should be before the last parenthesis and not at the very end.

I understand how to set the position of the cursor with javascript, the problem is since at the same time I modify the value of the input and set the cursor position, the latter doesn't work.

I reproduced on fiddle the problem with a simpler context: https://jsfiddle.net/joparisot/j8ourfa1/31/

My html:

<div id="app">
    <autocomplete v-model="selection"></autocomplete>
</div>

<template id="autocomplete">
  <div>
    <h2>gernerogrnio</h2>
    <input id="my-input" 
           class="form-control" 
           type="text" 
           :value="value"
           @keydown.enter="updateValue($event.target.value)">
    <p>{{ value }}</p>
  </div>
</template>

My script:

    Vue.component('autocomplete', {
    template: '#autocomplete', 
  props: {
    value: {
      type: String,
      required: true
    }
  }, 
  methods: {
    updateValue (value) {
        var new_value = ''
      if (value.length < 4) {
        new_value = 'Inferior'
      } else {
        new_value = 'Superior'
      }

      this.$emit('input', new_value)
      var myInput = document.getElementById('my-input');
      this.setCaretPosition(myInput, 5)
    }, 
    setCaretPosition(ctrl, pos) {
        ctrl.focus();
        ctrl.setSelectionRange(pos, pos);
    }
  }
});

new Vue({
    el: '#app', 
  data: {
    selection: 'test'
  }
});

I don't bother there with the autocomplete, but depending on what you type, when you press enter, the input will be filled with a new value. You can see that if you comment lines 11 to 16 and just set new_value to value, then setting the cursor position will work.

I can't seem to be able to do both things at the same time. Any thoughts?

回答1:

Thanks to Roy J's comment, I was able to find the solution.

Add the following in the updateValue function:

this.$nextTick(() => {
  this.setCaretPosition(myInput, 5)
});


回答2:

I learned about setSelectionRange from this question, and I used it to handle credit card number input:

template:

<input
    ref="input"
    v-model="value"
    @input="handleChange"
>

instance methods:

data() {
    return {
        lastValue: '',
    }
},

methods: {
    setCursorPosition(el, pos) {
        el.focus();
        el.setSelectionRange(pos, pos);
    },
    handleChange() {
        // handle backspace event
        if (this.value.length < this.lastValue.length) {
            this.lastValue = this.value;
            this.$emit('input-changed', this.value);
            return;
        }
        // handle value-edit event
        if (this.$refs.input.selectionStart < this.value.length) {
            const startPos = this.$refs.input.selectionStart;
            this.value = this.value.replace(/\W/gi, '').replace(/(.{4})/g, '$1 ').trim();
            this.$nextTick(() => this.setCursorPosition(this.$refs.input, startPos));
            this.lastValue = this.value;
            this.$emit('input-changed', this.value);
            return;
        }
        // handle everything else
        this.value = this.value.replace(/\W/gi, '').replace(/(.{4})/g, '$1 ').trim();
        this.lastValue = this.value;
        this.$emit('input-changed', this.value);
    },
},

The goal with the above code is to add spaces into a credit card input, so 1234123412341234 is automatically reformatted to 1234 1234 1234 1234. A person venturing into this territory will notice that problems arise when editing the input value.

You can see there are three conditions in my sample above. The last one is the default which simply reformats the current value with a 2-step combo: remove all spaces then adds a space every 4th character.

If you comment out the two if blocks, you can watch the problems emerge.

The first if block handles the backspace event. As you can see, every time the input changes, the value is captured as this.lastValue. When you press backspace, the goal of the first condition is to NOT run the regex. In my opinion, this is better UX. If you comment out that condition, you can see.

The second if block handles the editing events. A good way to test it, is to enter a valid CC but omit the 3rd character, so that everything is off by one. Then add the character in. Everything should be good. Likewise if you backspace multiple characters out. The goal of the second condition is to properly manage the cursor position (or caret position if you prefer that nomenclature).

You can safely delete the first condition and all references to lastValue and the code will still work. This is arguably simpler but worse UX.