Using Events with JavaScript and jQuery in Blazor – Initializing Libraries

In this post we are going to see how to use events in HTML elements using “pure” JavaScript and jQuery.

Sometimes we want to use events with JavaScript because we have libraries made in this language which we have not migrated to C#. It is clear that with C# and Blazor we can use events in a very convenient way, but when we have libraries already created in JavaScript, it is sometimes easier to use them than to redo them in C#.

Let’s see how to use events with JavaScript and jQuery.

Note: The reason for this post is to shed light on how to use Blazor with certain JavaScript libraries in a comfortable way. It is clear that in some cases this is very simple, since we can call JavaScript methods from .NET, but in other cases it is not so much, or at least it is not so convenient to proceed in this way.

I’m including jQuery mainly because some students has asked me about it.

Basics

If you create a new Blazor application, by default the Counter component will be created, in it we have a button. Let’s say we want to execute a JavaScript function when we click the button, but we don’t want to call this function from .NET, instead we want to use addEventListener in JavaScript. For that we create a JavaScript file in our wwwroot directory, and put the following in it (counterClick will be the Id of the button):

function initializeCounterComponent() {
let counterClick = document.querySelector("#counterClick");
if (counterClick) {
counterClick.addEventListener("click", (e) => {
console.log("click JavaScript");
});
}
}

view raw
utilities.js
hosted with ❤ by GitHub

Here what we do is create a function called initializeCounterComponent, which we will run by initializing everything related to JavaScript of our Counter component, including the addEventListener to our button.

With jQuery the above would be:

$(document).on('click', '#counterClick', function(){
console.log("click JavaScript");
});

view raw
Utilities.js
hosted with ❤ by GitHub

Then, in the Counter component we have the following:

@page "/counter"
@inject IJSRuntime js
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button id="counterClick" class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await js.InvokeVoidAsync("initializeCounterComponent");
}
}
private void IncrementCount()
{
currentCount++;
}
}

view raw
Counter.razor
hosted with ❤ by GitHub

Notice that I use OnAfterRenderAsync to call the JavaScript initializeCounterComponent function. If you use jQuery, you don’t need to call any JavaScript function, because jQuery is in charge of appending the event to the button without your interaction.

Making a Reference to an HTML Element

We can refer to the HTML element from our JavaScript code in a direct way. What we have to do is use the parameter of the anonymous function and navigate to the target, which is the element on which the action has been executed. For example, let’s say we want to change the text of the clicked button to “Hello”. We can do this in the following way by modifying our JavaScript file:

function initializeCounterComponent() {
let counterClick = document.querySelector("#counterClick");
if (counterClick) {
counterClick.addEventListener("click", (e) => {
console.log("click JavaScript");
let myButton = e.target;
myButton.innerHTML = "Hello";
});
}
}

view raw
Utilities.js
hosted with ❤ by GitHub

With the above, if we click the button of the Counter component, it will change its text to “Hello”. A code similar to the one above works in jQuery:

$(document).on("click", "#counterClick", function (e) {
$(this).html("Hello");
});

view raw
Utilities.js
hosted with ❤ by GitHub

Multiple Instances of a Component

Unfortunately, in JavaScript, the above does not scale to multiple instances of the Counter component. For example, let’s put the following in the Index component:

@page "/"
<h1>Index Component</h1>
<Counter />
<hr/>
<Counter />

view raw
Index.razor
hosted with ❤ by GitHub

If you run the application and go to the Index component, you will see that if you click the first button, apparently everything works, but it is not. There are two problems:

  1. The second button does not work (its text is not changed to Hello)
  2. The first button executes the JavaScript function twice.

Why does the above happen? Well, because we are using the ID selector, which only selects the first element with the indicated ID. Since we have two buttons with the same ID, then only the first is selected, and the second is ignored.

In jQuery, the above doesn’t happen :), but the following is important for both JS and jQuery:

So, when we click on a button, we want that button to be edited. How can we do this? Well one way is by sending the button as a parameter to the JavaScript function. To do this, we add the @ref directive to the button, and send the ElementReference to JavaScript (summary code to save space):

<button @ref="myButton" id="counterClick" class="btn btn-primary counterClickC" @onclick="IncrementCount">Click me</button>
@code {
private ElementReference myButton;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await js.InvokeVoidAsync("initializeCounterComponent", myButton);
}
}
}

view raw
Counter.razor
hosted with ❤ by GitHub

Now, we do the following change in the initializeCounterComponent:

function initializeCounterComponent(boton) {
boton.addEventListener("click", (e) => {
console.log("click JavaScript");
let button = e.target;
button.innerHTML = "Hola";
});
};

view raw
Utilities.js
hosted with ❤ by GitHub

With this we have solved our two previous problems. Now when we click on a button, only that button is edited, and not another. And no matter how many buttons we have on the screen, our functionality remains correct.

However,  passing HTML elements individually to the initializeCounterComponent method doesn’t seem right to me. What happens if we have several HTML elements to initialize in a component? Do we have to pass each of those HTML elements to the initializeCounterComponent function? Personally, I don’t like this. I prefer to pass a container to the initializeCounterComponent and look for the HTML elements in it.

For example, let’s put a div that covers all the HTML of the Counter component, and put it @ref to be able to refer to this div from C#, and pass the div to the initializeCounterComponent function:

@page "/counter"
@inject IJSRuntime js
<div @ref="container">
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button id="counterClick" class="btn btn-primary counterClickC" @onclick="IncrementCount">Click me</button>
</div>
@code {
private ElementReference container;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await js.InvokeVoidAsync("initializeCounterComponent", container);
}
}
}

view raw
Counter.razor
hosted with ❤ by GitHub

Then we can modify the initializeCounterComponent to take the references of any element inside the container div. For example, let’s say we want to take the references of the h1 and the counterClick button, we can do this as follows:

function initializeCounterComponent(container) {
var boton = container.querySelector("#counterClick");
var title = container.querySelector("h1");
if (boton) {
boton.addEventListener("click", (e) => {
console.log("click JavaScript");
let button = e.target;
button.innerHTML = "Hello";
title.innerHTML = "The button has been clicked!";
});
}
};

view raw
Utilities.js
hosted with ❤ by GitHub

In the case of jQuery we have to do something similar to the above if what we want is to initialize all the elements of a component, since jQuery does not have a way to execute functionality when loading a specific HTML element (what we were doing previously it was to append a functionality to an event).

Libraries

Now that we have all the information above, we can use certain libraries in a simple way in Blazor.

We will take the example of the ChartJS libraries and a DateTimePicker (calendar control) called Tempus Dominus.

Basically what we want is to initialize a component that will use these libraries. We must first add the references to these libraries in our index.html (_host.cshtml if you use Blazor Server):

<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.1/css/tempusdominus-bootstrap-4.min.css" />
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js" integrity="sha256-R4pqcOYV8lt7snxMQO/HSbVCFRPMdrhAFMH+vr9giYI=" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.1/js/tempusdominus-bootstrap-4.min.js"></script>
<script src="Utilities.js"></script>
<script src="_framework/blazor.webassembly.js"></script>

view raw
index.html
hosted with ❤ by GitHub

Then, in our component we will have the following code. As we can see, we use our pattern of having a div with a @ref to be able to reference the internal elements of that div. We have two text fields that will use the DateTimePicker, and a canvas where we will place a chart. Also in the OnAfterRenderAsync we invoke a JavaScript function that will initialize the DateTimePickers and the chart:

@page "/counter"
@inject IJSRuntime js
<div @ref="container" class="counter-container">
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<div class="form-group">
<button id="counterClick" class="btn btn-primary counterClickC" @onclick="IncrementCount">Click me</button>
</div>
<div class="form-group">
<label>Date Start</label>
<input type="text" class="form-control datetimepicker-input" id="dateStart" data-toggle="datetimepicker" data-target="#dateStart" />
</div>
<div class="form-group">
<label>Date End</label>
<input type="text" class="form-control datetimepicker-input" id="dateEnd" data-toggle="datetimepicker" data-target="#dateEnd" />
</div>
<div style="width: 500px;">
<h5>Gráfico Importante</h5>
<canvas id="myChart"></canvas>
</div>
</div>
@code {
private int currentCount = 0;
private ElementReference container;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await js.InvokeVoidAsync("initializeCounterComponent", container);
}
}
private void IncrementCount()
{
currentCount++;
}
}

view raw
Counter.razor
hosted with ❤ by GitHub

Finally, in our JavaScript file we have the following initialization code. In this, we initialize the chart with test data, we attach an event to the counterClick button as we had seen previously, and we initialize the two calendars so that they work as an interval, so that one marks a start date and the other an end date :

function initializeCounterComponent(container) {
var chart = container.querySelector('#myChart');
inicializarChart(chart);
var button = container.querySelector("#counterClick");
var title = container.querySelector("h1");
if (button) {
button.addEventListener("click", (e) => {
let button = e.target;
button.innerHTML = "Hello";
title.innerHTML = "The button has been clicked!";
});
}
var $dateStart = $(container.querySelector('#dateStart'));
var $dateEnd = $(container.querySelector('#dateEnd'));
$dateStart.datetimepicker(
{ useCurrent: false }
);
$dateEnd.datetimepicker();
$dateStart.on("change.datetimepicker", function (e) {
$dateEnd.datetimepicker('minDate', e.date);
});
$dateEnd.on("change.datetimepicker", function (e) {
$dateStart.datetimepicker('maxDate', e.date);
});
};
function inicializarChart(chart) {
var ctx = chart.getContext('2d');
var data = [0, 10, 5, 2, 20, 30, 45];
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'line',
// The data for our dataset
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
data: data
}]
},
// Configuration options go here
options: {}
});
}

view raw
Utilities.js
hosted with ❤ by GitHub

In this way, in the same place, we initialize different JavaScript code that we need to use libraries that, at the moment, are not available in Blazor natively.

Course

If you want a step by step Blazor course that teaches you everything you need to know to build interactive web applications with C#, consider getting my Udemy course and start building awesome Blazor apps today:

https://www.udemy.com/course/programming-in-blazor-aspnet-core/?referralCode=8EFA9D9FF38E3065DF0C

Regards!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s