Author : MD TAREQ HASSAN | Updated : 2020/09/07
Call JavaScript from Blazor
- Blazor server will be used for demo and Bootstrap Toast will be shown using
IJSRuntime
- In order to call JS function, put
<script>
tag in head- Blazor server: in head of ‘
Pages/_Host.cshtml
’ - Blazor WebAssembly: in head of ‘
wwwroot/index.html
’
- Blazor server: in head of ‘
- We are gonna call JS function from custom BaseComponent using
JSRuntime.InvokeVoidAsync(...)
Bootstrap toast in ‘Shared/MainLayout.razor
’
@inherits LayoutComponentBase
<!-- removed for brevity -->
<div class="container" style="position: relative;">
<div class="toast mt-sm-2 fade hide" data-delay="2500" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 0; right: 0; z-index:1000; min-width: 25%;">
<div class="toast-header bg-success text-white">
<strong class="mr-sm-auto">Success</strong>
<button type="button" class="ml-2 mb-1 close text-white" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="toast-body"></div>
</div>
<main role="main" class="pb-3">
@Body
</main>
</div>
Notes:
- Toast was preventing click to button behind (when toast is hidden)
- By adding ‘
fade hide
’ class, it was resolved (<div class="toast mt-sm-2 fade hide" ...>
) - Similar issue: https://github.com/CodeSeven/toastr/issues/235
- By adding ‘
$().toast(options)
might not work, use data attribute for settings i.e.<div class="toast fade hide" data-delay="2500" data-animation="true" ... >
’<script>
’ tag in ‘Pages/_Host.cshtml
’
@page "/"
<!-- removed for brevity -->
<!DOCTYPE html>
<html lang="en">
<head>
<!-- removed for brevity -->
<script>
window.JSInterop = {
ShowToast: function (toastId, toastMessage) {
console.log(`showing toast for id: "${toastId}"`);
$('.toast-body').text('');
$('.toast-body').text(toastMessage);
$(toastId).toast('show');
}
};
</script>
</head>
<body>
<!-- removed for brevity -->
</body>
</html>
See: CrudOperation enum: useful-extension#navigationmanagerextention
BaseComponent.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.JSInterop;
using Serilog;
using System;
using System.Linq;
public class BaseComponent: ComponentBase
{
[Inject]
protected NavigationManager NavigationManager { get; set; }
[Inject]
IJSRuntime JSRuntime { get; set; }
protected async Task ShowToastMessageIfNeeded()
{
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
var parsedQuery = QueryHelpers.ParseQuery(uri.Query);
//
// Look for a single query param that indicates crud operation flag
//
if ((parsedQuery?.Any()).HasValue && parsedQuery.Count == 1)
{
var performedOperation = parsedQuery.First().Key;
// need to check if redirection happended from a valid page
if (Enum.IsDefined(typeof(CrudOperation), performedOperation))
{
// show toast
var toastMessage = $"Item {performedOperation}";
await JSRuntime.InvokeVoidAsync(Interop.ShowToast, ".toast", toastMessage);
}
else
{
Log.Warning("Unknown query parameter (not a crud operation flag)");
}
}
}
protected void PerformRedirection(CrudOperation @for)
{
NavigationManager.NavigateTo($"/items?{@for}");
}
}
Usage: Delete.razor.cs
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
public class DeleteBase : BaseComponent
{
[Parameter]
public int Id { get; set; }
protected Item Item = new Item();
// ... ... ...
protected override async Task OnInitializedAsync()
{
Item = await Service.FindItemAsync(Id);
}
protected async Task PerformDeletion()
{
var success = await Service.DeleteItemAsync(Id);
if (!success)
{
ErroMessage = "Failed to Delete Item";
return;
}
PerformRedirection(@for: CrudOperation.deleted);
}
}
If delete operation is successful:
PerformRedirection(@for: CrudOperation.deleted)
: redirect to index page with query flag i.e. ‘hovermind.com/items?deleted
’BaseComponent
will show Toast by calling JS function using Introp
Call Blazor from JavaScript
Using static delegate
- Use
Func<T1, T2 ..., Task>
type so that you cann pass params and it reurns Task (means the C# fucntion will be async) - Depending on need (how many params you need to pass form JS), you can add T1, T2 etc.
Func<Task>
if do not need to pass any param
delete.razor.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using System;
using System.Threading.Tasks;
public class DeleteBase : BaseComponent
{
[Parameter]
public int Id { get; set; }
protected FooModel FooModel { get; set; } = null;
#region JSInterop
protected static Func<Task> DeleteFunc { get; set; }
[JSInvokable]
public static async Task DeleteFoo()
{
await DeleteFunc?.Invoke(); // await DeleteFunc?.Invoke(params...);
}
#endregion
protected override async Task OnInitializedAsync()
{
DeleteFunc = PerformDeletion;
try
{
DbContext = DbContextFactory.CreateDbContext();
FooModel = await FindFooAsync(Id);
}
catch (AppException ex)
{
// ... ... ...
}
}
protected async Task PerformDeletion()
{
// ... ... ...
var entryExists = await IsFooAlreadyExist(Id);
if (!entryExists)
{
// Error...
return;
}
try
{
var fooToDelete = await DbContext.Foos.FindAsync(Id);
DbContext.Foos.Remove(fooToDelete);
await DbContext.SaveChangesAsync();
NavigationManager.NavigateTo("/items?deleted");
}
catch (Exception)
{
// ... ... ...
}
}
}
Using event
- There are some restrictions
wwwroot/js/foo.js
window.barWindowObject = {
bazz: function (elementId, width, height) {
// ... ... ...
},
bax: function (...) {
// ... ... ...
}
}
Javascript.cs
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FooNameSpace
{
public static class Javascript
{
public static event Action FooEventHandler;
// ... ... ...
[JSInvokable]
public static Task FireFooEvent()
{
return Task.Run(() => FooEventHandler?.Invoke());
}
}
}
Pages/foo.cshtml
@page "/settings"
@inject FooService fs
@implements IDisposable
// ... ... ...
<button id="fooBtn" onClick="barWindowObject.bax"></button>
@functions{
// ... ... ...
void OnFiredFooEvent()
{
//count++;
// ... ... ...
StateHasChanged();
}
protected override void OnAfterRender()
{
base.OnAfterRender();
Javascript.FooEventHandler += OnFiredFooEvent;
}
public void Dispose()
{
Javascript.FooEventHandler -= OnFiredFooEvent;
}
}
<script>
window.barWindowObject = {
// ... ... ...
bax: function (...) {
// ... ... ...
// when some condition met
DotNet.invokeMethodAsync("FooNameSpace", "FireFooEvent");
}
}
</script>