Kanban board Using LWC without Apex

What you will learn in this project

  1. How to create a lightning Page
  2. How to create a component
  3. How to create a local properties
  4. How to use getter
  5. How to use @wire
  6. How to use getObjectInfo, getPicklistValues , updateRecord, and getListUi wire adapters
  7. How to apply CSS using css file
  8. How to apply CSS dynamically
  9. How to achieve components composition
  10. How to pass data from parent to child
  11. How to pass data from child to parent
  12. How Drag and Drop functionality works
  13. How to update Records without APEX
  14. How to fetch records without APEX
  15. How to show toast message

Component Design

Fig: Kanban Using LWC without APEX
Fig: Kanban Using LWC without APEX


Final Output

Fig: Kanban Using LWC without APEX App Demo
Fig: Kanban Using LWC without APEX App Demo


Video Tutorial


Steps and code

  1. Go to setup => Quick Find => Lightning App Builder => create New Lightning App Page(Kanban Drag and Drop Using LWC)
  2. Create Lwc component DragAndDropLwc and add the following code to the respective files.
dragAndDropLwc.js
import { LightningElement, wire } from 'lwc';
import { getListUi } from 'lightning/uiListApi';
import { updateRecord } from 'lightning/uiRecordApi';
import { refreshApex } from '@salesforce/apex';
import { getPicklistValues, getObjectInfo } from 'lightning/uiObjectInfoApi';
import OPPORTUNITY_OBJECT from '@salesforce/schema/Opportunity'
import STAGE_FIELD from '@salesforce/schema/Opportunity.StageName'
import ID_FIELD from '@salesforce/schema/Opportunity.Id'
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class DragAndDropLwc extends LightningElement {
    records
    pickVals
    recordId
    /*** fetching Opportunity lists ***/
    @wire(getListUi, {
        objectApiName: OPPORTUNITY_OBJECT,
        listViewApiName:'AllOpportunities'
    })wiredListView({error, data}){
        if(data){
            console.log("getListUi", data)
            this.records = data.records.records.map(item => {
                let field = item.fields
                let account = field.Account.value.fields
                return { 'Id': field.Id.value, 'Name': field.Name.value, 'AccountId': account.Id.value, 'AccountName': account.Name.value, 'CloseDate': field.CloseDate.value, 'StageName': field.StageName.value, 'Amount': field.Amount.value }

            })
        }
        if(error){
            console.error(error)
        }
    }

/** Fetch metadata abaout the opportunity object**/
@wire(getObjectInfo, {objectApiName:OPPORTUNITY_OBJECT})
objectInfo
/*** fetching Stage Picklist ***/

    @wire(getPicklistValues, {
        recordTypeId:'$objectInfo.data.defaultRecordTypeId',
        fieldApiName:STAGE_FIELD
    })stagePicklistValues({ data, error}){
        if(data){
            console.log("Stage Picklist", data)
            this.pickVals = data.values.map(item => item.value)
        }
        if(error){
            console.error(error)
        }
    }


    /****getter to calculate the  width dynamically*/
    get calcWidth(){
        let len = this.pickVals.length +1
        return `width: calc(100vw/ ${len})`
    }

    handleListItemDrag(event){
        this.recordId = event.detail
    }

    handleItemDrop(event){
        let stage = event.detail
        // this.records = this.records.map(item=>{
        //     return item.Id === this.recordId ? {...item, StageName:stage}:{...item}
        // })
        this.updateHandler(stage)
    }
    updateHandler(stage){
        const fields = {};
        fields[ID_FIELD.fieldApiName] = this.recordId;
        fields[STAGE_FIELD.fieldApiName] = stage;
        const recordInput ={fields}
        updateRecord(recordInput)
        .then(()=>{
            console.log("Updated Successfully")
            this.showToast()
            return refreshApex(this.wiredListView)
        }).catch(error=>{
            console.error(error)
        })
    }

    showToast(){
        this.dispatchEvent(
            new ShowToastEvent({
                title:'Success',
                message:'Stage updated Successfully',
                variant:'success'
            })
        )
    }
}
dragAndDropLwc.html
<template>
    <div class="card_wrapper">
        <template for:each={pickVals} for:item="item">
            <div class="stageContainer" key={item} style={calcWidth}>
                <h1 class="column_heading">{item}</h1>
                <c-drag-and-drop-list records={records} stage={item}
                 onlistitemdrag={handleListItemDrag}
                 onitemdrop={handleItemDrop}
                 ></c-drag-and-drop-list>
            </div>
            
        </template>
    </div>
</template>

create dragAndDropLwc.css file in the component folder and the following code

dragAndDropLwc.css
.card_wrapper{
    padding: 0.5rem;
    display: flex;
    justify-content: space-around;
    background: #fff;
}
.column_heading{
    padding: 0.5rem 0.25rem;
    font-size: 16px;
    background: #005fb2;
    color: #fff;
    margin-bottom: 1rem;
}
.stageContainer{
    float:left;
    border: 2px solid #d8dde6;
    margin: 0.05rem;
    border-radius: .25rem;
}
dragAndDropLwc.js-meta.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__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>
  1. Create another LWC component dragAndDropList and add the following code to the respective files.
dragAndDropList.js
import { LightningElement, api } from 'lwc';

export default class DragAndDropList extends LightningElement {
    @api records
    @api stage
    handleItemDrag(evt){
        const event = new CustomEvent('listitemdrag', {
            detail: evt.detail
        })
        this.dispatchEvent(event)
    }
    handleDragOver(evt){
        evt.preventDefault()
    }
    handleDrop(evt){
        const event = new CustomEvent('itemdrop', {
            detail: this.stage
        })
        this.dispatchEvent(event)
    }
}
dragAndDropList.html
<template>

    <ul class="slds-has-dividers_around-space dropZone" 
    ondrop={handleDrop}
    ondragover={handleDragOver}
    style="height:70vh; overflow-y:auto;">
        <template for:each={records} for:item="recordItem">
            <c-drag-and-drop-card stage={stage} record={recordItem} key={recordItem.Id} onitemdrag={handleItemDrag}></c-drag-and-drop-card>
        </template>
    </ul>
</template>
dragAndDropList.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>
  1. Create another LWC component dragAndDropCard and add the following code to the respective files.
dragAndDropCard.js
import { LightningElement, api } from 'lwc';
import { NavigationMixin } from 'lightning/navigation'
export default class DragAndDropCard extends NavigationMixin(LightningElement) {
    @api stage
    @api record

    get isSameStage(){
        return this.stage === this.record.StageName
    }
    navigateOppHandler(event){
        event.preventDefault()
        this.navigateHandler(event.target.dataset.id, 'Opportunity')
    }
    navigateAccHandler(event){
        event.preventDefault()
        this.navigateHandler(event.target.dataset.id, 'Account')
    }
    navigateHandler(Id, apiName) {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: Id,
                objectApiName: apiName,
                actionName: 'view',
            },
        });
    }
    itemDragStart(){
        const event = new CustomEvent('itemdrag', {
            detail: this.record.Id
        })
        this.dispatchEvent(event)
    }
}
dragAndDropCard.html
<template>
    <template if:true={isSameStage}>
   <li class="slds-item slds-var-m-around_small" draggable="true" ondragstart={itemDragStart}>
       <article class="slds-tile slds-tile_board">
           <h3 class="slds-truncate" title={record.Name}>
               <a href="#" data-id={record.Id} onclick={navigateOppHandler}>
                   <span class="slds-truncate" data-id={record.Id}>
                    {record.Name}
                   </span>
               </a>
           </h3>
           <div class="slds-tile__detail slds-text-body_small">
               <p class="slds-text-heading_small">Amount: ${record.Amount}</p>
               <p class="slds-truncate" title={record.AccountName}>
                    <a href="#" data-id={record.AccountId} onclick={navigateAccHandler}>
                    <span class="slds-truncate" data-id={record.AccountId}>
                        {record.AccountName}
                    </span>
                </a>
               </p>
               <p class="slds-truncate" title={record.CloseDate}>Closing {record.CloseDate}</p>
           </div>
       </article>
   </li>
</template>
</template>

create dragAndDropCard.css file in the component folder and the following code

dragAndDropCard.css
.slds-item {
    border: 1px solid #d8dde6;
    border-radius: 0.25rem;
    background-clip: padding-box;
    padding: 0.45rem;
    margin: 0.25rem;
}
dragAndDropCard.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>

Deployment

Deploy your component to the salesforce org in the following order

  1. dragAndDropCard
  2. dragAndDropList
  3. dragAndDropLwc

Final Output

Now place your component to the page you have created. You will see the following output

Fig: Kanban Using LWC without APEX App Demo
Fig: Kanban Using LWC without APEX App Demo


Site developed by Nikhil Karkra © 2023 | Icons made by Freepik