Kanban Using Lightning Web Components

In this post, we will see how to implement Kanban view using Lightning web component 

Use Case:

Business has a requirement to change the case status using Kanban view. As developer we should implement this functionality by using Lightning web component in drag and drop approach .


Pre-requisite: 

pubsub component for event communication using publisher-subscribe pattern.

/**
* A basic pub-sub mechanism for sibling component communication
*
* TODO - adopt standard flexipage sibling communication mechanism when it's available.
*/
const events = {};
const samePageRef = (pageRef1, pageRef2) => {
const obj1 = pageRef1.attributes;
const obj2 = pageRef2.attributes;
return Object.keys(obj1)
.concat(Object.keys(obj2))
.every(key => {
return obj1[key] === obj2[key];
});
};
/**
* Registers a callback for an event
* @param {string} eventName - Name of the event to listen for.
* @param {function} callback - Function to invoke when said event is fired.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
const registerListener = (eventName, callback, thisArg) => {
// Checking that the listener has a pageRef property. We rely on that property for
filtering purpose in fireEvent()
if (!thisArg.pageRef) {
throw new Error(
'pubsub listeners need a "@wire(CurrentPageReference) pageRef" property'
);
}
if (!events[eventName]) {
events[eventName] = [];
}
const duplicate = events[eventName].find(listener => {
return listener.callback === callback && listener.thisArg === thisArg;
});
if (!duplicate) {
events[eventName].push({ callback, thisArg });
}
};
/**
* Unregisters a callback for an event
* @param {string} eventName - Name of the event to unregister from.
* @param {function} callback - Function to unregister.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
const unregisterListener = (eventName, callback, thisArg) => {
if (events[eventName]) {
events[eventName] = events[eventName].filter(
listener => listener.callback !== callback || listener.thisArg !== thisArg
);
}
};
/**
* Unregisters all event listeners bound to an object.
* @param {object} thisArg - All the callbacks bound to this object will be removed.
*/
const unregisterAllListeners = thisArg => {
Object.keys(events).forEach(eventName => {
events[eventName] = events[eventName].filter(
listener => listener.thisArg !== thisArg
);
});
};
/**
* Fires an event to listeners.
* @param {object} pageRef - Reference of the page that represents the event scope.
* @param {string} eventName - Name of the event to fire.
* @param {*} payload - Payload of the event to fire.
*/
const fireEvent = (pageRef, eventName, payload) => {
if (events[eventName]) {
const listeners = events[eventName];
listeners.forEach(listener => {
if (samePageRef(pageRef, listener.thisArg.pageRef)) {
try {
listener.callback.call(listener.thisArg, payload);
} catch (error) {
// fail silently
}
}
});
}
};
export {
registerListener,
unregisterListener,
unregisterAllListeners,
fireEvent
};

Apex class : kanbanUtilityClass


public class kanbanUtilityClass{
    @AuraEnabled(Cacheable = true)
 public static List<SObject> findRecords(String searchKey, String objectName,
String searchField, String columns){
        if (searchKey != ''){
            String key = '%' + searchKey + '%';
            String QUERY = 'SELECT Id, ' + columns + ' FROM ' + objectName +
' WHERE ' + searchField + ' LIKE :key' + ' ORDER BY ' + searchField;
            List<SObject> sObjectList = Database.query(QUERY);
            return sObjectList;
        } else{
            return null;
        }
    }
}

 Parent Component: kanbanView Component

 kanbanView.html

 This is the parent component which contain three child components. Each child component will refer  the different status of cases.


<template>
<div class="slds-grid slds-gutters">
<div class="slds-col">
<c-kanban-list-l-w-c class="New" onrefresh={refreshComp} object-name="Case" field-name="Status"
search-key="New" columns="Subject,Status,CaseNumber,Priority,Origin">
</c-kanban-list-l-w-c>
</div>
<div class="slds-col">
<c-kanban-list-l-w-c class="Working" onrefresh={refreshComp} object-name="Case" field-name="Status"
search-key="Working" columns="Subject,Status,CaseNumber,Priority,Origin">
</c-kanban-list-l-w-c>
</div>
<div class="slds-col">
<c-kanban-list-l-w-c class="Closed" onrefresh={refreshComp} object-name="Case" field-name="Status"
search-key="Closed" columns="Subject,Status,CaseNumber,Priority,Origin">
</c-kanban-list-l-w-c>
</div>
</div>
</template>

kanbanView.js

There is one js method refreshcomp, to refresh the child component . This method will fire automatically, if user changing the status using drag and drop. For this scenario, using child to parent event communication .

import { LightningElement } from 'lwc';

export default class KanbanViewLWC extends LightningElement {

refreshComp() {
this.template.querySelector(".New").refreshMethod();
this.template.querySelector(".Working").refreshMethod();
this.template.querySelector(".Closed").refreshMethod();
}
}

 

kanbanView.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>

 

ChildComponent: kanbanList Component

This is the child component which is having all the logic of drag & drop and updating the records.

kanbanList.html

To make the div as draggable one , added draggable=true and ondragstart={handleDragStart} event handler has been used.


<template>
<lightning-card class="" title={searchKey}>
<div class="slds-grid ">
<lightning-layout>
<lightning-layout-item padding="around-small">
<template for:each={data} for:item="item" for:index="indexVar">
<div draggable="true" key={item.Id} data-item={item.Id} ondragstart={handleDragStart}>
<div class="slds-text-heading_small slds-box">
<strong> case Number : </strong> {item.CaseNumber} <br />
<strong> Subject : </strong> {item.Subject} <br />
<strong> Origin : </strong> {item.Origin} <br />
<strong> Priority : </strong> {item.Priority} <br />
</div> <br />
</div>
</template>
</lightning-layout-item>
</lightning-layout>
</div>
</lightning-card>
</template>


kanbanList.js

In js only have implemented all the logic's.  Some important points highlighted as below.

  •      Importing api ,wire ,track variables
  •      Importing the apex class method KanbanUtilityClass.findRecords to return the list
  •      Importing the methods fireEvent, registerListener, unregisterAllListeners from pub sub     component
  •       Importing update record from uiRecordApi to update the record
  •      Importing the ShowToastEvent to display the Success and error messages
  •      Importing refreshApex to refresh the js list
  •    Importing handleDragStart ,get the selected record id and fire the event to subscribers.
  •    handleDropMethod will listen the event which is fired from method handleDragStart, once it listen the event, using uiRecordApi will update the record to appropriate status. 
  •    Once status updated, dispatch the child to parent event (refresh) to refresh the all the child components.


import { LightningElement, api, wire, track } from 'lwc';
import findRecords from '@salesforce/apex/kanbanUtilityClass.findRecords';
import { CurrentPageReference } from 'lightning/navigation';
import { fireEvent } from 'c/pubsub';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '@salesforce/apex';

export default class KanbanListLWC extends LightningElement {

@api objectName;
@api fieldName;
@api searchKey;
@api columns;
@track data;
@track refreshTable;
@track dragRecordId;
@track updateRecord;

@wire(CurrentPageReference) pageRef;

// retrieving the data using wire service
@wire(findRecords, {
searchKey: '$searchKey', objectName: '$objectName',
searchField: '$fieldName', columns: '$columns'
})
relations(result) {
this.refreshTable = result;
if (result.data) {
this.data = result.data;
this.emptyList = true;
}
}

constructor() {
super();
//register dragover event to the template
this.template.addEventListener('dragover', this.handleDragOver.bind(this));
this.template.addEventListener('drop', this.handleDrop.bind(this));
}

//method which firimng on drag of the component
handleDragStart(event) {
event.dataTransfer.dropEffect = 'move';
let itemId = event.target.dataset.item;
//fire an event to the subscribers
fireEvent(this.pageRef, 'dropSelectedItem', itemId);

}

handleDragOver(event) {
event.dataTransfer.dropEffect = 'move';
event.preventDefault();
}

handleDrop(event) {
if (event.stopPropagation) {
event.stopPropagation();
}
event.preventDefault();

this.updateRecord = {
Id: this.dragRecordId,
Status: this.template.querySelector('lightning-card').title
};

//To update the record
updateRecord({ fields: this.updateRecord })
.then(() => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Record Updated',
variant: 'success'
})
);

//Child to parent event to refresh all child components , after the
updation of records
const selectEvent = new CustomEvent('refresh');
this.dispatchEvent(selectEvent);
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: 'There is an Error',
message: error.body.message,
variant: 'error'
})
);
});

}

connectedCallback() {
// subscribe to dropSelectedItem event
registerListener('dropSelectedItem', this.handledropSelectedItem, this);
}

disconnectedCallback() {
// unsubscribe from dropSelectedItem event
unregisterAllListeners(this);
}

//method is called due to listening dropSelectedItem event
handledropSelectedItem(accountInfo) {
this.dragRecordId = accountInfo;
}

@api
refreshMethod() {
//To refrsh the list using refreshapex
return refreshApex(this.refreshTable);
}
}

kanbanList.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>

kanbanList.css

.slds-box{
width: 400px;
background-color: darksalmon;
}



GitHub :

https://github.com/rcsaravananmkd/Saravanan/tree/master/LWC/kanbanViewLWC

https://github.com/rcsaravananmkd/Saravanan/tree/master/LWC/kanbanListLWC


 Demo :



 

 

 


No comments:

Post a Comment