Vue.js - Emit event from directive

后端 未结 7 433
北海茫月
北海茫月 2021-02-02 12:37

Is it possible to emit a custom event from the directive in the component to which this directive is attached.

I was expecting it to work as described

相关标签:
7条回答
  • 2021-02-02 13:20

    You can emit custom native javascript events. Create a directive that dispatches an event from the node, using node.dispatchEvent

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

    Which can be used like this

    h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )
    
    0 讨论(0)
  • 2021-02-02 13:26

    So the solution I am using in Vue 2+ (considering there were no answers so far):

    In directive add method:

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

    And call it this way:

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

    The benefits of an approach:

    1 Keep the same code-style in your project, meaning that every handler can be declared as
    v-on:handler_name and be handled in meaningful (for developer) way. Other solutions, like sending callback as parameter, are sometimes confusing and not obvious without digging into documentation/code.

    2 Using built-in events system also allows to gracefully handle event objects. For example, this code will work perfectly fine:

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

    EDIT:

    In v2.1+ you can use this inside directive binding:

    vnode.context.$emit(eventname)
    
    0 讨论(0)
  • 2021-02-02 13:26

    Your solution was not working for me. Indeed vnode.data.on was always undefined

    What worked to trigger an event was

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

    Hope this helps.

    0 讨论(0)
  • 2021-02-02 13:28

    I know it is an old issue, but if someone has problems with this and it is not working. You can use use javascript custom events events.

        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 i can use it like

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

    i do not have to set $event because the default is standard emitted as last param. this event has a detail property, which contains your custom data in this case this object:

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

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

    0 讨论(0)
  • 2021-02-02 13:30

    The above answers are great but some of them are out-of-date. Here's the way I solve the problem by integrating them into a workable POC.

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

    Import the directives in main.js:

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

    Use the directive with listening to the event emission in a Vue component:

    // src/components/Component.vue
    <template>
      <!-- Please fill in sensible context. This example doesn't really care about the DOM presentation -->
      <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>
    

    Basically, in vnode.context.$emit, the binding.expression is the string (i.e., "hideElement" in this example) you declared in the v-close-outside. To retrieve the emission from the directive, use this.$on('hideElement') to listen to it.

    0 讨论(0)
  • 2021-02-02 13:31

    The easiest way to do this is just use dispatchEvent on the el like this

    el.dispatchEvent(new Event('change'));
    
    0 讨论(0)
提交回复
热议问题