There are lots of scenarios where this is needed. See descriptions in aspnet/blazor#715.
Since we don't want to rely on synchronous APIs, the solution will probably be a variation on event registration syntax, e.g.,
<button @onclick="MyHandler" @onclick:stopPropagation="true" />
We still need to design what terminology to use (e.g., "propagation" vs "bubbling") and what features to include (e.g., whether there is also control over "preventDefault").
IMO both are independant and required:
onclick-stop-propagation
onclick-prevent-default
Usage
<button onclick=@MyHandler
onclick-stop-propagation
onclick-prevent-default />
Please read this comment:
https://github.com/aspnet/Blazor/issues/715#issuecomment-403171755
to see an example where we need to make a decision at runtime.
What about use Attributes on the target method
[StopPropagation]
[PreventDefault] ?
Instead of:
<button onclick.prevent.stop=@MyHandler
onclick-stop-propagation
onclick-prevent-default />
Alternative vue-inspired syntax:
<button onclick.prevent.stop=@MyHandler />
+1 this is really needed indeed, thinking of individual cells on tabels, other use cases etc..
otherwise the framework would be deemed very limited I'm afraid.
Yes, for me this feature is also very important.
I haven't been using Blazor very long, it may be something that's been covered and isn't possible.
I think these
button onclick.prevent.stop=@MyHandler
button onclick.prevent.stop=@MyHandler
onclick-stop-propagation
onclick-prevent-default
are both undesirable formats. My preference would be a parameter list, something like this
less clutter, no changes to well known events, doesn't look like xaml .
Blazor 0.6 is coming with new wonderful features and I think it is time to schedule this issue for Blazor 0.7. It is a showstopper for all component creators. Please read #1293, #1063, #988, #951, #902, #803, #715. Please note, that sometimes we have to decide at runtime what to do. Simple example is here: https://github.com/aspnet/Blazor/issues/715#issuecomment-403171755
and some suggestion how to solve this problem at the bottom of this comment: https://github.com/aspnet/Blazor/issues/715#issuecomment-401950907
I have just updated my comment here https://github.com/aspnet/Blazor/issues/715#issuecomment-403171755 with an example which (I believe) can be implemented in Blazor using async API.
Add this to the 0.7 release please.
We'll take a look at getting this into 0.7.0.
Hi,
I have tried this out for myself on a clone of the 0.6.0 release, and one small change in EventDelegator.ts has given me the ability to use preventDefault() on any event on any element by specifying on the element.
For my testing, I just went with a simple attribute "bl-preventdefault" which contains a list of events you want to enable preventDefault on.
<div draggable="true" dropzone="move"
ondragstart="@OnDragStart"
ondragend="@OnDragEnd"
ondragenter="@OnDragEnter"
ondragleave="@OnDragLeave"
ondragover="@OnDragOver"
ondrop="@OnDrop"
bl-preventdefault="dragenter,dragleave,dragover,drop"
class="@Class"
style="@(Styles())">
@ChildContent(Data)
</div>
What do you think of using a simple fix like this for now?
@SteveSandersonMS , @danroth27 some time ago this was scheduled for Blazor 0.7. Any chance to reconsider for 0.8?
Blazor is a great product but to succeed it needs ecosystem with ready to use components and I'm not talking about something simple like enhanced combobox or date picker. There are projects where we need powerful components from leading component vendors, for example report designer, spreadsheet, etc. Those vendors have to have full control over events and strictly speaking we need it also even for some very simple components.
as a workaround, i'm currently using js interop to register event handlers instead of the native event binding. dynamic input handling is really necessary for complex components, even if it doesn't fit nicely into the async/worker-thread model. even simple buttons and fields need a plethora of weird special cases when you're handling touch input, different browers, history state etc.
OK, I've been reading through everything that's ever been written about this, and am formulating it into a proposed design.
<div>
, each with onclick
- maybe you want clicks on inner ones to also trigger events on outer ones, or maybe you don't. Same with onmouseover
or anything else.<a>
tag, to control whether the browser navigates on click (whether or not it's a client-side navigation). Maybe you want to run some async C# logic and then do the navigation.The big limitation, which we've discussed many times, is that for this to work within the Razor Components programming model, we need to know in advance whether you want to cancel bubbling or preventDefault
.
We can't have some .NET API for controlling this after the user has triggered the event, because code running on the server receives it later, and the browser APIs are synchronous. Nor can we have some system like suggested here where we block bubbling by default, but then re-raise the event asynchronously if the .NET code says we should - that doesn't work because many browser events can't be simulated from script (e.g., typing), nor are events treated the same if they are triggered asynchronously (e.g., attempting to open a popup). I know people want a way to do this, but there just isn't a way, so we have to come up with a nice design around it.
Controlling propagation of bubbling events
Bubbling events propagate by default, so all we have to do is create a way to say if you don't want a certain event to bubble up from a certain element.
<div onclick-stop-propagation="true">...</div> <!-- Or any other event -->
Note that we have to phrase it positively. We can't have it as "onsomeevent-bubble=false", because in Razor, a false bool attribute is shorthand for not emitting the attribute at all.
The use of onevent-stop-propagation
is completely independent of whether you also have an onevent
handler (C# or JS) on that same element, so this is very flexible.
If you need to use event-time logic to decide whether to bubble (e.g., whether the user is holding "shift" or not), your scenario is advanced and you can implement it with JS. Example:
<div onclick="conditionallyStopPropagation(event)">...</div>
// Separately, in a `.js` file:
function conditionallyStopPropagation(event) {
if (!event.shiftKey) {
event.stopPropagation();
}
}
It's not unreasonable to use JS for advanced scenarios like this, and given the synchrony limitation, is the only way you're going to be able to put in totally arbitrary logic that run synchronously.
Implementation note: Our EventDelegator.ts
implements its own bubbling mechanism to simulate regular bubbling while also using event delegation. The new feature we add is going to need to prevent bubbling of both the native event and the simulated event.
Control over default actions
Currently, our behavior is that we automatically preventDefault
for submit
events that have a .NET handler, but don't for any other type of event (whether or not they have a .NET handler).
We can make it possible to prevent default in a similar way to preventing event propagation:
<input type="checkbox" onclick="@DoSomething" onclick-prevent-default="true" />
Again, onX-prevent-default=true
can be used independently of whether you also have an onX
event on that element. Internally it will register its own event handler that calls event.preventDefault()
.
As with above, it's only possible to prevent default based on information that exists prior to the event. You can't use .NET logic to prevent default conditionally after the event has started, because of the synchrony limitation. If you really do need conditional logic, this is an advanced scenario and you'll solve it via JS interop.
Example:
<input bind="@SomeString" onkeydown="blockDigits(event)" />
// Separately, in a `.js` file:
function blockDigits(event) {
if (event.keyCode >= 48 && event.keyCode <= 57) {
event.preventDefault();
}
}
Again, it's not unreasonable to use JS for advanced and uncommon scenarios like this.
Non-scenarios
Controlling event capture: In JS, it's possible to register event listeners that fire during the "capture" phase of event handling, instead of during the "bubble" phase. However with Razor Components we don't expose that distinction - we use a mixture of capturing and bubbling handlers, depending on the event type, to produce the desired behaviors. If, one day, we have a way of changing your listener to be capturing, then we would also want something like onclick-stop-capture-propagation
. But that's not a scenario we have today.
Avoiding preventDefault
for submit
when there's a .NET handler It's just wrong to ever want to do that. It's just not a scenario.
This seems to be just what I expected 馃憤 concise and orthonogonal to other features.
One small nitpick - it should also be possible to write these examples with a minimized attribute, and maybe that's the preferred style.
<div onclick-stop-propagation>...</div>
It's obvious that there has to be some runtime component to this, do you imagine that there's a compiler/tooling angle as well?
it should also be possible to write these examples with a minimized attribute, and maybe that's the preferred style
Totally agree. I was only writing it with ="true"
because I wanted to emphasise that you can specify a bool value if you want to, as people might want it to vary dynamically (e.g., onclick-stop-propagation=@shouldStopPropagation
). But if it's given as a minimized attribute, that's interpreted as "true". Also I agree it's the natural and preferred style.
do you imagine that there's a compiler/tooling angle as well?
We can add tooling for intellisense, but I was expecting us not to do so. It's sufficiently advanced that people are only going to be doing this because they're following an example or read about it in docs - the value of discovery through intellisense is lower than for most features.
So how about in the first cut we do it as a runtime feature only, and leave open the option to add a built-in tag helper to provide intellisense later if we want? It will make this much cheaper to implement, and you know how much we need to do in the P5 timeframe.
OK, agreed on all points.
Will these attributes contribute to diffing when StateHasChanged()? I'd like to be able to render either <foo onlick-stop-propagation>
or <foo>
based on some parameter, for asynchronous dynamism.
@gulbanana Yes, they will be normal attributes whose values can change. That's what I meant by this above:
you can specify a bool value if you want to, as people might want it to vary dynamically (e.g., onclick-stop-propagation=@shouldStopPropagation)
another way could be a syntax like:
<button onclick="@(x => x.StopPropagation().Then(() => IncrementCount2(x)))">aa</button>
and also
@for (var i = 0; i <= 2; i++)
{
<button onclick="@(x => x.StopPropagation(() => i % 2 == 0 )
.Then(() => IncrementCount2(x)))">@i</button>
}
with a class like this
namespace WebApplication1.App
{
public class JSLambda<T> : UIEventArgs where T: UIEventArgs
{
Action _action;
public bool RequestStopPropagation { get; set; }
public JSLambda<T> StopPropagation()
{
RequestStopPropagation = true;
return this;
}
public JSLambda<T> StopPropagation(Func<bool> func)
{
RequestStopPropagation = func();
return this;
}
public JSLambda<T> Then(Action action)
{
_action = action;
return this;
}
}
[EventHandler("js-click", typeof(JSLambda<UIMouseEventArgs>))]
public static class EventHandlers
{
}
}
with the only drawback is that StopPropagation (Func <bool> func)
would always be called in the rendering phase and not during the real click, but in this way you could be able to control the bubbling from the server side, and possibly also client side if JSLambda could turn c # into javascript (or a series of instructions to be interpreted in javascript), all in c # without dirtying the html.
just to make it clear that it would probably be possible, here a project that transforms c # lambda into javascript
If you are able to do:
onclick-stop-propagation=@shouldStopPropagation
why can't @shouldStopPropagation be an annonymous function? And if that is the case, why can't we just have a property on UIKeyboardEventArgs (or whatever args)? Honestly, using attributes seems wrong and only covers a small portion of what is needed. Personally, I'd prefer to have this left out completely if it's not going to be implemented nicely and rely on interop rather than muddying the water.
I have always found that stopPropagation is something that is either set, or it isn't (typically, but not always). However, preventDefault I use ypically with conditions. Prevent key strokes, button presses based on other inputs ... Very seldom do I blindly override preventDefault for all paths in a control.
I really don't know the technical limitations, but if adding a parameter to the UIKeyboardEventArgs is possible (which I'm 99.9% sure you can't or it would have been done already) or using an annonymous function to be passed to event-stop-propagation instead of a boolean variable, this to me would be prefered.
I don't mean any of this to sound rude at all. I am primarily a front end developer, so not being able to have my conditions in the function that is handling the event seems wrong and limiting. Or maybe I just need to change how I am used to doing things.
@sbuchok It's because of the asynchrony. I know it's not immediately obvious, but it is explained above in this thread (i.e., why making post-event decisions about preventDefault etc will have to be done in JS code, not .NET).
@SteveSandersonMS Stupid question, can we have both synchronous and asynchronous events? In the synchronous events, allow for preventDefault and stopPropagation. Chances are, this is not a minor change, so I'm guessing it won't go in, but throwing it out there.
No, it's async only.
@SteveSandersonMS
DOH! Ok, having preventDefault for a textbox though doesn't make sense without conditions as you won't be able to type in the textbox.
I can understand there being an issue with async for stopPropagation, however, preventDefault I think is different (this is without reading understanding any of the code you guys have written). They don't have to be implemented the same way.
BTW, I'm loving working with Blazor/RazorComponents (I originally was using Blazor and switched to Razor Components) so far, with this exception ;)
I've primarily worked as a front end developer writing JS so having it work similarly is obviously ideal ... for me. Good luck and thanks.
Does that mean that one day when we are able to access the browser APIs directly from webassembly and we no longer need to write js, we will still rely on js interop for the event propagation and preventDefault? I thought js interop is a temporary solution until webassembly gets mature enough and we get rid of all the js.
I fully agree with @sbuchok that we usually want to handle preventDefault and often stopPropagation conditionally. I understand that it is a problem in Blazor on the server, but I believe that people will prefer client side Blazor in the future (when it will be fully supported). For those of us it would be nice to have fully functional framework without artificial limitations.
@mkArtakMSFT @danroth27 Realistically I'm not going to be in a position to do this during preview 6.
It's been waiting for the new directive attributes feature so it could build on that, but since that isn't in yet so I couldn't start, and I'm unavailable next week, and I will still need the last bits of this week for auth, it just won't fit.
We can either punt the whole thing, or try to identify someone else to implement it. I hope my design description above would give enough info for someone else to implement it, though the final APIs would look a bit different since they need to align with the new directive attributes once done.
Let's talk about this during team sync today.
I personally would prefer to see this feature gone and force us to use JS Interop rather than bring in something that only works for a small set of desired scenarios.
When I first started using Blazor, I thought that it was going to be able to do everything that JS did. However, I'm guessing it won't and probably was never meant to. If that's the case, maybe forcing us to use JS Interop makes more sense rather than muddying the water.
That's my 2... 5 cents ;)
I also think I'm bias. I love JS. But I'm currently working on something (for myself) where I'm trying to do it 100% in Blazor without any JS. So far, this has been my biggest stumbling block, which really speaks to how much is already available in Blazor.
Thanks again
It's not unreasonable to use JS for advanced scenarios like this, and given the synchrony limitation, is the only way you're going to be able to put in totally arbitrary logic that run synchronously.
That makes me sad 馃槩
Has there been any news or progress on this? It's blocking me on a number of fronts. Is there any actual workaround currently besides handling the event in JS and invoking the C# handler manually?
If we have this syntax to stop event propagation will we have full control of stopping events in code-behind based on some condition?
<button @onclick="MyHandler" @onclick:stopPropagation="true" />
Maybe better solution is to have behaviour similar to events in WinForms where we can control parameters on event args. Like this:
public class MouseEventArgs : EventArgs
{
// other properties
public bool StopPropagation { get; set; }
public bool PreventDefault { get; set; }
}
void OnClick( MouseEventArgs eventArgs )
{
if ( someCondition )
{
eventArgs.PreventDefault = true;
return;
}
// do something else
}
@stsrki
That's not possible because of the async event handling. The decision has to be upfront. Read Steve's comment above: https://github.com/aspnet/AspNetCore/issues/5545#issuecomment-469748054
@manigandham Thanks, I missed that comment. Steve said there is no way to preventDefault
once the event has happened. It seems we're going to be stuck with JavaScript for a long time.
@danieldegtyarev Could you please let me know about this issue's estimated arrival time?
@kameshrajandran .NET Core 3.1, which is scheduled for Nov/Dec of this year.
The runtime part of this is now done and was merged in https://github.com/aspnet/AspNetCore/pull/14509
The tooling part is going to happen in 3.1.0-preview2, so assigning to @ajaybhargavb for the remaining work.
In fact we have a different issue tracking the tooling work for this (https://github.com/aspnet/AspNetCore/issues/14517), so closing this one.
Was this finally added to the released Blazor 3.0?
Unfortunately it didn't make it into 3.0, but it has now been implemented for 3.1. We will have a preview of the functionality it a couple of weeks.
Awesome. :) I just started fiddling with Blazor and ran into this requirement for drag and drop functionality.
It's unfortunate JS won't let us decide asynchronously. I tried cancelling and then emulating mouse events but some browsers just wouldn't allow it.
A tricky problem indeed.
WPF has the same issue. You have to mark RoutedEventArgs
as Handled
before your event handler does anything async
.
I wonder if the Blazor team has considered handling bubbling through the Blazor framework itself and bypassing Javascript event bubbling? Or would that just be too overwhelming for every DOM element to trigger an event in C# just for the possibility of bubbling it up to a parent?
Most helpful comment
OK, I've been reading through everything that's ever been written about this, and am formulating it into a proposed design.
Scenarios
<div>
, each withonclick
- maybe you want clicks on inner ones to also trigger events on outer ones, or maybe you don't. Same withonmouseover
or anything else.<a>
tag, to control whether the browser navigates on click (whether or not it's a client-side navigation). Maybe you want to run some async C# logic and then do the navigation.Constraint: Synchrony
The big limitation, which we've discussed many times, is that for this to work within the Razor Components programming model, we need to know in advance whether you want to cancel bubbling or
preventDefault
.We can't have some .NET API for controlling this after the user has triggered the event, because code running on the server receives it later, and the browser APIs are synchronous. Nor can we have some system like suggested here where we block bubbling by default, but then re-raise the event asynchronously if the .NET code says we should - that doesn't work because many browser events can't be simulated from script (e.g., typing), nor are events treated the same if they are triggered asynchronously (e.g., attempting to open a popup). I know people want a way to do this, but there just isn't a way, so we have to come up with a nice design around it.
Design
Controlling propagation of bubbling events
Bubbling events propagate by default, so all we have to do is create a way to say if you don't want a certain event to bubble up from a certain element.
Note that we have to phrase it positively. We can't have it as "onsomeevent-bubble=false", because in Razor, a false bool attribute is shorthand for not emitting the attribute at all.
The use of
onevent-stop-propagation
is completely independent of whether you also have anonevent
handler (C# or JS) on that same element, so this is very flexible.If you need to use event-time logic to decide whether to bubble (e.g., whether the user is holding "shift" or not), your scenario is advanced and you can implement it with JS. Example:
It's not unreasonable to use JS for advanced scenarios like this, and given the synchrony limitation, is the only way you're going to be able to put in totally arbitrary logic that run synchronously.
Implementation note: Our
EventDelegator.ts
implements its own bubbling mechanism to simulate regular bubbling while also using event delegation. The new feature we add is going to need to prevent bubbling of both the native event and the simulated event.Control over default actions
Currently, our behavior is that we automatically
preventDefault
forsubmit
events that have a .NET handler, but don't for any other type of event (whether or not they have a .NET handler).We can make it possible to prevent default in a similar way to preventing event propagation:
Again,
onX-prevent-default=true
can be used independently of whether you also have anonX
event on that element. Internally it will register its own event handler that callsevent.preventDefault()
.As with above, it's only possible to prevent default based on information that exists prior to the event. You can't use .NET logic to prevent default conditionally after the event has started, because of the synchrony limitation. If you really do need conditional logic, this is an advanced scenario and you'll solve it via JS interop.
Example:
Again, it's not unreasonable to use JS for advanced and uncommon scenarios like this.
Non-scenarios
Controlling event capture: In JS, it's possible to register event listeners that fire during the "capture" phase of event handling, instead of during the "bubble" phase. However with Razor Components we don't expose that distinction - we use a mixture of capturing and bubbling handlers, depending on the event type, to produce the desired behaviors. If, one day, we have a way of changing your listener to be capturing, then we would also want something like
onclick-stop-capture-propagation
. But that's not a scenario we have today.Avoiding
preventDefault
forsubmit
when there's a .NET handler It's just wrong to ever want to do that. It's just not a scenario.