Bind multiple events to v-on directive in Vue
Solution 1
1. With Event Modifiers
If you are relying on event
s, 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()
ontouchmove
rather thantouchstart
. 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 }">
Ben Clarke
Updated on June 27, 2022Comments
-
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
ORmousedown
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 over 5 yearsThe 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 over 5 yearsThank you for such a detailed answer. But unfortunately on touch devices it fires the event twice: once for
touchstart
and again formousedown
. 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 over 5 yearsThank 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 formousedown
. This isn't an option for me unfortunately as I need touchdown for touch devices OR mousedown for laptops/desktops - not both. -
vzwick over 5 yearsAny 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 over 5 yearsI 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 over 5 yearsI'm confused here. You said "on touch devices it fires the event twice: once for
touchstart
and again formousedown
", now you're saying "mousedown
doesn't seem to work on touch screens". Those appear mutually exclusive to me. -
Ben Clarke over 5 years— you are correct: I meant
mousedown
doesn't work in the same way astouchstart
on touch devices; it only fires when you lift your finger off the screen, whereastouchstart
fires immediately when touched. -
Ben Clarke over 5 yearsAfter 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 over 5 yearsYou 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 over 2 yearsusing 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) => {} }