Bind multiple events to v-on directive in Vue

19,614

Solution 1

1. With Event Modifiers

If you are relying on events, you could try binding with event modifiers and sort of chain them inline. Something like:

<a @click.stop="doThis" @click.right="showContextMenu"></a>

2. Attaching events programmatically

Or, you could create your list of events and their respective implementations to attach with and do the loop with v-on, a workaround from this post:

created() {
    const EVENTS = [
      {name: 'my-event1', callback: () => console.log('event1')},
      {name: 'my-event2', callback: () => console.log('event2')},
      {name: 'my-event3', callback: () => console.log('event3')}
    ]

    for (let e of EVENTS) {
      this.$on(e.name, e.callback); // Add event listeners
    }
  }

<button @click="$emit('my-event1')">Raise event1</button>
<button @click="$emit('my-event2')">Raise event2</button>
<button @click="$emit('my-event3')">Raise event3</button>

3. v-on multiple values

Otherwise, just like you can do v-bind on multiple values, you can actually do the same with v-on for events.

<div id="mydDiv" v-on="handlers"></div>

// ...

data() {
  const vm = this;

  return {
    handlers: {
      mousedown: vm.divMousedown,
      touchstart: vm.divTouchstart
    }
  }
},

methods: {
  divMousedown() {
    console.log('event: mousedown');
  },
  divTouchstart() {
    console.log('event: touched');
  }
}

If you need to break down the handlers by type of event, try examining the type while the event is being fired, so in your case, since touchstart seems to also trigger mousedown, perhaps:

methods: {
  onTouched(evt) {
    evt.preventDefault();

    if (evt.type === 'mousedown') {
        // handle mousedown
    }
    else if (evt.type === 'touchstart') {
        // ...
    }
  }
}

Note: You might want to call preventDefault() on touchmove rather than touchstart. That way, mouse events can still fire and things like links will continue to work.

Solution 2

No you are not missing anything obvious. "touchstart mousedown" is a short-hand notation provided by jQuery for convenience. Vue does not provide a similar convenience (probably because the event binding syntax for attrs can already get pretty hairy with all the provided options).

The correct method would be as you stated, using two separate attributes: @touchstart="doSomething()" or @mousedown="doSomething()"

Solution 3

Binding to multiple events is not supported by vue on purpose because of inherent limitations in attribute names and the desire to avoid runtime overhead.

If you absolutely must have multi-event bindings, a custom directive would be the way to go.

(The following code is shamelessly copied from the linked JSFiddle because SO has disallowed linking to fiddles without code in the answer)

function functionWrapper(e) {
  /* add filters to handle event type before propagating to callback function for custom event handler */
  e.target.__handler__.fn(e)
}

Vue.directive('multiEvent', {
  bind: function(el, binding, vnode) {
    el.__handler__ = binding.value
    binding.value.evt.forEach(e => el.addEventListener(e, functionWrapper))
  },
  unbind: function(el, binding) {
    el.__handler__.evt.forEach(e => el.removeEventListener(e, functionWrapper))
    el.__handler__ = null
  }
})

Usage:

<input v-multi-event="{ evt: ['click', 'blur', 'change', 'keydown'], fn: someCallback }">
Share:
19,614
Ben Clarke
Author by

Ben Clarke

Updated on June 27, 2022

Comments

  • Ben Clarke
    Ben Clarke almost 2 years

    In jQuery you can bind multiple events by doing something like this:

    $('#myDiv').on('touchstart mousedown', // more code here

    And as far as I understand this will listen for touchstart OR mousedown simultaneously. But I can't work out how to do the equivalent with Vue. I can only do @touchstart="doSomething()" or @mousedown="doSomething()". Am I missing something obvious? Thanks

  • Ben Clarke
    Ben Clarke over 5 years
    The problem with using two separate attributes is that on touch devices it fires the event twice: once for @touchstart and again for @mousedown. Seems to work fine on desktop as I think desktop browsers just ignore the touchstart directive. So this isn't an option for me unfortunately.
  • Ben Clarke
    Ben Clarke over 5 years
    Thank you for such a detailed answer. But unfortunately on touch devices it fires the event twice: once for touchstart and again for mousedown. This isn't an option for me unfortunately as I need touchdown for touch devices OR mousedown for laptops/desktops - not both. jQuery seems to handle this effortlessly from what I remember. Just simply $('#myDiv').on('touchstart mousedown', // more code here
  • Ben Clarke
    Ben Clarke over 5 years
    Thank you for taking the time to write such a comprehensive answer. Option 3 looked very promising for my particular use case, but unfortunately on touch devices it fires the event twice: once for touchstart and again for mousedown. This isn't an option for me unfortunately as I need touchdown for touch devices OR mousedown for laptops/desktops - not both.
  • vzwick
    vzwick over 5 years
    Any particular reason to not omit touchstart then? Either way, you could always wrap your downstream callback with a debounce to prevent the multi-trigger.
  • Ben Clarke
    Ben Clarke over 5 years
    I need touchstart for mobile devices; mousedown doesn't seem to work on touch screens :( And if I understood what "wrap your downstream callback with a debounce to prevent the multi-trigger" meant I probably wouldn't need Stack Overflow! :) But thank you. I'll update this page if I have any progress when I resume the project tomorrow.
  • vzwick
    vzwick over 5 years
    I'm confused here. You said "on touch devices it fires the event twice: once for touchstart and again for mousedown", now you're saying "mousedown doesn't seem to work on touch screens". Those appear mutually exclusive to me.
  • Ben Clarke
    Ben Clarke over 5 years
    — you are correct: I meant mousedown doesn't work in the same way as touchstart on touch devices; it only fires when you lift your finger off the screen, whereas touchstart fires immediately when touched.
  • Ben Clarke
    Ben Clarke over 5 years
    After reading the MDN touchevents section I found a paragraph which said "It's important to note that in many cases, both touch and mouse events get sent (in order to let non-touch-specific code still interact with the user). If you use touch events, you should call preventDefault() to keep the mouse event from being sent as well." So I amended my code to read @touchstart.prevent="buzzIn()" @mousedown="buzzIn()" and this seems to have solved the problem of the event firing twice. Now it fires just once on either touch or mouse.
  • Ben Clarke
    Ben Clarke over 5 years
    You were correct, but on reading the MDN touchevents section I learned that you need to add preventDefault() to the touchstart event in order to stop the mouse event being fired as well. So I amended my code to read @touchstart.prevent="buzzIn()" @mousedown="buzzIn()" and this seems to have solved the problem of the event firing twice. Now it fires just once on either touch or mouse.
  • David Torrey
    David Torrey over 2 years
    using the object notation , is there a way to pass additional data beyond the event itself? I have a v-for in conjunction and want to be able to do something like v-on:"attributes" combined with attributes: { click: (event, my_data) => { }, mousedown: (event, my_data) => {} }