Skip to main content Skip to footer

How to Use a JavaScript Interop Inside a Blazor Component

Sometimes it’s necessary to execute JavaScript code from a Blazor component to interact with the HTML DOM. For example, JavaScript is required to set the focus in an HTML element and to retrieve the size (width and height) of a component. Blazor has no way to do these tasks directly, but instead provides us the JSRuntime class to interact with JavaScript.

In this article, I’m going to show you how to use the JSRuntime for a component lifecycle that needs this capability. We'll create a checkbox component to demonstrate the necessary steps. The checkbox supports three states (checked, unchecked, and indeterminate), but, since it’s not possible to set the indeterminate state from HTML, we’re forced to use JavaScript and the JSRuntime.

**In another post, we introduce WijmoBlazor - a set of .NET classes that wrap Wijmo controls to be used in Blazor.

How to Use a JavaScript Interop Inside a Blazor Component

Creating a Three-State Blazor Checkbox Component

First, we'll create a CheckBox.js file to put the JavaScript code that will be part of the checkbox Blazor component. This file will be in the “wwwroot” folder of the component library.

Despite JavaScript files are deployed automatically it is necessary to reference them manually in the html page, by adding the corresponding <script/> tag. This way JavaScript functions are available to the Blazor components.

Second, we’re going to create the CheckBox.cs file that will hold the component code. To make this component interesting we’re going to add the property IsChecked and IsThreeState.

[Parameter]
public bool? IsChecked { get; set; }
[Parameter]
public bool IsThreeState { get; set; }

In the BuildRenderTree method we will create an “input” type element: “checkbox”. Unfortunately, we cannot bind the IsChecked property to the HTML element, so we’ll use JavaScript to update the element and listen to the DOM events.

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    builder.OpenElement(0, "input");
    builder.AddAttribute(1, "type", "checkbox");
    builder.AddAttribute(2, "onClick", "window.CheckBox.onClick(this);");
    builder.AddElementReferenceCapture(3, r => CheckBoxReference = r);
    builder.CloseElement();
}

The two first lines are trivial. In the third, we’re setting a call to a JavaScript function that will be placed in the CheckBox.js file we previously created. This function will be called when the user clicks the Checkbox. In the forth line, we’ll keep a reference to the element we’re going to need when calling the JSRuntime later.

To make sure the HTML element is displaying the IsChecked value of the component, we’re going to implement the OnAfterRenderAsync method to initialize the html element.

protected override async Task OnAfterRenderAsync()
{
    await JSRuntime.InvokeAsync<object>("CheckBox.init", CheckBoxReference, IsChecked, IsThreeState);
}

This call does not need to be executed every time the OnAfterRenderAsync method is called. It is only necessary the first time. We can set a flag IsInitialized for that purpose, and avoid the extra calls.

Meanwhile, the CheckBox.js code will contain the script that will update the HTML element.

window.CheckBox = {
    init: function (element, value, isThreeState) {
        element.IsThreeState = isThreeState;
        window.CheckBox.setValue(element, value);
    },
    setValue: function (element, value) {
        element.IsChecked = value;
        if (value == null) {
            element.indeterminate = true;
        }
        else {
            element.indeterminate = false;
            element.checked = value;
        }
    },
};

So far, we have a component whose IsChecked property value is reflected in the html element via JavaScript interop. Now we need to create the JavaScript function that will be called when the user clicks the checkbox and send that value back to the Blazor component.

To allow the JavaScript code to call the component, we need to create a method in the component and add the JSInvokable attribute on it:

[JSInvokable]
public Task OnClickAsync(bool? value)
{
    …
}

Since this method must be public, I recommend putting it in a separate class to avoid exposing a method that should be internal.

Also, we need to pass an instance of the component to JavaScript to be used when the user clicks the checkbox. We’re going to modify the OnAfterRenderAsync to include this parameter.

protected override async Task OnAfterRenderAsync()
{
    await JSRuntime.InvokeAsync<object>("CheckBox.init", CheckBoxReference, new DotNetObjectRef(this), IsChecked, IsThreeState);
}

In the JavaScript init method, it will save the component reference in the HTML element, and then will be used in the onClick method.

Here is the whole script:

window.CheckBox = {
    init: function (element, componentReference, value, isThreeState) {
        element.ComponentReference = componentReference;
        element.IsThreeState = isThreeState;
        window.CheckBox.setValue(element, value);
    },
    setValue: function (element, value) {
        element.IsChecked = value;
        if (value == null) {
            element.indeterminate = true;
        }
        else {
            element.indeterminate = false;
            element.checked = value;
        }
    },
    onClick: function (element) {
        var newValue;
        if (element.IsChecked == null) {
            newValue = true;
        }
        else if (element.IsChecked) {
            newValue = false;
        }
        else {
            newValue = element.IsThreeState ? null : true;
        }
        window.CheckBox.setValue(element, newValue);
        var componentReference = element.ComponentReference;
        componentReference.invokeMethodAsync('OnValueSetAsync', newValue)
            .then(r => console.log(r));
    },
};

Blazor JSRuntime Conclusion

By making use of the Blazor JSRuntime and DotNetObjectRef, we can create a component that incorporates the use of JavaScript to bridge in the areas where Blazor binding can not be used. This way we encapsulate the complexities and give customers a component that can be used in both client and server-side scenarios.

GrapeCity has a complete set of JavaScript UI components and powerful Excel-like JavaScript spreadsheet components. We have deep support for Angular (as well as React and Vue) and are dedicated to extending our components for use in modern JavaScript frameworks.

Empower your development. Build better applications.

Try GrapeCity's Tools for JavaScript and .NET

Alvaro Rivoir

Computer Engineer
comments powered by Disqus