Vue.js - Dispatching events from a directive

Can a custom event be triggered from the directive inside the component it is attached to?

I tried following the example provided, but it didn't work as expected.

Example:

//Basic Directive
<script>
  Vue.directive('foo', {
    bind(el, binding, vnode) {
      setTimeout(() => {
        //vnode.context.$emit('bar'); <- this will trigger in parent
        vnode.$emit('bar');
      }, 3000);
    }
  });
</script>


//Basic Component
<template>
  <button v-foo @bar="change">{{label}}</button>
</template>
<script>
  export default{
    data() {
      return {
        label: 'i dont work'
      }
    },
    methods: {
      change() {
        this.label = 'I DO WORK!';
      }
    }
  }
</script>

Any suggestions on how to make this work? Am I overlooking something?

JSFiddle: https://jsfiddle.net/0aum3osq/4/

Update 1:

After some investigation, I found that calling vnode.data.on.bar.fn(); (or fns() in newer Vue versions) in the directive triggers the bar event handler.

Update 2:

Temporary workaround:

/*temp. solution*/
var emit = (vnode, name, data) => {
  var handlers = vnode.data.on;

  if (handlers && handlers.hasOwnProperty(name)) {
    var handler = handlers[name];
    var fn = handler.fns || handler.fn;

    if (typeof fn === 'function') {
      fn(data);
    }
  }
} 

//Basic Directive
<script>
Vue.directive('foo', {
  bind(el, binding, vnode) {
    setTimeout(() => {
      emit(vnode, 'bar');
    }, 3000);
  }
});
</script>

Answer №1

Here's the solution I came up with for Vue 2+ (since there were no previous answers):

Include this method in your directive:

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);
  }
}

To call it, do the following:

bind(el, binding, vnode) {
  emit(vnode, 'bar' , {some: 'event', data: 'here'});
}

Benefits of this approach include:

1. Consistency in code style throughout the project, where each handler can be declared as
v-on:handler_name and easily understood by developers. Other methods, like passing callbacks as parameters, can be confusing without thorough documentation/code exploration.

2. Utilizing the built-in events system enables seamless handling of event objects. For instance, the following code will work smoothly:

<button v-foo @bar="bar(1, $event, 2)">{{label}}</button>
...
methods: {
  bar(one, event, two) { console.log(one, event, two); }
} 

UPDATE:

In Vue version 2.1 or higher, you can use this within the directive binding:

vnode.context.$emit(eventname)

Answer №2

The solution provided did not work in my case as the vnode.data.on was consistently undefined.

However, I found that the following approach successfully triggered the desired event:

vnode.child.$emit('myevent');

I wanted to share this alternative method in case it may be helpful to others facing similar issues.

Answer №3

Although this is an older problem, if you're still experiencing issues and the traditional method isn't working for you, try implementing custom events in JavaScript.

    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 you can use it like this:

<div v-click @customevent="func">hello world</div>

No need to set $event because the default behavior passes it as the last parameter. The event includes a detail property containing your custom data, such as:

{custom: "data", 
 can: "be", 
 in: "detail property"}

Source: https://github.com/vuejs/vue/issues/7147

Answer №4

While the previous answers provided some valuable insights, it is worth noting that some of them may be outdated. I have devised a way to address this issue by consolidating them into a practical proof of concept.

// 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);

To import the directives in main.js:

// src/main.js
import './directives';

Utilize the directive by listening to the event emission in a Vue component:

// src/components/Component.vue
<template>
  <!-- Fill in appropriate context. DOM presentation is not the focus -->
  <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>

In vnode.context.$emit, the binding.expression refers to the specified string (e.g., "hideElement" in this instance) declared in the v-close-outside. Listen for the emission from the directive using this.$on('hideElement').

Answer №5

A simple method to accomplish this task is by utilizing the dispatchEvent function on the el, as shown below:

el.dispatchEvent(new Event('change'));

Answer №6

If you want to trigger custom native JavaScript events, consider creating a directive that can dispatch an event from a node by utilizing node.dispatchEvent method.

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)
    }
})

This functionality can be incorporated as follows:

h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )

Answer №7

I believe that using a function as an argument for your directive could be a simpler and more organized approach compared to @euvl's solution. This method also streamlines the interface of your directive.

<script>
  Vue.directive('foo', {
    bind(el, binding) {
      setTimeout(() => {
        binding.value();
      }, 3000);
    }
  });
</script>

<template>
  <button v-foo="change">{{label}}</button>
</template>

<script>
  export default{
    data() {
      return {
        label: 'i dont work'
      }
    },
    methods: {
      change() {
        this.label = 'I DO WORK!';
      }
    }
  }
</script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way in JavaScript to launch a link in a new tab and overlay a div on top of the existing content of the page?

Is there any method in JavaScript to open a link in a new tab and add a div element on that page? Let's say I have a page called page1.html which has a div containing a link and two radio buttons "yes" and "no". I want to open the link in a separate t ...

Automatically scroll a div when the internal div is in focus

I want to create a scrollable div that allows for scrolling even when the mouse is over the content inside the div, not just on the blank areas beside it. Here is the code I am currently using: var main = document.getElementById('main-site'); va ...

If the condition is not met, Vue directive will skip rendering the element

I've decided to create my own system for managing roles and rights in Vue since the existing options are not meeting my needs. Currently, I am able to hide an element when a user lacks the necessary role, but what I really want is to completely preve ...

Render function in Next.js did not yield anything

Recently, I came across the next.js technology and encountered an error. Can anyone help me solve this issue? What could be causing it?View image here import React from 'react' import Button from "../components/button" function HomePa ...

In what scenarios is it more suitable to utilize style over the sx prop in Material-UI?

When it comes to MUI components, the style and sx prop serve similar purposes. While the sx prop provides some shorthand syntaxes and access to the theme object, they essentially function the same way. So, when should you opt for one over the other? ...

What is the best way to integrate a Ruby object into JavaScript?

I'm attempting to integrate Ruby into the JS.erb file, where I need access to the @user object and its relationships. Instead of converting to a JSON object, I prefer the ERB to precompile on the server-side. My goal is to iterate in the JS file rat ...

Why is my component not utilizing the implementation in this Jest mock?

I'm currently attempting to run tests on a React component using Jest. Within the component, there is a required module that acts as a factory function returning an object of functions. //tracker.js export default () => { track: click => doso ...

Tips for preserving filters or results following filtering with DataTables' searchPane

Utilizing the searchPane extension of DT, which is an R interface to the DataTables library. When constructing a dataTable with a searchPane, the code would appear as follows: library(DT) datatable( iris, options = list(dom = 'Pfrtip', ...

How can we map a promise that resolves to foo to a new promise that resolves to bar?

I am working on a function that uses an XMLHttpRequest to retrieve data and returns a promise with the response. But now I want to modify it so that the promise only contains a specific string from the response. Instead of resolving to response = {status ...

Enhance your Vue app by dynamically modifying classes with Tailwind

I am working on a Vue3 application that utilizes Tailwinds configured in the tailwind.config.js file. My question is, can I dynamically modify the value of a preconfigured class from the tailwind.config.js file? For instance: tailwind.config.js: const d ...

Navigating - Utilizing dot-notation to reach the top-level function in Express

If we want to use express in a basic javascript file, all we need to do is add the following two lines of code at the beginning (after installing it through npm): var foo = require('express'); var app = foo(); According to the express API guide ...

Error message "Undefined is not a function" occurred while using jQuery's .replace and scrollTop functions

I'm having issues with the scroll function in my code. It doesn't seem to be able to locate the ids in my HTML, even though I can't figure out why. I had a previous version that worked perfectly fine (unfortunately, I didn't save it D:) ...

Tips for implementing Vuesax $vs.loading feature beyond its components

Has anyone had success using Vuesax's $vs.loading in the router (router.js) file? I attempted to use $vs.loading but encountered an undefined error. I also tried Vue.prototype.$vs.loading without success. ...

Limit the input to a maximum number of characters

I am in need of input boxes that only accept hexadecimal characters and I also want to set a maximum length for the input. Although I have successfully implemented accepting hex characters only, I am facing an issue when pasting a string - the invalid cha ...

The Swiper slider will activate only upon resizing the window

I am facing an issue with displaying a horizontal timeline that can be scrolled. Currently, I am only able to scroll the timeline when I resize the window page, and I cannot figure out why this is happening. I believe my code is correct, but I want the t ...

Rotating Images in 3D with CSS

I am looking for guidance on how to create a rotating image effect on a webpage using CSS/JS. The image should rotate into the plane of the page, similar to the example shown in this post. Can you assist me with this? To better illustrate what I'm tr ...

Eliminate spacing between divs of varying heights

I'm facing an issue with my small gallery of images. Despite having the same width, they vary in height causing the second row to start below the tallest image from the previous row. How can I eliminate these empty spaces while still maintaining the v ...

Angular select element is not functioning properly with the `addEventListener` method

My current project involves creating a table using the primeng library. The table consists of three rows and three columns, and all the data is static. Even though I am utilizing an external library, I find myself traversing the DOM directly. <p-table ...

Ensure that function calls within a for loop are executed synchronously

In the request, I am receiving an array of strings where each string represents a command that needs to be executed on the native shell. var process = require('child_process'); function executeCommand(req,res,callback){ var params = req.param ...

Displaying retrieved data following AJAX request in ASP.NET MVC is currently not functioning

I have a situation where I need to populate two <p> fields with text Below is the HTML code snippet: <p id="appId" style="visibility: hidden;"></p> <p id="calculationId" style="visibility: hidden;"></p> I am making an AJAX ...