Is it possible to emit a custom event from the directive in the component to which this directive is attached.
I was expecting it to work as described
You can emit custom native javascript events. Create a directive that dispatches an event from the node, using node.dispatchEvent
let handleOutsideClick;
Vue.directive('out-click', {
bind (el, binding, vnode) {
handleOutsideClick = (e) => {
e.stopPropagation()
const handler = binding.value
if (el.contains(e.target)) {
el.dispatchEvent(new Event('out-click')) <-- HERE
}
}
document.addEventListener('click', handleOutsideClick)
document.addEventListener('touchstart', handleOutsideClick)
},
unbind () {
document.removeEventListener('click', handleOutsideClick)
document.removeEventListener('touchstart', handleOutsideClick)
}
})
Which can be used like this
h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )
So the solution I am using in Vue 2+ (considering there were no answers so far):
In directive add method:
var emit = (vnode, name, data) => {
var handlers = (vnode.data && vnode.data.on) ||
(vnode.componentOptions && vnode.componentOptions.listeners);
if (handlers && handlers[name]) {
handlers[name].fns(data);
}
}
And call it this way:
bind(el, binding, vnode) {
emit(vnode, 'bar' , {some: 'event', data: 'here'});
}
The benefits of an approach:
1 Keep the same code-style in your project, meaning that every handler can be declared as
v-on:handler_name
and be handled in meaningful (for developer) way. Other solutions, like sending callback as parameter, are sometimes confusing and not obvious without digging into documentation/code.
2 Using built-in events system also allows to gracefully handle event objects. For example, this code will work perfectly fine:
<button v-foo @bar="bar(1, $event, 2)">{{label}}</button>
...
methods: {
bar(one, event, two) { console.log(one, event, two); }
}
EDIT:
In v2.1+ you can use this inside directive binding:
vnode.context.$emit(eventname)
Your solution was not working for me. Indeed vnode.data.on was always undefined
What worked to trigger an event was
vnode.child.$emit('myevent');
Hope this helps.
I know it is an old issue, but if someone has problems with this and it is not working. You can use use javascript custom events events.
vue.directive('click',{bind(el, binding, vnode) {
el.addEventListener('click', (e)=>{
const event = new CustomEvent('customevent', {detail: {
custom: "data",
can: "be",
in: "detail property"}, bubbles: true});
el.dispatchEvent(event);
})
}
})
now i can use it like
<div v-click @customevent="func">hello world</div>
i do not have to set $event
because the default is standard emitted as last param. this event has a detail
property, which contains your custom data in this case this object:
{custom: "data",
can: "be",
in: "detail property"}
src https://github.com/vuejs/vue/issues/7147
The above answers are great but some of them are out-of-date. Here's the way I solve the problem by integrating them into a workable POC.
// src/directives/ClickOutside.js
export default {
stopProp(e) {
e.stopPropagation();
},
bind(el, binding, vnode) {
el._clickOutside = e => {
vnode.context.$emit(binding.expression, e);
};
el.addEventListener('click', binding.def.stopProp);
document.body.addEventListener('click', el._clickOutside);
},
unbind() {
if (!el._clickOutside) {
return;
}
el.removeEventListener('click', binding.def.stopProp);
document.body.removeEventListener('click', el._clickOutside);
delete el._clickOutside;
}
};
// src/directives/index.js
import Vue from 'vue';
import ClickOutside from './ClickOutside';
Vue.directive('ClickOutside', ClickOutside);
Import the directives in main.js:
// src/main.js
import './directives';
Use the directive with listening to the event emission in a Vue component:
// src/components/Component.vue
<template>
<!-- Please fill in sensible context. This example doesn't really care about the DOM presentation -->
<div @click="showElement" v-click-outside="hideElement">
<div v-if="shouldShow">Hello</div>
</div>
</template>
<script>
export default {
data() {
return {
shouldShow: true
};
},
mounted() {
this.$on('hideElement', this.hideElement);
},
destroyed() {
this.$off('hideElement', this.hideElement);
},
methods: {
showElement() {
this.shouldShow = true;
},
hideElement() {
this.shouldShow = false;
}
}
};
</script>
Basically, in vnode.context.$emit
, the binding.expression
is the string (i.e., "hideElement" in this example) you declared in the v-close-outside
. To retrieve the emission from the directive, use this.$on('hideElement')
to listen to it.
The easiest way to do this is just use dispatchEvent on the el
like this
el.dispatchEvent(new Event('change'));