Vue.js global event bus - javascript

I am trying to create a global event bus so that two sibling components can communicate with each other. I have searched around; however, I cannot find any examples of how to implement one. This is what I have so far:
var bus = new Vue();
Vue.component('Increment', {
template: "#inc",
data: function() {
return ({count: 0})
},
methods: {
increment: function(){
var increment = this.count++
bus.$emit('inc', increment)
}
}
})
Vue.component('Display', {
template: "#display",
data: function(){
return({count: 0})
},
created: function(){
bus.$on('inc', function(num){
alert(num)
this.count = num;
});
}
})
vm = new Vue({
el: "#example",
})
I created my templates like so: http://codepen.io/p-adams/pen/PzpZBg
I'd like the Increment component to communicate the count to the Display component. I am not sure what I am doing wrong in bus.$on().

The problem is that within your bus.$on function, this refers to the bus. You just need to bind the current Vue instance to that function using .bind():
bus.$on('inc', function(num){
alert(num)
this.count = num;
}.bind(this));
You should also check out https://github.com/vuejs/vuex if you want to manage global application states.
EDIT: Since this page seems to get a lot of clicks I want to edit and add another method, per ChristopheMarois in the comments:
EDIT: In effort to make this answer a little clearer, and so future readers don't need to read comments here's what's happening:
Using a fat arrow like below binds the lexical scope of 'this' to the component instead of to the event bus.
bus.$on('inc', (num) => {
alert(num);
this.count = num;
});
Or removing the alert:
bus.$on('inc', (num) => this.count = num);

As you write ES5 JavaScript you have to be aware of the fact that what you refer to by using the this keyword might change, according to the scope, it is called from.
A useful metaphor to get your head around the this concept is to think of the curly braces in ES5 as fences, that contain/bind its own this.
When you use this in the callback function of your event bus, this does not refer to your Vue component, but the bus object, which has no count data, so the data you expect to update doesn't.
If you have/want to write ES5 syntax a common workaround (besides binding this as suggested by the accepted answer) is to assign the this keyword to a variable like so:
created: function(){
var self = this;
bus.$on('inc', function(num){
alert(num)
self.count = num;
});
}
If you can write ES6, do so whenever possible. You can always compile/transpile down to ES5 with Babel. The accepted answer shows you how by using arrow functions.
Arrow functions work in that case because they do not bind their own this.
To stick with the fence metaphor: imagine the ES6 arrow poking a hole in your function fence, so the outer this can pass through and you can call this as intended.
To learn more about ES6 arrow functions visit:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

This is answered long back, here is my solution using in vue.js-2
main.js
import Vue from 'vue'
import App from './App'
export const eventBus = new Vue({
methods:{
counter(num) {
this.$emit('addNum', num);
}
}
});
new Vue({
el: '#app',
template: '<App/>',
components: { App }
});
comp1.vue
//Calling my named export
import { eventBus } from '../../main'
<template>
<div>
<h1>{{ count }}</h1>
<button #click="counterFn">Counter</button>
</div>
</template>
<script>
import { eventBus } from '../../main'
export default {
name: 'comp-one',
data() {
return {
count: 0
}
},
methods: {
counterFn() {
eventBus.counter(this.count);
}
},
created() {
eventBus.$on('addNum', () => {
this.count++;
})
}
}
</script>

How about this? Assume Vue.js 2.
Create a reusable Event-Bus component and attach it to Vue via plugin pattern:
// ./components/EventBus.vue
import Vue from 'vue'
export const EventBus = new Vue()
// ./plugins/EventBus.js
export default {
install(Vue) {
const { EventBus } = require('../components/EventBus')
Vue.prototype.$bus = EventBus
}
}
// ./main.js
import EventBus from './plugins/EventBus'
Vue.use(EventBus)
Then, you can do anywhere in your code:
this.$bus.$emit('some-event', payload)
As a side note, try to utilize the Event-Bus pattern as last resort.

Related

VueJS - "this" is undefined in common function

I'm trying to extract a function for use across multiple components, but "this" is undefined and I'm unsure of the best practice approach of how to attach the scope so my function knows what "this" is. Can I just pass it as an argument?
Component:-
import goToEvent from "#/common";
export default {
name: "update",
methods: {
goToEvent
common function:-
let goToEvent = (event, upcoming=false) => {
this.$store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
export default goToEvent
When I call goToEvent in my component, I get TypeError: Cannot read property '$store' of undefined. How do I avoid this?
In this situation I recommend to define eventable as a mixin :
const eventable= {
methods: {
goToEvent(event, upcoming=false) {
this.$store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
}
}
export default eventable;
in your vue file :
import eventable from "#/eventable";
export default {
name: "update",
mixins:[eventable],
....
second solution :
export an object with the function as nested method then import it and spread it inside the methods option :
export default {
goToEvent(event, upcoming=false){
this.$store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
}
then :
import goToEvent from "#/common";
export default {
name: "update",
methods: {
...goToEvent,
otherMethod(){},
}
//....
}
You're tagged with Typescript, so you need to tell TS that this actually has a value (note, I do not know VueJS, am using the generic Event types here, there is likely a more valid and correct type!)
First option, manually tell it what there is -
let goToEvent = (this:Event, event, upcoming=false) => {
Other option - tell it what type it is -
let goToEvent: EventHandler = (event, upcoming=false) => {
Of the two I personally prefer the second style for readability.
There are numerous ways to achieve this, here are some that I like to use in my projects:
Method 1: Mixins
Mixins are great for sharing a bunch of methods across components and also easy to implement, although one big con is that you will not be able to import specific methods that you need. Within the mixin, this follows the rules as in components.
File: #/mixins/eventable
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([])
goToEvent (event, upcoming = false) {
store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
}
}
Usage in component:
import eventable from '#/mixins/eventable'
export default {
name: 'ComponentName',
mixins: [eventable],
methods: {
componentMethod () {
this.goToEvent()
}
}
...
Method 2: Static JavaScript files
In some cases, you might have a collection of helper functions kept in a file and want the ability to import as you need.
In your case, you seem to be using a store actions (assumed from the dispatch), hence I'll be including importing and using the store within the static JS file.
File: #/static/js/eventable.js
import store from 'path_to_store_file'
const goToEvent = () => {
store.dispatch('actionName', payload)
}
export default {
goToEvent
}
Note:
Although this is not entirely necessary, but only by declaring the imported function as a method within the component will it be bound to the component instance. This will allow you to access the function in the HTML portion.
Usage in component:
import { goToEvent } from '#/static/js/eventable.js'
export default {
name: 'ComponentName',
methods: {
// Read note before this code block
goToEvent,
componentMethod () {
// When declared as a method
this.goToEvent()
// When not declared, it can still be accessed in the js portion like this
goToEvent()
}
}
...

How to import a external function in a Vue component?

I'm a newbie in javascript and vue.js and I'm facing somme issue when trying to add a new function in an existing programme.
I have put my new function (with others) in a separate file:
export const MyFunctions = {
MyFunction: function(param) {
// Doing stuff
}
}
Then I import the file in the component file and calling my function :
<script>
import {MyFunctions} from "#/components/MyFunctions.js";
export default {
name:"Miniature",
computed: {
useMyFunction() {
MyFunction("Please do some stuff !");
}
}
}
</script>
When the component is used, I get an error message
[Vue warn]: Property or method "MyFunction" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
I've read a lot of documentation and fail to understand why it doesn't work. Can anyone help me with this ??
You're exporting an object then in order to use the MyFunction you need to access to that function using dot notation, like this: MyFunctions.MyFunction("Please do some stuff !")
I made a working example for this use-case: https://codesandbox.io/s/62l1j19rvw
MyFunctions.js
export const MyFunctions = {
MyFunction: function(param) {
alert(param);
}
};
Component
<template>
<div class="hello">
{{msg}}
<button #click="handleClick">Click me</button>
</div>
</template>
<script>
import {MyFunctions} from "../MyFunctions.js";
export default {
name: "HelloWorld",
data() {
return {
msg: "Welcome to Your Vue.js App"
};
},
methods:{
handleClick: function(){
MyFunctions.MyFunction("Please do some stuff !");
}
}
};
</script>
You can just import your javascript files into .vue files as long as they are inside <script> tags. Since Vue.js is after all javascript, the first part where you should look at while debugging is if you have some kind of mistake in your syntax. From what I see, there is some confusion with the import and export statements, which could be quite complex at first!
Check MDN's Documentation specially under named exports:
In the module, we could use the following
// module "my-module.js"
function cube(x) {
return x * x * x;
}
const foo = Math.PI + Math.SQRT2;
var graph = { /* nice big object */ }
export { cube, foo, graph };
This way, in another script, we could have:
import { cube, foo, graph } from 'my-module';
// Use your functions wisely
what you export is an object, and what you use is a field/method inside this object, so you need to use your function this way:
MyFunctions.MyFunction("Please do some stuff !");

How to create Vue.js slot programmatically?

I have the following component with a slot:
<template>
<div>
<h2>{{ someProp }}</h2>
<slot></slot>
</div>
</template>
For some reasons, I have to manually instantiate this component. This is how I am doing it:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
}).$mount(body);
The problem is: I am not able to create slot contents programmatically. So far, I can create simple string based slot:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
});
// Creating simple slot
instance.$slots.default = ['Hello'];
instance.$mount(body);
The question is - how can I create $slots programmatically and pass it to the instance I am creating using new?
Note: I am not using a full build of Vue.js (runtime only). So I don't have a Vue.js compiler available to compile the template on the fly.
I looked into TypeScript definition files of Vue.js and I found an undocumented function on Vue component instance: $createElement(). My guess is, it is the same function that is passed to render(createElement) function of the component. So, I am able to solve it as:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
});
// Creating simple slot
const node = instance.$createElement('div', ['Hello']);
instance.$slots.default = [node];
instance.$mount(body);
But this is clearly undocumented and hence questionable approach. I will not mark it answered if there is some better approach available.
I think I have finally stumbled on a way to programmatically create a slot element. From what I can tell, the approach does not seem to work for functional components. I am not sure why.
If you are implementing your own render method for a component, you can programmatically create slots that you pass to child elements using the createElement method (or whatever you have aliased it to in the render method), and passing a data hash that includes { slot: NAME_OF_YOUR_SLOT } followed by the array of children within that slot.
For example:
Vue.config.productionTip = false
Vue.config.devtools = false;
Vue.component('parent', {
render (createElement) {
return createElement('child', [
createElement('h1', { slot: 'parent-slot' }, 'Parent-provided Named Slot'),
createElement('h2', { slot: 'default' }, 'Parent-provided Default Slot')
])
}
})
Vue.component('child', {
template: '<div><slot name="parent-slot" /><slot /></div>'
})
new Vue({
el: '#app',
template: '<parent />'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
</div>
(This doesn't really answer How to create Vue.js slot programatically?. But it does solve your problem.)
This trick is less hackish compared to using $createElement().
Basically, create a new component that register MyComponent as a local component.
const Constr = Vue.extend({
template: `
<MyComponent someProp="My Heading">
<div>slot here !!!</div>
</MyComponent>
`,
components: {
MyComponent: MyComponent
}
});
const instance = new Constr().$mount('#app');
Demo: https://jsfiddle.net/jacobgoh101/shrn26p1/
I just came across an answer to this in vue forum:
slots
The principle is: There is nothing like createElement('slot'..)
Instead there is a render function which provides the slotted innerHtml as function:
$scopedSlots.default()
Usage:
render: function (createElement) {
const self = this;
return createElement("div", this.$scopedSlots.default());
}
If you want to provide a default in case there is no content given for the slots, you need to code a disctinction yourself and render something else.
(The link above holds a more detailed example)
The function returns an array, therefore it can not be used as a root for the render function. It need to be wrapped into single container node like div in the example above.

Setting up a utility class and using it within a vue component

A vue application I am working on currently has lots of code redundancies relating to date functions. In an effort to reduce these redundancies, I'd like to create a utility class as shown below, import it and set it to a Vue data property within the component, so I can call the date functions within it.
I am not certain on the best way to implement this. The current implementation results in an error saying TypeError: this.dates is undefined and my goal is not only to resolve this error but create/utilize the class in the Vue environment using best standards.
Importing utility class
import Dates from "./utility/Dates";
...
Component
const contactEditView = Vue.component('contact-edit-view', {
data() {
return {
contact: this.myContact
dates: Dates
}
},
...
Dates.js
export default {
dateSmall(date) {
return moment(date).format('L');
},
dateMedium(date) {
return moment(date).format('lll');
},
dateLarge(date) {
return moment(date).format('LLL');
}
};
View
Date of Birth: {{ dates.dateMedium(contact.dob) }}
My suggestion for this is to use a plugin option in Vue. About Vue plugin
So you will crate a new folder called services, add file yourCustomDateFormater.js:
const dateFormater = {}
dateFormater.install = function (Vue, options) {
Vue.prototype.$dateSmall = (value) => {
return moment(date).format('L')
}
Vue.prototype.$dateMedium = (value) => {
return moment(date).format('lll')
}
}
In main.js:
import YourCustomDateFormater from './services/yourCustomDateFormater'
Vue.use(YourCustomDateFormater)
And you can use it anywhere, like this:
this.$dateSmall(yourValue)
Or, if you want to use mixin. Read more about mixin
Create a new file dateFormater.js
export default {
methods: {
callMethod () {
console.log('my method')
}
}
}
Your component:
import dateFormater from '../services/dateFormater'
export default {
mixins: [dateFormater],
mounted () {
this.callMethod() // Call your function
}
}
Note: "Use global mixins sparsely and carefully, because it affects every single Vue instance created, including third party components. In most cases, you should only use it for custom option handling like demonstrated in the example above. It’s also a good idea to ship them as Plugins to avoid duplicate application." - Vue documentation
dateUtilsjs
import moment from 'moment-timezone'
function formatDateTime(date) {
return moment.utc(date).format("M/D/yyyy h:mm A")
}
export { formatDateTime }
Component JS
...
import { formatDateTime } from '../utils/dateUtils'
...
methods: {
formatDateTime,
}
Used within component
{{ formatDateTime(date) }}

Call parent method with component

I have a component and want to add a click listener that runs a method in the parent template in Vue. Is this possible?
<template>
<custom-element #click="someMethod"></custom-element>
</template>
<script>
export default {
name: 'template',
methods: {
someMethod: function() {
console.log(true);
}
}
</script>
Yes!
It's possible to call a parent method from a child and it's very easy.
Each Vue component define the property $parent. From this property you can then call any method that exist in the parent.
Here is a JSFiddle that does it : https://jsfiddle.net/50qt9ce3/1/
<script src="https://unpkg.com/vue"></script>
<template id="child-template">
<span #click="someMethod">Click me!</span>
</template>
<div id="app">
<child></child>
</div>
<script>
Vue.component('child', {
template: '#child-template',
methods: {
someMethod(){
this.$parent.someMethod();
}
}
});
var app = new Vue({
el: '#app',
methods: {
someMethod(){
alert('parent');
}
}
});
</script>
Note: While it's not recommended to do this kind of thing when you are building disconnected reusable components, sometimes we are building related non-reusable component and in this case it's very handy.
Directly from the Vue.js documentation:
In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events...
So you need to emit a click event from your child component when something happens, which can then be used to call a method in your parent template.
If you don't want to explicitly emit an event from the child (using this.$emit('click') from your child component), you can also try to use a native click event, #click.native="someMethod".
Relying on calling this.$parent hides the dependency and will break when you use component libraries which create a longer child hierarchy
The prefered methods are to either:
Explicitly pass methods as properties to child components (the same as passing data props)
Or declare global methods as mixins
From nils's answer on Vue.js inheritance call parent method:
Passing props (parent-child)
var SomeComponentA = Vue.extend({
methods: {
someFunction: function () {
// ClassA some stuff
}
}
});
var SomeComponentB = Vue.extend({
props: [ 'someFunctionParent' ],
methods: {
someFunction: function () {
// Do your stuff
this.someFunctionParent();
}
}
});
and in the template of SomeComponentA:
<some-component-b :someFunctionParent="someFunction"></some-component-b>
Use Mixins
If this is common functionality that you want to use in other places, using a mixin might be more idiomatic:
var mixin = {
methods: {
someFunction: function() {
// ...
}
}
};
var SomeComponentA = Vue.extend({
mixins: [ mixin ],
methods: {
}
});
var SomeComponentB = Vue.extend({
methods: {
someFunctionExtended: function () {
// Do your stuff
this.someFunction();
}
}
});
Further Reading
Vue.js - Making helper functions globally available to single-file components
Vue Docs Mixins
You can either pass the parent method down to the child component via props or you can get the child component to emit either a custom or native event.
Here's a Plunker to demonstrate both approaches.
You can use $root like this with vanilla Vue, but, If you use nuxt with vue that response won't work. Why? because $root is nuxt itself. Let me show you an example:
this.$root.$children[1].myRootMethod()
$root: As I said before, this is nuxt.
$children[0]: this is nuxtloading.
$children[1]: this is your main component, in my case, it was a basic layout with a few global components and a global mixin.
$children[n]: other components on your app.
Hope it helps.
In current vue version, this solution:
Passing props (parent-child)
var SomeComponentA = Vue.extend({
methods: {
someFunction: function () {
// ClassA some stuff
}
}
});
var SomeComponentB = Vue.extend({
props: [ 'someFunctionParent' ],
methods: {
someFunction: function () {
// Do your stuff
this.someFunctionParent();
}
}
});
The HTML part:
<some-component-b someFunctionParent="someFunction"></some-component-b>
Base on this post, should be modified in this way:
<some-component-b v-bind:someFunctionParent="someFunction"></some-component-b>
Solution for Vue 3:
Another way to provide a function to a child component is to use provide/inject. The advantage is that you can avoid passing on props through multiple components, as inject can be used at any depth of child components.
Parent component:
<script setup>
import { provide } from 'vue'
myFunction(){
console.log('message from parent');
}
provide('message', myFunction);
Some child component:
<script setup>
import { inject } from 'vue'
const msg = inject('message')
//calling the injected function
msg()
</script>
Link to the docs.

Categories