Event Handling In Blazor

Introduction

The earlier version of Blazor supports a limited number of events. It only supports onclick and onchange events. The current version of Blazor provides pretty rich event handling. In the current version of Blazor, you can access most of the DOM events with the HTML element. The value of the attribute is treated as an event handler. 

Following is Razor syntax to define the event in the Blazor component,

@on{DOM EVENT}="{DELEGATE}"

Blazor also supports an asynchronous delegate event handler that returns the Task. The delegate event handler of Blazor has automatically triggered a UI render event, so there is no need to manually call StateHasChanged every time on the event. The EventCallback<T> class is exposed as a parameter to the component so, it can easily notify consumers when something happened. 

Example

In the following example, the "IncrementCount" method is called every time when the button is clicked.

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    private int currentCount = 0;
    private void IncrementCount()
    {
        currentCount++;
    }
}

Event arguments

Blazor also provides the event argument if event-supported event arguments are in the event method definition. For example, MouseEventArgs provide the mouse coordinates when the user moves the mouse pointer in the UI. 

The following table contains supported Event arguments,

Events Class DOM Events
Clipboard ClipboardEventArgs oncut, oncopy, onpaste
Drag DragEventArgs ondrag, ondragstart, ondragenter, ondragleave, ondragover, ondrop, ondragend
Error ErrorEventArgs onerror
Event EventArgs onactivate, onbeforeactivate, onbeforedeactivate, ondeactivate, onfullscreenchange, onfullscreenerror, oncanplay, oncanplaythrough, oncuechange, ondurationchange, onemptied, etc.
Focus FocusEventArgs onfocus, onblur, onfocusin, onfocusout
Input ChangeEventArgs onchange, oninput
Keyboard KeyboardEventArgs onkeydownonkeypressonkeyup
Mouse MouseEventArgs onclick, oncontextmenu, ondblclick, onmousedown, onmouseup, onmouseover, onmousemove, onmouseout
Mouse pointer PointerEventArgs onpointerdown, onpointerup, onpointercancel, onpointermove, onpointerover, onpointerout, onpointerenter, onpointerleave, ongotpointercapture, onlostpointercapture
Mouse wheel WheelEventArgs onwheel, onmousewheel
Progress ProgressEventArgs onabort, onload, onloadend, onloadstart, onprogress, ontimeout
Touch TouchEventArgs ontouchstart, ontouchend, ontouchmove, ontouchenter, ontouchleave, ontouchcancel

Example

In the following example, the MouseClick method used MouseEventArgs, and this method show the mouse coordinates of a button when it clicked.

@*MousePointerExample.razor*@
@page "/mouseclick"

<h3>Mouse Coordinates</h3>
<button class="btn btn-primary" @onclick="MouseClick">Button 1</button>

<br />
<br />

<button class="btn btn-primary" @onclick="MouseClick">Button 2</button>

<p>@mouseCoordinatesString</p>

@code {
    private string mouseCoordinatesString;

    private void MouseClick(MouseEventArgs e)
    {
        mouseCoordinatesString = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
    }
}

Define delegate using Lambda expressions

You can also define event delegate using the Lambda expressions. It is very useful to define a small inline function with an event handler. 

In the following example, I have defined an inline function to increment the counter.

@*LambdaExpressionExample.razor*@
@page "/lambdaexpressions"

<h3>Lambda Expression Example</h3>

<button @onclick="@(e => currentCount++)">
    Click Me
</button>
<p> Current Count:@currentCount </p>


@code {
    private int currentCount = 0;
}

You can also pass additional information as a parameter of the method along with the event argument. In the following example, I have sent the button number along with the MouseEventArgs argument.

@*LambdaExpressionExample.razor*@
@page "/lambdaexpressions"

<h3>Lambda Expression Example</h3>

<button class="btn btn-primary" @onclick="@(e => MouseClick(e, 1))">Button 1</button>

<br />
<br />

<button class="btn btn-primary" @onclick="@(e => MouseClick(e, 2))">Button 2</button>
<p>@mouseCoordinatesString</p>

@code {

    private string mouseCoordinatesString;

    private void MouseClick(MouseEventArgs e, int buttonNumber)
    {
        mouseCoordinatesString = $"Mouse coordinates of button {buttonNumber}: {e.ScreenX}:{e.ScreenY}";
    }
}

Event Callback

Using event callback, you can expose events across the components. The most common scenario is to execute the parent component method from the child component's button click. The EventCallback<T> class is exposed as a parameter to the component, so it can easily notify consumers when something happened. The public property of type EventCallback<T> is declared and decorated with the Parameter attribute. 

The following example demonstrates how a child component's button click event handler is set up to received event callback on the parent component. Here, I am using MouseEventArgs for even callback type when the child component's button click then the parent component's "EventFromChild" method called. As described earlier, the callback method does not require to call StateHasChanged as it automatically called when a child component event trigger.

@*ChildComponent.razor*@
<h4>Child Component</h4>

<button @onclick="OnClickCallback">
    Click Me
</button>

@code {

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
@*ParentComponent.razor*@
@page "/eventcallback"

<h3>Parent Component</h3>

<ChildComponent OnClickCallback="@EventFromChild" />

<p>@message</p>

@code {
    private string message;

    private void EventFromChild(MouseEventArgs e)
    {
        message = $"Mouse coordinates of child component button - ({e.ScreenX}:{e.ScreenY})";
    }
}

The EventCallback also allows asynchronous delegates and weakly typed (allows to pass any type of argument). It is recommended to use strongly typed. 

Differences between Blazor's EventCallback<T> and .NET events

There are few differences between Blazor's EventCallback<T> and .NET events,

  • Blazor's EventCallback<T> is a single-cast event (it can assign to a single value or single method) handler but the .NET event is multi-cast 
  • Blazor's EventCallback<T> is a read-only structure, so it cannot be null but .NET Event is classes. 
  • Blazor's EventCallback<T> can be asynchronous but standard .NET events are synchronous

Apart from this, Blazor's EventCallback<T> can be set using Razormarkup and provides automatic state change detection 

Prevent default actions

Sometimes, you want to prevent an event's default action. For example, the anchor's default action is to navigate to the URL set in the "href" attribute. Now, you want to suppress the default behavior but perform the event. The Blazor provides  @on{DOM EVENT}:preventDefault directive attribute to prevent the default action for an event.

In the following example, the default action of the anchor tag is suppressed but still executes the DOM event.

@*PreventDefault.razor*@
@page "/preventDefault"

<h3>Prevent Default Example</h3>

<p>
    
    <a href="https://www.c-sharpcorner.com/"
       @onclick="KeyHandler"
       @onclick:preventDefault="true">
        Click Here
    </a>
</p>
<p>@count</p>

@code {
    private int count = 0;

    private void KeyHandler()
    {
        count++;
    }
}

If you are not specified value with @on{DOM EVENT}:preventDefault attribute, it will set to true. Also, you can provide the value of this attribute using expression.  

Stop event propagation

It is also possible to stop event propagation with Blazor. The  @on{DOM EVENT}:stopPropagation directive attribute is used to stop event propagation. It stops the event bubbling to the parent tag.

In this following example, there are two <div> element inside the parent <div> element. The first <div>'s event does not stop to propagate event to parent but second <div> event stop event propagation to parent.

@*StopEventPropagation.razor*@
@page "/stopeventpropagation"
<h3>Stop Event Propagation</h3>

<input @bind="stopPropagation" type="checkbox" name="stopPropagation" />
<label for="stopPropagation"> Stop Propagation</label>
<br>

<div class="m-1 p-1 border" id="primary-div" @onclick="OnClickParentDiv">
    <p>
        Parent Div
    </p>
    <div class="m-1 p-1 border" @onclick="OnClickChildDiv">
        Child div doesn't stop event propagation when clicked.
    </div>

    <div class="m-1 p-1 border" @onclick="OnClickChildDiv"
         @onclick:stopPropagation="stopPropagation">
        Child div stops event propagation when clicked.
    </div>
</div>

<p>
    @message
</p>

@code {

    private bool stopPropagation= true;

    private string message;

    private void OnClickParentDiv() =>
        message = $"The parent div was Clicked. {DateTime.Now}";

    private void OnClickChildDiv() =>
        message = $"A child div was Clicked. {DateTime.Now}";
}

Summary 

The current version of the Blazor provides rich event handling. It supports almost all DOM events. 

You can view or download the source code from the GitHub link here.