Reputation: 1241
In my scenario I have mouseover and mouseout events that I want to bind to conditionally (e.g only if user is on a device that has a mouse).
I realize I can have the condition in the event handler itself but that would still be allocating the memory for the event handlers which is unnecessary.
Is there a way to make the event binding itself conditional?
(to be clear, what I'd like is to be able to short-circuit the event subscription so the underlying addEventListener operation never happens if the condition is false)
Upvotes: 88
Views: 77985
Reputation: 6144
This is what I'm using:
v-on="condition ? { click: handler } : {}"
(Reference)
I get Invalid handler for event "click": got null
with v-on="{ click: condition ? handler : null }"
Upvotes: 26
Reputation: 7448
For me this solution is the best to understand and 100% compatible with any VUE JS version
<div v-on:click="click()">...</div>
methods : {
click() {
if (yourCondition)
yourhandler();
},
},
If yourCondition evaluates to false, then click() method behaves such as no operation (a.k.a. noop)
Upvotes: 0
Reputation: 912
None of the above solutions work, when you want to make the handler dependent on a condition, that uses props passed to the slot by the child.
In that case the below syntax can be used:
:[enableClick?`onClick`:``]="clickHandler"
where enableClick
is a prop received from the children as described here:
https://vuejs.org/guide/components/slots.html#scoped-slots
and onClick
is the event name (click
) prefixed with on
in camelCase
e.g.:
<MyComponent v-slot="{ enableClick }">
<OtherComponent :[enableClick?`onClick`:``]="clickHandler" />
</MyComponent>
and inside MyComponent template:
<div>
<slot :enableClick="isClickEnabled"></slot>
</div>
Upvotes: 2
Reputation: 93
I am unable to comment on answers so I am adding my own.
Solution 3 from the approved answer does not work - parentheses are missing. The correct syntax in this case would be:
@click="enableClick && clickHandler()"
Here is working example
Upvotes: 6
Reputation: 10653
If you need to use a .stop
modifier, then none of the listed solutions will work. But it is actually easy, just implement whatever the modifier does in the event handler itself.
In the template: @click="clickHandler"
(yes, always attached!)
Then for your handler:
function clickHandler(event) {
if (whateverCondition) {
event.stopPropagation()
…
}
}
Upvotes: 4
Reputation: 505
I needed to conditionally bind multiple events so I managed by doing this:
v-on="isEnabled ? {mouseenter:open, mouseleave:close, touchend:toggle} : null"
Upvotes: 2
Reputation: 20791
Several solutions seem to be accepted on Github, whereas my original answer is not. I'm rounding them up here for convenience:
Solution 1a (see Engin Yapici's answer below):
v-on="enableClick ? { click: clickHandler } : {}"
Solution 1b (see Heals Legodi's answer below):
v-on="enableClick ? { click: () => clickHandler(params) } : {}"
Solution 2a (see rvy's answer below and this working demo)
@[eventName]="clickHandler"
Solution 2b (from coyotte508's comment; 2a without the computed property):
@[isClickable&&`click`]="clickHandler"
Solution 3 (mentioned here; seems to be compatible with event modifiers):
@click="enableClick && clickHandler"
This works as of Vue 2.6:
<div @mouseover="enableMouseover ? mouseoverHandler : null" @click="enableClick ? clickHandler : null" ... >
While an event resolves to
null
, the binding will be removed.https://github.com/vuejs/vue/issues/7349#issuecomment-458405684
It seems to be something I came up with accidentally by misunderstanding that Github thread. But I know from my own testing that it definitely worked back when I posted it. And people continued to upvote it right up to when I made this edit, implying the solution still had some merit. So give it a try if you like.
But after a downvote and a couple of negative comments, I felt the need to improve my answer to save future readers potential headaches. Note that pretty much all the answers to this question refer to the same Github thread, so if you've got time, you might want to go right to the source to learn more. There are many options discussed there, and I suspect more will continue to be posted over time.
Upvotes: 140
Reputation: 121
Refer to this by Evan You
<div v-on="{ mouseover: condition ? handler : null }">
should work and be clean.
Upvotes: 3
Reputation: 6045
Conditional event binding works as follows:
<div @[event]="handler" />
While event resolves to null, the binding will be removed.
(directly from https://github.com/vuejs/vue/issues/7349#issuecomment-458405684 )
Example:
<template>
...
<span @[mayclick]="onClick">...</span>
...
</template>
<script>
export default {
...
computed: {
mayclick: function() {
return this.isClickable ? "click" : null;
}
},
methods: {
onClick: function (message) {
...
}
}
}
Upvotes: 1
Reputation: 269
If you get the 'Invalid handler for event "click": got null' error and your handler function expects some parameters then you should wrap your handler in a function.
So this >
v-on="condition ? { blur: () => handler(params) } : {}"
Instead of
v-on="condition ? { click: handler(params) } : {}"
Upvotes: 9
Reputation: 1241
Following this discussion it appears the best way to achieve this is to bind v-on to a specification object containing the events you are interested in subscribing to and place your conditionals there like so:
<div v-on="{ mouseover: condition ? handler : null, click: ... }">
Some notes:
addEventLisetener
will not happen - which is what we wantThis means grouping all the event subscriptions into one v-on
attribute rather then splitting it into separate and explicit
bindings (<div @mouseover='...' @click='...'/>
)
If this is a long living component and the underlying data changes
frequently (leading to rebinding) you should be paying attention
to the disposal of the subscriptions (i.e the corresponding removeEventListener
) as subscriptions made in one bind pass
will not be disposed of on subsequent ones. Evaluate as per your
use case...
Upvotes: 31
Reputation: 32694
If you want to do something like that you could just apply the event listener manually by adding a ref
on the element
you want to apply the event to, then using that to bind the event listener in the mounted
hook if the condition is met:
Markup
<button ref="button">
Mouse Over Me
</button>
Vue Instance
new Vue({
el: '#app',
mounted() {
let hasMouse = true;
// If the user has a mouse, add the event listeners
if (hasMouse) {
let button = this.$refs.button
button.addEventListener('mouseover', e => {
this.mouseover = true
})
button.addEventListener('mouseout', e => {
this.mouseover = false
})
}
},
data: {
mouseover: false
}
})
Here's a JSFiddle for that: https://jsfiddle.net/0fderek6/
If you don't like that approach, you could also use a directive
and place the conditional in there, you could then place that in a mixin
to make it reusable:
Mixin
const mouseEvents = {
directives: {
mouseEvents: {
bind(el, binding, vnode) {
let hasMouse = true;
if (hasMouse) {
el.addEventListener('mouseover', e => {
vnode.context.mouseover = true
})
el.addEventListener('mouseout', e => {
vnode.context.mouseover = false
})
}
}
}
},
data: {
mouseover: false
}
}
Vue Instance
new Vue({
el: '#app',
mixins: [mouseEvents]
})
Markup
<button v-mouse-events>
Mouse Over Me
</button>
Here's the JSFiddle for that: https://jsfiddle.net/nq6x5qeq/
EDIT
If you like the directive
approach, all you need to do is add an unbind
hook to remove the listener, you can then have the binding arg
be the event type and the binding value
be the handler:
Vue.directive('mouse', {
bind(el, binding) {
if (hasMouse) {
console.log(binding.arg + ' added')
// bind the event listener to the element
el.addEventListener(binding.arg, binding.value)
}
},
unbind(el, binding) {
if (hasMouse) {
console.log(binding.arg + ' removed')
el.removeEventListener(binding.arg, binding.value)
}
}
});
Now all you need to do is add each listener exactly like you would with v-bind
:
<div v-mouse:mouseover="mouseOverFunction"></div>
Here's the JSFiddle to show you how that works: https://jsfiddle.net/59ym6hdb/
Upvotes: 4
Reputation: 652
Even more simpler would be to use render functions for that. You won't need to be manually removing the listeners and taking care of them. Also uses simple JS syntax with no mixins.
new Vue({
el: "#app",
data: () => ({
counter: 0
}),
methods: {
handleClick() {
this.counter++;
}
},
render(h) {
return h(
"div",
IS_MOBILE_DEVICE
? {}
: {
on: { click: this.handleClick }
},
this.counter
);
}
});
Full example: https://codesandbox.io/s/nw6vyo6knj
Upvotes: 5