Vue: Be able to pass multiple events the same function in templates

Created on 27 Aug 2017  Â·  22Comments  Â·  Source: vuejs/vue

What problem does this feature solve?

Having multiple event handlers that call the very same function add noise to the templates.
I propose to DRY that a bit.

What does the proposed API look like?

Currently you can pass multiple functions to a single event handler like so:

<component
     @click="func1(); func2()"
></component>

However, I think it would be really handy to be able to do the inverse, ie. passing the same function to multiple event handlers.
Today for instance I have to do this:

<component v-model.number="foo"
    @doDelayedStuff="delayedStuff"
    @wheel.native="delayedStuff"
    @mouseenter.native="callFooUpdate"
    @focus.native="callFooUpdate"
    @keyup.native="callBar"
    @mouseleave.native="callFooClear"
    @blur.native="callFooClear"
    @change.native="callFooClear"
    @click.prevent="doStuff"
    @keyup.75="doStuff"
></component>

It would be great if you could instead do that:

<component v-model.number="foo"
    @[doDelayedStuff, wheel.native]="delayedStuff"
    @[mouseenter.native, focus.native]="callFooUpdate"
    @keyup.native="callBar"
    @[mouseleave.native, blur.native, change.native]="callFooClear"
    @[click.prevent, keyup.75]="doStuff"
></component>

That way you can more clearly see in one glance how many different behavior are attached to a component. Also, modifying the callbacks is easier.

Thoughts? 😄

feature request

Most helpful comment

This seems to be a topic that a few people are passionate about and that's great, and there a some really creative ideas about how to tackle this, which also is great.

However for me personally, every syntax proposal so far still seems too complicated, too "abstract", too far away from HTML & JS. It's great to save keystrokes and get rid of repetition, but not at the cost of an easy to grok syntax.

And generally, I'm increasingly opposed to any new syntax, at least any that doesn't bring new features with it because in my opinion, our API surface is big enough as it is.

I would rather see that we get v-on="{...}" working with modifiers:

<test v-on={ 'click.native': handler }>

Then it would be trivial to write a helper method like this:

createlistenerObject(['click.native', 'keyup.enter.native'], this.handler)

and we can keep all of those listener definitions out of the templates.

All 22 comments

Hmm, there're many problems at the moment to implement that. The syntax is not currently possible

<c @[mouseleave.native,blur.native,change.native]="callFooClear" />

_Note that spaces are not possibles, so that would be a common source of error that we want to avoid (people will want to use them between events for readability_. We could use other characters like : though

A more realistic version with current syntax

<c @mouseleave.native:blur.native:change.native="callFooClear" />

Wouldn't work because there's only one arg for directives and that would lead to this modiers object:

[
    'native:blur',
    'native:change',
//etc
]

So, to put it in a nutshell, a syntax like:

<c @[mouseleave.native:blur.native:change.native]="callFooClear" />

could be possible but it's a new syntax that people will need to learn to replace something that is already feasible:

<cle
    @mouseleave.native="callFooClear"
    @blur.native="callFooClear"
    @change.native="callFooClear"
/>

Personally, I think the second version is more straightforward and easier to read so IMO we should pass on this but I think it's worth listening to others' opinions

Why is spaces not possible? Is that a limitation from the parser or from Html perhaps?
If it's the former case, could it be possible to modify the parser so it accepts those spaces in that particular case?

I really do not like that version:

<c @mouseleave.native:blur.native:change.native="callFooClear" />

since it's really hard to read (and it looks like the events are mouseleave, native:blur, native:change and native!).

Perhaps with a character that is more visible?

<c @[mouseleave.native|blur.native|change.native]="callFooClear" />
<c @[mouseleave.native~blur.native~change.native]="callFooClear" />
<c @[mouseleave.native_blur.native_change.native]="callFooClear" />

I think I would prefer the | character since it's unlikely to be used in a custom event name, whereas _ could more likely be.

could be possible but it's a new syntax that people will need to learn to replace something that is already feasible

Indeed, it's already possible but more verbose and less legible in my opinion.
My solution would be a bit like using @click instead of v-on:click ;)

Anyway, thanks for taking the time to review this!

Is this posible?

<cle
    @[callFooClear]="['mouseleave.native', 'blur.native', 'change.native']"
/>

This would somehow inverse the current <action>="<callback>" order that we all know.
I'm not sure having it reversed just so we can use multiple callbacks is the way to go.
It'll likely confuse the users.

If the syntax I have mentioned above is possible:
1/ The brackets will let users know that this kind of event handling will be inverse,
2/ I think this syntax is clearer since as @posva said, spaces is not possible in the attributes part,
so why don't we put all of that events and modifiers to the value part and the only one function call in brackets or something like that so users will know the difference.

this would be a totally different syntax but what If you could just add all the events in a single attribute, like this:

<my-component @listens="[
  {
    events: ['mouseenter.native', 'blur.native', 'change.native'],
    handler: doStuff
  },
  {
    events: 'keyup.native',
    handler: _ => { keyup = true }
  }
]"></my-component>

i'm not sure this is realistic since there might be some conflicts with the normal syntax, but i think this is a lot more readable and to grasp than adding a extremely long attribute

This seems to be a topic that a few people are passionate about and that's great, and there a some really creative ideas about how to tackle this, which also is great.

However for me personally, every syntax proposal so far still seems too complicated, too "abstract", too far away from HTML & JS. It's great to save keystrokes and get rid of repetition, but not at the cost of an easy to grok syntax.

And generally, I'm increasingly opposed to any new syntax, at least any that doesn't bring new features with it because in my opinion, our API surface is big enough as it is.

I would rather see that we get v-on="{...}" working with modifiers:

<test v-on={ 'click.native': handler }>

Then it would be trivial to write a helper method like this:

createlistenerObject(['click.native', 'keyup.enter.native'], this.handler)

and we can keep all of those listener definitions out of the templates.

good point @LinusBorg. so what do you think about extending the v-on directive to accept a array of objects, just like the one passed to @listens in my previous comment ?
I think its quite nice to have the listener definitions in the templates

agree with @LinusBorg, the syntax is simplier and more realistic.

I'm not sure to follow @LinusBorg, does that syntax means that for multiple events, we would have something like that?:

<test v-on="{ mouseenter.native' : handler, 'blur.native' : handler, 'change.native' : handler }">

If that's the case, then you have the same problem that today, which is a lot of duplication.
@[mouseleave.native, blur.native, change.native]="callFooClear" has the advantage to just go to the point, without any repetition.

I think taking the listener out of the template prevent you to get a full picture of what is going on when looking at it. With this, you'd have some listener defined in the template, others elsewhere. That seems not optimal.

About the long attribute, this notation would just be a shorthand for what already exists.
If you prefer to use one line per event and duplicate the handler name, that's fine. If the handler definition is too long, you can just use the non-shorthand definition format.
I guess if you have 50 events that calls the very same handler, you could perhaps divide those in the template on multiple lines too.

Yes and no. I'm saying that once the v-bind syntax that I showed is working (it currently is for normal events, but isn't for native modifiers), then it's trivial to write a helper function like the one I showed, which creates that object for v-bind.

That would mean that we don't have to introduce new template syntax and can instead solve this with about 3 lines of JS.

How about a config setting where you can define custom events a bit like the keyCodes api

Vue.config.events = {
  delaySomething: ['blur', 'change', 'mouseleave']
}
<tag @delaySomething="method" />

just an idea I had

Having all the events there, in one place, in the template gives you a good idea of what the component react to.
I'm not sure putting some logic elsewhere will help achieve that goal.
Sorry for shooting down the ideas :x

I'm just thinking that you might need to listen to the same set of events in multiple places so this could help to avoid repeating yourself which could cause a lot of other problems.

Putting it in in the global config would also make it informant of components

If you want you could also give them more declarative names like blur-change-mleave

Also is the goal here to easily pass multiple events the same function?

The goal here is indeed to be able to use the same function for multiple events, without having to copy/paste the function name X times in the template.

If you "need to listen to the same set of events in multiple places", then this point is moot since the goal is to call the same function within that same component.

If you need to do:

<component v-model.number="foo"
    @[mouseleave.native, blur.native, change.native]="doStuff"
    @[click.prevent, keyup.75]="doStuff"
></component>

then just do:

<component v-model.number="foo"
    @[mouseleave.native, blur.native, change.native, click.prevent, keyup.75]="doStuff"
></component>

On the other hand, if you are thinking about calling the doStuff function from multiple components, then I think it's out of this feature request scope.
For this I would just use a mixin in order to keep doStuff DRY.

So:

  • We are in general trying to avoid adding more alternative syntax to the templating system, and in this case the benefit (saving keystrokes) really isn't worth the addition imo.

  • The fact that you cannot use spaces in an attribute name makes most of the proposals impractical.

  • What @LinusBorg suggested might be the best solution. However, many of the modifiers are actually compiled into code during the compilation phase, thus making it costly to add runtime support for them. I think the best path forward would be a userland library that exposes the createlistenerObject method and implements the runtime behavior for each modifier internally (instead of duplicating it in Vue core).

We are in general trying to avoid adding more alternative syntax to the templating system, and in this case the benefit (saving keystrokes) really isn't worth the addition imo.

I was thinking more of how we currently have v-on:foo and @foo that do the same thing.
There is in my opinion not so much difference when doing @[foo, bar] for avoiding repetition.

The fact that you cannot use spaces in an attribute name makes most of the proposals impractical.

Yes, if there is no way around it, then this solution is a bit 'dead in the water' :/

I still think having this kind of repetition makes the components behavior harder to comprehend:
vue_component_call_repetition
...or perhaps I'm creating too complex components or just not the right way!

Oh well :)

why complex syntax? we already have a syntax, why add {} or []? don't break vue js please, KISS

You guys are too smart to see it

just
<form @[email protected]@mouseenter="doStuff()">

that would be all we want

I find that concatenating all the handlers with dots and ampersand everywhere is pretty hard to parse when reading that template.

Adding new type of syntax is not necessary

You will cover probsbly 95% use cases with 2 @ anyway

On Mon, Apr 23, 2018, 1:40 AM Alexandre notifications@github.com wrote:

I find that concatenating all the handlers with dots and ampersand
everywhere is pretty hard to parse when reading that template.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/6457#issuecomment-383469996, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB-d-tFazbKFxHC9p6oVMq_MdiShFhs1ks5trXdDgaJpZM4PDr67
.

I don't care for this feature, but I think if someone combined these two ideas, they would be onto something:

a userland library that exposes the createlistenerObject method and implements the runtime behavior for each modifier internally (instead of duplicating it in Vue core).

<form @[email protected]@mouseenter="doStuff()">

This seems to be a topic that a few people are passionate about and that's great, and there a some really creative ideas about how to tackle this, which also is great.

However for me personally, every syntax proposal so far still seems too complicated, too "abstract", too far away from HTML & JS. It's great to save keystrokes and get rid of repetition, but not at the cost of an easy to grok syntax.

And generally, I'm increasingly opposed to any new syntax, at least any that doesn't bring new _features_ with it because in my opinion, our API surface is big enough as it is.

I would rather see that we get v-on="{...}" working with modifiers:

<test v-on={ 'click.native': handler }>

Then it would be trivial to write a helper method like this:

createlistenerObject(['click.native', 'keyup.enter.native'], this.handler)

and we can keep all of those listener definitions out of the templates.

has there been any changes regarding this? I couldn't find anything so far

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bdedardel picture bdedardel  Â·  3Comments

paulpflug picture paulpflug  Â·  3Comments

wufeng87 picture wufeng87  Â·  3Comments

bfis picture bfis  Â·  3Comments

hiendv picture hiendv  Â·  3Comments