Trigger Method in Sibling Component Vue JS - javascript

need your assistance once again. Here's my situation. VueJS2 (vue-cli project) no Vuex.
I have parent component, child1 and child2.
child1 is a form that gets data from child2(which is a table). Upon clicking on table row's checkbox in child2, we fill up the form in child1 this is done by event emitting via parent(picture 2).
child1 has a button and reset method(see picture 1) to clear its fields. My task is: when I 'uncheck' checkbox in table row child2, form in child1 has to be cleared. How do I do this, specifically, how do I access reset method in child1 from child2 and use that method from child2 basically.
I know how to pass data from child to child, but cant get my head around how to be able just manipulate child's data from its sibling.
Please help! Thank you in advance!

If I understand it correctly, you have 2 child components, and you want to tell child1 to execute a method (reset) from your child2 compoent without passing any props.
Well, in that cases your option is limited to using an event Bus.
Check this example. When you click on a button from CompB it executes a methods inside another child component: CompA
var Bus = new Vue()
var compA = {
data: () => ({ name: 'Primary' }),
template: `<p> Comp A is: {{name}}</p>`,
methods: {
reset () {
this.name = 'RESETED'
}
},
created () {
let self = this;
Bus.$on('resetA', function (payload) {
self.reset()
})
}
}
var compB = {
template: `<p> Comp B <button #click="resetCompA"> Clear A </button> </p>`,
methods: {
resetCompA () {
Bus.$emit('resetA')
}
}
}
new Vue({
el: '#app',
components: {
'comp-a' : compA,
'comp-b' : compB
}
})
<script src="https://unpkg.com/vue#2.5.9/dist/vue.js"></script>
<div id="app">
<comp-a></comp-a>
<comp-b></comp-b>
</div>

Related

How do I use $emit in the loop to pass the array value to the parent?

The child component will pass the value to the parent component through a loop
And the parent component will do something with the value
I have a mistake, the parent component cannot receive the child component, and each passed value will be missing
child component
let array =[{a:'1',b:'2'},{a:'3',b:'4'},{a:'5',b:'6'},{a:'1',b:'2'},....]
for(let index in array){
this.$emit('update', array[index])
}
parent component
update(product) {
console.log(product);
}
In my console I can only get part of the children pass value
What is this error and how can I fix it?
This is my sample, although the error cannot be reproduced, the code is the same, except that the parent does not display all
example
Your code should work fine, I did not see any issue in your code. I am assuming you are capturing the event emitted from the child into the parent component like this.
<child #update="update"></child>
Working Demo :
Vue.component('child', {
data() {
return {
arr: [{a:'1',b:'2'}, {a:'3',b:'4'}, {a:'5',b:'6'},{a:'1',b:'2'}]
}
},
mounted() {
for(let index in this.arr){
this.$emit('update', this.arr[index])
}
}
});
var app = new Vue({
el: '#app',
methods: {
update(product) {
console.log(product);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.0/vue.js"></script>
<div id="app">
<child #update="update"></child>
</div>
Suggestion : To improve the performance, Instead of emitting each element into parent component, My suggestion would be to pass whole array and then do the data manipulations in the parent component itself.

Vue Watcher not working on component created with Vue.extend

I have a Parent component with a select input which is bound through v-model to a variable in data.
Besides, I create child components dynamically using Vue.extend, which i pass the propsData which also includes the value of the select.
This components have a watcher for the prop that is related to the select input.
When i create the component it receives the props succesfully, The problem comes when I update the value of the select input that doesn't trigger the watcher on the child component.
I've been looking for similar situations but have not found something that helps me solve this problem, i don't know why it doesn't trigger the watcher on the child component when the select input changes.
Any help would be very preciated.
Here i create the component dynamically:
let PresupuestoFormularioVue = Vue.extend(PresupuestoFormulario)
let instance = new PresupuestoFormularioVue({
propsData: {
//The prop related to select input
seguro: this.seguro,
}
})
instance.$mount()
this.$refs.formularioContenedor.appendChild(instance.$el)
And this is the watcher in the component which isn't working:
watch:{
seguro:{
handler: function( newVal ){
console.log(newVal)
},
},
},
It's not the watch that doesn't work. It's the bindings. You're assigning the current value of this.seguro, not the reactive object itself. However, a new Vue() can add this binding for you.
As a sidenote, whether PresupuestoFormulario is a Vue.extend() doesn't matter. It can be any valid VueConstructor: a Vue.extend(), Vue.component() or a valid SFC (with name and template): export default {...}.
Here's how to do it:
methods: {
addPresupuestoFormulario() {
const div = document.createElement('div');
this.$el.appendChild(div);
new Vue({
components: { PresupuestoFormulario },
render: h => h("presupuesto-formulario", {
props: {
seguro: this.seguro
}
})
}).$mount(div)
}
}
The <div> initially appended to the parent will get replaced upon mounting with the actual template of PresupuestoFormulario and the bindings will be set, exactly as if you had <presupuesto-formulario :seguro="seguro" /> in the parent template from the start.
The really cool part about it is that the parent component doesn't need to have PresupuestoFormulario declared in its components.
Here's a working example:
const Test = Vue.component('test', {
template: `<div>message: {{message}}</div>`,
props: ['message'],
watch: {
message: console.log
}
})
new Vue({
el: '#app',
data: () => ({
msg: "¯\\_(ツ)_/¯"
}),
methods: {
addComponent() {
const div = document.createElement("div");
this.$el.appendChild(div);
new Vue({
components: {
Test
},
render: h => h("test", {
props: {
message: this.msg
}
})
}).$mount(div);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2"></script>
<div id="app">
<input v-model="msg">
<button #click="addComponent">Add dynamic child</button>
</div>
A separate note, about using this.$el.appendChild(). While this works when you're using a root Vue instance (a so-called Vue app), it will likely fail when using a normal Vue component, as Vue2 components are limited to having only 1 root element.
It's probably a good idea to have an empty container (e.g: <div ref="container" />) in the parent, and use this.$refs.container.appendChild() instead.
All of props that you want check in watcher, should be a function. If you want read more about this go to vue document codegrepper.
watch: {
// whenever seguro changes, this function will run
seguro: function (newValue, oldValue) {
console.log(newValue,oldValue)
}
}

PointerEvent object being returned instead of child data on emit

I am working on creating a vue component library. I have build a button component that has data of it's width and left position. I'm trying to emit that data to the parent (a tabs component) when it's clicked. I have troubleshooted quite a bit, and have narrowed down most of the problem. My child component (button) is emitting the correct thing, but it looks like the parent component (tabs) is receiving the value of the click/pointerevent object instead of the data passed on the emit. I'm certain this is some issue in my parent click handle method, but can't pinpoint what exactly. I've included code snippets for the components and their click handler methods.
This is pared down, but essentially, I want to emit the width (and eventually left position) of the child button to the parent tab upon clicking the child/button. I want to assign that emitted width/left position to the slider to move some reactive underlining whenever a button is clicked in the tabs. I built in a console log statement on the click event that returns the emitted value from the child, and then returns the received value from the parent. Right now, the child is emitting the correct value when button is clicked, but parent is receiving and trying to assign a PointerEvent object. Thanks for any feedback!
Child (button) template and relevant script:
<template>
<div class="button #click="click" ref="button">
<slot />
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Button',
emits: [
'click'
],
data () {
return {
width: '',
left: ''
}
},
setup() {
const button = ref(null)
return {
button
}
},
mounted () {
this.$nextTick(() => {
this.left = Math.ceil(this.button.getBoundingClientRect().left)
this.width = Math.ceil(this.button.getBoundingClientRect().width)
})
},
methods: {
click () {
this.$emit('click', this.width)
console.log(`${this.width} has been emitted to the tabs component`)
}
}
}
</script>
Parent (tab) template and relevant script:
<template>
<div class="tabs" #click="updateSliderWidth">
slot
</div>
<div class="slider" :style="sliderWidth">
</template>
<script>
import Button from './Button.vue'
export default {
name: 'Tabs',
components: {
Button
},
methods: {
updateSliderWidth (value) {
this.sliderWidth = value
console.log(`${value} has been received and assigned by parent`)
}
},
data () {
return {
sliderWidth: ''
}
}
}
</script>
I can't see any problems with your code, except that you don't use the Button component in the parent component. Instead you are using a div. This would explain, why you're getting a PointerEvent. This Event is passed as first parameter to the event, if you don't pass anything explicitly.
Here a demo: https://stackblitz.com/edit/vue-opruyd?file=src%2FApp.vue

Vue Calling function of the child component when rendering the child component in v-if

I want to call the method of the child component.
I think that the developer uses $nextTick function to process the data after all child component rendered.
But how could I call the method of the child component when rendering by v-if directive.
Here is an example.
var comp = Vue.component('child', {
data:function(){
return {
}
},
template:`
<div class="child">
I'm a child
</div>
`,
methods:{
callFunction:function(){
console.log("I'm called");
}
}
});
var vm = new Vue({
el:'#app',
data:{
if_child:false
},
methods:{
showChild(){
this.if_child = !this.if_child;
//Calling child's function
this.$refs.child.callFunction();
}
}
})
.child{
display:inline-block;
padding:10px;
background:#eaeaea;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="if_child">
<child ref="child" class="child"></child>
</div>
<button type="button" #click="showChild">
Toggle Child
</button>
</div>
When I trying to call the method callFunction() of the child component in showChild() , it throws an error.
Uncaught TypeError: Cannot read property 'callFunction' of undefined
I think that the reason is because it calls the function of the child component before rendering the child component.
How could I solve this issue?
Thanks.
As stated in the question, $nextTick is the solution here.
Vue batches together rendering. When you change reactive data, such as if_child, it won't immediately cause any rendering to happen. Instead the component is added to a list of components that need rendering. Once you've finished making all your data changes Vue will render all the components in the list.
There are two reasons for this. Firstly, rendering is quite expensive. Secondly, if you're in the middle of updating your data then it might be in an inconsistent state that can't be rendered correctly.
The name 'rendering' is a little misleading. It makes it sound a bit like drawing something. However, it also includes things like creating and destroying child components.
The $refs are updated just after a component renders. This all happens at the start of the next tick. To wait for that we use $nextTick.
Vue.component('child', {
template: `
<div class="child">
I'm a child
</div>
`,
methods: {
callFunction () {
console.log("I'm called");
}
}
});
var vm = new Vue({
el: '#app',
data: {
if_child: false
},
methods: {
showChild () {
this.if_child = !this.if_child;
this.$nextTick(() => {
const child = this.$refs.child;
if (child) {
child.callFunction();
}
});
}
}
});
.child {
display: inline-block;
padding: 10px;
background: #eaeaea;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="if_child">
<child ref="child" class="child"></child>
</div>
<button type="button" #click="showChild">
Toggle Child
</button>
</div>
Here's the key section:
showChild () {
this.if_child = !this.if_child;
this.$nextTick(() => {
const child = this.$refs.child;
if (child) {
child.callFunction();
}
});
}
You might wonder why it needs the if (child) {. That's because the button toggles the value of if_child. The method name, showChild, is actually misleading. The first time you click the button the child is created but the next time you click it will be destroyed.
If you don't pass a callback to $nextTick it will return a promise instead. This allows it to be used with async/await if you prefer:
async showChild () {
this.if_child = !this.if_child;
await this.$nextTick();
const child = this.$refs.child;
if (child) {
child.callFunction();
}
}
Why it doesn't work ?
because when your v-if is on the false state your child component doesn't exist. Vue hasn’t created it yet so your ref child is still undefined which means the callFunction won't be executed (undefined)
how about using Vue.nextTick API ?
i tried implementing it on the code (i tried both synchronous and asynchronous syntax) , but it works only on the first attempt then the child ref became undefined again ... it's because the component got destroyed (after if_child turned to false) so ´ref´ will be ´undefined´.
How can i fix this ?
I found two ways that can solve your problem :
1 - by using v-show on your child instead of v-if ... this will make your child always available(always rendered so your ref will be always defined) with a display : none on the false state;
2 - however if you insist on using v-if you can add another variable that will be mutated when the DOM finish rendering (using nextTick API) ... and your child component will watch that variable and execute the function upon that ... here is how you can do it :
Vue.component('child', {
props: ['exe'],
watch: {
exe() {
this.callFunction()
}
},
template: `
<div class="child">
I'm a child
</div>
`,
methods: {
callFunction: function() {
console.log("I'm called");
}
}
});
var vm = new Vue({
el: '#app',
data: {
if_child: false,
executeTheChildFunction: false,
},
methods: {
showChild() {
this.if_child = !this.if_child;
//Calling child's function
this.$nextTick(function() {
this.executeTheChildFunction = !this.executeTheChildFunction;
})
}
}
})
.child {
display: inline-block;
padding: 10px;
background: #eaeaea;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="if_child">
<child id="child" class="child" :exe="executeTheChildFunction"></child>
</div>
<button type="button" #click="showChild">
Toggle Child
</button>
</div>

Vuejs emit click from component to main vue instance

I am trying to emit an click Event from my component to my main vue instance but I am able to solve it. Currently I get no message in my console so it does not Event go to that function so Code Looks like this so far:
// component LeftUserListTileComponent
<template>
<v-list-tile #click="$emit('toggleLeftUserPanel')">
<v-list-tile-action>
<v-icon>exit_to_app</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>User Settings</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</template>
<script>
export default {
data () {
return {
}
},
methods:{
toggleLeftUserPanel () {
// what to do here?
}
};
</script>
Vue.component('left-user-list-tile', require('./components/LeftUserListTileComponent.vue'));
Vue.component('left-user-panel', require('./components/LeftUserPanelComponent.vue'));
const app = new Vue({
el: '#app',
props: {
source: String
},
data: {
leftUserPanel: null
},
toggleLeftUserPanel () {
console.log("In toggleLeftUserPanel func");
this.leftUserPanel != this.leftUserPanel;
}
});
If you emit directly the event when clicking, in the first toggleLeftUserPanel () you should do nothing more.
In order to work, you should declare what you expect from parent element when receiving an toggleLeftUserPanel event from it's childrens, like:
<toggle-left-user-panel #toggleLeftUserPanel='togglePanel' />
And then in the parent define this method in methods:
methods(){
togglePanel() {
//your action here
}
}
In this way, the parent-child communication is enabled and the parent should react to the children emit event.
Hope it helps!
If you want to emit an event you need to set this inside your component. If you only interested fetching a click when a user click on the title you should be able to use #click.native:
<v-list-tile #click.native="console.log('Title is clicked!')">
...

Categories

Resources