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

Enable control over event propagation and default actions #5545

Closed
SteveSandersonMS opened this issue Jul 3, 2018 · 45 comments
Closed

Enable control over event propagation and default actions #5545

SteveSandersonMS opened this issue Jul 3, 2018 · 45 comments
Assignees
Labels
area-blazor Includes: Blazor, Razor Components Components Big Rock This issue tracks a big effort which can span multiple issues Done This issue has been fixed enhancement This issue represents an ask for new feature or an enhancement to an existing one Tooling This issue has a tooling impact, which means the execution and release should be in sync with VS rel

Comments

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Jul 3, 2018

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").

@danieldegtyarev
Copy link

danieldegtyarev commented Jul 5, 2018

IMO both are independant and required:

onclick-stop-propagation
onclick-prevent-default

Usage

<button onclick=@MyHandler 
        onclick-stop-propagation 
        onclick-prevent-default />

@Andrzej-W
Copy link

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.

@danielmeza
Copy link

What about use Attributes on the target method
[StopPropagation]
[PreventDefault] ?

@jaredthirsk
Copy link

Instead of:

<button onclick.prevent.stop=@MyHandler 
        onclick-stop-propagation 
        onclick-prevent-default />

Alternative vue-inspired syntax:

<button onclick.prevent.stop=@MyHandler />

@aredfox
Copy link

aredfox commented Sep 11, 2018

+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.

@osintsevvladimir
Copy link

Yes, for me this feature is also very important.

@footysteve
Copy link

footysteve commented Sep 11, 2018

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

<button onclick=(@handler, prevent-default, no-propagation) />

less clutter, no changes to well known events, doesn't look like xaml .

@Andrzej-W
Copy link

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

@Andrzej-W
Copy link

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.

@osintsevvladimir
Copy link

Add this to the 0.7 release please.

@danroth27
Copy link
Member

We'll take a look at getting this into 0.7.0.

@SQL-MisterMagoo
Copy link
Contributor

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?

SQL-MisterMagoo referenced this issue in SQL-MisterMagoo/Blazor Oct 25, 2018
@Andrzej-W
Copy link

@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.

@aspnet-hello aspnet-hello transferred this issue from dotnet/blazor Dec 17, 2018
@aspnet-hello aspnet-hello added this to the Backlog milestone Dec 17, 2018
@aspnet-hello aspnet-hello added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one area-blazor Includes: Blazor, Razor Components labels Dec 17, 2018
@gulbanana
Copy link

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.

@mkArtakMSFT mkArtakMSFT modified the milestones: Backlog, 3.0.0-preview4 Feb 6, 2019
@danroth27 danroth27 added this to To do in Blazor via automation Feb 9, 2019
@rynowak rynowak mentioned this issue Mar 5, 2019
56 tasks
@rynowak rynowak added the Components Big Rock This issue tracks a big effort which can span multiple issues label Mar 5, 2019
@SteveSandersonMS
Copy link
Member Author

SteveSandersonMS commented Mar 5, 2019

OK, I've been reading through everything that's ever been written about this, and am formulating it into a proposed design.

Scenarios

  • Control over event bubbling
    • Example: Multiple nested <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.
    • Example: Modal dialog, where if you click outside the dialog (which is a parent element) it should dismiss it, but clicks inside the dialog should not.
    • Overall, you need some way to control whether an certain event X bubbles upwards from a certain element Y (which is independent of whether or not you have a handler for event X on element Y)
  • Control over default actions
    • Example: on an <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.
    • Example: on a checkbox or select, to avoid changing state on click even though you do respond in another way
    • Example: on a text box, to block forbidden characters on keydown (and maybe also respond to them in some other way than appending the char to the box)

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.

    <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.

@SteveSandersonMS
Copy link
Member Author

The runtime part of this is now done and was merged in #14509

The tooling part is going to happen in 3.1.0-preview2, so assigning to @ajaybhargavb for the remaining work.

@SteveSandersonMS
Copy link
Member Author

In fact we have a different issue tracking the tooling work for this (#14517), so closing this one.

@SteveSandersonMS SteveSandersonMS added Done This issue has been fixed and removed Done This issue has been fixed labels Oct 1, 2019
@pranavkm pranavkm moved this from In progress to Done in Blazor 3.1 Oct 2, 2019
@mkArtakMSFT mkArtakMSFT added the Done This issue has been fixed label Oct 4, 2019
@arivoir
Copy link

arivoir commented Oct 23, 2019

Was this finally added to the released Blazor 3.0?

@danroth27
Copy link
Member

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.

@MaverickMartyn
Copy link

Awesome. :) I just started fiddling with Blazor and ran into this requirement for drag and drop functionality.

@mrpmorris
Copy link

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.

@legistek
Copy link

WPF has the same issue. You have to mark RoutedEventArgs as Handled before your event handler does anything async.

@legistek
Copy link

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?

@ghost ghost locked as resolved and limited conversation to collaborators Dec 22, 2019
@jaredpar jaredpar removed this from Done in Blazor 3.1 Jan 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components Components Big Rock This issue tracks a big effort which can span multiple issues Done This issue has been fixed enhancement This issue represents an ask for new feature or an enhancement to an existing one Tooling This issue has a tooling impact, which means the execution and release should be in sync with VS rel
Projects
None yet
Development

No branches or pull requests