vue.jsvuejs2directiveautofocus

custom v-focus not work when there are many v-if in parent node


I define the custom directive "focus" in my component:

<script>
    export default {
        name: 'demo',
        data () {
            return {
                show: true
            }
        },
        methods: {
            showInput () {
                this.show = false
            }
        },
        directives: {
            focus: {
                inserted: function (el) {
                    el.focus()
                }
            }
        }
    }

And this is my html template:

<template>
    <div>
        <input type="number" id="readonly" v-if="show">
        <button type="button" @click="showInput" v-if="show">show</button>
        <input type="number" id="timing" v-model="timing" v-if="!show" v-focus>
   </div>
</template>

But when I click the button, input#timing can't autofocus.

When I put input#readonly and button into a div and use only one v-if, input#timing can be autofocus:

<template>
    <div>
        <div v-if="show">
            <input type="number" id="readonly">
            <button type="button" @click="showInput">show</button>
        </div>
        <input type="number" id="timing" v-model="timing" v-if="!show" v-focus>
   </div>
</template>

This is why???


Solution

  • The directive's code is indeed running and focusing the <input>.

    But it is being removed from the DOM! When this happens, it loses focus. Check the console of the fiddle below: https://jsfiddle.net/acdcjunior/srfse9oe/21/

    Another important point is that, when inserted is called, the <input id="timing"> is in the DOM (as mentioned above), but it is in the DOM at the wrong location (between <p>a</p> and <p>b</p> where it was never supposed to be). This happens because Vue tries to reuse elements.

    And when the nextTick triggers (see fiddle), it is in its correct placement (between <p>c</p> and <p>d</p>), because Vue moved it to the correct location. And is this moving that is taking focus out.

    And because nextTick runs after the DOM moving around is done, the focus persists (see below).

    Using Vue.nextTick():

    Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update.

    new Vue({
      el: '#app',
      data() {
        return {
          show: true,
          timing: 123
        }
      },
      methods: {
        showInput() {
          this.show = false
        }
      },
      directives: {
        focus: {
          inserted: function(el) {
            Vue.nextTick(() => el.focus());               // <======== changed this line
          }
        }
      }
    })
    <script src="https://unpkg.com/vue"></script>
    
    <div id="app">
      <div>
        <input type="number" id="readonly" v-if="show">
        <button type="button" @click="showInput" v-if="show">show</button>
        <input type="number" id="timing" v-model="timing" v-if="!show" v-focus>
      </div>
    </div>