In Part I, I walked through the details of simple JavaScript module I created to support drag and drop functionality in Blazor. In this post I will go over the design of the C# class which leverages the JavaScript module and provides the ability to send movement data to Blazor components.
public DragDropInterop(IJSRuntime jsRuntime)
{
ModuleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
"import", "./_content/Finaltouch.DragDrop.Components/dragDropInterop.js").AsTask());
ObjRef = DotNetObjectReference.Create(this);
}
public async ValueTask Initialize(Func<DragDropResult, Task>? func, DragDropOptions options)
{
if (func == null)
{
throw new ArgumentNullException(nameof(func));
}
Func = func;
options ??= new DragDropOptions();
JsonSerializerOptions SerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false,
};
var jsonString = JsonSerializer.Serialize(options, SerializerOptions);
var value = await ModuleTask.Value;
await value.InvokeVoidAsync("initialize", ObjRef, jsonString);
}
public async ValueTask AddListeners()
{
var value = await ModuleTask.Value;
await value.InvokeVoidAsync("addListeners");
}
[JSInvokable]
public async Task OnPointerUp(DragDropResult result)
{
if (Func != null)
{
await Func(result);
}
}
The DragDropInterop class is intended for use as a dependency injection service. Following best practices described in the Microsoft documentation and examples for JavaScript interoperability, I use lazy instantiation to create a reference to the JavaScript module. Three exported methods are called by this class. Before any drag and drop functionality can be used, you have to call Initialize() on this class. Initialize receives a Func delegate and any non-default options that you want to specify, for example whether to disable sorting or non-default classnames for containers and draggable items in your Blazor components. The Func delegate is is used so you can apply changes to the component UI. It's deliberately meant to call an async method so that you can easily call external REST services, etc. when you update your UI.
The second method AddListeners() is called after Initialize has completed. You might wonder why I didn't simply add my task / item listeners as part of the initialization process. The reason is simple. The Func delegate we pass to this class must call StateHasChanged() in order to signal the Blazor framework that the UI needs to be updated. Remember that this JavaScript module was specifically designed not to mutate the DOM. The delegate makes sure that all DOM changes are managed by Blazor. Unfortunately, when you call StateHasChanged(), any event listeners which we added earlier are removed, thus we have to add back new ones before we can initiate another drag and drop operation. You call AddListeners() in the OnAfterRenderAsync() method of your Blazor components so that any task/item elements to which you add listeners are already created in the DOM.
The OnPointerUp() method is called by the JavaScript module when the user releases a mouse button, or removes their finger from a touch screen, etc. Note that the method is only called if a change in the DOM needs occurred. If the the pointer is released when it is not located over a container (target), then there is really no need to update the DOM.
builder.Services.AddScoped(typeof(DragDropInterop));
Be sure to remember to register this service in the Program.cs class of your WASM client project so you can inject it into your Blazor pages and components. In Part III we'll examine my demo project to give you a better idea of how to apply this library in your own applications. Remember, all the code here is available from my public GitHub repository.
No comments:
Post a Comment