I am using Vue.Js v2. I want to call component1->c1method in component2->c2method for reload data after submitting.
Vue.component('component1', {
methods: {
c1method: function(){
alert('this is c1method')
},
}
})
Vue.component('component2', {
methods: {
c2method: function(){
component('component1').c1method()//like this
},
}
})
For non-parent-child relation, then this is the same as this one. Call one method, apparently any method of a component from any other component. Just add a $on function to the $root instance and call form any other component accessing the $root and calling $emit function.
On First component
....
mounted() {
this.$root.$on('component1', () => {
// your code goes here
this.c1method()
}
}
and in the second component call the $emit function in $root
...
c2method: function(){
this.$root.$emit('component1') //like this
},
It acts more like a socket. Reference here
https://stackoverflow.com/a/50343039/6090215
// Component A
Vue.component('A', {
created() {
this.$root.$refs.A = this;
},
methods: {
foo: function() {
alert('this is A.foo');
}
}
});
// Component B
Vue.component('B', {
methods: {
bar: function() {
this.$root.$refs.A.foo();
}
}
});
No need for hacky solutions.
In vuejs we can create events that can be listened globally.
With this feature, whenever we want to call our beloved function, we just emit this event.
Now, we just listen to this event all the time from the component. whenever this global event happens we can execute our method we want to call.
It's pretty simple:
you go to main.js, before creating the vue instance, write this:
export const eventBus = new Vue(); // added line
new Vue({
...
...
...
render: h => h(App)
}).$mount('#app');
Anywhere we want to fire the target function, we dont fire it, we just emit this event:
eventBus.$emit('fireMethod');
Now in our component that has the target function, we always listen to this event:
created() {
eventBus.$on('fireMethod', () => {
this.myBelovedMethod();
})
}
Dont forget to import eventBus in top.
import {eventBus} from "path/to/main.js";
thats it, few lines of code, no hacky, all vuejs power.
The docs address this situation
https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
If your components have the same parent, you can emit an event that the parent listens to. Then with the ref property set, you can call the c1method from the parent.
https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs
Try this out.
<template>
...
<component1 ref='componentOne'>
...
</template>
<script>
Vue.component('component2', {
methods: {
c2method: function(){
this.$refs.componentOne.c1method();
}
}
});
</script>
If anyone is looking for a solution in Vue.js v3:
https://v3-migration.vuejs.org/breaking-changes/events-api.html#event-bus
https://github.com/developit/mitt#install
import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// listen to all events
emitter.on('*', (type, e) => console.log(type, e) )
// fire an event
emitter.emit('foo', { a: 'b' })
// clearing all events
emitter.all.clear()
Related
I'm dealing with some legacy code which is dispatching a custom event like so:
class AuthStatus {
constructor() {
this.init();
}
async init() {
const event = new CustomEvent("qwerty-authentication", {
detail: {
isLoggedIn,
sessionId,
submittedEmail,
user,
},
});
window.dispatchEvent(event);
}
}
export default AuthStatus;
But how can I listen for the event triggering from another file? Is that even possible? Everytime I run: window.addEventListener('qwerty-authentication', (e) => console.log(e)); it's always undefined, but if I run it in the same file I do get the values.
You need to use addEventListener to listen to that event, as the event applied to the window window.dispatchEvent(event);, so you need.
window.addEventListener('qwerty-authentication', function () {
console.log('event fired');
})
I have a Vue component that generates a dynamic component, and within that dynamic component is a click handler for a button that makes an Ajax call. Upon the Ajax call being successfully completed, I want to inform the component that generates the dynamic component that the Ajax call has finished. How do I do that?
The basic structure of the code in question is as follows:
<template>
<div>
<!-- Other markup here. -->
<div class="contentPlaceholder">
</div>
</div>
</template>
<script>
export default {
// Props, etc.
data: function () {
return {
// ...
content: 'long-html-string-that-contains-Vue-components'
};
},
mounted: function () {
Vue.component(`content-component`, {
template: `
<div class="content">
${this.content}
</div>
`,
data: function () {
return {
// Local data here.
};
}
methods: {
// Button in this.content markup clicked.
btnCicked: function () {
ajax.post('url', {
// Params
success: () => {
// Want to report back to the parent component
// that we're here now.
}
});
}
}
});
const res = Vue.compile(`
<content-component>
</content-component>
`);
new Vue({
render: res.render,
staticRenderFns: res.staticRenderFns
}).$mount(`.contentPlaceholder`);
}
}
</script>
My initial thought was to do this.$emit('btnClicked', 'data-here') in the Ajax success callback, but when I try to attach an #btnClicked event handler to the content-component in either the template part of the Vue.component method call or the Vue.compile method call, I get a Vue error.
Basically, I have no clue what to do. The this context is definitely different in the dynamic component, so I can't just add a data property to the parent component and then set it in the Ajax callback of the dynamic component. I tried that and it doesn't work.
I trust there is a simple way to do this, but I'm honestly not sure how. Any help would be greatly appreciated. Thank you.
Edit: It's worth noting that I tried to treat the dynamic component as if it were just a regular child component of the parent component. As such, I added a this.$emit('btnClicked') call within the Ajax success callback and then added an #btnClicked handler to the content-component, but it didn't work.
Maybe I'm just doing it wrong, but I tried both of the following:
template: `
<div class="content" #btnClicked="btnClicked">
${this.content}
</div>
`,
// And
const res = Vue.compile(`
<content-component #btnClicked="btnClicked">
</content-component>
`);
But neither seem to work. Thanks.
btnCicked: () => { console.log(this) }.
Try to use arrow function to save the context.
Another option is to create a function that already has access to outer this, and invoke it in your method.
const method = () => {
console.log('I have access to outer this', this)
}
...
btnCicked: function () { method(); console.log('Local this', this) }
...
I am not able to $emit an event from a child component to it's parent.
I can successfully send the event, but not receive it in the parent.
Results.vue (Child):
//
methods: {
sendResultValues: function () {
this.$emit('send-result-values', 'carrier');
}
},
When I click on the <a>, I can see with Vue DevTools that an $emit event is fired:
However, nothing is received in the console.log as my code below (parent):
Input.vue (Parent):
<search-results></search-results> //Results.vue component
<search-popover v-on:send-result-values="showResultData"></search-popover>
//
methods: {
showResultData: function () {
console.log("Data received from child: ")
}
},
You need to listen to the event on the search-results component, not on the search-popover.
Input.vue (Parent):
<search-results v-on:send-result-values="showResultData"></search-results>
<search-popover></search-popover>
methods: {
showResultData: function () {
console.log("Data received from child: ")
}
},
In my opinion, you should do the event declaration equation on the parent side.
you are wrong on :
the click event shouldn't be that, it should be send-result-values. So the result is
there is way more way to solve this problem.
You can use vuex.js
you can use $emit and $on methods
for example
vm.$emit('test', 'hi');
vm.$on('test', function (msg) { console.log(msg) }); // => "hi"
It seems that Vue.js 2.0 doesn't emit events from a grand child to his grand parent component.
Vue.component('parent', {
template: '<div>I am the parent - {{ action }} <child #eventtriggered="performAction"></child></div>',
data(){
return {
action: 'No action'
}
},
methods: {
performAction() { this.action = 'actionDone' }
}
})
Vue.component('child', {
template: '<div>I am the child <grand-child></grand-child></div>'
})
Vue.component('grand-child', {
template: '<div>I am the grand-child <button #click="doEvent">Do Event</button></div>',
methods: {
doEvent() { this.$emit('eventtriggered') }
}
})
new Vue({
el: '#app'
})
This JsFiddle solves the issue https://jsfiddle.net/y5dvkqbd/4/ , but by emtting two events:
One from grand child to middle component
Then emitting again from middle component to grand parent
Adding this middle event seems repetitive and unneccessary. Is there a way to emit directly to grand parent that I am not aware of?
Vue 2.4 introduced a way to easily pass events up the hierarchy using vm.$listeners
From https://v2.vuejs.org/v2/api/#vm-listeners :
Contains parent-scope v-on event listeners (without .native modifiers). This can be passed down to an inner component via v-on="$listeners" - useful when creating transparent wrapper components.
See the snippet below using v-on="$listeners" in the grand-child component in the child template:
Vue.component('parent', {
template:
'<div>' +
'<p>I am the parent. The value is {{displayValue}}.</p>' +
'<child #toggle-value="toggleValue"></child>' +
'</div>',
data() {
return {
value: false
}
},
methods: {
toggleValue() { this.value = !this.value }
},
computed: {
displayValue() {
return (this.value ? "ON" : "OFF")
}
}
})
Vue.component('child', {
template:
'<div class="child">' +
'<p>I am the child. I\'m just a wrapper providing some UI.</p>' +
'<grand-child v-on="$listeners"></grand-child>' +
'</div>'
})
Vue.component('grand-child', {
template:
'<div class="child">' +
'<p>I am the grand-child: ' +
'<button #click="emitToggleEvent">Toggle the value</button>' +
'</p>' +
'</div>',
methods: {
emitToggleEvent() { this.$emit('toggle-value') }
}
})
new Vue({
el: '#app'
})
.child {
padding: 10px;
border: 1px solid #ddd;
background: #f0f0f0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<parent></parent>
</div>
NEW ANSWER (Nov-2018 update)
I discovered that we could actually do this by leveraging the $parent property in the grand child component:
this.$parent.$emit("submit", {somekey: somevalue})
Much cleaner and simpler.
The Vue community generally favors using Vuex to solve this kind of issue. Changes are made to Vuex state and the DOM representation just flows from that, eliminating the need for events in many cases.
Barring that, re-emitting would probably be the next best choice, and lastly you might choose to use an event bus as detailed in the other highly voted answer to this question.
The answer below is my original answer to this question and is not an approach I would take now, having more experience with Vue.
This is a case where I might disagree with Vue's design choice and resort to DOM.
In grand-child,
methods: {
doEvent() {
try {
this.$el.dispatchEvent(new Event("eventtriggered"));
} catch (e) {
// handle IE not supporting Event constructor
var evt = document.createEvent("Event");
evt.initEvent("eventtriggered", true, false);
this.$el.dispatchEvent(evt);
}
}
}
and in parent,
mounted(){
this.$el.addEventListener("eventtriggered", () => this.performAction())
}
Otherwise, yes, you have to re-emit, or use a bus.
Note: I added code in the doEvent method to handle IE; that code could be extracted in a reusable way.
Yes, you're correct events only go from child to parent. They don't go further, e.g. from child to grandparent.
The Vue documentation (briefly) addresses this situation in the Non Parent-Child Communication section.
The general idea is that in the grandparent component you create an empty Vue component that is passed from grandparent down to the children and grandchildren via props. The grandparent then listens for events and grandchildren emit events on that "event bus".
Some applications use a global event bus instead of a per-component event bus. Using a global event bus means you will need to have unique event names or namespacing so events don't clash between different components.
Here is an example of how to implement a simple global event bus.
If you want to be flexible and simply broadcast an event to all parents and their parents recursively up to the root, you could do something like:
let vm = this.$parent
while(vm) {
vm.$emit('submit')
vm = vm.$parent
}
Another solution will be on/emit at root node:
Uses vm.$root.$emit in grand-child, then uses vm.$root.$on at the ancestor (or anywhere you'd like).
Updated: sometimes you'd like to disable the listener at some specific situations, use vm.$off (for example: vm.$root.off('event-name') inside lifecycle hook=beforeDestroy).
Vue.component('parent', {
template: '<div><button #click="toggleEventListener()">Listener is {{eventEnable ? "On" : "Off"}}</button>I am the parent - {{ action }} <child #eventtriggered="performAction"></child></div>',
data(){
return {
action: 1,
eventEnable: false
}
},
created: function () {
this.addEventListener()
},
beforeDestroy: function () {
this.removeEventListener()
},
methods: {
performAction() { this.action += 1 },
toggleEventListener: function () {
if (this.eventEnable) {
this.removeEventListener()
} else {
this.addEventListener()
}
},
addEventListener: function () {
this.$root.$on('eventtriggered1', () => {
this.performAction()
})
this.eventEnable = true
},
removeEventListener: function () {
this.$root.$off('eventtriggered1')
this.eventEnable = false
}
}
})
Vue.component('child', {
template: '<div>I am the child <grand-child #eventtriggered="doEvent"></grand-child></div>',
methods: {
doEvent() {
//this.$emit('eventtriggered')
}
}
})
Vue.component('grand-child', {
template: '<div>I am the grand-child <button #click="doEvent">Emit Event</button></div>',
methods: {
doEvent() { this.$root.$emit('eventtriggered1') }
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<parent></parent>
</div>
VueJS 2 components have a $parent property that contains their parent component.
That parent component also includes its own $parent property.
Then, accessing the "grandparent" component it's a matter of accessing the "parent's parent" component:
this.$parent["$parent"].$emit("myevent", { data: 123 });
Anyway, this is kinda tricky, and I recommend using a global state manager like Vuex or similar tools, as other responders have said.
I've made a short mixin based on #digout answer. You want to put it, before your Vue instance initialization (new Vue...) to use it globally in project. You can use it similarly to normal event.
Vue.mixin({
methods: {
$propagatedEmit: function (event, payload) {
let vm = this.$parent;
while (vm) {
vm.$emit(event, payload);
vm = vm.$parent;
}
}
}
})
Riffing off #kubaklam and #digout's answers, this is what I use to avoid emitting on every parent component between the grand-child and the (possibly distant) grandparent:
{
methods: {
tunnelEmit (event, ...payload) {
let vm = this
while (vm && !vm.$listeners[event]) {
vm = vm.$parent
}
if (!vm) return console.error(`no target listener for event "${event}"`)
vm.$emit(event, ...payload)
}
}
}
When building out a component with distant grand children where you don't want many/any components to be tied to the store, yet want the root component to act as a store/source of truth, this works quite well. This is similar to the data down actions up philosophy of Ember. Downside is that if you want to listen for that event on every parent in between, then this won't work. But then you can use $propogateEmit as in above answer by #kubaklam.
Edit: initial vm should be set to the component, and not the component's parent. I.e. let vm = this and not let vm = this.$parent
This is the only case when I use event bus!! For passing data from deep nested child, to not directly parent, communication.
First: Create a js file (I name it eventbus.js) with this content:
import Vue from 'vue'
Vue.prototype.$event = new Vue()
Second: In your child component emit an event:
this.$event.$emit('event_name', 'data to pass')
Third: In the parent listen to that event:
this.$event.$on('event_name', (data) => {
console.log(data)
})
Note: If you don't want that event anymore please unregister it:
this.$event.$off('event_name')
INFO: No need to read the below personal opinion
I don't like to use vuex for grand-child to grand-parent communication (Or similar communication level).
In vue.js for passing data from grand-parent to grand-child you can use provide/inject. But there is not something similar for the opposite thing. (grand-child to grand-parent) So I use event bus whenever I have to do that kind of communication.
Riffing off #digout answer. I am thinking that if the purpose is to send data to a far-ancestor then we don't need $emit at all. I did this for my edge-case and it seems to work. Yes, it could be implemented via a mixin but it doesn't have to be.
/**
* Send some content as a "message" to a named ancestor of the component calling this method.
* This is an edge-case method where you need to send a message many levels above the calling component.
* Your target component must have a receiveFromDescendant(content) method and it decides what
* to do with the content it gets.
* #param {string} name - the name of the Vue component eg name: 'myComponentName'
* #param {object} content - the message content
*/
messageNamedAncestor: function (name, content) {
let vm = this.$parent
let found = false
while (vm && !found) {
if (vm.$vnode.tag.indexOf('-' + name) > -1) {
if (vm.receiveFromDescendant) {
found = true
vm.receiveFromDescendant(content)
} else {
throw new Error(`Found the target component named ${name} but you dont have a receiveFromDescendant method there.`)
}
} else {
vm = vm.$parent
}
}
}
Given an ancestor:
export default {
name: 'myGreatAncestor',
...
methods: {
receiveFromDescendant (content) {
console.log(content)
}
}
}
A great grand-child says
// Tell the ancestor component something important
this.messageNamedAncestor('myGreatAncestor', {
importantInformation: 'Hello from your great descendant'
})
As of Vue 3, a number of fundamental changes have happened to root events:
The $on, $off and $once root methods no longer exist. There is to a certain extent something to replace this, since you can listen to root events by doing this:
createApp(App, {
// Listen for the 'expand' event
onExpand() {
console.log('expand')
}
})
Another solution are event buses, but the Vue.js documents take a dim view - they can cause maintenance headaches in the long run. You might get an ever spreading set of emits and event sinks, with no clear or central idea of how it is managed or what components could be affected elsewhere. Nonetheless, examples given by the docs of event buses are mitt and tiny-emitter.
However the docs make it clear that they recommend handling these sorts of situations in this order:
Props A convenient solution for parent / child communications.
Provide/Inject A simple way for ancestors to communicate with their descendants (although critically, not the other way around).
Vuex A way of handling global state in a clear fashion. It's important to note that this is not solely for events, or communications - Vuex was built primarily to handle state.
Essentially the choice for the OP would come down to using an event bus, or Vuex. In order to centralise the event bus, you could place it inside Vuex, if state was also needed to be globally available. Otherwise using an event bus with strict centralised controls on it's behaviour and location might help.
I really dig the way this is handled by creating a class that is bound to the window and simplifying the broadcast/listen setup to work wherever you are in the Vue app.
window.Event = new class {
constructor() {
this.vue = new Vue();
}
fire(event, data = null) {
this.vue.$emit(event, data);
}
listen() {
this.vue.$on(event, callback);
}
}
Now you can just fire / broadcast / whatever from anywhere by calling:
Event.fire('do-the-thing');
...and you can listen in a parent, grandparent, whatever you want by calling:
Event.listen('do-the-thing', () => {
alert('Doing the thing!');
});
I have a function that helps filter data. I am using v-on:change when a user changes the selection but I also need the function to be called even before the user selects the data. I have done the same with AngularJS previously using ng-init but I understand that there is no such a directive in vue.js
This is my function:
getUnits: function () {
var input = {block: this.block, floor: this.floor, unit_type: this.unit_type, status: this.status};
this.$http.post('/admin/units', input).then(function (response) {
console.log(response.data);
this.units = response.data;
}, function (response) {
console.log(response)
});
}
In the blade file I use blade forms to perform the filters:
<div class="large-2 columns">
{!! Form::select('floor', $floors,null, ['class'=>'form-control', 'placeholder'=>'All Floors', 'v-model'=>'floor', 'v-on:change'=>'getUnits()' ]) !!}
</div>
<div class="large-3 columns">
{!! Form::select('unit_type', $unit_types,null, ['class'=>'form-control', 'placeholder'=>'All Unit Types', 'v-model'=>'unit_type', 'v-on:change'=>'getUnits()' ]) !!}
</div>
This works fine when I select a specific item. Then if I click on all lets say all floors, it works. What I need is when the page is loaded, it calls the getUnits method which will perform the $http.post with empty input. In the backend I have handled the request in a way that if the input is empty it will give all the data.
How can I do this in vuejs2?
My Code: http://jsfiddle.net/q83bnLrx
You can call this function in the beforeMount section of a Vue component: like following:
// .....
methods: {
getUnits: function() { /* ... */ }
},
beforeMount() {
this.getUnits()
},
// ......
Working fiddle: https://jsfiddle.net/q83bnLrx/1/
There are different lifecycle hooks Vue provide:
I have listed few are :
beforeCreate: Called synchronously after the instance has just been initialized, before data observation and event/watcher setup.
created: Called synchronously after the instance is created. At this stage, the instance has finished processing the options which means the following have been set up: data observation, computed properties, methods, watch/event callbacks. However, the mounting phase has not been started, and the $el property will not be available yet.
beforeMount: Called right before the mounting begins: the render function is about to be called for the first time.
mounted: Called after the instance has just been mounted where el is replaced by the newly created vm.$el.
beforeUpdate: Called when the data changes, before the virtual DOM is re-rendered and patched.
updated: Called after a data change causes the virtual DOM to be re-rendered and patched.
You can have a look at complete list here.
You can choose which hook is most suitable to you and hook it to call you function like the sample code provided above.
You need to do something like this (If you want to call the method on page load):
new Vue({
// ...
methods:{
getUnits: function() {...}
},
created: function(){
this.getUnits()
}
});
you can also do this using mounted
https://v2.vuejs.org/v2/guide/migration.html#ready-replaced
....
methods:{
getUnits: function() {...}
},
mounted: function(){
this.$nextTick(this.getUnits)
}
....
Beware that when the mounted event is fired on a component, not all Vue components are replaced yet, so the DOM may not be final yet.
To really simulate the DOM onload event, i.e. to fire after the DOM is ready but before the page is drawn, use vm.$nextTick from inside mounted:
mounted: function () {
this.$nextTick(function () {
// Will be executed when the DOM is ready
})
}
If you get data in array you can do like below. It's worked for me
<template>
{{ id }}
</template>
<script>
import axios from "axios";
export default {
name: 'HelloWorld',
data () {
return {
id: "",
}
},
mounted() {
axios({ method: "GET", "url": "https://localhost:42/api/getdata" }).then(result => {
console.log(result.data[0].LoginId);
this.id = result.data[0].LoginId;
}, error => {
console.error(error);
});
},
</script>
methods: {
methodName() {
fetch("url").then(async(response) => {
if (response.status === 200) {
const data = await response.json();
this.xy = data.data;
console.log("Success load");
}
})
}
}
you can do it using created() method. it will fire once page fully loaded.
created:function(){
this.fillEditForm();
},