vue icon indicating copy to clipboard operation
vue copied to clipboard

Extending object syntax of v-on to support modifiers

Open brianwhu opened this issue 6 years ago • 14 comments

What problem does this feature solve?

The object syntax of the v-on directive is vital in achieving conditional binding of event listeners, as shown in the discussions at https://github.com/vuejs/vue/issues/7349.

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

However, the current object syntax does not allow modifiers.

This feature request suggest that we extend the object syntax in the following way to allow modifiers.

<div v-on="{ click: { left: { prevent: condition ? leftClickHandler : null } },  mouseover: { stop: mouseoverHandler } }">

The above example would conditionally install leftClickHandler on "click.left.prevent" and mouseoverHandler on "mouseover.stop".

The embedded object notation is also conceptually consistent with the dot-notation already adopted in both function and inline syntax.

What does the proposed API look like?

The proposed v-on object syntax would like like this, which is an extension of the current syntax.

<div v-on="{ click: { left: { prevent: condition ? leftClickHandler : null } },  mouseover: { stop: mouseoverHandler } }">

brianwhu avatar Mar 16 '18 18:03 brianwhu

I think the below syntax would be better (no object embedding):

<div v-on="{ 'click.left.prevent': condition ? leftClickHandler : null,  'mouseover.stop': mouseoverHandler }">

andrewsm80 avatar Jun 06 '18 15:06 andrewsm80

This is possible today by using https://vuejs.org/v2/guide/render-function.html#Event-amp-Key-Modifiers

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

posva avatar Jun 04 '19 07:06 posva

Hi! I'm sorry to bring up such an old issue, but with the update to 2.6.11 i found that a thing related to this i used a lot in my project broke down. I'm attaching v-on:click and v-on:click.native to a <component tag, which can dynamically be a Vue component or a DOM element. This now emits warnings, flooding logs and affecting development performance. I tried to attach those listeners dynamically but there's no object syntax for the .native modifier. How can i handle this?

zsavajji avatar Dec 18 '19 06:12 zsavajji

@Zsavajji if you think you found a bug please file a new issue with a boiled down reproduction

posva avatar Dec 18 '19 08:12 posva

Not sure if it is a bug or expected behavior. Will open a bug report tomorrow :)

zsavajji avatar Dec 18 '19 15:12 zsavajji

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!

magistr4815 avatar Mar 13 '20 09:03 magistr4815

This does not seem to work for all event methods. Only those which have the proprietary prefixes.

madmod avatar Sep 07 '20 22:09 madmod

This is possible today by using vuejs.org/v2/guide/render-function.html#Event-amp-Key-Modifiers

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

This seems nice, even if undocumented. However, the .native modifier has no prefix, and seems unsupported with the object syntax. Is there any workaround for now?

Glandos avatar Sep 10 '20 10:09 Glandos

There doesn't seem to be an event amp key modifier for .native, which is what I need.

MarcelloTheArcane avatar Sep 17 '20 12:09 MarcelloTheArcane

I needed .prevent

To work around this I did: <div @contextmenu="condition ? ($event.preventDefault(), handler($event)) : null">

If the object syntax for v-on included .prevent I would probably be able to bind conditionally, which would be my preference.

Tyler-RCSD avatar Dec 18 '20 19:12 Tyler-RCSD

This is now documented here:

v-on also supports binding to an object of event/listener pairs without an argument. Note when using the object syntax, it does not support any modifiers.

This means { 'keyup.left': onLeftRelease } doesn't work. The alternative is obviously to handle this the old school way - the native JS equivalents of some modifiers are described here (Event Modifiers section)

On one hand I know v-on object syntax is rarely used, however it's surprising this doesn't exist. As rationale, Vue is doing something similar with multiple v-models no? Seeing the magic behavior of emitting update:propName sort of has the same vibe as passing the magic string keyup.left to v-on. This probably reads funny but hopefully you can see where I'm coming from.

sethidden avatar Feb 09 '21 17:02 sethidden

Read the update at the bottom, you probably don't want this solution

I previously ran into this issue (you can see the PR where I linked here in the feed above about a month ago) and then today I ran into this same problem again. Not content with not having a solution, I kept digging around, and discovered that you can use withModifiers (you can import from @vue/runtime-dom, though it is marked as @private :shrug:). You can see it working on this codesandbox:

https://codesandbox.io/s/nice-brown-j4q94s?file=/src/components/HelloWorld.vue:2364-2565

In fact, I randomly tried to get it working with multiple different listeners (because I wanted to be able to bind both a capture and non-capture event listener) and it totally works.

I have no idea if this is documented anywhere. I'd searched the Vue documentation so carefully before for some solution to this problem. I have also not looked into how this is working in the Vue source code to tell whether this is working by accident or by design (or if there are any significant drawbacks to using this method.

Hope this helps someone else too :grinning:

Update: After checking the implementation, there are limitations:

const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'];
const modifierGuards = {
    stop: e => e.stopPropagation(),
    prevent: e => e.preventDefault(),
    self: e => e.target !== e.currentTarget,
    ctrl: e => !e.ctrlKey,
    shift: e => !e.shiftKey,
    alt: e => !e.altKey,
    meta: e => !e.metaKey,
    left: e => 'button' in e && e.button !== 0,
    middle: e => 'button' in e && e.button !== 1,
    right: e => 'button' in e && e.button !== 2,
    exact: (e, modifiers) => systemModifiers.some(m => e[`${m}Key`] && !modifiers.includes(m))
};
/**
 * @private
 */
const withModifiers = (fn, modifiers) => {
    return (event, ...args) => {
        for (let i = 0; i < modifiers.length; i++) {
            const guard = modifierGuards[modifiers[i]];
            if (guard && guard(event, modifiers))
                return;
        }
        return fn(event, ...args);
    };
};

That list are the only modifiers supported, all ones that can be easily applied manually anyway without pulling in @vue/runtime-dom into your bundle.

So probably this isn't a very good solution, sorry for the false alarm :cry:

sarayourfriend avatar May 08 '22 21:05 sarayourfriend

This problem still at vue3.

v-on supports binding to an object but it does not support any modifiers.

edwin2jiang avatar Aug 05 '22 03:08 edwin2jiang

I ended up creating a wrapper component like this:

<template>
  <router-link v-bind="$attrs" v-on="$listeners" @click.native="onClick">
    <slot />
  </router-link>
</template>

<script>
export default {
  name: 'RouterLinkWithClickEvent',
  methods: {
    onClick(...args) {
      this.$emit('click', ...args);
    }
  }
};
</script>

janverhulst avatar Apr 25 '23 09:04 janverhulst