BlazorJS 2.3.0
dotnet add package BlazorJS --version 2.3.0
NuGet\Install-Package BlazorJS -Version 2.3.0
<PackageReference Include="BlazorJS" Version="2.3.0" />
<PackageVersion Include="BlazorJS" Version="2.3.0" />
<PackageReference Include="BlazorJS" />
paket add BlazorJS --version 2.3.0
#r "nuget: BlazorJS, 2.3.0"
#:package BlazorJS@2.3.0
#addin nuget:?package=BlazorJS&version=2.3.0
#tool nuget:?package=BlazorJS&version=2.3.0
<p align="center"> <img src="https://raw.githubusercontent.com/fgilde/BlazorJS/master/logo.png" alt="BlazorJS" width="200" /> </p>
<h1 align="center">BlazorJS</h1>
<p align="center">Better JavaScript interaction for Blazor.</p>
<p align="center"> <a href="https://quickrun.org/run?repo=fgilde/BlazorJS"><img src="https://quickrun.org/badge.svg" alt="Run the sample with QuickRun" /></a> <a href="https://www.nuget.org/packages/BlazorJS"><img src="https://img.shields.io/nuget/v/BlazorJS?style=flat-square&color=8b5cf6&label=nuget" alt="NuGet" /></a> <a href="https://www.nuget.org/packages/BlazorJS"><img src="https://img.shields.io/nuget/dt/BlazorJS?style=flat-square&color=22d3ee&label=downloads" alt="Downloads" /></a> <img src="https://img.shields.io/badge/net6%20%E2%80%93%20net10-6366f1?style=flat-square" alt="Target frameworks" /> <img src="https://img.shields.io/badge/license-MIT-8b5cf6?style=flat-square" alt="MIT" /> </p>
<p align="center"> <b><a href="https://fgilde.github.io/BlazorJS/">📖 Documentation</a></b> · <a href="https://www.nuget.org/packages/BlazorJS">NuGet</a> · <a href="https://quickrun.org/run?repo=fgilde/BlazorJS">Run the sample</a> </p>
Run the sample
The repository ships a sample app with a live page for every feature. With QuickRun installed it is one click:
Or the manual way:
dotnet run --project BlazorJSSample
What it does
- A Scripts component to load JavaScript and stylesheet files per page or component, unloaded again on dispose.
- IJSRuntime extensions for dynamic invocation that remove the need to write JS wrapper functions for everything.
- Event interop to hook any browser event, plus
ResizeObserverandIntersectionObserver, onto a Blazor component. - Saving files through the File System Access API, streamed, with a download fallback.
- Clipboard, dialog and DOM helpers with the browser quirks already handled.
- A base component to import a module and create a JS object reference from it.
Target frameworks: net10.0, net9.0, net8.0, net7.0, net6.0 and netstandard2.1.
Installation
dotnet add package BlazorJS
Open _Imports.razor and add the usings:
@using BlazorJS
@using BlazorJS.Attributes
@using BlazorJS.JsInterop
The browser side registers itself through a Blazor JS initializer, so there is no service registration and no script tag to add.
<ins>Scripts Component</ins>
The scripts component allows you to include every javascript file easily to your pages or components.
For example open any page like the index.razor and add
<Scripts src="js/myjsfile.js"></Scripts>
This component can also load stylesheet files
<Scripts src="js/myjsfile.js,css/mystyle.css"></Scripts>
Include multiple javascript files
Multiple js files can be loaded with a comma seperator ,
<Scripts src="js/myjsfile.js, js/myjsfile2.js"></Scripts>
Parameters
| Parameter | Default | Description |
|---|---|---|
Src |
– | One or more files, comma separated |
UnloadOnDispose |
true |
Removes the elements again when the component is disposed |
SourceLoadBehaviour |
OnAfterRender |
OnInitialized, OnInitializedAsync, OnAfterRender or OnAfterRenderAsync |
SourceLoaded |
– | EventCallback<string> raised per file once it finished loading |
The same from code:
await jsRuntime.LoadFilesAsync("js/chart.js", "css/chart.css");
await jsRuntime.UnloadFilesAsync("js/chart.js");
<ins>Extended Dynamic JS Invocation</ins>
The Dynamic Invocation extension for IJSRuntime allows for dynamic invocation of JavaScript functions from C#.
This extension provides a method DInvokeVoidAsync, which takes in a function to be invoked and an array of
objects to be passed as arguments to that function.
Simple Call
await jsRuntime.DInvokeVoidAsync(window => window.alert("test"));
Passing Parameters
Only the source text of the lambda is transferred, so variables from your component do not exist in the browser. Something like this is not enough:
// DONT COPY THIS!! SAMPLE FOR NOT WORKING
await jsRuntime.DInvokeVoidAsync(window => window.alert(currentCount));
Pass the parameters instead:
await jsRuntime.DInvokeVoidAsync((window, c) => window.alert(c), currentCount);
await jsRuntime.DInvokeVoidAsync((window, c, p2, p3) => window.alert(c), currentCount, param2, param3);
await jsRuntime.DInvokeVoidAsync((window, c, x) =>
{
window.alert(c);
window.console.log(x);
}, currentCount, "Flo");
Or add parameters with the class JSArgument, which keeps the original variable names:
var date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var name = "John";
await jsRuntime.DInvokeVoidAsync(window => window.alert(currentCount + " - " + date + name),
JSArgument.For(currentCount).And(date).And(name));
Using return values
All samples above are also callable with a generic argument to use return values.
var res = await jsRuntime.DInvokeAsync<string>(window => window.prompt());
Console.WriteLine(res);
Results can be reused as parameters:
var hash = await jsRuntime.DInvokeAsync<string>(window =>
{
window.alert(currentCount);
return window.location.hash + "_" + currentCount;
}, new[] { JSArgument.For(currentCount) });
await jsRuntime.DInvokeVoidAsync(document => document.location.hash = hash, new[] { JSArgument.For(hash) });
// After alerting the currentCount we update the url in browser like this #1_2_3_4...
<ins>Clipboard</ins>
navigator.clipboard only exists in a secure context. CopyToClipboardAsync uses it when available and
falls back to the legacy execCommand path otherwise, so it also works on plain http during development.
var copied = await jsRuntime.CopyToClipboardAsync("Copied with BlazorJS");
if (!copied)
await jsRuntime.AlertAsync("The browser refused the copy.");
// reading always needs a secure context and a user permission, returns null when denied
var text = await jsRuntime.ReadClipboardAsync();
<ins>Saving files</ins>
Blazor can read files with <InputFile>, but writing one back out is still a pile of JavaScript.
SaveFileAsync uses the File System Access API when the browser has it, so the user gets a real save
dialog and picks the location, and falls back to a plain download otherwise.
// a string, the mime type defaults to text/plain
await jsRuntime.SaveFileAsync("notes.txt", "Written by BlazorJS");
// bytes
await jsRuntime.SaveFileAsync("report.pdf", pdfBytes, "application/pdf");
// or a stream, nothing is buffered in memory twice
await using var stream = File.OpenRead(path);
var saved = await jsRuntime.SaveFileAsync("export.csv", stream, "text/csv");
if (!saved)
Console.WriteLine("The user closed the save dialog.");
The content is streamed with a DotNetStreamReference, so large files also work in Blazor Server, where a
single SignalR message is capped at 32 KB. Call it from a user interaction, browsers reject a save dialog
that no click asked for.
<ins>Simple event interop helper</ins>
A simple possibility to hook events, with an easy OnBlur extension for the click-outside case.
private BlazorJSEventInterop<PointerEventArgs> _jsEvent;
_jsEvent = new BlazorJSEventInterop<PointerEventArgs>(_jsRuntime);
await _jsEvent.OnBlur(OnFocusLeft, ".element-selector");
private Task OnFocusLeft(PointerEventArgs arg)
{
return Task.CompletedTask;
}
You can also use it manually with any event you want to.
private BlazorJSEventInterop<PointerEventArgs> _jsEvent;
_jsEvent = new BlazorJSEventInterop<PointerEventArgs>(_jsRuntime);
await _jsEvent.AddEventListener("NameOfEvent", async args => { await YourCallBack(); }, ".element-selector");
If the selector does not exist yet, a MutationObserver waits for it and attaches the listener as soon as Blazor rendered the element.
<ins>Resize and visibility observers</ins>
The same interop class also exposes a ResizeObserver and an IntersectionObserver. Both are disconnected
when the interop instance is disposed.
private BlazorJSEventInterop<ElementSizeArgs> _resize;
private BlazorJSEventInterop<ElementVisibilityArgs> _visibility;
_resize = new BlazorJSEventInterop<ElementSizeArgs>(_jsRuntime);
await _resize.OnResize(OnResized, ".my-chart"); // without a selector: the whole viewport
_visibility = new BlazorJSEventInterop<ElementVisibilityArgs>(_jsRuntime);
await _visibility.OnVisibilityChanged(LoadMore, "#load-more-marker", threshold: 0.5);
private Task OnResized(ElementSizeArgs args) // Width, Height, Top, Left
=> InvokeAsync(() => RedrawChart(args.Width, args.Height));
private async Task LoadMore(ElementVisibilityArgs args) // IsVisible, Ratio
{
if (args.IsVisible)
await LoadNextPage();
}
Typical use cases: redrawing a canvas or chart when its container changes, and infinite scrolling or lazy loading with a marker element at the end of a list.
<ins>Dialogs and small helpers</ins>
await jsRuntime.AlertAsync("Saved");
var ok = await jsRuntime.ConfirmAsync("Delete this item?");
var name = await jsRuntime.PromptAsync("Your name?", "Flo");
await jsRuntime.AddCss(".demo { color: #22d3ee }", "my-styles", skipIfElementExists: true);
await jsRuntime.LoadCss("css/component.css"); // from an <EmbeddedResource>
var exists = await jsRuntime.IsElementAvailableAsync("my-element-id");
await jsRuntime.RemoveElementAsync("my-element-id");
var scripts = await jsRuntime.GetLoadedScriptsAsync();
// wait for a global that a third party script defines
var ready = await jsRuntime.WaitForNamespaceAsync("google.maps");
<ins>BaseComponent for Js wrapper components</ins>
BlazorJS provides a small base component called BlazorJsBaseComponent<T> to create a JS wrapper component.
This is a simple way to create a JS object reference from a module and use it in your blazor component.
- Create a razor component
<ins>YourComponent.razor</ins>
@inherits BlazorJs.BlazorJsBaseComponent<YourComponent>
<div @ref="ElementReference">
</div>
<ins>YourComponent.razor.cs</ins>
public partial class YourComponent
{
protected override string ComponentJsFile() => "./js/PathToYourComponent.js";
protected override string ComponentJsInitializeMethodName() => "initializeMethodForYourComponent";
[Parameter]
public string SomeGeneralParam { get; set; }
[Parameter, ForJs]
public int ParamForJs { get; set; } = 100;
[Parameter, ForJs("anotherParamForJsWithDifferentNameInJs")]
public int AnotherParamForJs { get; set; } = 100;
protected override async Task OnJsOptionsChanged()
{
// This method will automatically be called when a parameter marked with [ForJs] has changed
if (JsReference != null)
await JsReference.InvokeVoidAsync("setOptions", MyJsOptions());
}
private object MyJsOptions()
{
return this.AsJsObject(new
{
configValueWirthoutParam = 123,
});
}
/// <summary>
/// Gets the JavaScript arguments to pass to the component.
/// We override here because by default only the element reference and dotnet reference is passed
/// but we want to have directly the JsOptions available.
/// </summary>
public override object[] GetJsArguments() => new[] { ElementReference, CreateDotNetObjectReference(), MyJsOptions() };
}
- Create your js file thats located in the path you have defined in
ComponentJsFile()
<ins>./js/PathToYourComponent.js</ins>
class YourComponent {
elementRef;
dotnet;
constructor(elementRef, dotNet, options) {
this.elementRef = elementRef;
this.dotnet = dotNet;
this.createWhatever(options);
}
createWhatever(options) {
// Do something with the options
console.log(options.paramForJs);
console.log(options.anotherParamForJsWithDifferentNameInJs);
console.log(options.configValueWirthoutParam);
}
setOptions(options) {
// Just update the options with the new ones
}
dispose() {
// Dispose everything you created
}
}
window.YourComponent = YourComponent;
// This method will be called from the BlazorJsBaseComponent and should match the name you have defined in `ComponentJsInitializeMethodName()`
export function initializeMethodForYourComponent(elementRef, dotnet, options) {
return new YourComponent(elementRef, dotnet, options);
}
<ins>Browser detect</ins>
<BrowserDetect @bind-browserInfo="@Info"
OSVersionUpdate="v => osVersion = v"
OSArchitectureUpdate="a => architecture = a" />
@code {
public BrowserInfo Info { get; set; }
}
BrowserInfo carries browser name and version, engine, operating system, screen resolution, time zone,
user agent and the IsMobile / IsAndroid / IsIPhone / IsIPad flags.
The full documentation lives at fgilde.github.io/BlazorJS. BlazorJS is MIT licensed.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Microsoft.AspNetCore.Components.Web (>= 3.1.0)
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.11)
-
net6.0
- Microsoft.AspNetCore.Components.Web (>= 6.0.36)
-
net7.0
- Microsoft.AspNetCore.Components.Web (>= 7.0.20)
-
net8.0
- Microsoft.AspNetCore.Components.Web (>= 8.0.30)
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.19)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on BlazorJS:
| Package | Downloads |
|---|---|
|
MudBlazor.Extensions
MudBlazor.Extensions is a small extension library for MudBlazor from https://mudblazor.com/ |
|
|
AuralizeBlazor
AuralizeBlazor is a wrapper component for audioMotion-analyzer. |
|
|
MudExRichTextEditor
MudExRichTextEditor is a custom reusable control that allows us to easily consume Quill combining in a MudBlazor project. |
GitHub repositories (2)
Showing the top 2 popular GitHub repositories that depend on BlazorJS:
| Repository | Stars |
|---|---|
|
fgilde/MudBlazor.Extensions
MudBlazor.Extensions from https://www.mudex.org is a small extension for MudBlazor from https://mudblazor.com
|
|
|
fgilde/MudExRichTextEditor
Quill based RichEditor component for MudBlazor
|
| Version | Downloads | Last Updated |
|---|---|---|
| 2.3.0 | 32 | 9/4/2026 |
| 2.2.0 | 313,268 | 1/7/2025 |
| 2.1.6 | 72,061 | 6/28/2024 |
| 2.1.5 | 65,975 | 2/2/2024 |
| 2.1.4 | 15,344 | 11/25/2023 |
| 2.1.3 | 557 | 11/24/2023 |
| 2.1.2 | 10,660 | 11/2/2023 |
| 2.0.9 | 11,595 | 9/13/2023 |
| 2.0.8 | 5,396 | 7/29/2023 |
| 2.0.7 | 652 | 7/28/2023 |
| 2.0.6 | 1,792 | 7/14/2023 |
| 2.0.5 | 869 | 6/13/2023 |
| 2.0.4 | 16,796 | 4/21/2023 |
| 2.0.3 | 6,699 | 3/2/2023 |
| 2.0.2 | 2,801 | 2/15/2023 |
| 2.0.1 | 782 | 2/8/2023 |
| 2.0.0 | 3,304 | 1/23/2023 |
| 1.0.4 | 3,084 | 12/9/2022 |
| 1.0.3 | 807 | 12/9/2022 |