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
- Build todo app with Salesforce LWC and apex
- Creating Lighting Application In Salesforce
- Implement Drag & Drop Functionality In LWC
- Build multi column layout with lighting web components
- Show profile icon in using lightning-avatar
- 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);
}
}