Blazor listen to javascript event
Asked Answered
F

2

8

I have a javascript event called Hello:

addEventListener('hello', function () {
  alert("event listener");
})

and, in another javascript function, I raise the event:

let event = new Event("hello", { bubbles: true });
document.dispatchEvent(event);

What I want to do now is let the event trigger in a javascript function. Blazor should listen to the event, not javascript calling a Blazor method.

Hope anyone can assist me.

Regards me,

Froze answered 18/5, 2021 at 22:26 Comment(5)
What version of .NET are you using?Quin
i'm using .Net 5.0Froze
I only asked because .NET 6 preview 2 introduces custom event types to Blazor. Until then, if you want to handle a custom event like yours, you would need to "trick" blazor by having your custom event handler dispatch a standard event that your are listening for, e.g. have something like <div id='foo' @onchange=HandleHello></div> in your Blazor code, then in your JS code dispatch a change event to foo whenever you capture a hello event.Quin
Thanks for the tip. i can still change to version 6 prev 2 i just started building the app. What is your sugestion?Froze
Go to .NET 6 preview 3, then you can do this: devblogs.microsoft.com/aspnet/…Quin
C
11

For custom events, you will need to manually utilize JavaScript/.NET interoperability.

Using the Instance Method Call method:

  • Pass the .NET instance by reference to JavaScript:
    • Make a static call to DotNetObjectReference.Create.
    • Wrap the instance in a DotNetObjectReference instance and call Create on the DotNetObjectReference instance. Dispose of DotNetObjectReference objects (an example appears later in this section).
  • Invoke .NET instance methods on the instance using the invokeMethod or invokeMethodAsync functions. The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.

Example

Note, this is a very simplified example. You probably want to add a few things; start by IDisposable on your interop classes to avoid memory leaks.

  1. In C#, create a helper class to manage the interop:
public class CustomEventHelper
{
    private readonly Func<EventArgs, Task> _callback;

    public CustomEventHelper(Func<EventArgs, Task> callback)
    {
        _callback = callback;
    }

    [JSInvokable]
    public Task OnCustomEvent(EventArgs args) => _callback(args);
}

public class CustomEventInterop : IDisposable
{
    private readonly IJSRuntime _jsRuntime;
    private DotNetObjectReference<CustomEventHelper> Reference;

    public CustomEventInterop(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    public ValueTask<string> SetupCustomEventCallback(Func<EventArgs, Task> callback)
    {
        Reference = DotNetObjectReference.Create(new ScrollEventHelper(callback));
        // addCustomEventListener will be a js function we create later
        return _jsRuntime.InvokeAsync<string>("addCustomEventListener", Reference);
    }

    public void Dispose()
    {
        Reference?.Dispose();
    }
}
  1. In a Blazor component, add an instance of the interop class (Interop) and add a local method as a callback (HandleCustomEvent):
private CustomEventInterop Interop { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender) {
    if (!firstRender)
    {
        return;
    }
    Interop = new(JS);
    await Interop.SetupCustomEventCallback(args => HandleCustomEvent(args));
    HasRendered = true;
}

private void HandleCustomEvent(EventArgs args) {
    // ... handle custom event here
}
  1. In JavaScript, add a method that references the DotNetObjectReference and can call the interop in C#:
function addCustomEventListener(dotNetObjectRef) {
  document.addEventListener('hello', (event) => {
    // Calls a method by name with the [JSInokable] attribute (above)
    dotNetObjectRef.invokeMethodAsync('OnCustomEvent')
  });
}

If using TypeScript, you might check out this GitHub Issue.

Creditor answered 18/5, 2021 at 23:11 Comment(12)
I Only want to a subscibe to a event created in javascript. There are no button or ui elements used. IT's like the observer paternFroze
Yeah, you have to use JS Interop for that. As of .NET 5, this is the only way to listen for custom events in blazor.Creditor
alright :), will look into it. I'm using it to create a interop with pixijs :)Froze
Ps: What more thing could be added? Example?Froze
Depends on what you want to do. You might want a way to tell JS to stop sending events after the component disposes, for example. I'd ask that in a new question though; bit out of scope for this post.Creditor
This is not the way to create custom event in Blazor. @Teckniel, you should follow the advice made by Mister MagooSophister
@Sophister unless you are using .NET 6 preview, JS interop is the best way to handle real custom events. mrmagoo's hack may work, but it's still that: a hack.Creditor
On the contrary, yours is a hack, his is the native Blazor way to create custom events.Sophister
Let's not be confused, moving to .NET6 P2/3 and using a custom event type is the "blazor way" but is still in preview. Anything else is a workaround. I wouldn't personally use this method but it will certainly work and is a solid way to do it.Quin
Exactly, and this isn't the only way to use JS interop to achieve an event listener in Blazor. Until .NET 6 ships, I think JS interop is the most robust way to solve the problem from the question. It could even be expanded to accept a serializable payload from js, which you can't do with the @onclick workaround. If you don't need anything fancy, then @mister magoo has given you a fine workaround as well. I suspect more appealing answers will arrive when .NET 6 ships.Creditor
@Connor Switching to .Net 6.0 prev 3 since it is the best way to implement i guess, for long time support. and also to have a more style of sperate of concern. Regards and thanks fr the help!Froze
Even now, after .Net6 shipped, I cant make the .net6 way work.Mejia
P
14

For anyone wondering the solution proposed by @Mister Magoo is no longer a preview for .NET 6 and is documented here with some exemples.

In a nutshell :

Create a C# class with the EventHandlerAttribute :

[EventHandler("oncityclicked", typeof(CustomSelectionCityEventArgs),
    enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

public class CustomSelectionCityEventArgs: EventArgs
{
    public Guid Id { get; set; }
}

Add JS inside ./wwwroot/index.html and after <script src="_framework/blazor.webview.js" autostart="false"></script> :

<script>
    Blazor.registerCustomEventType('cityclicked', {
        createEventArgs: event => {
            return {
                id: event.detail
            };
        }
    });
</script>

In you razor :

@code {
    private void HandleCityClicked(CustomSelectionCityEventArgs eventArgs)
    {
        Console.WriteLine("Bouep");
    }
}

<div id="CarteLeaflet" @oncityclicked="HandleCityClicked"></div>

And finally in the JS you can dispatch the event :

function OnMarkerClick(pId) {
    const event = new CustomEvent('cityclicked', {
        bubbles: true,
        detail: pId
    });
    document.getElementById('CarteLeaflet').dispatchEvent(event);
}

Don't make the same mistake as me, the event name in the C# should start with "on" (JS : "cityclicked", C# : "oncityclicked").

Payable answered 21/12, 2022 at 10:43 Comment(0)
C
11

For custom events, you will need to manually utilize JavaScript/.NET interoperability.

Using the Instance Method Call method:

  • Pass the .NET instance by reference to JavaScript:
    • Make a static call to DotNetObjectReference.Create.
    • Wrap the instance in a DotNetObjectReference instance and call Create on the DotNetObjectReference instance. Dispose of DotNetObjectReference objects (an example appears later in this section).
  • Invoke .NET instance methods on the instance using the invokeMethod or invokeMethodAsync functions. The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.

Example

Note, this is a very simplified example. You probably want to add a few things; start by IDisposable on your interop classes to avoid memory leaks.

  1. In C#, create a helper class to manage the interop:
public class CustomEventHelper
{
    private readonly Func<EventArgs, Task> _callback;

    public CustomEventHelper(Func<EventArgs, Task> callback)
    {
        _callback = callback;
    }

    [JSInvokable]
    public Task OnCustomEvent(EventArgs args) => _callback(args);
}

public class CustomEventInterop : IDisposable
{
    private readonly IJSRuntime _jsRuntime;
    private DotNetObjectReference<CustomEventHelper> Reference;

    public CustomEventInterop(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    public ValueTask<string> SetupCustomEventCallback(Func<EventArgs, Task> callback)
    {
        Reference = DotNetObjectReference.Create(new ScrollEventHelper(callback));
        // addCustomEventListener will be a js function we create later
        return _jsRuntime.InvokeAsync<string>("addCustomEventListener", Reference);
    }

    public void Dispose()
    {
        Reference?.Dispose();
    }
}
  1. In a Blazor component, add an instance of the interop class (Interop) and add a local method as a callback (HandleCustomEvent):
private CustomEventInterop Interop { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender) {
    if (!firstRender)
    {
        return;
    }
    Interop = new(JS);
    await Interop.SetupCustomEventCallback(args => HandleCustomEvent(args));
    HasRendered = true;
}

private void HandleCustomEvent(EventArgs args) {
    // ... handle custom event here
}
  1. In JavaScript, add a method that references the DotNetObjectReference and can call the interop in C#:
function addCustomEventListener(dotNetObjectRef) {
  document.addEventListener('hello', (event) => {
    // Calls a method by name with the [JSInokable] attribute (above)
    dotNetObjectRef.invokeMethodAsync('OnCustomEvent')
  });
}

If using TypeScript, you might check out this GitHub Issue.

Creditor answered 18/5, 2021 at 23:11 Comment(12)
I Only want to a subscibe to a event created in javascript. There are no button or ui elements used. IT's like the observer paternFroze
Yeah, you have to use JS Interop for that. As of .NET 5, this is the only way to listen for custom events in blazor.Creditor
alright :), will look into it. I'm using it to create a interop with pixijs :)Froze
Ps: What more thing could be added? Example?Froze
Depends on what you want to do. You might want a way to tell JS to stop sending events after the component disposes, for example. I'd ask that in a new question though; bit out of scope for this post.Creditor
This is not the way to create custom event in Blazor. @Teckniel, you should follow the advice made by Mister MagooSophister
@Sophister unless you are using .NET 6 preview, JS interop is the best way to handle real custom events. mrmagoo's hack may work, but it's still that: a hack.Creditor
On the contrary, yours is a hack, his is the native Blazor way to create custom events.Sophister
Let's not be confused, moving to .NET6 P2/3 and using a custom event type is the "blazor way" but is still in preview. Anything else is a workaround. I wouldn't personally use this method but it will certainly work and is a solid way to do it.Quin
Exactly, and this isn't the only way to use JS interop to achieve an event listener in Blazor. Until .NET 6 ships, I think JS interop is the most robust way to solve the problem from the question. It could even be expanded to accept a serializable payload from js, which you can't do with the @onclick workaround. If you don't need anything fancy, then @mister magoo has given you a fine workaround as well. I suspect more appealing answers will arrive when .NET 6 ships.Creditor
@Connor Switching to .Net 6.0 prev 3 since it is the best way to implement i guess, for long time support. and also to have a more style of sperate of concern. Regards and thanks fr the help!Froze
Even now, after .Net6 shipped, I cant make the .net6 way work.Mejia

© 2022 - 2024 — McMap. All rights reserved.