I'm trying to create an event from an instance property in nuxt, however the event is not emitted or received.
plugins/internal/bus.js
import Vue from 'vue';
const eventBus = {}
eventBus.install = function (Vue) {
Vue.prototype.$bus = new Vue()
}
Vue.use(eventBus)
plugins/internal/index.js
import Vue from 'vue';
export default function (_ctx, inject) {
const notify = function (msg) {
console.log('emitting', msg);
setInterval(() => {
this.$bus.$emit('add', msg);
}, 500);
}
.bind(new Vue());
inject('notify', notify);
}
nuxt.config.js
plugins: [
'~/plugins/internal/bus.js',
'~/plugins/internal/index.js',
...
]
And in my component,
mounted() {
this.$bus.$on('add', (val) => {
console.log(val);
})
this.$bus.$on('close', this.onClose)
},
Doing this.$notify({ foo: 'bar' }), calls the instance property correctly, however either the event is not emitted or is not received, frankly not sure how to debug this. What am I missing here?
At the end, the issue was coming from a component (Notification) that was not properly registered.
Related
I am figuring how to pass callback to vuex action
I tried below code but not working. The code run before I fire it
src/store/modules/web3.module.js
import Web3 from "web3";
const state = {};
const getters = {};
const mutations = {};
const actions = {
async test(context, confirmCallback, rejectCallback) {
confirmCallback();
rejectCallback();
}
};
export default {
state,
getters,
actions,
mutations
};
App.vue
<template>
<div id="app"></div>
</template>
<script>
import { mapActions } from "vuex";
export default {
name: "App",
methods: {
...mapActions(["test"]),
onModalOpen() {
console.log("open");
},
onModalClose() {
console.log("Close");
},
},
async created() {
let result = await this.test({
confirmCallback: this.onModalOpen(),
rejectCallback: this.onModalClose(),
});
},
};
</script>
The issue takes place in 2 places:
the payload syntax in your store is wrong
you are firing the functions with the () at the end when passing it through an object:
Solve the payload issue
An action has 2 parameters, first comes the context which is an object that contains the state, mutations etc. then comes the payload which is an object aswell
const actions = {
async test(context, {confirmCallback, rejectCallback}) {
confirmCallback();
rejectCallback();
}
}
Solve the decleration issue
To solve the decleration issue simply remove the () at the end like shown below:
async created() {
let result = await this.test({
confirmCallback: this.onModalOpen,
rejectCallback: this.onModalClose,
});
},
I am using the composition api plugin for vue2 (https://github.com/vuejs/composition-api) to reuse composables in my app.
I have two components that reuse my modalTrigger.js composable, where I'd like to declare some sort of shared state (instead of using a bloated vuex state management).
So in my components I do something like:
import modalTrigger from '../../../../composables/modalTrigger';
export default {
name: 'SearchButton',
setup(props, context) {
const { getModalOpenState, setModalOpenState } = modalTrigger();
return {
getModalOpenState,
setModalOpenState,
};
},
};
And in my modalTrigger I have code like:
import { computed, ref, onMounted } from '#vue/composition-api';
let modalOpen = false; // needs to be outside to be accessed from multiple components
export default function () {
modalOpen = ref(false);
const getModalOpenState = computed(() => modalOpen.value);
const setModalOpenState = (state) => {
console.log('changing state from: ', modalOpen.value, ' to: ', state);
modalOpen.value = state;
};
onMounted(() => {
console.log('init trigger');
});
return {
getModalOpenState,
setModalOpenState,
};
}
This works, but only because I declare the modalOpen variable outside of the function.
If I use this:
export default function () {
const modalOpen = ref(false); // <------
const getModalOpenState = computed(() => modalOpen.value);
...
It is not reactive because the modalTrigger is instantiated twice, both with it's own reactive property.
I don't know if that is really the way to go, it seems, that I am doing something wrong.
I also tried declaring the ref outside:
const modalOpen = ref(false);
export default function () {
const getModalOpenState = computed(() => modalOpen.value);
But this would throw an error:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function.
So what would be the correct way to achieve this?
I somehow expected Vue to be aware of the existing modalTrigger instance and handling duplicate variable creation itself...
Well, anyway, thanks a lot in advance for any hints and tipps.
Cheers
Edit:
The complete header.vue file:
<template>
<header ref="rootElement" :class="rootClasses">
<button #click="setModalOpenState(true)">SET TRUE</button>
<slot />
</header>
</template>
<script>
import { onMounted, computed } from '#vue/composition-api';
import subNavigation from '../../../../composables/subNavigation';
import mobileNavigation from '../../../../composables/mobileNavigation';
import search from '../../../../composables/searchButton';
import { stickyNavigation } from '../../../../composables/stickyNav';
import metaNavigation from '../../../../composables/metaNavigation';
import modalTrigger from '../../../../composables/modalTrigger';
export default {
name: 'Header',
setup(props, context) {
const { rootElement, rootClasses } = stickyNavigation(props, context);
mobileNavigation();
subNavigation();
search();
metaNavigation();
const { getModalOpenState, setModalOpenState } = modalTrigger();
onMounted(() => {
console.log('Header: getModalOpenState: ', getModalOpenState.value);
setModalOpenState(true);
console.log('Header: getModalOpenStat: ', getModalOpenState.value);
});
return {
rootClasses,
rootElement,
getModalOpenState,
setModalOpenState,
};
},
};
</script>
The composition API is setup somewhere else where there are Vue components mounted a bit differently than you normally would.
So I can't really share the whole code,but it has this inside:
import Vue from 'vue';
import CompositionApi from '#vue/composition-api';
Vue.use(CompositionApi)
The composition API and every other composable works just fine...
I'm trying to use createEventDispatcher to catch the child component's event from the parent's component but seems like doesn't work. If I remove the custom element, the dispatcher event works. Am I doing something wrong or svelte custom element doesn't support the dispatcher event?
child component Inner.svelte
<svelte:options tag="my-inner"/>
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
parent component App.svelte
<svelte:options tag="my-app" />
<script>
import {} from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<my-inner on:message={handleMessage}></my-inner>
My rollup.config.js settings
plugins: [
svelte({
compilerOptions: {
customElement: true,
tag: null
},
}),
This is known issue. At least in 3.32 and before, events are not emitted from svelte component compiled into custom-element.
See https://github.com/sveltejs/svelte/issues/3119
This thread discuss about various workaround, but it depends of your usecase. The simple one seem to emit yourself an event :
import { createEventDispatcher } from 'svelte';
import { get_current_component } from 'svelte/internal';
const component = get_current_component();
const svelteDispatch = createEventDispatcher();
const dispatch = (name, detail) => {
svelteDispatch(name, detail);
component.dispatchEvent && component.dispatchEvent(new CustomEvent(name, { detail }));
// or use optional chaining (?.)
// component?.dispatchEvent(new CustomEvent(name, { detail }));
};
I am trying to long time Vue $emit and $on functionality but still I am not getting any solution. I created simple message passing page its two pages only. One is Component Sender and Component Receiver and added eventbus for $emit and $on functionality.
I declared $emit and $on functionality but I don't know where I made a mistake.
please help some one.
Component Sender:
<script>
import { EventBus } from '../main';
export default {
name: 'Send',
data () {
return {
text: '',
receiveText: ''
}
},
methods: {
sender() {
EventBus.$emit('message', this.text);
this.$router.push('/receive');
}
}
}
</script>
Component Receiver:
<script>
import { EventBus } from '../main';
export default {
name: 'Receive',
props: ["message"],
data () {
return {
list: null
}
},
created() {
EventBus.$on('message', this.Receive);
},
methods: {
Receive(text){
console.log('text', text);
this.list = text;
},
save() {
alert('list', this.list);//need to list value but $emit is not working here
}
}
}
</script>
Router View:
const router = new Router({
mode: 'history',
routes: [
{
path: "/",
name: "Send",
component: Send
},
{
path: "/receive",
name: "Receive",
component: Receive,
props: true
}
]
})
Main.JS
import Vue from 'vue'
import App from './App.vue'
import router from './router';
export const EventBus = new Vue();
new Vue({
el: '#app',
router,
render: h => h(App)
})
The EventBus is designed to allow communication between two components that exist on the page at the same time. If you need data that persists between router-views, then you should look at using a Vuex store.
As it is, the Receiver component doesn't exist at the time of the message being sent, and therefore it has no listener attached to the event.
Well your mistake is here:
created() {
EventBus.$on('message', this.Receive);
},
the second argument is an handler it should look like this:
created() {
EventBus.$on('message', function(data){
this.Receive(data);
console.log(data)
});
},
you should see now 2 console.log() messages
Let's say I have a very basic vue-class-component as shown below:
<template>
<div>Nothing of interest here</div>
</template>
<script>
import Vue from 'vue';
import Component from 'vue-class-component';
import http from './../modules/http';
#Component
export default class Example extends Vue {
user = null;
errors = null;
/**
* #returns {string}
*/
getUserId() {
return this.$route.params.userId;
}
fetchUser() {
this.user = null;
this.errors = null;
http.get('/v1/auth/user/' + this.getUserId())
.then(response => this.user = response.data)
.catch(e => this.errors = e);
}
}
</script>
I want to test the fetchUser() method so I just mock the './../modules/http' dependency and make http.get return a Promise. The problem is that in my assertion I want to check if the URL is being built properly and in order to do so the user ID has to come from an hard-coded variable in the test.
I tried something like this but it doesn't work:
import ExampleInjector from '!!vue-loader?inject!./../../../src/components/Example.vue';
const mockedComponent = ExampleInjector({
'./../modules/http': {
get: () => new Promise(/* some logic */)
},
methods: getUserId: () => 'my_mocked_user_id'
});
Unfortunately it doesn't work and I could not find anything this specific in the Vue docs so the question is, how am I supposed to mock both external dependencies and a class component method?
NOTE: I do not want to mock this.$route.params.userId as the userId could potentially come from somewhere else as well (plus this is just an example). I just want to mock the getUserId method.
Since I specifically asked about how I could mock Vue class component methods with the inject loader here's the complete solution for the question at hand:
import ExampleInjector from '!!vue-loader?inject!./../../../src/components/Example.vue';
const getComponentWithMockedUserId = (mockedUserId, mockedComponent = null, methods = {}) => {
if (!mockedComponent) {
mockedComponent = ExampleInjector();
}
return Vue.component('test', {
extends: mockedComponent,
methods: {
getUserId: () => mockedUserId,
...methods
}
});
};
And then in my test case:
const component = getComponentWithMockedUserId('my_mocked_user_id');
const vm = new Vue(component);
I found this to be very helpful when you need to create a partial mock AND inject some dependencies too.
The easiest way to do this is to extend the component and override the method:
const Foo = Vue.extend({
template: `<div>{{iAm}}</div>`,
created() {
this.whoAmI();
},
methods: {
whoAmI() {
this.iAm = 'I am foo';
}
},
data() {
return {
iAm: ''
}
}
})
Vue.component('bar', {
extends: Foo,
methods: {
whoAmI() {
this.iAm = 'I am bar';
}
}
})
new Vue({
el: '#app'
})
In this example I'm using the extends property to tell Vue that Bar extends Foo and then I'm overriding the whoAmI method, you can see this is action here: https://jsfiddle.net/pxr34tuz/
I use something similar in one of my open source projects, which you can check out here. All I'm doing in that example is switching off the required property for the props to stop Vue throwing console errors at me.