5

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

Sampwood
  • 53
  • 1
  • 4

1 Answers1

6

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>
tony19
  • 125,647
  • 18
  • 229
  • 307
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • I feel like this should be considered a bug. nextTick works but I don't get why it's required in the first place. From https://vuejs.org/v2/guide/custom-directive.html#Hook-Functions, `inserted: called when the bound element has been inserted into its parent node` – Jacob Goh Mar 07 '18 at 03:39
  • @JacobGoh I agree, I think there's some moving going on. I'm not sure. I just noticed the incoherence, since, by the name, "inserted" should be called after the insert... – acdcjunior Mar 07 '18 at 03:41
  • @JacobGoh See what I mean: https://jsfiddle.net/acdcjunior/srfse9oe/1/ check the console. – acdcjunior Mar 07 '18 at 03:49
  • (btw i am the the one who ask this SO question, I am just curious.) from your example, I got this https://i.imgur.com/ZKWeFYa.png . The input was indeed inserted already in the `inserted` hook, even though the button wasn't removed yet. I extended your example and added focus & blur event listener. https://jsfiddle.net/jacobgoh101/srfse9oe/11/ . The result is that the element's focus did happen, but something else trigger the blurring of it after the inserted hook ended (perhaps removing the button cause the blurring to happen ?). – Jacob Goh Mar 07 '18 at 04:03
  • The element was already indeed inserted when `inserted` was called. But something else moved it. The key from that fiddle (and your image) is not that `` is in the DOM when `inserted` is called, but that is in the DOM **at the wrong location** (between `

    a

    ` and `

    b

    ` where it was never supposed to be). And when the `nextTick` triggers, it is in its correct placement (between `

    c

    ` and `

    d

    `), so something **moved** it.
    – acdcjunior Mar 07 '18 at 04:12
  • So, in other words (and I think this matches your last fiddle), the `inserted` did focus, but then the `` was moved (which probably involved a DOM remove and new DOM insert), which made it lose focus when it was removed from the DOM. – acdcjunior Mar 07 '18 at 04:12
  • Thanks I understand your example now. You wanna report it on github ? – Jacob Goh Mar 07 '18 at 04:15
  • I think it should be reported, yes. I would do it, but I think you should have the honor :) after all, you did discover it. – acdcjunior Mar 07 '18 at 04:17
  • I have updated the question with what we talked here. If you think it is appropriate, link it from the issue. – acdcjunior Mar 07 '18 at 04:24