Blazor already supported custom events. With .NET 6, Blazor ads support for custom event arguments and JavaScript initializers. By using these new Blazor functionalities, you can make this a really seamless experience for your component consumers. 

Let's take a step back first and see why custom events and custom arguments are useful. The docs describe event handlers like this:

Blazor supports all the normal Document Object Model (DOM) events out of the box with an @on{DOM EVENT}="{DELEGATE}" Razor syntax: 

It also supports a large set of event arguments. Nice, but what if you would like to add some additional data, like a timestamp, when you paste from the clipboard? Or what if you would like to paste pictures from the clipboard like Twitter does? Or what if you need to repurpose an existing browser event? That is where cusom events and custom arguments come in.

I'll follow the example that was being used in the blog post that accompanied the .NET 6 preview 2 release. Don't worry, it is not going to be a straight copy ot that example. It will be updated with some .NET 6 final release goodness. The example describes the first use case I mentioned earlier (add some data to a clipboard paste).

The Blazor part

First we need to define a name for the custom event as well a a .NET class that holds the (custom) event arguments. You could do this in a filed called EventHandlers.cs for example:

[EventHandler("oncustompaste", typeof(CustomPasteEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
    // This static class doesn't need to contain any members. It's just a place where we can put
    // [EventHandler] attributes to configure event types on the Razor compiler. This affects the
    // compiler output as well as code completions in the editor.
}

public class CustomPasteEventArgs : EventArgs
{
    // Data for these properties will be supplied by custom JavaScript logic
    public DateTime EventTimestamp { get; set; }
    public string PastedData { get; set; }
}

We are folowing the 'on...' syntax described in the docs mentioned earlier.

Once the compiler has picked this up, you can add an event called @oncustompaste to your components. This could look as follows:

@page "/"

<p>Paste something into the following text box:</p>
<input @oncustompaste="HandleCustomPaste" />
<p>@message</p>

@code {
    string message;

    void HandleCustomPaste(CustomPasteEventArgs eventArgs)
    {
        message = $"At {eventArgs.EventTimestamp.ToShortTimeString()}, you pasted: {eventArgs.PastedData}";
    }
}

This is what needs to be done on the Blazor side of the house. For things to start working, we need to wire up some JavaScript that can supply the data to the CustomPasteEventArgs class (do not forget to inherit from EventArgs. Your code won't work!) 

The JavaScript part

The Javascript code needed looks like this:

Blazor.registerCustomEventType('custompaste', {
        browserEventName: 'paste',
        createEventArgs: event => {
            return {
                eventTimestamp: new Date(),
                pastedData: event.clipboardData.getData('text')
            };
        }
    });

You can see from this code that it is using the convention to not start an event name with 'on', that we hook into the normal browser 'paste' event and that we are enriching the event arguments with the date and time the text is being pasted. Up until .NET 6 RC 1 of ASP.NET Core, you would have to put the necessary script somewhere in a separate Javascript file in the wwwroot folder that you include in your index.html (or _Host.cshtml) file. Or you needed to include a script block in that file. That is no longer how it should be done. With RC1 JavaScript initializers were added to Blazor. As described in the RC 1 post:

JavaScript initializers provide a way to execute some logic before and after a Blazor app loads. This is useful for customizing how a Blazor app loads, initializing libraries before Blazor starts up, and configuring Blazor settings.To define a JavaScript initializer, add a JavaScript module to the web root of your project (usualy wwwroot) named {LIBRARY NAME}.lib.module.js. Your module can export the following well-known functions:
  • beforeStart: Called before Blazor boots up on the .NET side. Used to customize the loading process, logging level, and other hosting model specific options.
    • In Blazor WebAssembly, beforeStart receives the Blazor WebAssembly options and any extensions added during publishing.
    • In Blazor Server, beforeStart receives the circuit start options.
    • In BlazorWebViews, no options are passed.
  • afterStarted: Called after Blazor is ready to receive calls from JavaScript. Used to initialize libraries by making .NET interop calls, registering custom elements, etc.
    • The Blazor instance is always passed to afterStarted as an argument.
A basic JavaScript initializer would looks like this (in case the library is named RazorClassLibrary1):

RazorClassLibrary1.lib.module.js

export function beforeStart(options) {
    console.log("beforeStart");
}
export function afterStarted(blazor) {
    console.log("afterStarted");
}

So combining these two code samples we need to create a JavaScript file in the wwwroot folder with the filename {LIBRARY NAME}.lib.module.js containing the script:

export function afterStarted(blazor) {
Blazor
.registerCustomEventType('custompaste', { browserEventName: 'paste', createEventArgs: event => { return { eventTimestamp: new Date(), pastedData: event.clipboardData.getData('text') }; } });
}

After that, everything will be picked up by Blazor automatically. JavaScript initializers are detected as part of the build and then imported automatically in Blazor apps. This removes the need in many cases for manually adding script references when using Blazor libraries. Making life a lot easier for component consumers.

Putting it all together

The result of this looks like this (when using the Microsoft.Fast.Component.FluentUI library):

The source code for this example can be found at my GitHub: https://github.com/vnbaaij/FluentBlazorApp

Hope this helps!

Comments


Comments are closed