In this guide you will learn how to set up a reusable Weavy component in your Blazor app. This is based on the getting started guides at dotnet.microsoft.com/learn/aspnet/blazor-tutorial.
You will need .NET SDK installed.
Follow the instuctions for your platform at dotnet.microsoft.com/learn/aspnet/blazor-tutorial/install.
To verify that it's working, simply type dotnet
in your console.
This will create a new basic Blazor app in a new folder. If you already have an app you can simply skip this step.
dotnet new blazorserver -o BlazorApp --no-https
cd BlazorApp
dotnet watch run
This will build and launch the dev server, usually on http://localhost:5000
.
The simplest way of adding the Weavy script is to add it to the <head>
section in your Pages/_Host.cshtml
. Replace showcase.weavycloud.com
with the domain name of your own weavy server.
Pages/_Host.cshtml
<head>
...
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://showcase.weavycloud.com/javascript/weavy.js"></script>
</head>
To be able to work with Weavy in javascript we need a JS Interop in Blazor. The JS interop acts as a bridge between Blazor and JS. To not expose our bridge in the window object, we will create the bridge as a js module that we will import.
Create wwwroot/weavyJsInterop.js
. We just have to expose the creation of a Weavy instance. Everything after that will be acessible as a IJSObjectReference
on which we may call methods and use properties.
weavyJsInterop.js
export function weavy(...options) {
return new window.Weavy(...options);
}
We will create a service wrapped around the JS Interop and the Weavy instance. This will provide a much simpler C# syntax around Weavy, much alike the Weavy syntax used in javascript.
Create a folder Weavy
for our classes.
Place a C# class called WeavyJsInterop.cs
in the Weavy folder. In this file we will first import our JS Interop Module. We can then create a new Weavy instance using the JS Module and access all methods and properties of that instance.
We will delay the initialization of the module until we request the Weavy Instance, to only use it when needed.
WeavyJsInterop.cs
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
namespace BlazorApp.Weavy {
public class WeavyJsInterop : IDisposable {
private readonly IJSRuntime JS;
private bool Initialized = false;
private IJSObjectReference Bridge;
private ValueTask<IJSObjectReference> WhenImport;
// Constructor
// This is a good place to inject any authentication service you may use to provide JWT tokens.
public WeavyJsInterop(IJSRuntime js) {
JS = js;
}
// Initialization of the JS Interop Module
// The initialization is only done once even if you call it multiple times
public async Task Init() {
if (!Initialized) {
Initialized = true;
WhenImport = JS.InvokeAsync<IJSObjectReference>("import", "./weavyJsInterop.js");
Bridge = await WhenImport;
} else {
await WhenImport;
}
}
// Calling Javascript to create a new instance of Weavy via the JS Interop Module
public async ValueTask<IJSObjectReference> Weavy(object options = null) {
await Init();
// Demo JWT only for showcase.weavycloud.com
// Configure your JWT here when using your own weavy server
var jwt = new { jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzYW1hcmEiLCJuYW1lIjoiU2FtYXJhIEthdXIiLCJleHAiOjI1MTYyMzkwMjIsImlzcyI6InN0YXRpYy1mb3ItZGVtbyIsImNsaWVudF9pZCI6IldlYXZ5RGVtbyIsImRpciI6ImNoYXQtZGVtby1kaXIiLCJlbWFpbCI6InNhbWFyYS5rYXVyQGV4YW1wbGUuY29tIiwidXNlcm5hbWUiOiJzYW1hcmEifQ.UKLmVTsyN779VY9JLTLvpVDLc32Coem_0evAkzG47kM" };
return await Bridge.InvokeAsync<IJSObjectReference>("weavy", new object[] { jwt, options });
}
public void Dispose() {
Bridge?.DisposeAsync();
}
}
}
To be able to use the JS Interop as a sevice throughout the app, we need to register it in our Startup.cs
. Add the following line in the ConfigureServices
method. You also need to use the namespace.
Startup.cs
using BlazorApp.Weavy;
// ...
public void ConfigureServices(IServiceCollection services) {
// ...
services.AddScoped<WeavyJsInterop>();
}
When communicating with Javascript from your app, you'll get back references to the Javascript objects. For convenience, we will wrap weavy, spaces and apps in extended IJSObjectReference
classes. This is a convenient way to expose Javascript methods and properties to C#. It's also convenient to manage the Javascript cleanup here when you want to dispose them.
First of we'll make a base class for IJSObjectReference, so we can extend it easily. Create a class file ExtendableJSObjectREference.cs
in your Weavy folder
Weavy/ExtendableJSObjectReference.cs
using Microsoft.JSInterop;
using System.Threading;
using System.Threading.Tasks;
namespace BlazorApp.Weavy {
//
// Summary:
// Wrapper around a IJSObjectReference to enable extending
public class ExtendableJSObjectReference : IJSObjectReference {
public IJSObjectReference ObjectReference;
// Constructed using another IJSObjectReference
// Possibility to delay ObjectReference assignment
public ExtendableJSObjectReference(IJSObjectReference objectReference = null) {
ObjectReference = objectReference;
}
// IMPLEMENT DEFAULT
public ValueTask DisposeAsync() {
return ObjectReference.DisposeAsync();
}
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) {
return ObjectReference.InvokeAsync<TValue>(identifier, args);
}
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args) {
return ObjectReference.InvokeAsync<TValue>(identifier, cancellationToken, args);
}
}
}
Next, well create three classes for respecively WeavyReference
, SpaceReference
and AppReference
. Create a file WeavyReference.cs
in your Weavy folder. The classes will extend the ExtendableJSObjectReference
and just add the methods we want to reflect from our Javascript objects. The WeavyReference class also has automatic initialization, so it don't need to construct a Weavy instance in Javascript until it's needed.
Weavy/WeavyReference.cs
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace BlazorApp.Weavy {
//
// Summary:
// Wrapped IJSObjectReference to the Weavy instance in Javascript.
// Adds .Space() and .Destroy() methods.
public class WeavyReference : ExtendableJSObjectReference {
private bool Initialized = false;
public WeavyJsInterop WeavyService;
public object Options;
public ValueTask<IJSObjectReference> WhenWeavy;
public WeavyReference(WeavyJsInterop weavyService = null, object options = null, IJSObjectReference weavy = null) : base(weavy) {
Options = options;
WeavyService = weavyService;
}
public async Task Init() {
if(!Initialized) {
Initialized = true;
WhenWeavy = WeavyService.Weavy(Options);
ObjectReference = await WhenWeavy;
} else {
await WhenWeavy;
}
}
public async ValueTask<SpaceReference> Space(object spaceSelector = null) {
await Init();
return new(await ObjectReference.InvokeAsync<IJSObjectReference>("space", new object[] { spaceSelector }));
}
// Used for cleanup
public async Task Destroy() {
await ObjectReference.InvokeVoidAsync("destroy");
await DisposeAsync();
}
}
//
// Summary:
// Wrapped IJSObjectReference to a Weavy Space in Javascript.
// Adds .App() and .Remove() methods.
public class SpaceReference : ExtendableJSObjectReference {
public SpaceReference(IJSObjectReference space) : base(space) { }
public async ValueTask<AppReference> App(object appSelector = null) {
return new(await ObjectReference.InvokeAsync<IJSObjectReference>("app", new object[] { appSelector }));
}
// Used for cleanup
public async Task Remove() {
await ObjectReference.InvokeVoidAsync("remove");
await DisposeAsync();
}
}
//
// Summary:
// Wrapped IJSObjectReference to a Weavy App in Javascript.
// Adds .Open(), .Close(), .Toggle() and .Remove() methods()
public class AppReference : ExtendableJSObjectReference {
public AppReference(IJSObjectReference app) : base(app) { }
public ValueTask Open() {
return ObjectReference.InvokeVoidAsync("open");
}
public ValueTask Close() {
return ObjectReference.InvokeVoidAsync("close");
}
public ValueTask Toggle() {
return ObjectReference.InvokeVoidAsync("toggle");
}
// Used for cleanup
public async Task Remove() {
await ObjectReference.InvokeVoidAsync("remove");
await DisposeAsync();
}
}
}
Now we have a nice toolbox for being able to use Weavy in components or for custom code! Just add the namespace to your _Imports.razor
file to be able to use it anywhere.
_Imports.razor
// ...
@using BlazorApp.Weavy
You can now use Weavy in code like this:
@inject WeavyJsInterop WeavyService
@code {
// ...
var Weavy = new WeavyReference(WeavyService);
var Space = await Weavy.Space(new { key = "my-space" });
var FilesApp = await Space.App(new { key = "my-files", type = "files" });
// ...
}
Now that we have a nice syntax for Weavy, we can create components for simple declarative usage. You can easily create more custom components to fit your needs.
First we will create a component for the weavy instance. This will be a component creating a weavy scope for it's children.
First we inject the WeavyJsInterop
service in the component. We just make use of the service when creating a new WeavyReference. We map all component parameters to weavy options using attribute splatting.
We make use of the CascadingValue
tag to provide the WeavyReference
to any children. This means we can make use of the same weavy instance for multiple weavy apps that are placed as children of this component.
At last we also we let the Dispose
method call the .destroy()
method on our WeavyReference
to clean up in Javascript whenever the component gets disposed.
Create a Weavy.razor
component in your Shared
folder.
Shared/Weavy.razor
@implements IDisposable
@inject WeavyJsInterop WeavyService
<CascadingValue Value="WeavyRef">
@ChildContent
</CascadingValue>
@code{
WeavyReference WeavyRef;
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> Options { get; set; }
protected override void OnInitialized() {
WeavyRef = new(WeavyService, Options);
}
public void Dispose() {
WeavyRef?.Destroy();
}
}
This component may now be used both with our without attributes.
<Weavy>
<!-- Any children here will have access to the weavy instance using a cascading parameter -->
...
</Weavy>
<Weavy id="weavy-blazor" plugins="@(new { deeplinks = true })">
<!-- Another Weavy instance with some custom options passed to javascript -->
...
</Weavy>
The Weavy App component will make use of the weavy instance and must therefore be placed as a child of the Weavy Instance component. The weavy instance is catched by the CascadingParameter
, which will match the type of the CascadingValue
.
A <div>
with a @ref
is used to pass as a container reference in options when creating the app in Javascript.
We need to create the app in OnAfterRenderAsync
to be able to use the ElementReference
of the div.
Create a parameter for the space key. If you want more flexibility in more advanced scenarios, you can create a separate weavy space component as a layer in between the weavy component and the weavy app component. Most of the time it will do to just have the space key as a parameter on the weavy app.
Shared/WeavyApp.razor
@implements IDisposable
<div @ref="WeavyContainer" class="weavy-app"></div>
@code{
ElementReference WeavyContainer;
[CascadingParameter]
protected WeavyReference Weavy { get; set; }
[Parameter]
public string SpaceKey { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> Options { get; set; }
public SpaceReference Space;
public AppReference App;
protected override async Task OnAfterRenderAsync(bool firstRender) {
if (firstRender) {
Options.Add("container", WeavyContainer);
Space = await Weavy.Space(new { key = SpaceKey });
App = await Space.App(Options);
}
}
public void Dispose () {
App?.Remove();
}
}
Add som styling for the container. Weavy always adapts it's size to the container where it's places, therefore we need to somehow specify a height for the container otherwise the height will be 0. You man also make use of display: contents;
to make weavy adapt itself to any parent node of the component instead. This makes it more flexible to use the component in different layouts. Add a WeavyApp.razor.css
file in the Shared
folder.
Shared/WeavyApp.razor.css
.weavy-app {
display: contents;
}
The Weavy App component is now ready for usage. Weavy requires you to have at least a space key, an app key and an app type. You must also place the component within a Weavy Instance component and also define a height somehow on the parent.
<Weavy>
<div style="height: 44rem;">
<WeavyApp SpaceKey="blazor-space" key="blazor-posts" type="posts" />
</div>
</Weavy>
Now the components are set up for declarative usage of weavy! Lets create two pages for Posts and Files to demonstrate the usage.
Pages/Posts.razor
@page "/posts"
<Weavy>
<h1>Posts</h1>
<div style="height: 44rem;">
<WeavyApp SpaceKey="blazor-space" key="blazor-posts" type="posts" name="Blazor Posts" />
</div>
</Weavy>
To make use of Razor expressions for the attributes, you should make use of the @key
attribute and set it to the same as the key of the app. This way, the weavy app will get properly replaced when needed.
Pages/Files.razor
@page "/files"
<h1>Files</h1>
<nav class="nav my-2">
<button class="btn" @onclick="@(e => { FilesKey = "blazor-files-2019"; FilesName = "Blazor Files 2019"; })">2019</button>
<button class="btn" @onclick="@(e => { FilesKey = "blazor-files-2020"; FilesName = "Blazor Files 2020"; })">2020</button>
</nav>
<Weavy>
<div class="card" style="height: 32rem;">
<WeavyApp SpaceKey="blazor-space" type="files" @key="FilesKey" key="@FilesKey" name="@FilesName" />
</div>
</Weavy>
@code {
private string FilesKey { get; set; } = "blazor-files";
private string FilesName { get; set; } = "Blazor Files";
}
Don't forget to add the two pages in your NavMenu
.
Shared/NavMenu.razor
...
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
...
<li class="nav-item px-3">
<NavLink class="nav-link" href="posts">
<span class="oi oi-comment-square" aria-hidden="true"></span> Posts
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="files">
<span class="oi oi-folder" aria-hidden="true"></span> Files
</NavLink>
</li>
</ul>
</div>
Now you're all done and dotnet should have automatically have recompiled everything for you! Try it out in the browser!
Select what technology you want to ask about
Get answers faster with our tailored generative AI. Learn more about how it works
This is a custom LLM for answering your questions. Answers are based on the contents of the documentation and this feature is experimental.
To access live chat with our developer success team you need a Weavy account.