A Simple To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly

jollenmoyani

Jollen Moyani

Posted on December 19, 2022

A Simple To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly

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
    }

}
Enter fullscreen mode Exit fullscreen mode

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)
            });
        
    }
}
Enter fullscreen mode Exit fullscreen mode

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

ASP.NET Core Blazor event handling

Source: ASP.NET Core Blazor event handling

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)
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

‘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; }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

Now the application is ready. Refer to the following GIF image to see what the application should look like.

Creating a To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly

Creating a To-Do App Using JS Interop with HTML5 Drag and Drop in Blazor WebAssembly

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:

💖 💪 🙅 🚩
jollenmoyani
Jollen Moyani

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