How can I call method in other component on vue.js 2? - vue.js

My first component like this :
<template>
...
</template>
<script>
export default {
...
methods: {
addPhoto() {
const data = { id_product: this.idProduct}
const item = this.idImage
this.$store.dispatch('addImage', data)
.then((response) => {
this.createImage(item, response)
});
},
}
}
</script>
If the method addPhoto called, it will call ajax and then it will get response ajax
I want to send response ajax and another parameter to method createImage. Method createImage is located in other component (second component)
My second component like this :
<template>
<div>
<ul class="list-inline list-photo">
<li v-for="item in items">
<div v-if="clicked[item]">
<img :src="image[item]" alt="">
<span class="fa fa-check-circle"></span>
</div>
<a v-else href="javascript:;" class="thumb thumb-upload"
title="Add Photo">
<span class="fa fa-plus fa-2x"></span>
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
...
data() {
return {
items: [1,2,3,4,5],
clicked: [], // using an array because your items are numeric
}
},
methods: {
createImage(item, response) {
this.$set(this.clicked, item, true)
},
}
}
</script>
How can I run the createImage method on the second component and after that it can change the element in the second component?

No two components don't have a parent/child relation. They are all connected through the root vue instance. To access the root vue instance just call this.$root and you get the root instance.
....
.then((response) => {
this.$root.$emit('createImage', item, response)
});
and in the second component make the function that needs to be triggered
...
mounted() {
this.$root.$on('createImage', (item, response) => {
// your code goes here
})
}
It acts more like a socket. The event will be globally available, due to $root.
N.B. adding the vue instance to global window object is a bad practice

If these 2 components are siblings (no parent & child), then one solution is to use event bus.
General idea is to build a global event handler like so:
in main.js
window.Event = new Vue();
Then in your first component fire an event:
....
.then((response) => {
Event.$emit('createImage', item, response)
});
and in second component register a handler for listening to createImage event in mounted() hook:
...
mounted() {
Event.$on('createImage', (item, response) => {
// your code goes here
}
}
You can find more info by reading this turtorial and watching this screen cast.

Related

Vue SFC - Wait for child's event to be emitted then the function end

I have an async method in the parent component which opens a b-modal component inside the child component, I would like to wait until the hide event is emitted and the associated function ends in order to continue the execution of the function.
Parent :
<template>
<div>
<b-button #click="modal_queue()">
Open modal
</b-modal>
<child ref="myChild"></child>
</div>
</template>
export default {
methods: {
async modal_queue() {
for(let i=0,i<2<i++) {
await this.aFunction();
}
},
async aFunction() {
return new Promise(resolve => {
this.$refs.mychild.$refs.myModal.$on("hide",() => {
resolve();
}
})
}
}
}
Child :
<template>
<b-modal ref="myModal" #hide="hidden()">
</b-modal>
</template>
export default {
methods: {
hidden() {
// Something
}
}
}
So I would like to wait until the Something code ends in order to resolve my Promise.
How I could do that ? Is my code architecture bad ?
For the moment, I'm only waiting until the 'hide' event is emitted.

VueJS: Not printing data returned in method

I'm successfully getting data into the console. When I try to print that data to the page by calling the method in double moustache braces it doesn't appear on screen. All other data in template appears just fine.
Template:
<template>
<div>
<div v-for="data in imageData" :key="data.id">
<div class="card">
<img :src="data.source" :alt="data.caption" class="card-img" />
<div class="text-box">
<p>{{ moment(data.timestamp.toDate()).format("MMM Do YYYY") }}</p>
<p>{{ data.caption }}</p>
// The Geocoding method is the problem
<p>{{reverseGeocode(data.location.df, data.location.wf)}}</p>
</div>
</div>
</div>
</div>
</template>
Method:
methods: {
reverseGeocode: (lat, long) => {
fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${long}&key=API_KEY&result_type=locality`
).then((res) =>
res.json().then((data) => {
console.log(data.results[0].formatted_address); // works fine
return data.results[0].formatted_address;
})
);
},
},
Here's the image data I'm getting in props
Your problem is a common problem when you start making requests in JavaScript.
The date requests are asynchronous so the method cannot return a value after the execution of the method has finished.
Imagine the following call stack:
Start method.
Throw fetch. <- Asynchronous
Finish method.
Fetch ends.
You are trying to do a return in step 4 and it should be in 3.
To solve this you should use async with await. You could also solve it by making a component and passing the data (this is my favorite since you are using vue).
Component parent
<template>
<div>
<component-card v-for="data in imageData" :key="data.id" :dataItem="data">
</component-card>
</div>
</template>
Child component
<template>
<div class="card">
<img :src="dataItem.source" :alt="dataItem.caption" class="card-img" />
<div class="text-box">
<p>{{ moment(dataItem.timestamp.toDate()).format("MMM Do YYYY") }}</p>
<p>{{ dataItem.caption }}</p>
<p>{{formattedAddress}}</p>
</div>
</div>
</template>
<script>
export default {
props: {
dataItem: {
type: {},
default: () => ({})
}
},
data() {
return {
formattedAddress: ""
};
},
created() {
this.reverseGeocode(this.dataItem.location.df, dataItem.location.wf)
},
methods: {
reverseGeocode(lat, long) {
fetch(
`https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${long}&key=API_KEY&result_type=locality`
).then(res =>
res.json().then(data => {
console.log(data.results[0].formatted_address); // works fine
this.formattedAddress = data.results[0].formatted_address;
})
);
}
}
};
</script>
I have not tried it, surely some things are missing but the template should be that.
The above I think is correct as well, but I would push for async
async reverseGeocode(lat, long) {
const response = await fetch(
`https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${long}&key=API_KEY&result_type=locality`
);
const data = response.json();
return data.results[0].formatted_address;
}
You should change your approach to the following:
Do all requests in the created() lifecycle method and store the results in a data attribute then iterate over the data attribute. The created() lifecycle method executes before the DOM is mounted so all data fetching APIs should be called there. FYR: https://v2.vuejs.org/v2/guide/instance.html
Please also refer to Vue.js - Which component lifecycle should be used for fetching data?

vue js event bus calls few events

I'm struggling with event bus.
I have a list of notes I display on home ('/'). I use the event bus to update the array when in 3 different places - when deleting, creating or updating a new note.
After using different options(delete+create, create+update...) the functions that are triggered by the event listener are triggered few times, creating duplicate items or deleting more than it should.
I am using one event bus for all of them.
I am using $once and I have placed it on the created hook.
How can I see what's in the event bus and how can I clear it after using it?
//event bus def
var eventBus = new Vue();
export default eventBus;
//the main page, where notes being displayed
template: `
<div>
<h1>this is the home page</h1>
<div class="note-container" v-for="note in notes" :key="note.id">
<component :noteData="note" :is="note.type" class="note" :style="note.style"></component>
</div>
</div>
`,
data() {
return {
notes: []
}
},
created() {
eventBus.$once('noteAdded', data => {
var newNotes = notesService.createNewNote(data)
this.notes = newNotes;
console.log('created activated');
})
eventBus.$once('noteUpdated', note=>{
notesService.updateNote(note)
.then(updatedNotes=>{
this.notes=updatedNotes
console.log('update activated');
})
})
eventBus.$once('noteDeleted', note=>{
notesService.deleteNote(note)
.then(notes=>{
console.log('delete activated');
this.notes=notes
})
})
//where update is being called from
template: `
<div>
<form>
some form
</form>
<button #click="handleUpdate">Update</button>
</div> `,
methods:{
handleUpdate(){
eventBus.$emit('noteUpdated', this.note)
this.$router.push('/')
console.log('handle img');
},
}
//where create is being called from
handleSubmit(){
eventBus.$emit('noteAdded', this.data)
this.$router.push('/')
},
usually, eventBus.$off('event') should turn the event off.
https://v2.vuejs.org/v2/api/?#vm-off
https://alligator.io/vuejs/global-event-bus/

Renderless Vue component with a click listener

I have read this post which goes in depth about renderless components:
https://adamwathan.me/renderless-components-in-vuejs/
A renderless component would pretty much look like this:
export default {
render() {
return this.$scopedSlots.default({})
},
}
Now I would like to use this renderless component but also add a click listener to whatever is being passed into the slot.
In my case it would be a button. My renderless component would simply wrap a button and add a click listener to it, which in turn performs an AJAX request.
How would I go about adding a click listener to the element that is being passed into the slot?
Assuming you want to bind the click handler within the renderless component, I think from this post that you need to clone the vnode passed in to renderless, in order to enhance it's properties.
See createElements Arguments, the second arg is the object to enhance
A data object corresponding to the attributes you would use in a template. Optional.
console.clear()
Vue.component('renderless', {
render(createElement) {
var vNode = this.$scopedSlots.default()[0]
var children = vNode.children || vNode.text
const clone = createElement(
vNode.tag,
{
...vNode.data,
on: { click: () => alert('clicked') }
},
children
)
return clone
},
});
new Vue({}).$mount('#app');
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<renderless>
<button type="button" slot-scope="{props}">Click me</button>
</renderless>
</div>
Here's one way to go about this.
Your renderless component wrapper would consist of a single action (i.e. the function to issue the AJAX request) prop.
Vue.component('renderless-action-wrapper', {
props: ['action'],
render() {
return this.$scopedSlots.default({
action: this.action,
});
},
});
Then another component which uses the aforementioned wrapper would enclose a customisable slot with a #click handler, which invokes the action that is passed in when triggered.
Vue.component('clickable', {
props: ['action'],
template: `
<renderless-action-wrapper :action="action">
<span slot-scope="{ url, action }">
<span #click="action()">
<slot name="action"></slot>
</span>
</span>
</renderless-action-wrapper>
`,
});
Finally, wire up the specialised version of the wrapper.
<clickable :action="doAjaxRequest">
<button type="button" slot="action">Button</button>
</clickable>
Here's a live example of the above suggestion you can play around with.

How can I update data from parent component when I click child component on the vue component?

My first component (child component) like this :
<template>
...
</template>
<script>
export default {
...
methods: {
addPhoto() {
const data = { id_product: this.idProduct}
const item = this.idImage
this.$store.dispatch('addImage', data)
.then((response) => {
this.$parent.$options.methods.createImage(item, response)
});
},
}
}
</script>
If the method addPhoto called, it will call ajax and then it will get response ajax
I want to send response ajax and another parameter to method createImage. Method createImage is located in parent component (second component)
My second component (parent component) like this :
<template>
<div>
<ul class="list-inline list-photo">
<li v-for="item in items">
<div v-if="clicked[item]">
<img :src="image[item]" alt="">
<span class="fa fa-check-circle"></span>
</div>
<a v-else href="javascript:;" class="thumb thumb-upload"
title="Add Photo">
<span class="fa fa-plus fa-2x"></span>
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
...
data() {
return {
items: [1,2,3,4,5],
clicked: [], // using an array because your items are numeric
test: null
}
},
methods: {
createImage(item, response) {
console.log(item)
this.$set(this.clicked, item, true)
this.test = item
},
}
}
</script>
If the code executed, it success call createImage method on parent component. The console log display value of item
But my problem is the data on parent component not success updated
How can I solve this problem?
You really should get in the habit of using events instead of directly accessing the parent component from a child.
In your case, it would be simple to emit an event in the then handler of the child's async request:
.then((response) => {
this.$emit('imageAdded', item);
});
And listen for it in the parent scope:
<child-component #itemAdded="createImage"></child-component>
That way, any component that uses that child component can react to its imageAdded event. Plus, you won't ever need to spend time debugging why the createImage method is firing when it's never being called in the Vue instance.
Your code isn't working because the way you are invoking the createImage method means that the this inside the function will not be referencing the parent component instance when it is called. So setting this.clicked or this.test will not affect the parent instance's data.
To call the parent component's function with the right context, you would need to do this:
this.$parent.createImage(item, response)