vue
vue copied to clipboard
Extending object syntax of v-on to support modifiers
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 } }">
I think the below syntax would be better (no object embedding):
<div v-on="{ 'click.left.prevent': condition ? leftClickHandler : null, 'mouseover.stop': mouseoverHandler }">
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>
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 if you think you found a bug please file a new issue with a boiled down reproduction
Not sure if it is a bug or expected behavior. Will open a bug report tomorrow :)
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!
This does not seem to work for all event methods. Only those which have the proprietary prefixes.
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?
There doesn't seem to be an event amp key modifier for .native
, which is what I need.
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.
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.
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:
This problem still at vue3.
v-on supports binding to an object but it does not support any modifiers.
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>