11

Vue.js works great with browser events such as click or mousedown. But not work at all with custom events. Here is the code:

HTML:

<div id="app" style="display: none" v-show="true">
    <div v-el:ping v-on:ping="ping">
        <div>
            <button v-on:click="click">Click</button>
        </div>
    </div>
</div>

JavaScript:

new Vue({
    el: '#app',
    data: {
    },
    methods: {
        ping: function (event) {
            console.log('Vue ping', event);
            alert('Vue ping');
        },
        click: function (event) {
            jQuery(event.target).trigger('ping');
        }
    },
    ready: function () {
        console.log(this.$els);
        jQuery(this.$els.ping).on('ping', function (event) {
            console.log('jQuery ping', event);
            alert('jQuery ping');
        });
    }
});

I expect alert with Vue ping and jQuery ping. But only the later pops up.

CodePen

vbarbarosh
  • 3,502
  • 4
  • 33
  • 43

4 Answers4

9

Vue has its own internal system for custom events, which you should use instead of jQuery / native DOM events:

click: function (event) {
  // jQuery(event.target).trigger('ping');
  
  this.$dispatch('ping', event.target) // send event up the parent chain, optionally send along a reference to the element.
  
  // or:
  this.$emit('ping') // trigger event on the current instance
}

Edit: $dispatch is for parent-child communication, You seem to want to trigger a custom event from within the same comonent. In that case, you could instead simply call a method.

If you still want to listen to a custom event inside the same component, you:

  1. want to use $emit

  2. cannot use v-on:custom-event-name in the template (that's only to be used on components). Rather, add the event method to the events::

    events: { ping: function() {....} }

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Linus Borg
  • 23,622
  • 7
  • 63
  • 50
  • It doesn't work. "This event system is independent from the native DOM events and works differently." I think it for communication between Vue components, rather than between DOM nodes. – vbarbarosh Mar 23 '16 at 07:17
  • It does not work how? Since you are using Vue.js, I was under the impression you wanted to communicate beweteen components?` Why would you want to communicate between DOM nodes in Vue - since the goal of Vue is to decouple the business logic from the DOM? Maybe we could help you translate your usecase into "the Vue way" if you provide additional information about it. – Linus Borg Mar 23 '16 at 09:39
  • I would be very thankful if you fix source code I provided. The question was: how to attach a custom event handler in Vue so that it may intercept events emitted by jQuery.trigger. – vbarbarosh Mar 23 '16 at 13:21
  • jQuery emits DOM events. Vue does use its own event system. So the answer is: you cannnot use `v-on` for DOM events. You will have to use `jQuery.on()` with callbacks, set up in `ready()` like you already demonstrated. Depending on the usecase, this could be extracted into a custom directive (see docs). – Linus Borg Mar 24 '16 at 09:40
  • So how then can I listen to svg events that are in my embedded svg with internal vue custom event system? – Joeri May 10 '19 at 12:55
3

You should avoid to mix a dom events and vue-components related ones because it's a different layers of abstraction.

Anyway, if you still want to do that, I think you need to cache this.el inside a vue-component instance or take it via computed-property like this

{
    computed : {
        jqueryEl(){ return $(this.el) }
    }
}

And then trigger a custom jQuery events by this.jqueryEl.trigger('ping').

Sure to properly take care of keep the element's bindings up to date! For example you can bind jQuery events dynamically (and also unbind on component destroy!) like this:

 ready : function(){
    jQuery('body').on('ping.namespace', '[data-jquery="ping"]', function(){ ... })
 },
 destroy : function(){
      jQuery('body').off('ping.namespace')
 }

And don't forget to add attribute [data-jquery="ping"] to an element which you would like to response a ping event.

Hope this information helps you to achieve the expected result.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Panama Prophet
  • 1,027
  • 6
  • 6
2

Here it is in vanilla JS:

HTML:

<div id="app">
  <div v-el:ping>
    <div>
      <button v-on:click="click">Click</button>
    </div>
  </div>
</div>

JS:

(function() {

  new Vue({
    el: '#app',
    data: {
      event: null
    },
    methods: {
      ping: function(event) {
        alert('Vue ping');
      },
      click: function(event) {
        this.$els.ping.dispatchEvent(this.event);
      }
    },
    ready: function() {
      this.event = document.createEvent("HTMLEvents");
      this.event.initEvent("ping", true, true);
      this.$els.ping.addEventListener('ping', this.ping);
    }
  });

})();

pen: http://codepen.io/anon/pen/wGdvaV?editors=1010#0

Jeff
  • 24,623
  • 4
  • 69
  • 78
  • This is meaningless. I use ``jQuery(event.target).trigger('ping')`` because I don't know who will handle this event. I need to inform a parent that something happened with its descendant. – vbarbarosh Mar 22 '16 at 15:43
  • Yep. http://stackoverflow.com/questions/36156465/listen-to-custom-event-in-vue-js/36159698#comment59980051_36159698 – vbarbarosh Mar 23 '16 at 07:19
0

I came across the same problem and the solution is pretty simple if you're using v-autocomplete from Vuetify.

What I did

  1. Add a key in data, I'm calling it searchText
  2. Bind that property with v-model:search
  3. Add an event handler called @update:modelValue
  4. Declear a method and add that method on @update:modelValue="emptySearchText"

Here is an example.

<v-autocomplete
  label="Your Label"
  :items="items"
  v-model="selected"
  v-model:search="searchText"
  @update:modelValue="emptySearchText"
  multiple>
</v-autocomplete>

data: () => ({
  searchText: null
})


methods: {
  emptySearchText () {
    this.searchText = '';
  }
}

Check this out for more information. https://vuetifyjs.com/en/api/v-autocomplete/#events-update:modelValue

Umair Ahmed
  • 8,337
  • 4
  • 29
  • 37