A Simple To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly
Tags: blazor, webdev, csharp, dotnet
Published: 2022-12-19 13:10:19

---
title: A Simple To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly
published: true
date: 2022-12-19 11:30:26 UTC
tags: blazor, webdev, csharp, dotnet
canonical_url: https://www.syncfusion.com/blogs/post/to-do-app-using-js-interop-with-html5-drag-and-drop-in-blazor-webassembly.aspx
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49ty2nmyotc1c63o5hrp.png
---

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](https://visualstudiomagazine.com/articles/2022/03/30/blazor-drag-drop.aspx "Microsoft Won't Do Drag-and-Drop for Blazor") and the [GitHub issue](https://github.com/dotnet/aspnetcore/issues/18754 "Dragging and dropping in Blazor app demos on GitHub").

This blog will implement a simple to-do Blazor WebAssembly app using the HTML5 drag-and-drop API and Syncfusion [Blazor Card](https://www.syncfusion.com/blazor-components/blazor-card "Syncfusion Blazor Card component") and [Accordion](https://www.syncfusion.com/blazor-components/blazor-accordion "Syncfusion Blazor Accordion component") 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](https://blazor.syncfusion.com/documentation/getting-started/blazor-webassembly-visual-studio "Getting Started with Blazor WebAssembly App in Visual Studio").

## 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:

```csharp
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**.

```csharp
@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](https://blazor.syncfusion.com/documentation/card/getting-started "Getting Started with Blazor Card Component").

Refer to the following code to bind the draggable attribute.

```xml
<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](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-6.0 "ASP.NET Core Blazor event handling documentation").
<figure>

  <img src="https://www.syncfusion.com/blogs/wp-content/uploads/2022/12/ASP.NET-Core-Blazor-event-handling-documentation.png" alt="ASP.NET Core Blazor event handling" style="width:100%">

  <figcaption>[Source: ASP.NET Core Blazor event handling](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-6.0)</figcaption>
## 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.

```csharp
@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.

```csharp
@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.

```xml
<!DOCTYPE html>
<html lang="en">

<head>
  …
  <script src="DragAndDrop.js"></script>
</head>

<body>
</body>

</html>
```

Include the following code in the **DragAndDrop.js** file.

```csharp
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.

```csharp
[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](https://blazor.syncfusion.com/documentation/accordion/getting-started "Getting Started with Blazor Accordion Component").

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.

```csharp
[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.

```xml
<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** ).

```csharp
@code
{
…

    public void OnTaskStatusChanged(TaskModel task)
    {
        Tasks.FirstOrDefault(task).Status = TaskStatuses.Completed;
        StateHasChanged();
    }
}
```

Render a child component **TaskDetail** within the **CascadingValue** component.

```csharp
@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.
<figure>

  <img src="https://www.syncfusion.com/blogs/wp-content/uploads/2022/12/Creating-a-To-Do-App-Using-JS-Interop-with-HTML5-Drag-and-Drop-in-Blazor-WebAssembly.gif" alt="Creating a To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly" style="width:100%">

  <figcaption>Creating a To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly</figcaption>
<figure>
## Resources

You can access the complete project in the [to-do app using JS interop with HTML5 drag and drop in Blazor WebAssembly](https://github.com/SyncfusionExamples/blazor-drag-and-drop-todo "Simple to-do app using JS interop with HTML5 drag and drop in Blazer WebAssembly GitHub demo") 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](https://www.syncfusion.com/blazor-components "Syncfusion Blazor Components") 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](https://www.syncfusion.com/forums "Syncfusion Support Forum"), [support portal](https://support.syncfusion.com/ "Syncfusion Support Portal"), or [feedback portal](https://www.syncfusion.com/feedback/ "Syncfusion 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:

- [CRUD Operations Using Blazor and MongoDB [Webinar Show Notes]](https://www.syncfusion.com/blogs/post/crud-operations-using-blazor-and-mongodb-webinar-show-notes.aspx "Blog: CRUD Operations Using Blazor and MongoDB [Webinar Show Notes]")
- [Download Files from Azure Blob Storage with Blazor WebAssembly and ASP.NET Web API](https://www.syncfusion.com/blogs/post/download-files-from-azure-blob-storage-with-blazor-webassembly-and-asp-net-web-api.aspx "Blog: Download Files from Azure Blob Storage with Blazor WebAssembly and ASP.NET Web API")
- [A Full-Stack Web App Using Blazor WebAssembly and GraphQL—Part 7](https://www.syncfusion.com/blogs/post/a-full-stack-web-app-using-blazor-webassembly-and-graphql-part-7.aspx "Blog: A Full-Stack Web App Using Blazor WebAssembly and GraphQL—Part 7")
- [Easily Use a Dynamic Blob Container in the Blazor File Manager](https://www.syncfusion.com/blogs/post/easily-use-a-dynamic-blob-container-in-the-blazor-file-manager.aspx "Blog: Easily Use a Dynamic Blob Container in the Blazor File Manager")