A Simple To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly
Jollen Moyani
Posted on December 19, 2022
I hope you know that Microsoft rejected offering built-in support for drag-and-drop in Blazor. You can learn more about this denial in Visual Studio Magazine and the GitHub issue.
This blog will implement a simple to-do Blazor WebAssembly app using the HTML5 drag-and-drop API and Syncfusion Blazor Card and Accordion components via JavaScript interop. In the app, we will implement the following functionalities:
- Drag and drop tasks within the to-do section.
- Drag and drop tasks to the completed section.
- Mark a task as done on the change to the radio button.
Getting started with Blazor WASM
To get started with a Blazor WebAssembly app, refer to the Blazor documentation.
Create a Task Model class
Create a model class named TaskModel with the properties Id , Summary , Status , and Timeline to manage the tasks.
Create a TaskStatuses enum with the members Todo and Completed.
Refer to the following code:
namespace Blazor_WASM_Drag_and_Drop.Models
{
public class TaskModel
{
public int Id { get; set; }
public TaskStatuses Status { get; set; }
public string Summary { get; set; } = String.Empty;
public DateTime Timeline { get; set; }
}
public enum TaskStatuses
{
Todo,
Completed
}
}
Initialize the data based on the model class in OnInitializedAsync.
@code
{
public static List<TaskModel> Tasks = new List<TaskModel>();
protected override void OnInitialized()
{
InitTasks();
}
public void InitTasks()
{
Tasks.Add(new TaskModel
{
Id = 1,
Summary = "Planning Tasks for sprint",
Status = TaskStatuses.Todo,
Timeline = new DateTime(2022, 04, 09)
});
…
}
}
Enable drag and drop for tasks
Create a TaskDetail component using the Syncfusion Blazor Card.
To learn more about integrating the Syncfusion Blazor Card in a Blazor application, refer to its documentation.
Refer to the following code to bind the draggable attribute.
<SfCard>
<CardContent>
<div class="taskitem" id="@TaskModel.Id.ToString()" draggable="@IsDraggable">
<div class="taskstate">
<SfRadioButton Value="@TaskModel.Status.ToString()" Checked="IsCompleted"></SfRadioButton>
</div>
<div class="taskcontent">
<div class="tasksummary @TaskModel.Status.ToString().ToLower()"> @TaskModel.Summary </div>
<div class="tasktimeline"> @TaskModel.Timeline.ToLongDateString() </div>
</div>
</div>
</CardContent>
</SfCard>
@code {
string IsDraggable = string.Empty;
string IsCompleted = "Completed";
[Parameter]
public TaskModel TaskModel { get; set; }
protected override void OnInitialized()
{
IsDraggable = TaskModel.Status == TaskStatuses.Todo ? "true" : "false";
}
}
HTML5 drag-and-drop APIs in C
Blazor supports the C# representation of drag-and-drop events. You can find details about it in the ASP.NET Core Blazor event handling documentation.
ToDo component with drag-and-drop events
Create a ToDo component and bind drag-and-drop-related events to the root elements. Also, define C# functions for the following actions:
- By default, the drop action is not allowed. So, set preventDefault as true in *ondragover and dragenter * events.
- Include child component TaskDetail by passing TaskModel as a parameter.
Refer to the following code.
@page “/”
@using Syncfusion.Blazor.Navigations
@inject IJSRuntime JSInterop
<PageTitle>A simple To-Do App using Blazor WebAssembly</PageTitle>
<div class=”droptarget” @ondragstart=”DragStart” @ondragover=”DragOver”
@ondragover:preventDefault=”shouldPreventDefault” @ondragenter:preventDefault=”shouldPreventDefault”
@ondragleave=”DragLeave” @ondrop=”DropCard” @ondrop:preventDefault=”shouldPreventDefault”>
@foreach (var task in Tasks.FindAll(c => c.Status == TaskStatuses.Todo))
{
<CascadingValue Value=”this”>
<TaskDetail TaskModel=”task”></TaskDetail>
</CascadingValue>
}
</div>
@code
{
private bool shouldPreventDefault = true;
private void DragOver(System.EventArgs e)
{
}
private void DragStart(System.EventArgs e)
{
}
private void DragEnter(System.EventArgs e)
{
}
private void DragLeave(System.EventArgs e)
{
}
private void DropCard(System.EventArgs e)
{
}
}
JavaScript interop
Pass the data from the server to the client and the client to the server through JS interop.
- Inject IJSRuntime in the *ToDo * component.
- Call a JavaScript method using the InvokeVoidAsync method from ondragstart and ondrop to get the dragged task ID and the dropped task ID.
- The server method must be the *JSInvokableAttribute * attribute.
Refer to the following code.
@page "/"
@using Syncfusion.Blazor.Navigations
@inject IJSRuntime JSInterop
<PageTitle>A simple To-Do App using Blazor WebAssembly</PageTitle>
<div class="droptarget" ondragstart="DragStart(event,event.target.id);" @ondragover="DragOver"
@ondragover:preventDefault="shouldPreventDefault" @ondragenter:preventDefault="shouldPreventDefault"
@ondragleave="DragLeave" @ondrop="DropCard" @ondrop:preventDefault="shouldPreventDefault">
@foreach (var task in Tasks.FindAll(c => c.Status == TaskStatuses.Todo))
{
<CascadingValue Value="this">
<TaskDetail TaskModel="task"></TaskDetail>
</CascadingValue>
}
</div>
@code
{
private bool shouldPreventDefault = true;
public static List<TaskModel> Tasks = new List<TaskModel>();
private void DragOver(System.EventArgs e)
{
}
private async Task DragStart(System.EventArgs e, string Id)
{
await JSInterop.InvokeVoidAsync("DragStart", e, Id);
}
private void DragEnter(System.EventArgs e)
{
}
private void DragLeave(System.EventArgs e)
{
}
private async Task DropCard(System.EventArgs e)
{
await JSInterop.InvokeVoidAsync("DropCard", e);
}
[JSInvokableAttribute("UpdateCards")]
public static void UpdateCardsAfterDrop(bool IsCompleted, string dragTarget, string dropTarget)
{
}
}
Create a JavaScript file, DragAndDrop.js , within the wwwroot folder and add it to the index.html file.
<!DOCTYPE html>
<html lang="en">
<head>
…
<script src="DragAndDrop.js"></script>
</head>
<body>
</body>
</html>
Include the following code in the DragAndDrop.js file.
var dragCardId = null;
function DragStart(args, id) {
dragCardId = id;
}
function DropCard(args) {
var dropElement = document.elementFromPoint(args.clientX, args.clientY);
var IsDroppedOnCompletedPane = dropElement.closest('.e-accordion') ? true : false;
var dropCard = dropElement.closest('.taskitem');
DotNet.invokeMethodAsync('Blazor_WASM_Drag_and_Drop', 'UpdateCards', IsDroppedOnCompletedPane, dragCardId, dropCard != null ? dropCard.id : null);
}
In the above code, we:
- Get the drag task ID in the drag start event through its parameter which is passed from C# to JavaScript.
- Fetch the drop task IDd using elementFromPoint in the drop event.
- Call the C# method UpdateCards from JavaScript by invoking the DotNet.invokeMethodAsync method.
Reorder tasks within to-do items using drag and drop
Reorder the tasks by getting the corresponding index based on its ID.
[JSInvokableAttribute("UpdateCards")]
public static void UpdateCardsAfterDrop(bool IsCompleted, string dragTarget, string dropTarget)
{
int dragIndex = Tasks.IndexOf(Tasks.Single(d => d.Id == int.Parse(dragTarget)));
int dropIndex = Tasks.IndexOf(Tasks.Single(d => d.Id == int.Parse(dropTarget)));
if (dropIndex != -1)
{
TaskModel task = Tasks[dragIndex];
Tasks.RemoveAt(dragIndex);
Tasks.Insert(dropIndex, task);
}
}
In the above code, we:
- Store the dragged task in a temporary variable.
- Remove the task from the dragged position.
- Append the dragged task at the dropped position.
Update ‘Mark as Done’ when dragged and dropped
We include the Accordion component within the ToDo component to list the completed tasks. Learn more about the Syncfusion Blazor Accordion component from the getting started documentation.
When dragging a task from the to-do section and dropping it within the Accordion pane, update the dragged task status as Completed based on its index.
[JSInvokableAttribute("UpdateCards")]
public static void UpdateCardsAfterDrop(bool IsCompleted, string dragTarget, string dropTarget)
{
int dragIndex = Tasks.IndexOf(Tasks.Single(d => d.Id == int.Parse(dragTarget)));
Tasks[dragIndex].Status = TaskStatuses.Completed;
}
‘Mark as Done’ on change to radio button value
Mark the task as completed after clicking on the radio button within the to-do pane.
<SfCard>
<CardContent>
<div class="taskitem" id="@TaskModel.Id.ToString()" draggable="@IsDraggable">
<div class="taskstate">
<SfRadioButton Value="@TaskModel.Status.ToString()" Checked="IsCompleted" ValueChange="@((ChangeArgs<string> s) => OnTaskStatusChange(s))"></SfRadioButton>
</div>
<div class="taskcontent">
<div class="tasksummary @TaskModel.Status.ToString().ToLower()"> @TaskModel.Summary </div>
<div class="tasktimeline"> @TaskModel.Timeline.ToLongDateString() </div>
</div>
</div>
</CardContent>
</SfCard>
@code {
…
private void OnTaskStatusChange(ChangeArgs<string> args)
{
TaskParent.OnTaskStatusChanged(TaskModel);
args.Value = "Completed";
}
[CascadingParameter]
public ToDo TaskParent { get; set; }
}
In this code, we:
- Bind the ValueChange event to SfRadioButton.
- Define parent component ToDo with CascadingParameter.
- Pass the selected value from the child component ( TaskDetail ) to the parent component ( ToDo ) by calling the parent’s OnTaskStatusChanged
In the ToDo component, define the method OnTaskStatusChanged to receive a selected value from the child component ( TaskDetail ).
@code
{
…
public void OnTaskStatusChanged(TaskModel task)
{
Tasks.FirstOrDefault(task).Status = TaskStatuses.Completed;
StateHasChanged();
}
}
Render a child component TaskDetail within the CascadingValue component.
@page "/"
@using Syncfusion.Blazor.Navigations
@inject IJSRuntime JSInterop
<PageTitle>A simple To-Do App using Blazor WebAssembly</PageTitle>
<div class="droptarget" ondragstart="DragStart(event,event.target.id);" @ondragover="DragOver"
@ondragover:preventDefault="shouldPreventDefault" @ondragenter:preventDefault="shouldPreventDefault"
@ondragleave="DragLeave" @ondrop="DropCard" @ondrop:preventDefault="shouldPreventDefault">
@foreach (var task in Tasks.FindAll(c => c.Status == TaskStatuses.Todo))
{
<CascadingValue Value="this">
<TaskDetail TaskModel="task"></TaskDetail>
</CascadingValue>
}
</div>
@code
{
…
public void OnTaskStatusChanged(TaskModel task)
{
Tasks.FirstOrDefault(task).Status = TaskStatuses.Completed;
StateHasChanged();
}
}
Now the application is ready. Refer to the following GIF image to see what the application should look like.
Resources
You can access the complete project in the to-do app using JS interop with HTML5 drag and drop in Blazor WebAssembly GitHub repository.
Conclusion
In this blog post, we have seen how to create a simple to-do app in Blazor WebAssembly using the JS interop with HTML5 drag-and-drop feature. I hope you find this blog useful.
Syncfusion’s Blazor component suite offers over 70 UI components. They work with both server-side and client-side (WebAssembly) hosting models seamlessly. Use them to build marvelous apps!
If you have any questions or comments, you can contact us through our support forum, support portal, or feedback portal. We are always happy to assist you!
Related blogs
If you like this blog, we think you will enjoy the following Blazor articles as well:
Posted on December 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 19, 2022