8

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?

1
  • Why do you want to force another element's event? Wouldn't that be cleaner to just call the action associated to it?
    – Robo Robok
    Commented Mar 16, 2018 at 1:22

1 Answer 1

15

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>

5
  • Just a follow up question to this. How do I access the current value of the component's v-model from inside the directive?
    – David Alsh
    Commented Mar 16, 2018 at 2:20
  • It depends. Inside the component, you can't tell if the parent used v-model or simply :value (because v-model is just a shorthand/syntactic sugar for :value+@input). Anyway, what I think you want is vnode.componentInstance.$props, is it?
    – acdcjunior
    Commented Mar 16, 2018 at 2:37
  • Yes that is exactly what I wanted. Thanks man. Last question, with vnode.componentInstance.$on do you need to manually remove the listener? (just like you do with removeEventListener
    – David Alsh
    Commented Mar 16, 2018 at 2:40
  • You may need to. There are other different methods in the API that may handle your use case: $once() and $off(). See docs: vuejs.org/v2/api/#Instance-Methods-Events
    – acdcjunior
    Commented Mar 16, 2018 at 2:47
  • vnode.componentInstance is not defined in directive :)
    – Alexey Sh.
    Commented Feb 22, 2019 at 1:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.