Vlad
Vlad

Reputation: 792

How can I capture click event on custom directive on Vue.js?

I am trying to learn Vue.js and came to an practice example where I need to implement a custom directive whice works lice 'v-on'. This means that i need to capture the click event on my custom directive and call a method.

The template i was thinking of.

<template>
    <h1 v-my-on:click="alertMe">Click</h1>
</template>

The problem is i don't know how to capture the click event in the custom directive. Excuse the clumsy code below.

<script>
    export default {
        methods: {
            alertMe() {
                alert('The Alert!');
            }
        },
        directives: {
            'my-on': {
                bind(el, binding, vnode) {
                    console.log('bind');

                    el.addEventListener('click',()=>{
                        console.log('bind');
                        vnode.context.$emit('click');
                    });
                },

            }
        }
    }
</script>

Can anyone help me understand how this works? I didn't manage to find any example of something similar.

Upvotes: 4

Views: 13866

Answers (6)

Marcus Smith
Marcus Smith

Reputation: 81

The solution you found is, as far as I can tell, the very best solution for what you are looking for. However, for those who don't know much about Vue.JS I thought I'd give a quick explanation. I'd also suggest you check out the official Vue documentation for Custom Directives or my Medium article on the concepts.

This is the code that Vlad came to and I would support:

<template>
    <h1 v-my-on:click="alertMe">Click me!</h1>
</template>

<script>
    export default {
        methods: {
            alertMe() {
                alert('The Alert!');
            }
        },
        directives: {
            'my-on': {
                bind(el, binding) {
                    let type = binding.arg;
                    let myFunction = binding.value;
                    el.addEventListener(type, myFunction);
                }
            }
        }
    }
</script>

In short, Vue Directives are called on the lifecyle of the element they are attached to, based on the directive object definition. In the example the function defined is called "bind" so the directive will call that function when the element is bound into the DOM.

This function receives the element it's attached to "el" and the different content of the directive usage in the template "binding". In the binding usage in the template, the value after the colon ":" is the "arg" which in this example is the string literal "click". The value inside of the quotes '""' is the "value" which in this case is the object reference to the function "alertMe".

The vars that are defined by getting binding.arg and binding.value (with their respective content) can then be used to create an event listener contained inside of the element "el" that the directive is used on (el is modifiable). So, when the element is created and bound, this new event listener is created on the "click" event defined by "arg" and it will call the "alertMe" function defined by "value".

Because the modification is contained inside the element, you don't have to worry about cleaning up on unbind, because the listener will be destroyed when the element is destroyed.

And that is a basic description of what is happening in the suggested code. To see more about directives and how to use them, follow the suggested links. Hopefully that helps!

Upvotes: 3

oriondor
oriondor

Reputation: 1

Seems like addEventListener works only for native events

To catch events fired with Vue inside the directive use $on

    newdirective: {
        bind(el, key, vnode){
            vnode.componentInstance.$on('event-fired-from-component', someFunction)
        },
        ....
    }

You can put this code either inside your component or mixin under directives section like this

directives: {...}

And then connect it to the component you want to receive this event from

<some-component
    v-newdirective
></some-component>

Upvotes: 0

Vlad
Vlad

Reputation: 792

After some more searching i came to this solution:


<template>
  <h1 v-my-on:click="alertMe">Click me!</h1>
</template>

<script>

  export default {

    methods: {

      alertMe() {

        alert('The Alert!');

      }

    },

    directives: {

      'my-on': {

        // Add Event Listener on mounted.
        bind(el, binding) {
          el.addEventListener(binding.arg, binding.value);
        },

        // Remove Event Listener on destroy.
        unbind(el, binding) {
          el.removeEventListener(binding.arg, binding.value);
        }

      }

    }

  }
</script>

Upvotes: 14

John
John

Reputation: 197

@Vlad has an excellent solution!

May I also add an important point: if you wanna pass parameters to your callback, it will confuse you by the way Vue handles your expression. In short, for custom directives, whatever in between quotation marks gets evaluated and the resulted value is passed in (hence, you can get it via binding.value (duh!), while for built-in directives, at least for v-on, the contents between quotation marks get evaluated later on, when event is fired.

Maybe this is best demonstrated with a comparison between custom directive and the built-in v-on directive. suppose you have a "my-on" directive written exactly as what @Vlad does, and you use it side by side with v-on:

built-in: <h1 v-on:click="myAlert('haha')"> Click me!</h1>

It works as expected, when button is clicked, alert window pops up.

customized: <h1 v-my-on:click="myAlert('haha')">Click me!</h1>

As soon as button is displayed, the alert window pops up, and when you click on it, the event is fired but nothing visible happens. This is because "myAlert('haha')" is evaluated as soon as binding(?), hence the alert window, and its value gets passed to your directive(undefined or whatever), cuz its value is not a function, nothing seems to happen. now, the workaround is to have whatever in between the quotation marks returns a function upon evaluation, eg v-my-on:click="() => {myAlert('haha')}"

Hope it helps.

References:

https://stackoverflow.com/a/61734142/1356473

https://github.com/vuejs/vue/issues/5588

Upvotes: 2

Despertaweb
Despertaweb

Reputation: 1820

As @Vlad said it worked for me:

                    el.addEventListener('click',()=>{
                    console.log('bind');
                    vnode.context.$emit('click');

Here's my directive:

Vue.directive('showMenu', {
    bind: function (el, binding, vnode) {
        el.addEventListener('click', () => {
            console.log('bind')
            setTimeout(() => {
                this.$emit('toggleDrawer')
            }, 1000)
        })
    }
})

Thanks dude!

Upvotes: 0

user320487
user320487

Reputation:

You need to register a listener for the event being emitted within your directive.

// emit a custom event
// binding.expression is alertMe
vnode.context.$emit(binding.expression);

// listen for the event 
export default {
    created(){
        this.$on('alertMe', event => { 
            this.alertMe()
        })
    },
    ....
}

This is not calling the method alertMe, rather passing alertMe through to the directive as the binding expression:

<h1 v-my-on:click="alertMe">Click</h1>

Upvotes: 2

Related Questions