javascripthtmlvue.jsinput

focus an input element


HTML

<span :style="{ display : displayTitle }" @dblclick="showInput()">
  {{ node.title }}
</span>
<input :style="{ display : displayTitleInput }" type="text" 
       @blur="hideInput1" @keydown="hideInput2" 
       @input="changeTitle(node.id , $event.target.value)" 
       :value="node.title">

JS

data() {
  return {
      displayTitle: "inline-block",
      displayTitleInput: "none"
    };
},
showInput() {
    this.displayTitle = "none"
    this.displayTitleInput = "inline-block"
},
hideInput1() {
   this.displayTitle = "inline-block"
   this.displayTitleInput = "none"
},
hideInput2(event) {
    if (event.keyCode === 13) {
        this.hideInput1()
    }
},

HTML is in "v-for" (v-for="node in list").

When span element is double-clicked, it gets hidden and the <input> element is shown.

I want to make it possible to focus on input when it appears.

I tried this but it didn't work.

HTML

<span :style="{ display : displayTitle }" @dblclick="showInput(node.id)">
  {{ node.title }}
</span>
<input :ref='"input_" + node.id' :style="{display:displayTitleInput}" type="text" 
       @blur="hideInput1" @keydown="hideInput2" 
       @input="changeTitle(node.id , $event.target.value)" 
       :value="node.title">

JS

showInput(id) {
    this.displayTitle = "none"
    this.displayTitleInput = "inline-block"

    this.$nextTick(this.$refs["input_" + id][0].focus())
},
      

There was no error on console, but didn't work.


Solution

  • Your primary problem is that $nextTick takes a callback function but you are executing

    this.$refs["input_" + id][0].focus()
    

    immediately. You could get your code working correctly with

    this.$nextTick(() => {
      this.$refs["input_" + id][0].focus()
    })
    

    However, I think you'll run in to further problems and your code can be made much simpler.

    One problem you'll find is that all your node inputs will become visible when double-clicking on any of them due to your style rules.

    You could instead store an "editing" flag somewhere either on the node or in a separate object.

    Below is an example that simplifies your code by...

    1. Using the array-like nature of ref when used within a v-for loop, and
    2. Using the enter modifier on your @keydown event binding

    new Vue({
      el: '#app',
      data: {
        list: [
          {id: 1, title: 'Node #1'},
          {id: 2, title: 'Node #2'}
        ],
        editing: {}
      },
      methods: {
        showInput(id, index) {
          this.$set(this.editing, id, true)
          
          this.$nextTick(() => {
            this.$refs.input[index].focus()
          })
        },
        hideInput(id) {
          this.editing[id] = false
        }
      }
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
    <ul id="app">
      <li v-for="(node, index) in list">
        <span v-show="!editing[node.id]" @dblclick="showInput(node.id, index)">
          {{ node.title }}
        </span>
        <input v-show="editing[node.id]" type="text"
               ref="input" :value="node.title"
               @blur="hideInput(node.id)" @keydown.enter="hideInput(node.id)">
      </li>
    </ul>