David Alsh
David Alsh

Reputation: 7609

Adding eventListener to blur event on custom component?

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 addEventListeners 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

Answers (1)

acdcjunior
acdcjunior

Reputation: 135762

Why blur fails

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

Don't confuse Native DOM events with Vue events

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-ons, 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).


What is up with $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

Related Questions