Reputation: 1285
I want to replicate a common item list renaming feature, where you have a list of layers and if you double click a layer, it changes the layer item to an input and that input is automatically focused with its text selected as well.
In my example, I am not able to focus()
the DOM element by its ref
because it says it is not defined. It only works if I click a second time on the element once its changed to an input. How do I set this autofocus?
<div v-for="(item, i) in items">
<div @click="changeToInput(i)" v-if="!item.input">{{item.name}}</div>
<input ref="input" v-model="item.name" onfocus="select()" v-else>
</div>
changeToInput(i) {
this.items[i].input = true;
//this.$refs.input.focus()
}
Here is the complete example : https://codesandbox.io/s/reverent-khayyam-2x8mp?file=/src/App.vue:481-573
Upvotes: 1
Views: 889
Reputation: 10729
Two solutions:
First one: uses v-if
+ this.$nextTick
:
v-if
will insert/destroy the component when the binding expression is true/false, so in current cycle, input
hasn't been in Dom tree. You have to use nextTick
to wait for next cycle to get the Dom element of Input. And this.$refs.input
will be one array based on how many v-if=true
, so you have to filter out the this.items
to find out correct index (that is why I used one combination of Array.slice and Array.filter).
Updated: The order of the elements of this.$refs.input1
is the order VNode is created. For example: clicks input2 -> input3 -> input1, the order of this.$refs.input1
is [2, 3, 1], not [1, 2, 3].
Second one: uses v-show
+ this.$nextTick
:
It will make things easier, because v-show
only update the css styles for Dom elements, it will not add/remove component instance (Vnode) from VNode tree. So the this.$refs.input
will always equal this.items.length
.
new Vue ({
el:'#app',
data() {
return {
items1: [
{ name: "Joe", input: false },
{ name: "Sarah", input: false },
{ name: "Jeff", input: false }
],
items2: [
{ name: "Joe", input: false },
{ name: "Sarah", input: false },
{ name: "Jeff", input: false }
],
refSort: {}
};
},
methods: {
changeToInput1(i) {
this.items1[i].input = true;
let refCount = (this.$refs.input1 && this.$refs.input1.length) || 0
refCount < this.items1.length && (this.refSort[i] = refCount)
this.$nextTick(() => {
// the purpose uses this.refSort is record the order of this.$refs.input (Its order is same as the creating order of Ref), you can uncomment below line to see the root cause
//console.log(this.$refs.input1[0] && this.$refs.input1[0].value, this.$refs.input1[1] && this.$refs.input1[1].value, this.$refs.input1[2] && this.$refs.input1[2].value)
this.$refs.input1[this.refSort[i]].focus()
})
},
changeToInput2(i) {
this.items2[i].input = true;
this.$nextTick(() => this.$refs.input2[i].focus())
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h3>Uses v-if: <p>{{items1}}</p></h3>
<div v-for="(item, i) in items1">
<div @click="changeToInput1(i)" v-if="!item.input">{{item.name}}</div>
<input ref="input1" v-model="item.name" onfocus="select()" v-else>
</div>
<h3>Uses v-show: <p>{{items2}}</p></h3>
<div v-for="(item, i) in items2">
<div @click="changeToInput2(i)" v-show="!item.input">{{item.name}}</div>
<input ref="input2" v-model="item.name" onfocus="select()" v-show="item.input">
</div>
</div>
Upvotes: 2