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

1

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

Diminished answered 7/3, 2018 at 3:3 Comment(1)
details for the reason, follow the issue in githubDiminished
F
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>
Frication answered 7/3, 2018 at 3:33 Comment(9)
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 vuejs.org/v2/guide/custom-directive.html#Hook-Functions, inserted: called when the bound element has been inserted into its parent nodeSyncytium
@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...Frication
@JacobGoh See what I mean: jsfiddle.net/acdcjunior/srfse9oe/1 check the console.Frication
(btw i am the the one who ask this SO question, I am just curious.) from your example, I got this 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. 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 ?).Syncytium
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 <input id="timing"> is in the DOM when inserted is called, but that is in the DOM at the wrong location (between <p>a</p> and <p>b</p> where it was never supposed to be). And when the nextTick triggers, it is in its correct placement (between <p>c</p> and <p>d</p>), so something moved it.Frication
So, in other words (and I think this matches your last fiddle), the inserted did focus, but then the <input> was moved (which probably involved a DOM remove and new DOM insert), which made it lose focus when it was removed from the DOM.Frication
Thanks I understand your example now. You wanna report it on github ?Syncytium
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.Frication
I have updated the question with what we talked here. If you think it is appropriate, link it from the issue.Frication

© 2022 - 2024 — McMap. All rights reserved.