Reputation: 1199
I have a situation where in VUE I need to access a child function from within the child as well as from the parent. I have a working example, but there must be a better way to access the child function from the parent.
The example has a couple of event listeners that should expand when clicked or keypressed. I want to use the child.do_something
method from the parent with the global EventListener
.
Is there a better way to use the child than this?
// CHILD is a component that needs access to its own click method
var child = {
props: ['item'],
data: function() {
return {
isActive: false
}
},
template: `
<div class="key" :class="{playing: isActive}" v-on:click='do_something'>
<kbd class="noselect">{{ item.kbd }}</kbd>
</div>
`,
methods: {
do_something: function(event) {
this.isActive = !this.isActive
// DO OTHER STUFF too
},
},
}
//PARENT also need to access do_something for the right object when a key is pressed
var parent = new Vue({
el: '#parent',
data: {
keysList: [{
keyCode: "65",
kbd: "A"
},
{
keyCode: "83",
kbd: "S"
},
],
},
components: {
'child': child,
},
methods: {
keystroke: function(keyCode) {
//FIND THE CHILD AND EXECUTE...THIS IS THE TERRIBLE PART
const child = this.$children.find(child => {
return child.$vnode.data.key === keyCode.toString()
});
child.do_something()
}
},
created: function() {
window.addEventListener('keydown', (e) => this.keystroke(e.keyCode));
}
})
.keys {
display: flex;
flex: 1;
min-height: 100vh;
align-items: center;
justify-content: center;
}
.key {
margin: 1rem;
transition: all .07s ease;
color: white;
background: rgba(0, 0, 0, 0.77);
}
.playing {
transform: scale(1.1);
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
kbd {
display: block;
font-size: 4rem;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div class="keys" id='parent'>
<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode"></child>
</div>
Upvotes: 2
Views: 586
Reputation: 302
You can achieve this by adding one more property active to the items in your parent data keysList & update the active property for the matching keystroke . As you are passing the item as prop to your child, you can use the item.active from the props. You don't need the isActive data in your child.
Parent:
var parent = new Vue({
el: '#parent',
data: {
keysList: [{
keyCode: "65",
kbd: "A",
active: false
},
{
keyCode: "83",
kbd: "S",
active: false
}
],
},
components: {
'child': child,
},
methods: {
keystroke: function(keyCode) {
this.keysList.forEach(key => {key.active = key.keyCode === keyCode.toString()});
}
},
created: function() {
window.addEventListener('keydown', (e) => this.keystroke(e.keyCode));
}
})
<div class="keys" id='parent'>
<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" :isActive="item.active"></child>
</div>
Child:
var child = {
props: ['item'],
template: `
<div class="key" :class="{playing: item.active}" v-on:click='do_something'>
<kbd class="noselect">{{ item.kbd }}</kbd>
</div>
`,
methods: {
do_something: function(event) {
// DO OTHER STUFF too
},
},
}
This way you are managing the state just at one place and you don't need to access your child from your parent
Upvotes: 2
Reputation: 82439
You could use a ref and then reference the particular child you need by index. This would allow you to avoid using the internal values.
<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" ref="children"></child>
And in keystroke
:
const index = this.keysList.findIndex(k => k.keyCode == evt.keyCode)
this.$refs.children[index].do_something()
Here your code modified.
// CHILD is a component that needs access to its own click method
var child = {
props: ['item'],
data: function() {
return {
isActive: false
}
},
template: `
<div class="key" :class="{playing: isActive}" v-on:click='do_something'>
<kbd class="noselect">{{ item.kbd }}</kbd>
</div>
`,
methods: {
do_something: function(event) {
this.isActive = !this.isActive
// DO OTHER STUFF too
},
},
}
//PARENT also need to access do_something for the right object when a key is pressed
var parent = new Vue({
el: '#parent',
data: {
keysList: [{
keyCode: "65",
kbd: "A"
},
{
keyCode: "83",
kbd: "S"
},
],
},
components: {
'child': child,
},
methods: {
keystroke: function(evt) {
const index = this.keysList.findIndex(k => k.keyCode == evt.keyCode)
this.$refs.children[index].do_something()
}
},
created: function() {
window.addEventListener('keydown', this.keystroke);
}
})
.keys {
display: flex;
flex: 1;
min-height: 100vh;
align-items: center;
justify-content: center;
}
.key {
margin: 1rem;
transition: all .07s ease;
color: white;
background: rgba(0, 0, 0, 0.77);
}
.playing {
transform: scale(1.1);
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
kbd {
display: block;
font-size: 4rem;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div class="keys" id='parent'>
<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" ref="children"></child>
</div>
Or a slightly more direct approach using a different ref for each child.
<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" :ref="item.keyCode"></child>
And in keystroke
:
this.$refs[evt.keyCode][0].do_something()
The unfortunate part here is that because the ref is set as part of a loop, each ref is an array of one element. If I think of a way around that I'll edit it in.
And here is that working.
// CHILD is a component that needs access to its own click method
var child = {
props: ['item'],
data: function() {
return {
isActive: false
}
},
template: `
<div class="key" :class="{playing: isActive}" v-on:click='do_something'>
<kbd class="noselect">{{ item.kbd }}</kbd>
</div>
`,
methods: {
do_something: function(event) {
this.isActive = !this.isActive
// DO OTHER STUFF too
},
},
}
//PARENT also need to access do_something for the right object when a key is pressed
var parent = new Vue({
el: '#parent',
data: {
keysList: [{
keyCode: "65",
kbd: "A"
},
{
keyCode: "83",
kbd: "S"
},
],
},
components: {
'child': child,
},
methods: {
keystroke: function(evt) {
this.$refs[evt.keyCode][0].do_something()
}
},
created: function() {
window.addEventListener('keydown', this.keystroke);
},
})
.keys {
display: flex;
flex: 1;
min-height: 100vh;
align-items: center;
justify-content: center;
}
.key {
margin: 1rem;
transition: all .07s ease;
color: white;
background: rgba(0, 0, 0, 0.77);
}
.playing {
transform: scale(1.1);
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
kbd {
display: block;
font-size: 4rem;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div class="keys" id='parent'>
<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" :ref="item.keyCode"></child>
</div>
Upvotes: 3