© Peter Himschoot 2019
Peter HimschootBlazor Revealedhttps://doi.org/10.1007/978-1-4842-4343-5_8

8. JavaScript Interoperability

Peter Himschoot1 
(1)
Melle, Belgium
 

Sometimes there is just no escape from using JavaScript ☺. For example, Blazor itself uses JavaScript to update the browser’s DOM from your Blazor components. You can, too. In this chapter, you will look at interoperability with JavaScript and, as an example, you will build a Blazor component library to display a line chart using a popular open-source JavaScript library for charts. This chapter does require you to have some basic JavaScript knowledge.

Calling JavaScript from C#

Browsers have a lot of capabilities you might want to use in your Blazor web site. For example, you might want to use the Browser’s local storage to keep track of some data. Thanks to Blazor’s JavaScript interoperability, this is easy.

Providing a Glue Function

To call JavaScript functionality you start by building a glue function in JavaScript. I like to call these functions glue functions (my own naming convention) because they become the glue between .NET and JavaScript.

Glue functions are regular JavaScript functions. A JavaScript glue function can take any number of arguments, on the condition that they are JSON serializable (meaning that you can only use types that are convertible to JSON, including classes whose properties are JSON serializable). This is required because the arguments and return type are sent as JSON between .NET and JavaScript runtimes.

You then add this function to the global scope object, which in the browser is the window object. You will look at an example a little later, so keep reading. You can then call this JavaScript glue function from your Blazor component, as you will see in the next section.

Using JSRuntime to Call the Glue Function

Back to .NET land. To invoke your JavaScript glue function from C#, you use the .NET IJSRuntime instance provided through the JSRuntime.Current static property. This instance has the InvokeAsync<T> generic method, which takes the name of the glue function and its arguments and returns a value of type T, which is the .NET return type of the glue function. If this sounds confusing, you will look at an example right away...

The InvokeAsync method is asynchronous to support all asynchronous scenarios, and this is the recommended way of calling JavaScript. If you need to call the glue function synchronously, you can downcast the IJSRuntime instance to IJSInProcessRuntime and call its synchronous Invoke<T> method. This method takes the same arguments as InvokeAsync<T> with the same constraints.

Storing Data in the Browser with Interop

It’s time to look at an example and you will start with the JavaScript glue function. Open the MyFirstBlazor solution you used in previous chapters. Open the wwwroot folder from the MyFirstBlazor.Client project and add a new subfolder called scripts. Add a new JavaScript file to the scripts folder called interop.js and add the glue functions from Listing 8-1. These glue functions allow you to access the localStorage object from the browser, which allows you to store data on the client’s computer so you can access it later, even after the user has restarted the browser.
window.interop = {
  setProperty: function (name, value) {
    window.localStorage[name] = value;
    return value;
  },
  getProperty: function (name) {
    return window.localStorage[name];
  }
};
Listing 8-1

The getProperty and setProperty Glue Functions

Your Blazor web site needs to include this script, so open the index.html file from the wwwroot folder and add a script reference after the Blazor script, as shown in Listing 8-2.
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width">
    <title>MyFirstBlazor5</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css"
          rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
  <app>Loading...</app>
  <script src="_framework/blazor.webassembly.js"></script>
  <script src="scripts/interop.js"></script>
</body>
</html>
Listing 8-2

Including the Script Reference in Your HTML Page

When you use a Blazor component library you don’t need to include the script in the index.html page. You will see an example of this later in this chapter.

Now let’s look at how to call these setProperty/getProperty glue functions. Open the Index.cshtml Blazor component and modify it to look like Listing 8-3. To keep things simple, you will call the glue functions synchronously, which requires the IJSInProcessRuntime instance, which you will store in the IPR (in-process-runtime) variable.
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<input type="number" bind="@Counter" />
<SurveyPrompt Title="How is Blazor working for you?" />
@functions {
    private IJSInProcessRuntime IPR 
    => (IJSInProcessRuntime)JSRuntime.Current;
    public int Counter
    {
      get
      {
        string value =
          IPR.Invoke<string>("interop.getProperty",
                             nameof(Counter));
        if( value != null && int.TryParse(value, out var v))
        {
          return v;
        }
        return 0;
      }
      set
      {
        IPR.Invoke<string>("interop.setProperty",
                           nameof(Counter), $"{value}");
      }
    }
}
Listing 8-3

Invoking the Glue Functions from a Blazor Component

This looks a bit like the Counter component, but now the Counter stores its value in the browser’s window.localstorage. To do this you use a Counter property, which invokes your glue functions in the property setter and getter. These glue functions are synchronous, so you first create a private IPR property to store the IJSInProcessRuntime instance (because you don’t want to repeat yourself, and copy-paste has been the cause of so many subtle bugs). Then in the Counter property’s getter you invoke the window.interop.getProperty glue function. But because localstorage uses strings you need to convert between the Counter of type int and string. There’s one more caveat: initially localstorage will not have a value yet, so if this returns a null reference you simply return 0. Similar in the Counter property’s setter, you invoke the window.interop.setProperty glue function, making sure you convert the int to a string. This last conversion is quite essential; otherwise you will see errors in the browser’s console.

Run the solution and modify the Counter’s value. Now when you refresh your browser you will see the last value of Counter. The Counter is now persisted between sessions! You can exit your browser, open it again, and you will see the Counter again with the last value.

Passing a Reference to JavaScript

Sometimes your JavaScript needs to access one of your HTML elements. You can do this by storing the element in an ElementRef and then passing this ElementRef to the glue function.

Never use JavaScript interop to modify the DOM because this will interfere with the Blazor rendering process! If you need to modify the browser’s DOM, use a Blazor component.

You should use this ElementRef as an opaque handle, meaning you can only pass it to a JavaScript glue function, which will receive it as a JavaScript reference to the element.

Let’s look at an example by setting the focus on the Counter input element using interop. Start by adding a property of type ElementRef to the @functions area in Index.html as in Listing 8-4.
private ElementRef inputElement { get; set; }
Listing 8-4

Adding an ElementRef Property

Then modify the input element to set the inputElement property as in Listing 8-5.
<input ref="@inputElement" type="number" bind="@Counter" />
Listing 8-5

Setting the inputElement

Now add another glue function to interop.js as in Listing 8-6.
window.interop = {
  setProperty: function (name, value) {
    window.localStorage[name] = value;
    return value;
  },
  getProperty: function (name) {
    return window.localStorage[name];
  },
  setFocus: function (element) {
    element.focus();
  }
};
Listing 8-6

Adding the setFocus glue function

Now comes the “tricky” part. Blazor will create your component and then call the lifecycle methods such as OnInit. If you invoke the setFocus glue function in OnInit the DOM has not been updated with the input element so this will result in a runtime error because the glue function will receive a null reference. You need to wait for the DOM to be updated, which means that you should only pass the ElementRef to your glue function in the OnAfterRender/OnAfterRenderAsync method!

Add the OnAfterRender method to the @functions section as in Listing 8-7.
protected override void OnAfterRender()
{
  IPR.Invoke<string>("interop.setFocus", inputElement);
}
Listing 8-7

Passing the ElementRef in OnAfterRender

Run your solution and you should see that the input element receives focus automatically, as in Figure 8-1.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig1_HTML.jpg
Figure 8-1

The Counter receives focus automatically

Calling .NET Methods from JavaScript

You can also call .NET methods from JavaScript. For example, your JavaScript might want to tell your component that something interesting has happened, like the user clicking something in the browser. Or your JavaScript might want to ask the Blazor component about some data it needs. You can call a .NET method, but with a couple of conditions. First, your .NET method’s arguments and return value need to be JSON serializable, the method must be public, and you need to add the JSInvokable attribute to the method. The method can be a static or an instance method.

To invoke a static method, you use the JavaScript DotNet.invokeMethodAsync or DotNet.invokeMethod function, passing the name of the assembly, the name of the method, and its arguments. To call an instance method, you pass the instance wrapped as a DotNetObjectRef to a JavaScript glue function, which can then invoke the .NET method using the DotNetObjectRef’s invokeMethodAsync or invokeMethod function, passing the name of the .NET method and its arguments. Let’s continue with the previous example. When you make a change to local storage, the storage triggers a JavaScript storage event, passing the old and new value (and more). This allows you to register for changes in other browser tabs or windows and use it to update the page.

Adding a Glue Function Taking a .NET Instance

Open interop.js from the previous example and add a watch function, as in Listing 8-8.
window.interop = {
  setProperty: function (name, value) {
    window.localStorage[name] = value;
    return value;
  },
  getProperty: function (name) {
    return window.localStorage[name];
  },
  setFocus: function (element) {
    element.focus();
  },
  watch: function (instance) {
    window.addEventListener('storage', function (e) {
      console.log('storage event');
      instance.invokeMethodAsync('UpdateCounter');
    });
  }
};
Listing 8-8

The watch Function Allows You to Register for Local Storage Changes

The watch function takes a reference to a DotNetObjectRef instance and invokes the UpdateCounter method when storage changes.

Adding a JSInvokable Method to Invoke

Open Index.cshtml and add the UpdateCounter method to the @functions area, as shown in Listing 8-9.
[JSInvokable]
public Task UpdateCounter()
{
  this.StateHasChanged();
  return Task.CompletedTask;
}
Listing 8-9

The UpdateCounter Method

This method triggers the UI to update with the latest value of Counter. Please note that this method follows the .NET async pattern returning a Task instance. Because you are not actually calling any async API, you return the Task.CompetedTask. To complete the example, add the OnInit lifecycle method shown in Listing 8-10.
protected override void OnInit()
{
  IPR.Invoke<string>("interop.watch",
                     new DotNetObjectRef(this));
}
Listing 8-10

The OnInit Method

The OnInit method wraps the Index component’s this reference in a DotNetObjectRef and passes it to the interop.watch function.

To see this in action, open two browser tabs on your web site. When you change the value in one tab you should see the other tab update to the same value, as shown in Figure 8-2.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig2_HTML.jpg
Figure 8-2

Updating the Counter in one tab updates the other tab

Building a Blazor Chart Component Library

In this section, you will build a Blazor component library to display charts by using a popular open source JavaScript library called Chart.js ( www.chartjs.org ). However, wrapping the whole library would make this chapter way too long, so you’ll just use a simple line-chart component.

Creating the Blazor Component Library

Open Visual Studio and start by creating a new Blazor project called ChartTestProject, as shown in Figure 8-3. This project will only be used for testing the chart component.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig3_HTML.jpg
Figure 8-3

Creating a new Blazor project

If you are using Code, open a command prompt and type
dotnet new blazorhosted -o ChartTestProject
With both Visual Studio and Code, open a command prompt on the directory containing the ChartTestProject solution (the folder where the .sln file is) and type
dotnet new blazorlib -o U2U.Components.Chart
This will create a new Blazor component library. Unfortunately, you cannot create this kind of project with Visual Studio (yet). Go back to Visual Studio, right-click the solution, and select Add ➤ Existing Project. Select the U2U.Components.Chart project. Your solution should look like Figure 8-4.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig4_HTML.jpg
Figure 8-4

The solution containing the component library

If you’re using Code, simply type this command to add the component library to the solution:
dotnet sln add U2U.Components.Chart/U2U.Components.Chart.csproj

Adding the Component Library to Your Project

Now you have the Blazor component library project. Let’s use it in the test project. Look for Component1.cshtml in the U2U.Components.Chart project and rename it to LineChart.cshtml. Add a reference to the component library in the client project. In Visual Studio, right-click the ChartTestProject and select Add ➤ Reference. Check the U2U.Components.Chart project, shown in Figure 8-5, and click OK.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig5_HTML.jpg
Figure 8-5

Adding a reference to the component library

With Code, use the integrated terminal, change the current directory to ChartTestProject.Client, and type this command:
dotnet add reference ../U2U.Components.Chart/U2U.Components.Chart.csproj

This will add a reference to the U2U.Components.Chart component library.

Look for the _ViewImports.cshtml file (the one next to App.cshtml) in the ChartTestProject and open it in the editor. Remember from Chapter 3 that to use a component from a library you need to add it as an MVC tag helper. Insert the @addTagHelper directive shown in Listing 8-11.
@using System.Net.Http
@using Microsoft.AspNetCore.Blazor.Layouts
@using Microsoft.AspNetCore.Blazor.Routing
@using Microsoft.JSInterop
@using ChartTestProject
@using ChartTestProject.Shared
@addTagHelper *, U2U.Components.Chart
Listing 8-11

Adding the LineChart tagHelper to the Blazor Project

Open the Index.cshtml file from the Pages folder and add the LineChart component shown in Listing 8-12.
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<LineChart/>
<SurveyPrompt Title="How is Blazor working for you?" />
Listing 8-12

Adding the LineChart Component

Build and run your application. It should look like Figure 8-6.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig6_HTML.jpg
Figure 8-6

Testing if the component library has been added correctly

Adding Chart.js to the Component Library

The LineChart component doesn’t look like a chart, so it’s time to fix this! First, you need to add the Chart.js JavaScript library to the component library project. Go to www.chartjs.org/ . This is the main page for Chart.js. Now click the GitHub button, shown in Figure 8-7, to open the project’s GitHub page.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig7_HTML.jpg
Figure 8-7

The Chart.js main page

Scroll down this page looking for the GitHub releases link. Press this link with your mouse and the release page will open, as shown in Figure 8-8.

Since it takes some time between writing a book and you reading it there is a big chance that the version will have incremented. Make sure you select a version starting with 2, since version 3 will contain breaking changes.

Click Chart.bundle.min.js to download it, as shown in Figure 8-8.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig8_HTML.jpg
Figure 8-8

GitHub releases page for Chart.js

After it has been downloaded, copy this file to the content folder of the U2U.Components.Chart project, as shown in Figure 8-9. All files in this folder are automatically downloaded into the browser by Blazor so you don’t need to add them to index.html.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig9_HTML.jpg
Figure 8-9

Copying Chart.bundle.min.js into the contents folder

Verifying If the JavaScript Library Loaded Correctly

You know about Murphy’s Law? It states, “Anything that can possibly go wrong, does.” Let’s make sure that the Chart.js library gets loaded by the browser. Run your Blazor project and open the browser’s debugger. Check if Chart.bundle.min.js has been loaded correctly. The easiest way to do this is to see if window.Chart has been set (Chart.js adds one constructor function called Chart to the window global object). You can do this from the Console tab of the debugger by typing window.Chart, as shown in Figure 8-10.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig10_HTML.jpg
Figure 8-10

Using the browser’s console to check the value of window.Chart

If this returns undefined, rebuild the U2U.Components.Chart project. Then you can try refreshing the browser after emptying the browser’s cache. When the browser’s debugger is shown, right-click the refresh button and you’ll get a drop-down menu, as shown in Figure 8-11. Select the Empty Cache and Hard Reload menu item.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig11_HTML.jpg
Figure 8-11

Reloading the page after clearing the cache

Adding Chart.js Data and Options Classes

Open your browser and type in www.chartjs.org/docs/latest/ . Here you can see a sample of using Chart.js in JavaScript. This library requires two data structures to be passed to it: one containing the chart data and one containing the options. This section will add these classes to the Blazor component library, but now using C#. Again, I am not going for full coverage of all the features of Chart.js to keep things crisp.

The ChartOptions Class

Let’s start with the options class. Right-click the U2U.Components.Chart library and add a new class called ChartOptions as in Listing 8-13.

This is a fair amount of code. You might consider copying it from the sources provided with this book. I’ve also left out comments describing each property for conciseness.
public class ChartOptions
{
  public class TitleOptions
  {
    public static readonly TitleOptions Default
    = new TitleOptions();
    public bool Display { get; set; } = false;
  }
  public class ScalesOptions
  {
    public static readonly ScalesOptions Default
    = new ScalesOptions();
    public class ScaleOptions
    {
      public static readonly ScaleOptions Default
      = new ScaleOptions();
      public class TickOptions
      {
        public static readonly TickOptions Default
        = new TickOptions();
        public bool BeginAtZero { get; set; } = true;
        public int Max { get; set; } = 100;
      }
      public bool Display { get; set; } = true;
      public TickOptions Ticks { get; set; }
      = TickOptions.Default;
    }
    public ScaleOptions[] YAxes { get; set; }
    = new ScaleOptions[] { ScaleOptions.Default };
  }
  public static readonly ChartOptions Default
  = new ChartOptions { };
  public TitleOptions Title { get; set; }
  = TitleOptions.Default;
  public bool Responsive { get; set; } = true;
  public bool MaintainAspectRatio { get; set; } = true;
  public ScalesOptions Scales { get; set; }
  = ScalesOptions.Default;
}
Listing 8-13

The ChartOptions Class

This C# class, with nested classes, reflects the JavaScript options object (partially) from Chart.js. Note that I’ve added Default static properties to each class to make it easier for developers to construct the options hierarchy.

The LineChartData Class

Chart.js expects you to give it the data it will render. For this, it needs to know a couple of things, like the color of the line, the color of the fill beneath the line, and, of course, the numbers to plot the graph. So how will you represent colors and points in your Blazor component? As it turns out, there are classes in .NET to represent colors and points: System.Drawing.Color and System.Drawing.Point. Unfortunately, you cannot use Color because it doesn’t convert into a JavaScript color, but you can allow users to use it in their code. I’ll discuss how to do this a little later. Add a new class LineChartData to the component library called LineChartData, as shown in Listing 8-14.
using System;
using System.Collections.Generic;
using System.Drawing;
namespace U2U.Components.Chart
{
  public class LineChartData
  {
    public class DataSet
    {
      public string Label { get; set; }
      public List<Point> Data { get; set; } = null;
      public string BackgroundColor { get; set; }
      public string BorderColor { get; set; }
      public int BorderWidth { get; set; } = 2;
    }
    public string[] Labels { get; set; }
    = Array.Empty<string>();
    public DataSet[] Datasets { get; set; }
  }
}
Listing 8-14

The LineChartData Class

Most of this class should be clear, except maybe for Array.Empty<string>(). This method returns an empty array of the generic argument. But why is this better? You cannot modify an empty array, so you can use the same instance everywhere (this is also known as the Flyweight Pattern). This is like string.Empty and using it puts less strain on the garbage collector.

Registering the JavaScript Glue Function

To invoke the Chart.js library you need to add a little JavaScript of your own. Open the content folder of the component library project and start by renaming the exampleJsInterop.js file to JsInterop.js and replacing the code with Listing 8-15.
window.components = (function () {
  return {
    chart: function (id, data, options) {
      var context = document.getElementById(id)
                            .getContext('2d');
      var chart = new Chart(context, {
        type: 'line',
        data: data,
        options: options
      });
    }
  };
})();
Listing 8-15

Registering the JavaScript Glue Class

This adds a window.components.chart glue function that when invoked calls the Chart function (from Chart.js), passing in the graphics context for the canvas, data, and options. It is very important that you pass the id of the canvas because someone might want to use the LineChart component several times in the same page. By using a unique id for each LineChart component you end up with canvasses with unique ids.

Providing the JavaScript Interoperability Service

Your LineChart component will need to call the Chart.js library using your window.components.chart glue function. But putting all this logic in the LineChart component directly is something you want to avoid. Instead, you will build a service encapsulating this logic and inject the service into the LineChart component. Should the Blazor team at Microsoft decide to change the way JavaScript interoperability works (they have done that before) then you will only need to change one class (again, the Single Responsibility Principle). Start by adding a new interface to the U2U.Component.Chart library project called IChartInterop with the code from Listing 8-16.
namespace U2U.Components.Chart
{
  public interface IChartInterop
  {
    void CreateLineChart(string id, LineChartData data,
                                    ChartOptions options);
  }
}
Listing 8-16

The IChartInterop Interface

As you can see, this interface’s CreateLineChart method closely matches the window.components.chart glue function. Let’s implement this service. Add a new class called ChartInterop to the component library project and implement is as in Listing 8-17.
using Microsoft.JSInterop;
namespace U2U.Components.Chart
{
  /// <summary>
  /// It is always a good idea to hide specific implementation
  /// details behind a service class
  /// </summary>
  public class ChartInterop : IChartInterop
  {
    public void CreateLineChart(string id, LineChartData data,
                                ChartOptions options)
    {
      JSRuntime.Current
               .InvokeAsync<string>("components.chart",
                                    id, data, options);
    }
  }
}
Listing 8-17

Implementing the ChartInterop Class

This CreateLineChart method invokes the JavaScript components.chart function you added in Listing 8-15.

Time to configure dependency injection. You could ask the user of the library to add the IChartInterop dependency directly, but you don’t want to put too much responsibility in the user’s hands. Instead you will provide the user with a handy C# extension method that hides all the gory details from the user. Add the new class called DependencyInjection to the component library project with the code from Listing 8-18.
using Microsoft.Extensions.DependencyInjection;
namespace U2U.Components.Chart
{
  public static class DependencyInjection
  {
    public static IServiceCollection AddCharts(
      this IServiceCollection services)
    => services.AddSingleton<IChartInterop, ChartInterop>();
  }
}
Listing 8-18

The AddCharts Extension Method

This class provides you with the AddCharts extension method that the user of the LineChart component can now add to the client project. Let’s do this. Make sure everything builds first, and then open Startup.cs in the ChartTestProject and add a call to AddCharts as in Listing 8-19.
using Microsoft.AspNetCore.Blazor.Builder;
using Microsoft.Extensions.DependencyInjection;
using U2U.Components.Chart;
namespace ChartTestProject
{
  public class Startup
  {
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddCharts();
    }
    public void Configure(IBlazorApplicationBuilder app)
    {
      app.AddComponent<App>("app");
    }
  }
}
Listing 8-19

Convenient Dependency Injection with AddCharts

The user of the component does not need to know any implementation details to use the LineChart component. Mission accomplished!

Implementing the LineChart Component

Now you are ready to implement the LineChart component. Chart.js does all its drawing using an HTML5 canvas element, and this will be the markup of the LineChart component. Update LineChart.cshtml to match Listing 8-20.
@inject IChartInterop JsInterop
<canvas id="@Id" class="@Class">
</canvas>
@functions {
  [Parameter]
  protected string Id { get; set; }
  [Parameter]
  protected string Class { get; set; }
  [Parameter]
  LineChartData Data { get; set; }
  [Parameter]
  ChartOptions Options { get; set; } = ChartOptions.Default;
  protected override void OnAfterRender()
  {
    string id = Id;
    JsInterop.CreateLineChart(Id, Data, Options);
  }
}
Listing 8-20

The LineChart Component

The LineChart component has a couple of parameters. The Id parameter is used to give each LineChart’s canvas a unique identifier; this way you can use LineChart several times in the same page. The Class parameter can be used to give the canvas one or more CSS classes to add some style (and you can never have enough style). Finally, the Data and Options parameters get passed to JavaScript to configure the chart.

Now comes the tricky part (this is like the earlier section where you wanted to set the focus on the input). To call the JavaScript chart function, the canvas needs to be in the browser’s DOM. When does that happen? Blazor creates the component hierarchy, calls each component’s OnInit, OnInitAsync, OnParameterSet, and OnParameterSetAsync methods, and then uses the component hierarchy to build its internal tree, which then is used to update the browser’s DOM. Then Blazor calls each component’s OnAfterRender and OnAfterRenderAsync methods. Because the canvas element should already be part of the DOM you need to wait for the OnAfterRender method before calling JsInterop.CreateLineChart.

Using the LineChart Component

With everything in place, you can now complete the LineChart component from the Index page in your ChartTestProject. Update the Index.cshtml file to match Listing 8-21. You will add the toJS() extension method later, so ignore any errors till then.
@page "/"
@using U2U.Components.Chart
@using System.Drawing
<h1>Hello, world!</h1>
Welcome to your new app.
<LineChart Id="test" Class="linechart"
           Data="@Data" Options="@Options" />
<SurveyPrompt Title="How is Blazor working for you?" />
@functions {
private LineChartData Data { get; set; }
private ChartOptions Options { get; set; }
protected override void OnInit()
{
  this.Options = ChartOptions.Default;
  this.Data = new LineChartData
  {
    Labels = new string[] { "", "A", "B", "C" },
    Datasets = new LineChartData.DataSet[]
    {
      new LineChartData.DataSet
      {
        Label = "Test",
        BackgroundColor = Color.Transparent.ToJs(),
        BorderColor =  Color.FromArgb(10, 96, 157, 219)
                            .ToJs(),
        BorderWidth = 5,
        Data = new List<Point>
        {
          new Point(0,0),
          new Point(1, 11),
          new Point(2, 76),
          new Point(3,13)
        }
      }
    }
  };
}
}
Listing 8-21

Completing the Index Component

You start by adding two @using directives for the U2U.Components.Chart and System.Drawing namespaces. Then you add the Id, Class, Data, and Options parameters. You give these parameters values in the OnInit method (should you get this data from the server you would use the OnInitAsync method). One more thing before you can build and run the project and admire your work: add a new class called ColorExtensions to the U2U.Component.Chart project. Implement it as shown in Listing 8-22.
using System.Drawing;
namespace U2U.Components.Chart
{
  public static class ColorExtensions
  {
    public static string ToJs(this Color c)
    => $"rgba({c.R}, {c.G}, {c.B}, {c.A})";
  }
}
Listing 8-22

The ColorExtensions Class with the toJS Extension Method

Build and run your project. If all is well, you should see Figure 8-12.
../images/469993_1_En_8_Chapter/469993_1_En_8_Fig12_HTML.jpg
Figure 8-12

The finished chart example

Summary

In this chapter, you saw how you can call JavaScript from your Blazor components using the JSRuntime.Current.InvokeAsync<T> method. This requires you to register a JavaScript glue function by adding this function to the browser’s window global object.

You can also call your .NET static or instance method from JavaScript. Start by adding the JSInvokable attribute to the .NET method. If the method is static, you use the JavaScript DotNet.invokeMethodAsync function (or DotNet.invokeMethod if the call is synchronous), passing the name of the assembly, the name of the method, and its arguments. If the method is an instance method, you pass the .NET instance wrapped in a DotNetObjectRef to the glue function, which can then use the invokeMethodAsync function to call the method, passing the name of the method and its arguments.

Finally, you applied this knowledge by wrapping the Chart.js open source library to draw a nice line chart. You built a Blazor component library, added some classes to pass the data to the Chart function, and then used a glue function to draw the chart.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.97.204