Todo Board – Drag & Drop in LWC Components

Managing tasks effectively is key to staying productive, and what better way to achieve this than by creating a smart to-do board in Salesforce? In this post, we’ll guide you through building a modern to-do board using Salesforce Lightning Web Components (LWC) with drag-and-drop functionality. 

What you will learn
  1. Build todo app with Salesforce LWC and apex 
  2. Creating Lighting Application In Salesforce 
  3. Implement Drag & Drop Functionality In LWC
  4.  Build multi column layout with lighting web components 
  5. Show profile icon in using lightning-avatar
  6. Apex & LWC Connection  Add Update data
Watch Demo
Code Files
HTML
<template>
    <div class="slds-theme_shade">
        <div class="demo-only demo-only--sizing slds-grid slds-wrap">
            <div class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small slds-border_right">
                <h1 class="slds-align_absolute-center slds-text-title_bold header-color">New</h1> 
            </div>
            <div  class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small slds-p-left_small slds-border_right">
                <h1 class="slds-align_absolute-center slds-text-title_bold header-color">In Progress</h1>
            </div>
            <div  class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small slds-p-left_small slds-border_right">
                <h1 class="slds-align_absolute-center slds-text-title_bold header-color">Dev Completed</h1>
            </div>
            <div  class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small slds-p-left_small slds-border_right">
                <h1 class="slds-align_absolute-center slds-text-title_bold" style="color:#3BA755">Done</h1>
            </div>
        </div>
    </div>
    <div class="slds-theme_default slds-theme_shade">
        <div class="demo-only demo-only--sizing slds-grid slds-wrap ">
            <div class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small">
                <div id="newTask"  style="height:100%" data-role="drop-target" ondrop={handleDrop} ondragenter={handleDragEnter} ondragover={handleDragOver} ondragleave={handleDragLeave}>
                    <template if:true={taskNewList}>
                        <template for:each={taskNewList} for:item="data">
                            <div key={data.Id} draggable="true" id={data.Id} data-id={data.Id} ondragstart={taskDragStart} ondragend={taskDragEnd} class="slds-p-around_small">
                                <lightning-card style="width: 50px;" variant="Narrow">
                                    <!-- Title Section -->
                                    <div slot="title" class="todo-card-title">
                                        <a href={data.urlToLocate} target="_blank" class="slds-text-link">
                                            <strong>{data.TodoNumber}</strong>
                                        </a>
                                    </div>
                                    <!-- Body Section -->
                                    <div class="todo-card-body slds-p-horizontal_small">
                                        <!-- Description -->
                                        <p class="todo-card-description">
                                            {data.Title}
                                        </p>
                                        <!-- Priority -->
                                        <div class="slds-grid  slds-m-top_small">
                                            <div>
                                                <lightning-avatar size="x-small" src="https://www.lightningdesignsystem.com/assets/images/avatar2.jpg" initials="JD" fallback-icon-name="standard:person_account" alternative-text="Jane Doe" class="slds-m-right_small"></lightning-avatar>
                                            </div>
                                            <div class="slds-p-left_xx-small">
                                                <lightning-avatar
                                                    size="x-small"
                                                    alternative-text="Assignee"
                                                    fallback-icon-name={data.Priority}
                                                ></lightning-avatar>
                                            </div>
                                        </div>
                                    </div>
                                
                                   
                                </lightning-card>
                            </div>
                        </template>
                    </template>
                </div>
            </div>
            <div  class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small slds-p-left_small">
                <div id="InProgress" style="height:100%" data-role="drop-target" ondrop={handleDrop} ondragenter={handleDragEnter} ondragover={handleDragOver} ondragleave={handleDragLeave}>
                    <template if:true={taskInProgressList}>
                        <template for:each={taskInProgressList} for:item="data">
                            <div key={data.Id} draggable="true" id={data.Id} data-id={data.Id} ondragstart={taskDragStart} ondragend={taskDragEnd} class="slds-p-around_small">
                                <lightning-card style="width: 50px;" variant="Narrow">
                                    <!-- Title Section -->
                                    <div slot="title" class="todo-card-title">
                                        <a href={data.urlToLocate} target="_blank" class="slds-text-link">
                                            <strong>{data.TodoNumber}</strong>
                                        </a>
                                    </div>
                                    <!-- Body Section -->
                                    <div class="todo-card-body slds-p-horizontal_small">
                                        <!-- Description -->
                                        <p class="todo-card-description">
                                            {data.Title}
                                        </p>
                                        <!-- Priority -->
                                        <div class="slds-grid  slds-m-top_small">
                                            <div>
                                                <lightning-avatar size="x-small" src="https://www.lightningdesignsystem.com/assets/images/avatar2.jpg" initials="JD" fallback-icon-name="standard:person_account" alternative-text="Jane Doe" class="slds-m-right_small"></lightning-avatar>
                                            </div>
                                            <div class="slds-p-left_xx-small">
                                                <lightning-avatar
                                                    size="x-small"
                                                    alternative-text="Assignee"
                                                    fallback-icon-name={data.Priority}
                                                ></lightning-avatar>
                                            </div>
                                        </div>
                                    </div>
                                
                                   
                                </lightning-card>
                            </div>
                        </template>
                    </template>
                </div>
            </div>
            <div  class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small slds-p-left_small">
                <div id="devCompleted" style="height:100%" data-role="drop-target" ondrop={handleDrop} ondragenter={handleDragEnter} ondragover={handleDragOver} ondragleave={handleDragLeave}>
                    <template if:true={taskDevCompletedList}>
                        <template for:each={taskDevCompletedList} for:item="data">
                            <div key={data.Id} draggable="true" id={data.Id} data-id={data.Id} ondragstart={taskDragStart} ondragend={taskDragEnd} class="slds-p-around_small">
                                <lightning-card style="width: 50px;" variant="Narrow">
                                    <!-- Title Section -->
                                    <div slot="title" class="todo-card-title">
                                        <a href={data.urlToLocate} target="_blank" class="slds-text-link">
                                            <strong>{data.TodoNumber}</strong>
                                        </a>
                                    </div>
                                    <!-- Body Section -->
                                    <div class="todo-card-body slds-p-horizontal_small">
                                        <!-- Description -->
                                        <p class="todo-card-description">
                                            {data.Title}
                                        </p>
                                        <!-- Priority -->
                                        <div class="slds-grid  slds-m-top_small">
                                            <div>
                                                <lightning-avatar size="x-small" src="https://www.lightningdesignsystem.com/assets/images/avatar2.jpg" initials="JD" fallback-icon-name="standard:person_account" alternative-text="Jane Doe" class="slds-m-right_small"></lightning-avatar>
                                            </div>
                                            <div class="slds-p-left_xx-small">
                                                <lightning-avatar
                                                    size="x-small"
                                                    alternative-text="Assignee"
                                                    fallback-icon-name={data.Priority}
                                                ></lightning-avatar>
                                            </div>
                                        </div>
                                    </div>
                                
                                   
                                </lightning-card>
                            </div>
                        </template>
                    </template>
                </div>
            </div>
            <div  class="slds-size_3-of-12 slds-scrollable_y slds-m-vertical_x-small slds-p-left_small">
                <div id="completed" style="height:100%" data-role="drop-target" ondrop={handleDrop} ondragenter={handleDragEnter} ondragover={handleDragOver} ondragleave={handleDragLeave}>
                    <template if:true={taskCompletedList}>
                        <template for:each={taskCompletedList} for:item="data">
                            <div key={data.Id} draggable="true" id={data.Id} data-id={data.Id} ondragstart={taskDragStart} ondragend={taskDragEnd} class="slds-p-around_small">
                                <lightning-card style="width: 50px;" variant="Narrow">
                                    <!-- Title Section -->
                                    <div slot="title" class="todo-card-title">
                                        <a href={data.urlToLocate} target="_blank" class="slds-text-link">
                                            <p>{data.TodoNumber}</p>
                                        </a>
                                    </div>
                                    <!-- Body Section -->
                                    <div class="todo-card-body slds-p-horizontal_small">
                                        <!-- Description -->
                                        <p class="todo-card-description">
                                            {data.Title}
                                        </p>
                                        <div class="slds-grid  slds-m-top_small">
                                            <div>
                                                <lightning-avatar size="x-small" src="https://www.lightningdesignsystem.com/assets/images/avatar2.jpg" initials="JD" fallback-icon-name="standard:person_account" alternative-text="Jane Doe" class="slds-m-right_small"></lightning-avatar>
                                            </div>
                                            <div class="slds-p-left_xx-small">
                                                <lightning-avatar
                                                    size="x-small"
                                                    alternative-text="Assignee"
                                                    fallback-icon-name={data.Priority}
                                                ></lightning-avatar>
                                            </div>
                                        </div>
                                    </div>
                                </lightning-card>
                            </div>
                        </template>
                    </template>
                </div>
            </div>
        </div>
    </div>
</template>
Java Script
import { LightningElement, track } from 'lwc';
import getAllTodos from '@salesforce/apex/ToDoController.getAllTodos';
import updateTodoProgress from '@salesforce/apex/ToDoController.updateTodoProgress';
import { ShowToastEvent } from "lightning/platformShowToastEvent";

export default class TodoBoard extends LightningElement {

    @track taskNewList = [];
    @track taskInProgressList = [];
    @track taskCompletedList = [];
    @track taskDevCompletedList = [];
    @track droptodoId; 
    orgUrl;

    connectedCallback() { 
        this.orgUrl = window.location.origin;
        this.getTodosData();
    }

    getTodosData() {
        getAllTodos().then(result => {
            let taskNewData = [];
            let taskInProgressData = [];
            let taskCompletedData = [];
            let taskDevCompletedData = [];
            for (let i = 0; i < result.length; i++) {
                let task = new Object();
                task.Id = result[i].Id;
                task.Title = result[i].Name;
                task.TodoNumber = result[i].cbugs__Toto_Number__c;
                task.urlToLocate = this.orgUrl + '/' + result[i].Id
                task.Status = result[i].cbugs__Status__c;
                if(result[i].cbugs__Priority__c == 'Low') { 
                    task.Priority = 'utility:chevrondown'
                } else if(result[i].cbugs__Priority__c == 'Medium') { 
                    task.Priority = 'utility:justify_text'
                } else if(result[i].cbugs__Priority__c == 'High') { 
                    task.Priority = 'utility:jump_to_top'
                }
                if (task.Status === 'New') {
                    taskNewData.push(task);
                } 
                else if (task.Status == 'In Progress') {
                    taskInProgressData.push(task);
                } 
                else if (task.Status == 'Dev Complete') {
                    taskDevCompletedData.push(task);
                } else if (task.Status === 'Completed') {
                    taskCompletedData.push(task);
                }
            }
            this.taskNewList = taskNewData;
            this.taskInProgressList = taskInProgressData;
            this.taskDevCompletedList = taskDevCompletedData;
            this.taskCompletedList = taskCompletedData;
        }).catch(error => {
            this.showToast('Error', 'Error in getting todos list', error, 'error')
        })
    }

    handleDrop(event) {
        this.cancel(event);
        const columnUsed = event.currentTarget.id;
        let statusToUpdate;
        if (columnUsed.includes('InProgress')) {
            statusToUpdate = 'In Progress';
        } else if (columnUsed.includes('newTask')) {
            statusToUpdate = 'New';
        }
        else if (columnUsed.includes('devCompleted')) {
            statusToUpdate = 'Dev Complete';
        } else if (columnUsed.includes('completed')) {
            statusToUpdate = 'Completed';
        }
        this.showToast('Success', 'Moved To ' + statusToUpdate, 'success')
        this.updateTaskStatus(this.droptodoId, statusToUpdate);
        let draggableElement = this.template.querySelector('[data-role="drop-target"]');
        draggableElement.classList.remove('over');
    }

    updateTaskStatus(todoId, statusToUpdate) {
            updateTodoProgress({toDoId: todoId, newStatus: statusToUpdate}).then(result => {
                this.showToast('Success', 'Moved To ' + statusToUpdate, 'success')
                this.getTodosData();
            }).catch(error => {
                this.showToast('Error', 'Error in updating status',error, 'error')
            })
    }

    handleDragEnter(event) {
        this.cancel(event);
    }

    handleDragOver(event) {
        this.cancel(event);
        let draggableElement = this.template.querySelector('[data-role="drop-target"]');
        draggableElement.classList.add('over');
    }

    handleDragLeave(event) {
        this.cancel(event);
        let draggableElement = this.template.querySelector('[data-role="drop-target"]');
        draggableElement.classList.remove('over');
    }

    taskDragStart(event) {
        const todoId = event.target.id.substr(0, 18);
        this.droptodoId = todoId;
        let draggableElement = this.template.querySelector('[data-id="' + todoId + '"]');
        draggableElement.classList.add('drag');
        this.handleTaskDrag(todoId);
    }

    taskDragEnd(event) {
        const todoId = event.target.id.substr(0, 18);
        let draggableElement = this.template.querySelector('[data-id="' + todoId + '"]');
        draggableElement.classList.remove('drag');
    }

    showToast(title, message, variant) {
        const evt = new ShowToastEvent({
          title: title,
          message: message,
          variant: variant,
        });
        this.dispatchEvent(evt);
    }

    cancel(event) {
        if (event.stopPropagation) event.stopPropagation();
        if (event.preventDefault) event.preventDefault();
        return false;
    };
}
XML Config
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Todo Board</masterLabel>
    <targets>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>
Apex Controller
public with sharing class ToDoController {
    @AuraEnabled
    public static List<cbugs__ToDo__c> getAllTodos(){
        return [SELECT Id, Name, cbugs__Toto_Number__c, cbugs__Assign_To__c, cbugs__Status__c, cbugs__Priority__c FROM cbugs__ToDo__c];
    }
    
    @AuraEnabled
    public static void updateTodoProgress(String toDoId, String newStatus){
        cbugs__ToDo__c updateTodo = new cbugs__ToDo__c(Id = toDoId, cbugs__Status__c = newStatus);
        Database.update(updateTodo);
    }
}

Leave a Reply