Kanban board Using LWC without Apex
What you will learn in this project
- How to create a lightning Page
- How to create a component
- How to create a local properties
- How to use getter
- How to use @wire
- How to use getObjectInfo, getPicklistValues , updateRecord, and getListUi wire adapters
- How to apply CSS using css file
- How to apply CSS dynamically
- How to achieve components composition
- How to pass data from parent to child
- How to pass data from child to parent
- How Drag and Drop functionality works
- How to update Records without APEX
- How to fetch records without APEX
- How to show toast message
Component Design
 
    
    Final Output
 
    
    Video Tutorial
Steps and code
- Go to setup => Quick Find => Lightning App Builder => create New Lightning App Page(Kanban Drag and Drop Using LWC)
- Create Lwc component DragAndDropLwcand 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>- Create another LWC component dragAndDropListand 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>- Create another LWC component dragAndDropCardand 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
- dragAndDropCard
- dragAndDropList
- dragAndDropLwc
Final Output
Now place your component to the page you have created. You will see the following output
 
    
    