Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional event binding #7349

Closed
asiFarran opened this issue Dec 31, 2017 · 34 comments
Closed

Conditional event binding #7349

asiFarran opened this issue Dec 31, 2017 · 34 comments

Comments

@asiFarran
Copy link

asiFarran commented Dec 31, 2017

What problem does this feature solve?

Right now if we want an event handler to run conditionally we need to place the condition in the event handler which means that the event is still subscribed to and we pay the memory allocation fee for the event subscription (the underlying addEventListener and corresponding handler).

In some scenarios this is a pain. For example: say I've got mouse events (mouseover, mouseout) that are only meaningful on devices that actually have a pointer/mouse and so are meaningless on mobile and touch devices.

Right now, I'd have to create the event subscriptions and add the condition in the handler (or in this case no need even for that as the events would never fire) BUT I've still attached these handlers and allocated memory for them - which especially on memory constrained platforms like mobile browsers is a waste.

By making the event subscription itself conditional we can avoid this.

What does the proposed API look like?

In its simplest form we could just examine the handler provided and if falsy (or just null) short-circuit the event subscription and NOT apply the underlying addEventListener operation.

This way the condition can appear in the event binding declaration itself:

<div @mouseover="condition ? handler : null" /> 
@sqal
Copy link
Contributor

sqal commented Jan 1, 2018

@asiFarran

Right now, I'd have to create the event subscriptions and add the condition in the handler (or in this case no need even for that as the events would never fire)

Actually there's another, simple way how you can handle this case. You can pass computed property with your listeners object (or null if condition is not met) to v-on, example: https://jsfiddle.net/c0Le92xe/

@Kingwl
Copy link
Member

Kingwl commented Jan 3, 2018

maybe you need ?
<div @mouseover="e => condition && handler(e)" />

@yyx990803
Copy link
Member

@Kingwl that still adds the listener, which is what OP wants to avoid.

@sqal 's suggestion is actually a valid workaround, and can be simplified to:

<div v-on="{ mouseover: condition ? handler : null }">

@asiFarran
Copy link
Author

asiFarran commented Jan 3, 2018

Yes, @sqal's solution is a good one and with inlining like @yyx990803's shows is close enough to what I had in mind. Thanks!

This leads me to a deeper issue though: If a rebind happens (data change) and upon evaluation the condition changes (or more generally if the event spec object passed to v-on is different), existing event subscriptions are not cleaned up. They will still (hopefully) be disposed at the end of the component's lifecycle but not when they 'should' which is when v-on rebinds.

This may be an edge case that does not have a large impact for most scenarios but just for reference:

In my scenario I have a complex SVG and need to (optionally) attach mouse over/out handlers to specific elements based on some logic.
The component is a long lived one and the underlying data changes leading to rebinding where I need to attach the event handlers to different elements at each rebind - hence my need to dispose of the previous subscriptions so they don't dangle orphaned and sad and drink memory.

A typical solution - and what I'm going to end up doing - is to set up one 'top level' event listener (per event type) and let events bubble up to it but to explain my motivation for trying it the Vue way first, there's quite a bit of processing that has to happen in my handlers which I had intended to pre-calculate and bake right into the event subscriptions itself so that the handlers have less to do (quicker and more fluid response) and are not invoked at all for elements that don't require it (as opposed to filtering this out relying on DOM queries in the handler).

@asiFarran
Copy link
Author

@vsevolodtrofimov the whole point was to avoid the event subscription from happening at all. Applying the condition in the handler does not satisfy this requirement. Please note the comments above as they do provide a satisfactory resolution.

I am closing this issue to reflect that.

@pmayer
Copy link

pmayer commented Jan 16, 2018

<div v-on="{ mouseover: condition ? handler : null }">

Is it possible to pass $event and other arguments to handler?

@v-trof
Copy link

v-trof commented Jan 16, 2018

@pmayer
<div v-on="{ mouseover: condition ? $event => handler($event, arg) : null }">

Or curry the handler and use

<div v-on="{ mouseover: condition ? handler(arg) : null }">

@brianwhu
Copy link

With the suggested solution

<div v-on="{ mouseover: condition ? handler : null }">

Is there a way to apply modifiers like ".stop.prevent"?

@DawidMyslak
Copy link

Hey @yyx990803 @vsevolodtrofimov

The solution suggested here (using v-on="{ mouseover: condition ? handler : null }") is not really working with latest Vue.

I'm getting this error:
Invalid handler for event "mouseover": got null

So looks like Vue is actually trying to fire the handler instead of unbinding the event 🤔 .

@pbastowski
Copy link

pbastowski commented Apr 11, 2018

@DawidMyslak
Just change it to the below and it will work

v-on="condition ? { mouseover: handler } : {}"

or, if your handler is called mouseover

v-on="condition ? { mouseover } : {}"

@DawidMyslak
Copy link

Nice one @pbastowski !

@franciscobrba
Copy link

Thank you guys!

@vividvilla
Copy link

You may want to wrap with inline function if you are calling a handler with custom data. Something like this

v-on="condition ? { mouseover: () => handler(somedata) } : {}"

@lk-jeffpeck
Copy link

This should be in Vue docs

@Artkoch
Copy link

Artkoch commented Nov 20, 2018

@DawidMyslak
Just change it to the below and it will work

v-on="condition ? { mouseover: handler } : {}"

or, if your handler is called mouseover

v-on="condition ? { mouseover } : {}"

Is there a way to combine this with .once?

@posva
Copy link
Member

posva commented Nov 20, 2018

You can use https://vuejs.org/v2/guide/render-function.html#Event-amp-Key-Modifiers

<button v-on="{ '~click': () => foo = new Date() }">Trigger only once</button>

@dietergeerts
Copy link

One question on this, will there be memory leaks or event listeners not removed when I do the following, and the condition regularly changes?

<template>
<div  v-on="myListeners">
some content
</div>
</template>
<script>
...
computed: {
  myListeners() {
    return this.canExecute ? { click: () => this.$emit(...) } : {};
  },
},
...
</script>

So in short, does the v-on handles the change or not?

@yyx990803
Copy link
Member

@dietergeerts it does.

@AlansCodeLog
Copy link

Does this work with native events? I couldn't get it working with keydown.native.

@Justineo
Copy link
Member

FYI. Since 2.6 (not released yet) you will be able to apply a conditional event binding as follows:

<div @[event]="handler" /> 

While event resolves to null, the binding will be removed.

@AlansCodeLog
Copy link

@Justineo That's great, but I just tried the beta and it doesn't seem to work with modifiers (of any type) either. Has that not been implemented yet? Is it planned?

@yyx990803
Copy link
Member

@AlansCodeLog it should work with all modifiers. If it's not working you should open a new issue with a reproduction.

@AlansCodeLog
Copy link

@yyx990803 Okay, I'll see if I can reproduce it.

@AlansCodeLog
Copy link

AlansCodeLog commented Feb 4, 2019

I have opened an issue here: #9417

@capocasa
Copy link

maybe you need ?
<div @mouseover="e => condition && handler(e)" />

Browser JavaScript version

<div @mouseover="condition && handler(arguments[0])" />

@posva posva added the has PR label Jun 4, 2019
@pbastowski
Copy link

@kieryk123 Can you provide a CodeSandbox link to look at?

@arcreative
Copy link

In my case, I was trying to do this on a disabled button element (via Vuetify), but apparently Chrome doesn't fire the necessary events, and the element should be wrapped in a div or similar to catch the event.

@magistr4815
Copy link

magistr4815 commented Mar 13, 2020

Hello.
I tried to do it like this, but it does not work.

// not working
v-on="{ [condition ? 'click.stop' : 'click'] : eventfunc }"
// or
// error
v-on="{ condition ? 'click.stop' : 'click' : eventfunc }"

Is there any other solution?
Thanks in advance!

@proArtex
Copy link

Here is the trick for native events (using Vue v2.6.11):

@mouseenter.native="condition && handler($event)"

@AndrewBogdanovTSS
Copy link

AndrewBogdanovTSS commented Apr 27, 2020

<div @[event]="handler" />

@Justineo was this syntax added to Vue?
I can't get it to work. Maybe you could send a link to docs where I can read more about it?

@pbastowski
Copy link

pbastowski commented Apr 29, 2020

@AndrewBogdanovTSS have a look at this fiddle https://jsfiddle.net/pbastowski/v0wt5qpo/27/

The syntax works just fine for normal and native clicks. Note that .native events are only available on Vue components and not plain HTML elements.

@AndrewBogdanovTSS
Copy link

@pbastowski simple string is working, yes, but what I need is to be able to resolve the value of event based on some reactive data, so I need something like

<h2 @[() => someProp ? 'mouseup' : null]="alert('Normal click on an HTML element')">H2 Element - click it</h2>

and such syntax doesn't work for me

@pbastowski
Copy link

@AndrewBogdanovTSS Just move your logic to a computed, see the updated example here https://jsfiddle.net/pbastowski/v0wt5qpo/63/

@coyotte508
Copy link

@AndrewBogdanovTSS another solution:

<h2 @[someProp&&`mouseup`]="alert('blablabla')">abcdef</h2>

Just make sure to not use spaces / quotes in the event name (why there's text in antiquotes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests