Reputation: 7609
I have a component called styled-input
and it's an input with styles, icons, validation, etc.
I am trying to use addEventListener
inside a custom directive to listen to events and make things happen.
<styled-input
v-model=""
v-on:input=""
v-on:blur=""
></styled-input>
Internally:
value
is a prop (allowing for v-model
binding outside)
<template>
<div>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
v-on:blur="$emit('blur', $event.target.value)"
/>
</div>
</template>
I'm trying to addEventListener
s to the tag through custom directives for custom validation.
<styled-input
v-model=""
v-custom-validator:credit-card
></styled-input>
Inside the directive I am using addEventListener
to listen to events from the input field.
Vue.directive('custom-validator', {
bind: function(el, binding) {
el.addEventListener('blur', (event) => {
// Does not fire
});
el.addEventListener('input', (event) => {
/// Fires
});
},
});
Why doesn't the blur event fire, while the input event fires?
How can I get the blur event to fire?
Upvotes: 8
Views: 15996
Reputation: 135762
You are not picking the blur
because you are not specifying the useCapture
argument. It is mandatory because blur
event does not bubble. So adding the useCapture
would fix your problem:
el.addEventListener('blur', (e) => {
// it will work now
}, true); // useCapture is the third argument to addEventListener
From the your code (and see demo below), it may seem that when blur
and input
are triggered, the .addEventListener()
picks what was emitted by $emit()
. That is not the case.
You may get that idea because, besides those $emit()
s at your v-on
s, the <input>
will also trigger the native events blur
and input
.
So what you actually have is two different kinds of events for blur
and two for input
(see demo below).
$emit()
?$emit()
is internal to Vue. It is a part or every Vue instance events interface.
.addEventListener()
is for native DOM events. It will listen to .dispatchEvent()
s, not $emit()
s.
To listen to Vue's $emit()
s you must use Vue's .$on()
.
See demo below. Notice the e
(event) object from the .$on()
and from the .addEventListener()
are different.
Vue.component('my-comp', {
template: '#styled-input-tpl',
props: ['value']
});
Vue.directive('my-directive', {
bind: function (el, binding, vnode) {
vnode.componentInstance.$on('blur', function (e) {
console.log('received $on(blur) - event value:', event);
});
vnode.componentInstance.$on('input', function (e) {
console.log('received $on(input) - event value:', e);
});
el.addEventListener('blur', (e) => {
console.log('received NATIVE(blur) - event value:', e.target);
}, true); // <======================================================= IMPORTANT
el.addEventListener('input', (e) => {
console.log('received NATIVE(input) - event value:', e.target);
});
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<template id="styled-input-tpl">
<div>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
v-on:blur="$emit('blur', $event.target.value)"
/>
</div>
</template>
<div id="app">
Edit the input, focus out, and check the console.
<my-comp v-my-directive :value="'edit me'"></my-comp>
</div>
Upvotes: 15