Reputation: 5723
I'm trying to use this.$refs.cInput.focus()
(cInput
is a ref) and it's not working. I'd be able to hit g
and the input should pop up and the cursor should focus in it, ready to input some data. It's showing but the focus part is not working. I get no errors in the console.
Vue.component('coordform', {
template: `<form id="popup-box" @submit.prevent="process" v-show="visible"><input type="text" ref="cInput" v-model="coords" placeholder =""></input></form>`,
data() {
{
return { coords: '', visible: false }
}
},
created() {
window.addEventListener('keydown', this.toggle)
},
mounted() {
},
updated() {
},
destroyed() {
window.removeEventListener('keydown', this.toggle)
},
methods: {
toggle(e) {
if (e.key == 'g') {
this.visible = !this.visible;
this.$refs.cInput.focus() //<--------not working
}
},
process() {
...
}
}
});
Upvotes: 10
Views: 12348
Reputation: 638
In my case nextTick
didn't work because for some reason there is quite a noticeable random delay between a reactive value (e.g. visible
boolean model) change and the child component mounting. Instead of exploiting setTimeout
, I solved it using this "action queue" pattern:
<script setup lang="ts">
import Button from './ui/button/Button.vue'
const visible = defineModel<boolean>({ default: false })
const sidebarFocus = ref<InstanceType<typeof Button> | null>(null)
const actionQueue = ref<(() => void)[]>([])
watch(visible, (value) => {
if (value) {
actionQueue.value.push(() => {
if (sidebarFocus.value)
sidebarFocus.value.$el.focus() // Don't forget the `$el`
})
}
})
watch(sidebarFocus, (value) => {
if (value) {
actionQueue.value.forEach(action => action())
actionQueue.value = []
}
})
</script>
<template>
<div v-if="visible" class="my-sidebar">
<Button ref="sidebarFocus" @click="visible = false">
Close <!-- This button gets focused each time the sidebar opens -->
</Button>
</div>
</template>
Basically, the watcher of visible
pushes a callback to an array, then the moment the button mounts it calls that callback, and empties the queue (to prevent an infinite loop). The callback itself just puts focus on the button.
Upvotes: 0
Reputation: 5609
You can use the nextTick()
callback:
When you set
vm.someData = 'new value'
, the component will not re-render immediately. It will update in the next “tick”, when the queue is flushed. [...]
In order to wait until Vue.js has finished updating the DOM after a data change, you can use
Vue.nextTick(callback)
immediately after the data is changed. The callback will be called after the DOM has been updated.(source)
Use it in your toggle function like:
methods: {
toggle(e) {
if (e.key == 'g') {
this.visible = !this.visible;
this.$nextTick(() => this.$refs.cInput.focus())
}
}
}
Upvotes: 19
Reputation: 37
Not sure if this is only applicable on Vue3 but if you want to show focus on an input box from inside a component, it's best for you to setup a transition. In the transition, there is an event called @after-enter. So after the animation transition on enter is completed, the @after-enter is executed. The @after-enter will be the event that will always get executed after your component shows up.
I hope this helps everyone who are having issues with the:
***this.$nextTick(() => this.$refs.searchinput.focus());***
It may not be working the way you wanted it to because chances are the initial focus is still on the parent and the input element has not been displayed yet or if you placed it under mounted, it only gets executed once and no matter how you show/hide the component (via v-if or v-show), the nextTick line was already executed on mount() and doesn't get triggered on show of the component again.
Try this solution below:
<template>
<transition name="bounce" @after-enter="afterEnter" >
<div>
<input type="text" ref="searchinput" />
</div>
</transition>
</template>
<script>
methods: {
afterEnter() {
setTimeout(() => {
this.$refs.searchinput.focus();
}, 200);
},
}
</script>
Upvotes: 2
Reputation: 663
In my case nextTick does not worked well.
I just used setTimeout like example below:
doSearch () {
this.$nextTick(() => {
if (this.$refs['search-input']) {
setTimeout(() => {
this.$refs['search-input'].blur()
}, 300)
}
})
},
I think that for your case code should be like below:
toggle(e) {
if (e.key == 'g') {
this.visible = !this.visible;
setTimeout(() => { this.$refs.cInput.focus() }, 300)
}
}
Upvotes: 5