In this new post custom JavaScript function in Blazor, I present how to create functions in C# in a Blazor page and integrate them with JavaScript. What I explain in this post, it is coming fro my ChartJs component for Blazor: this component allows you to create nice graphs using ChartJs library.
Scenario
The ChartJs library allows the developers to customise the chart using callbacks
and here developers can write their own JavaScript code. For example, if you want to customise the Tooltip
for each point, you can use the Tooltip Callbacks
const chart = new Chart(ctx, {
type: 'line',
data: data,
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', {
style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return label;
}
}
}
}
}
});
So, if you look at the lines 6-18, the callbacks
has a property label
and here we can write our JavaScript code. Then, when the graph renders the tooltip
for a point, the library calls the callbacks
function and displays the label in the format you define. Here the screenshot for the code above.
Here, you can try yourself with this Codepen. When you move the mouse over a point, the tooltip is displaying the custom label.
So, the problem is how to replicate the same functionalities in the Blazor component and allow developers to create their custom code.
Then, I was looking for an implementation where I can add in a Blazor page the JavaScript code and call it in some way (see my first post in the Microsoft Learn). Nothing was working.
I couldn’t understand how to pass data and communicate among the JavaScript code, the Blazor component and the Blazor Page. Most of the people redirected me to the Microsoft Learn page called “Call JavaScript functions from .NET methods in ASP.NET Core Blazor” but wasn’t useful. After few months, a pull request on GitHub from Macias opened my mind.
Anatomy of the component
First, just few words about the ChartJs component for Blazor and how it is working. Obviously this is an example to implement your custom JavaScript function in your Blazor components and projects.
So, I’m telling you all this stuff to explain my thoughts about the component. But also, how I discovered the correct implementation.
Chart.js script
First, the Chart.js
script is the core of all component. As I said, this is based on the javascript library ChartJs. So, the component is a faced of it adding the ability to use it in Blazor applications. So, this script calls and communicates with the ChartJs library to create the chart.
The component exposes the properties, methods and events to developers in their Blazor pages. The real definition of the settings are coming from the Blazor page. Then, the script initialises the chart calling the ChartJS library.
In this script, there are other functions available out-of-the-box, such as the crosshair. Also, the component raises events and calls to the Blazor component. For example, when the user clicks on the chart, the Chart.js invoke ChartClick
in the Blazor component.
Blazor component
In order to create the chart, the script has to receives the configuration of the chart from the Blazor component. The component is simplified the creation exposing properties, events and methods that developers can use in their applications.
So, in same way I have to add here something that developers can use to add their code in C#.
Blazor page
Finally, this is the page where we want to generate the chart, passing to the component all the settings. Also, in here I want to add my custom code to change the chart.
What I need to do
So, focus on the Tooltip callbacks
, what I need is:
- the JavaScript has to call the Blazor component for a particular event (in this case when there is a callback during the creation of a tooltip)
- the Blazor component must to receive the call or connect the code between the JavaScript code and the custom code in the page
- the Blazor component must call the custom code, pass parameters if it needs them and sends back the result
Implementation in the component
Now, we know the structure of the component and what I want to do. So, we can start to analyse how to implement the solution. First, in the component I define
private DotNetObjectReference<IChartConfig>? dotNetObjectRef;
As the name says, this is a reference of my chart configuration that has all the data, labels and options. When the Chart.js
calls a function in this component, it will pass always this object to access the data and options.
Then, I have to define a function that Chart.js can call. For that, I have to define a JSInvokable
function like that
[JSInvokable]
public static string[] TooltipCallbacksLabel(
DotNetObjectReference<IChartConfig> config, int[] parameters)
{
var ctx = new CallbackGenericContext(parameters[0], parameters[1]);
if (config.Value.Options is Options options)
return options.Plugins.Tooltip.Callbacks.Label(ctx);
else
throw new NotSupportedException();
}
So, what is it happing here? The magic Is here. The Chart.js
calls TooltipCallbacksLabel
with 2 parameters: the configuration of the chart and some parameters
that in this particular case are the value of a specific point in the chart.
In the config
, the component checks if the Options
are defined and if there is a particular implementation for the Callbacks.Label
. If there is one, it pass the values to the function. Basically, this is how the Blazor component calls a custom C# code in the Blazor pageant return to the Chart.js
the result. In this case, the result of the C# is a string.
In the Blazor page
So, let me show you the implementation in the Blazor page.
protected override async Task OnInitializedAsync()
{
_config1 = new BarChartConfig()
{
Options = new Options()
{
Responsive = true,
MaintainAspectRatio = false,
Plugins = new Plugins()
{
Legend = new Legend()
{
Align = Align.Center,
Display = true,
Position = LegendPosition.Right
},
Tooltip = new Tooltip()
{
Callbacks = new Callbacks()
{
Label = (ctx) =>
{
return new[] {
$"DataIndex: {ctx.DataIndex}\nDatasetIndex: {ctx.DatasetIndex}" };
},
Title = (ctx) =>
{
return new[] { $"This is the value {ctx.Value}" };
}
}
}
},
Scales = new Dictionary<string, Axis>()
{
{
Scales.XAxisId, new Axis()
{
Stacked = true,
Ticks = new Ticks()
{
MaxRotation = 0,
MinRotation = 0
}
}
},
{
Scales.YAxisId, new Axis()
{
Stacked = true
}
}
}
}
};
Look at the lines between 17 and 30. Here is where the Blazor page receives from the Blazor component the values from the Chart.js
script. This code creates a string[]
to change the text in the tooltip for a particular point. So, there is a constant communication between the Blazor component and the JavaScript underneath. When it is the code, the Blazor component pass the control to the Blazor page and then the result to the JavaScript.
In the JavaScript
And what is the code in the Chart.js
? In the configuration of the chart, I pass the DotNetObjectReference
. When the ChartJs library wants to call the label
callbacks
, there is a function that invoke the JSInvokable
function I defined in the Blazor component. The call is using the namespace of the component (in this case PSC.Blazor.Components.Chartjs
) and the function I want to call (in this case TooltipCallbacksLabel
).
config.options.plugins.tooltip.callbacks.label = function (ctx) {
return DotNet.invokeMethod('PSC.Blazor.Components.Chartjs', 'TooltipCallbacksLabel',
dotnetConfig, [ctx.datasetIndex, ctx.dataIndex]);
};
As parameters, the function pass to the Blazor component the chart configuration and an array with some values (in this case datasetIndex
and dataIndex
available in this function). So, the Blazor component pass the parameters to the custom code in the Blazor page, receives the new string and pass the string to the Chart.js
that pass the value to the ChartJs library.
Wrap up
I took almost a year to understand how to create custom JavaScript function in a Blazor component that can communicate among them. I couldn’t find any documentation that explain how to do it. So, I hope this can help someone else.
For more info, you can visit:
- the GitHub repository for ChartJs component t for Blazor
- ChartJs Blazor component for Blazor post
- Labels and OnClickChart for ChartJs
- the forum for this component
- download the NuGet package
- play with the demo
Happy coding!