Which Vue handler is called? - vue.js

If one function is bound to multiple Vue event hooks, is there any way to know which event triggered the function?
Here's a contrived example:
<div v-on:click="handler" v-on:mouseover="handler">
Which event called handler? Click or Mouseover?
</div>
This could also help understand the context for Transition hooks and determining which was triggered.
<transition appear v-on:appear="handler" v-on:enter="handler">
From inside handler, can we tell which hook was the caller?
A practical workaround for these situations would be decorating common functionality behind two simple wrapper functions, but it seems like Vue probably has a way of understanding its event calling context which might have been overlooked.

The event object's type will tell you which event triggered the handler. You can get the event object in Vue using the $event variable:
Vue.component('demo', {
template: '<div v-on:click="handler($event)" v-on:mouseover="handler($event)">Click or hover on me</div>',
methods: {
handler(e) {
console.log(e.type); // e is the event object passed in as $event
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<demo></demo>
</div>

Related

How to bind a click event to a button when component is mounted vue js?

I am using a third party app (vue slick carousel) and I need to bind a click event to below button
<button type="button" data-role="none" class="slick-arrow slick-next" style="display: block;">
<svg...</svg>
</button>
So everytime I click this button a function gets triggered. This is what I tried in mounted and created life cycle, but didn't bind click event to it.
// console.log(document.querySelector(".slick-next")); // this returns element stated above
document.querySelector(".slick-next").addEventListener("click", () => {
console.log("Works");
});
I tried using setAttribute hoped it works, but it didn't work either.
Any help is appreciated. Thank you in advance.
I suspect they're probably using stopPropagation on the button click inside the component then.
There might be another way around this depending on your needs:
<VueSlickCarousel #beforeChange="checkChange">
...
</VueSlickCarousel>
methods: {
checkChange(oldSlideIndex, newSlideIndex) {
if (newSlideIndex > oldSlideIndex) {
console.log("Next button clicked");
}
}
}
Another option might be to use the <template #nextArrow="{ currentSlide, slideCount }"></template> slot inside the Carousel tag and use your own button. You'd probably have to implement your own logic for setting the next slide if you went this route.
You must to use the events handling of vuejs. Vuejs have many events for multiple cases.
For example in your case, you can use the event v-bind:click or simply #click. In documentation of vue slick, show this examples, wich is essentially the same.
I attach an code snippet:
<template>
<VueSlickCarousel ref="carousel">
<div><h3>1</h3></div>
/*...*/
</VueSlickCarousel>
<button #click="showNext">show me the next</button>
</template>
<script>
export default {
methods: {
showNext() {
this.$refs.carousel.next()
},
},
}
</script>

Vue.js devtools doesn't catch an emit in mounted

It seems that when I try to emit an event in mounted it doesn't get triggered! therefore It doesnt show up in devtools
main.js
const app = new Vue({
el: '#app',
mounted() {
console.log('asd');
this.$emit("clicked", "someValue");
}
});
--
<body>
<div id="app">
<h1>vuejs</h1>
</div>
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<script src="main.js"></script>
</body>
Why is that? and how can I do it?
It works fine - your console.log outputs as it should, and you're emitting the event without issues.
In the code you've shown, though, you haven't set anything up to listen to the emitted event... Here's a simple, contrived example of how to do that with this.$on:
const app = new Vue({
el: '#app',
mounted() {
console.log('asd');
this.$on('clicked', function(value) {
alert('clicked!' + value);
});
this.$emit("clicked", "someValue");
}
});
Demonstration: https://codepen.io/anon/pen/qKeWxJ
When you add an event handler in the parent's mounted lifecycle event
for an event that is emitted in a child's mounted event, the handler
will not catch the event emitted by the child because the handler is
added after the child has already emitted the event. Basically the
cycle of events is like this.
Parent's create Child's create Child's mounted Parent's mounted
Obviously there are other lifecycle events that occur, but that's the
sequence that matters in this case.
If you want to handle an event emitted in the child, you need to
create the handler (call $on) before the child's mounted event.
Is it possible to emit from mounted in Vuejs?

vue transition-group appear event not being called

I am trying to hook into the appear event lifecycle and call a method in the component but the method getCalled() in my component is never called upon. The transition animation for after-leave event works as intended its just the appear events that do not.
according the documentation https://v2.vuejs.org/v2/api/#transition I can hook into the appear event system as demonstrated.
<transition-group name="order" tag="div" appear v-on:after-leave="activateScrollToBottom" v-on:after-appear="getCalled" v-on:before-appear="getCalled" v-on:appear="getCalled">
I had a similar situation and found out that I was missing the done callback in the appear method.
I put together a very simple CodePen to see, if it is actually working
https://codepen.io/rowild/pen/LJvEKm
JavaScript:
let app = new Vue({
el: '#app',
methods:{
// Transition hooks
beforeAppear(el){
console.log(' BEFORE APPEAR')
},
appear(el, done){
console.log(' APPEAR')
},
afterAppear(el){
console.log(' AFTER APPEAR')
}
}
},
// Lifecycle hooks
beforeCreate(){
console.log("BEFORE CREATE")
},
[...]
Template
<div id='app'>
<transition
appear
#before-appear="beforeAppear"
#appear="appear"
#after-appear="afterAppear"
#appear-cancelled="appearCancelled"
:css="false"
>
<p>TEST CONTENT</p>
</transition>
</div>
Console output
BEFORE CREATE
CREATED
BEFORE MOUNT
BEFORE APPEAR
APPEAR
AFTER APPEAR
MOUNTED
In case you found, what your problem was, would you mind to post an explanation and how you solved it? Thank you!

Why v-on doen't work on Vue component while using it in outer template?

The situation
Let's say I have a component BaseButton which is just a simple wrapper arround a <button> element:
Vue.component('base-button', {
template: `
<button>
<slot />
</button>
`
});
When using the button, I'll want to bind handler to a click event on this button:
<base-button #click="handler">
Click me
</base-button>
Pen with above code
The problem
The above solution doesn't work. The handler is not fired at all.
Can someone explain me why exactly? I'm guessing the handler is bound to the element, before it gets replaced with the component template, but it's just a guess - I can't find anything about it in vue.js docs. In addition to that docs
state:
A non-prop attribute is an attribute that is passed to a component,
but does not have a corresponding prop defined.
While explicitly defined props are preferred for passing information
to a child component, authors of component libraries can’t always
foresee the contexts in which their components might be used. That’s
why components can accept arbitrary attributes, which are added to the
component’s root element.
#click (v-on:click) seems to me to be a non-prop attribute and according to the above text should get inherited. But it's not.
Prop solution
I know I can declare a prop and pass the handler inside the component (code below). Then it works as expected.
The problem with this solution for me is that I don't have a fine grain control over how the handler is declared. What if in one usage of BaseButton I'd like to use on #click
some of the event modifiers Vue.js exposes (e.g. .stop, .prevent, .capture)? I'd have to use another prop (like capture) and use v-if, but it'd get the component template very messy. If I leave the handler in the template, where I use it, I can modify the event declaration as I want in a clean and flexible way.
Vue.component('base-button', {
prop: {
clickHandler: {
type: Function,
required: true
}
},
template: `
<button>
<slot />
</button>
`
});
<base-button :click-handler="handler">
Click me
</base-button>
The v-on directive behaves differently when used on a normal / native DOM element or on a Vue custom element component, as stated in the API docs:
When used on a normal element, it listens to native DOM events only. When used on a custom element component, it listens to custom events emitted on that child component.
In your case you apply it on your custom <base-button> element component, therefore it will listen only to custom event, i.e. ones that you explicitly $emit on this component instance.
Native "click" events bubbling phase from your underlying <button> will not trigger your #click listener…
…unless you use the .native modifier:
.native - listen for a native event on the root element of component.
Vue.component('base-button', {
template: `
<button>
<slot />
</button>
`
});
new Vue({
el: '#app',
data: {
handler(event) {
console.log('submit from where the component was used');
//console.log(event);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<base-button #click.native="handler">
Click
</base-button>
</div>

vue component is not passing events through

I have three nested components
<parent #event="handle">
<inbetween #event="$emit('event')">
<child> // emits event
</child>
</inbetween>
</parent>
so, when a child emits the event, I have to add that annoying part #event="$emit('event')" to the component inbetween, otherwise parent does not receive any event.
I hope it is not supposed to work that way. I wonder what could be wrong with it?
Yes thats how its supposed to work. Events go only from child to parent, it does not go from child up to grand parent. Therefore you have to emit the event from both child and inbetween component.
If you want to avoid this method you can use Event Bus:
https://medium.com/#andrejsabrickis/https-medium-com-andrejsabrickis-create-simple-eventbus-to-communicate-between-vue-js-components-cdc11cd59860
alligator.io/vuejs/global-event-bus
This is actually intentional. The reasoning is, when looking at the code for one component and you see that it's listening to an event, you can then look at the template to see where that event is coming from. If events could reach a component arbitrarily deep, it would be harder to figure out how and from where that event is being triggered.
However, Vue used to have a way of doing what you want to do, through the methods $broadcast and $dispatch, and they were eventually removed for the reasons talked about here. Here's a page from the docs which explains why, along with possible solutions, such as using a global event bus, or a centralized state management solution such as Vuex.
Vue custom events don't bubble.
The recommended way to handle ancestor/sibling communication in complex cases is using Vuex. If you have simple needs you can create Vue instance to use as event hub.
You would create a global variable:
var eventHub = new Vue(); // use a Vue instance as event hub
To emit events you would use in any component:
eventHub.$emit('myevent', 'some value');
And, to listen to that event, again, in any component, do:
eventHub.$on('myevent', (e) => {
console.log('myevent received', e)
});
Demo:
var eventHub = new Vue(); // use a Vue instance as event hub
Vue.component('parent', {
template: "#parent-tpl",
created() {
eventHub.$on('event', (e) => {
console.log('event received at parent! value:', e);
});
}
});
Vue.component('inbetween', {
template: "#inbetween-tpl"
});
Vue.component('child', {
template: "#child-tpl",
methods: {
emitEvent() {
eventHub.$emit('event', 123);
}
}
});
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<template id="parent-tpl">
<div>
<inbetween></inbetween>
</div>
</template>
<template id="inbetween-tpl">
<div>
<child></child>
</div>
</template>
<template id="child-tpl">
<div>
<h1>I'm the child</h1>
<button #click="emitEvent">Trigger EVENT</button>
</div>
</template>
<div id="app">
<p>{{ message }}</p>
<parent></parent>
</div>
Note: If creating a dedicated instance as event hub is something complicated in your environment, you can replace eventHub with this.$root (inside your components) and use your own Vue instance as hub.