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:
To get started with a Blazor WebAssembly app, refer to the Blazor documentation.
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) }); … } }
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"; } }
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.
Create a ToDo component and bind drag-and-drop-related events to the root elements. Also, define C# functions for the following actions:
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) { } }
Pass the data from the server to the client and the client to the server through JS interop.
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:
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:
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 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:
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.
You can access the complete project in the to-do app using JS interop with HTML5 drag and drop in Blazor WebAssembly GitHub repository.
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!
If you like this blog, we think you will enjoy the following Blazor articles as well: