How to create Custom DataTable in LWC
In order to display records (rows) we generally use <lightning-datatable> but sometimes it is not sufficient. For example we might need to have a checkbox column. Or we might want to display a custom DataType column. In such cases we can create our own DataTable with a custom data type. Salesforce Documentation provides a good example, pls refer to the link below.
But If our custom data type is more complex then we might need some additional changes. So let's start understanding it.
STEP 1: We will add a custom checkbox Data type by making some minor changes in the example given in salesforce documentation.
STEP 2: Then We will enhance our data type to handle the events. We will consider two approaches.
Dispatching the Event
Using the Lightning Message Service(LMS).
Let's create one checkbox field in Account Object named as “Has Support Plan.”
STEP 1: custom checkbox Data type by making minor changes in example provided in Salesforce Documentation
Final Output:
Let's first create our custom Data Type(s) Table myCustomTypeDatatable:
We will create couple of files in below structure
myCustomTypeDatatable.js
/* myCustomTypeDatatable.js */
import LightningDatatable from 'lightning/datatable';
import customNameTemplate from './customName.html';
import customNumberTemplate from './customNumber.html';
export default class MyCustomTypeDatatable extends LightningDatatable {
static customTypes = {
customName: {
template: customNameTemplate,
standardCellLayout: true,
typeAttributes: ['accountName'],
},
customNumber: {
template: customNumberTemplate,
standardCellLayout: false,
typeAttributes: ['status'],
},
customCheckbox: {
template: customCheckboxTemplate,
standardCellLayout: true,
typeAttributes: ['nameValue'],
}
// Other types here
}
}
We can notice that we have defined 3 custom data types named as customCheckbox, customName and customNumber. Template attributes provide the template/structure of the data type. We are getting this from the respective html file which we are importing in the beginning. All these html files are located in the same folder. Let's look into each html file.
Please notice the usage of {value} in customCheckbox.html
customCheckbox.html
<template>
<lightning-input
type="checkbox"
checked={value}>
</lightning-input>
</template>
customName.html
<template>
<lightning-badge
label={typeAttributes.accountName}
icon-name="standard:account">
</lightning-badge>
<!--label={typeAttributes.accountName} will also work if we are
providing the field in COLS under myDatatable.js -->
</template>
customNumber.html
<template>
<div class="slds-p-around_x-small">
<lightning-formatted-number value={value} class="slds-float_right"></lightning-formatted-number>
<lightning-icon icon-name={typeAttributes.status} alternative-text="Employer Status"></lightning-icon>
</div>
</template>
2. Now let's add our custom data table into the parent component myDatatable.
myDatatable.html
<!--myDatatable.html-->
<template>
<c-my-custom-type-datatable
key-field="Id"
data={accounts}
columns={columns}
show-row-number-column>
</c-my-custom-type-datatable>
</template>
myDatatable.js
/* myDatatable.js */
import { LightningElement, wire, track } from 'lwc';
import getAccountList from '@salesforce/apex/AccountController.getAccountList';
const COLS = [
{ label: 'Account Name', type: 'customName',//fieldName: 'Name',
typeAttributes: {
accountName: { fieldName: 'Name' }
},
},
{ label: 'Industry', fieldName: 'Industry',
cellAttributes: {
class: {fieldName: 'industryColor'},
}
},
{ label: 'Has Support Plan', type: 'customCheckbox',
fieldName: 'Has_Support_Plan__c',
typeAttributes: {
nameValue: {fieldName : 'Name'}
}
},
{ label: 'Employees', type: 'customNumber', fieldName: 'NumberOfEmployees',
typeAttributes: {
status: {fieldName: 'status'}
},
cellAttributes: {
class: 'slds-theme_alert-texture'
}
},
];
export default class MyDatatable extends LightningElement {
columns = COLS;
@track accounts = [];
@wire(getAccountList)
wiredAccounts({error, data}) {
if (error) {
// Handle error
} else if (data) {
// Process record data
this.accounts = data.map((record) => {
let industryColor = record.Industry === 'Energy' ? 'slds-text-color_success': '';
let status = record.NumberOfEmployees > 10000 ? 'utility:ribbon' : '';
return {...record,
'industryColor': industryColor,
'status': status
}
});
}
}
}
myDatatable.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>57.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
3. Now let's create the Apex class to get the account records along with “”Has Support Plan” field.
AccountController.cls
/* AccountController.cls */
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List<Account> getAccountList() {
return [SELECT Id, Name, Industry, NumberOfEmployees, Has_Support_Plan__c
FROM Account WITH SECURITY_ENFORCED LIMIT 10];
}
}
STEP 2: custom checkbox Data type event handling ( onchange() )
Dispatching the Event:
We will use Custom Event to send the data. When a user clicks on the checkbox, a custom event will be sent by one component and received by another component.
Please note that while dispatching the event in “myCheckboxComponent.js” we are setting the bubbles and composed properties to true. So that event can bubbles up through the DOM and can pass through the shadow boundary. Please refer the links below to understand Creating and Dispatching Events, Event Propagation, Shadow DOM and Event Best Practices.
Final Output: If we select the GenePoint checkbox then we can see the captured event details in below the table (selectedRecords variable is holding this detail as explained below).
1. For this we will create a separate component and it will be dispatching the event myCheckboxComponent
myCheckboxComponent.html
<template>
<lightning-input
type="checkbox"
checked={isChecked}
label = {name}
onchange={handleCheckboxEvent}>
</lightning-input>
</template>
myCheckboxComponent.js
import { LightningElement, api} from 'lwc';
export default class MyCheckboxComponent extends LightningElement {
@api name;
@api isChecked;
handleCheckboxEvent(event){
console.log('event.target.label '+ event.target.label);
this.dispatchEvent(new CustomEvent('checkboxevent',
{ bubbles: true ,
composed: true ,
detail: event.target.label
}));
//event.preventDefault();
}
}
2. Now we will use this component in our customCheckbox.html
<template>
<!--lightning-input
type="checkbox"
checked={value}>
</lightning-input-->
<c-my-checkbox-component
name={typeAttributes.nameValue}
is-checked={value}>
</c-my-checkbox-component>
</template>
We are providing the name and isChecked attributes.
3. Now we will update the myDatatable to handle this event. We are also adding one variable called as “selectedRecords” so that we can display the captured event details.
myDatatable.html
<!--myDatatable.html-->
<template>
<c-my-custom-type-datatable
key-field="Id"
data={accounts}
columns={columns}
show-row-number-column
oncheckboxevent={handleEventCheckbox}>
</c-my-custom-type-datatable>
{selectedRecords}
</template>
myDatatable.js
Now we will add handleEventCheckbox()method into the .js file as given below:
/* myDatatable.js */
import { LightningElement, wire, track } from 'lwc';
import getAccountList from '@salesforce/apex/AccountController.getAccountList';
const COLS = [
{ label: 'Account Name', type: 'customName',//fieldName: 'Name',
typeAttributes: {
accountName: { fieldName: 'Name' }
},
},
{ label: 'Industry', fieldName: 'Industry',
cellAttributes: {
class: {fieldName: 'industryColor'},
}
},
{ label: 'Has Support Plan', type: 'customCheckbox',
fieldName: 'Has_Support_Plan__c',
typeAttributes: {
nameValue: {fieldName : 'Name'}
}
},
{ label: 'Employees', type: 'customNumber', fieldName: 'NumberOfEmployees',
typeAttributes: {
status: {fieldName: 'status'}
},
cellAttributes: {
class: 'slds-theme_alert-texture'
}
},
];
export default class MyDatatable extends LightningElement {
columns = COLS;
@track accounts = [];
@track selectedRecords = [];
@wire(getAccountList)
wiredAccounts({error, data}) {
if (error) {
// Handle error
} else if (data) {
// Process record data
this.accounts = data.map((record) => {
let industryColor = record.Industry === 'Energy' ? 'slds-text-color_success': '';
let status = record.NumberOfEmployees > 10000 ? 'utility:ribbon' : '';
return {...record,
'industryColor': industryColor,
'status': status
}
});
}
}
handleEventCheckbox(event){
console.log('inside myDatatable ');
console.log('event.detail '+ event.detail);
this.selectedRecords.push(event.detail);
//event.preventDefault();
}
}
Using the Lightning Message Service:
In this approach, instead of Custom Event we will use the Lightning Message Service (LMS) to send data through messages. Please refer the links below to understand Creating Lightning Message Channel, Publishing on a Message Channel, Subscribing & Unsubscribing from a Message Channel.
Final Output : If we select the GenePoint checkbox then we can see the captured message details in below the table (selectedRecords variable is holding this detail as explained below).
1.First we will create the Lightning Message Service named as “customDatatableEvents.messageChannel-meta.xml”.
customDatatableEvents.messageChannel-meta.xml
<?xml version="1.0" encoding="UTF-8" ?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>CustomDatatableEvent</masterLabel>
<isExposed>true</isExposed>
<description>Message Channel to pass Custom Datatable Events</description>
<lightningMessageFields>
<fieldName>key</fieldName>
<description>Name or Key of the event</description>
</lightningMessageFields>
<lightningMessageFields>
<fieldName>value</fieldName>
<description>Value or Detail of the event</description>
</lightningMessageFields>
</LightningMessageChannel>
2. Now instead of custom Event, we will be using Lightning Message Service to send the data in the form of messages in myCheckboxComponent.js
we will publish the message on the message channel “CustomDatatableEvent” which we have created above.
/* myCheckboxComponent.js */
import { LightningElement, api, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import CUSTOMDATATABLEEVENT_CHANNEL from '@salesforce/messageChannel/customDatatableEvents__c';
export default class MyCheckboxComponent extends LightningElement {
@api name;
@api isChecked;
@wire(MessageContext)
messageContext;
handleCheckboxEvent(event){
console.log('event.target.label '+ event.target.label);
const data = {
key: 'checkboxevent' ,
value: event.target.label
}
publish(this.messageContext, CUSTOMDATATABLEEVENT_CHANNEL, data);
//event.preventDefault();
}
/*
handleCheckboxEvent(event){
console.log('event.target.label '+ event.target.label);
this.dispatchEvent(new CustomEvent('checkboxevent',
{ bubbles: true ,
composed: true ,
detail: event.target.label
}));
//event.preventDefault();
}
*/
}
3. Now we will update the myDatatable to subscribe to messages on this message channel.
myDatatable.html
We have just removed oncheckboxevent as we are no longer dispatching or listening to Custom Events.
<!--myDatatable.html-->
<template>
<c-my-custom-type-datatable
key-field="Id"
data={accounts}
columns={columns}
show-row-number-column
><!--oncheckboxevent={handleEventCheckbox}-->
</c-my-custom-type-datatable>
{selectedRecords}
</template>
myDatatable.js
Here we are subscribing to messages on the “customDatatableEvents__c” message channel.
/* myDatatable.js */
import { LightningElement, wire, track } from 'lwc';
import { subscribe, MessageContext } from 'lightning/messageService';
import CUSTOMDATATABLEEVENT_CHANNEL from '@salesforce/messageChannel/customDatatableEvents__c';
import getAccountList from '@salesforce/apex/AccountController.getAccountList';
const COLS = [
{ label: 'Account Name', type: 'customName',//fieldName: 'Name',
typeAttributes: {
accountName: { fieldName: 'Name' }
},
},
{ label: 'Industry', fieldName: 'Industry',
cellAttributes: {
class: {fieldName: 'industryColor'},
}
},
{ label: 'Has Support Plan', type: 'customCheckbox',
fieldName: 'Has_Support_Plan__c',
typeAttributes: {
nameValue: {fieldName : 'Name'}
}
},
{ label: 'Employees', type: 'customNumber', fieldName: 'NumberOfEmployees',
typeAttributes: {
status: {fieldName: 'status'}
},
cellAttributes: {
class: 'slds-theme_alert-texture'
}
},
];
export default class MyDatatable extends LightningElement {
columns = COLS;
@track accounts = [];
@track selectedRecords = [];
@wire(MessageContext)
messageContext;
connectedCallback() {
this.subscribeToMessageChannel();
}
subscribeToMessageChannel() {
this.subscription = subscribe(
this.messageContext,
CUSTOMDATATABLEEVENT_CHANNEL,
(message) => this.handleMessage(message)
);
}
handleMessage(message){
console.log('inside example component ');
console.log('message '+ message);
if(message.key == 'checkboxevent') {
console.log('value '+ message.value);
this.selectedRecords.push(message.value);
}
}
/*
handleEventCheckbox(event){
console.log('inside myDatatable ');
console.log('event.detail '+ event.detail);
this.selectedRecords.push(event.detail);
//event.preventDefault();
}
*/
@wire(getAccountList)
wiredAccounts({error, data}) {
if (error) {
// Handle error
} else if (data) {
// Process record data
this.accounts = data.map((record) => {
let industryColor = record.Industry === 'Energy' ? 'slds-text-color_success': '';
let status = record.NumberOfEmployees > 10000 ? 'utility:ribbon' : '';
return {...record,
'industryColor': industryColor,
'status': status
}
});
}
}
}
REFERENCES:
You can refer to the Salesforce Documentation for more details (Component Library or platform both). Couple of links for directly accessing the respective topic:
Example provided in Salesforce Documentation(both urls have same content) : https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.data_table_custom_types OR
https://developer.salesforce.com/docs/platform/lwc/guide/data-table-custom-types.html
Creating and Dispatching Events
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/events_create_dispatch
Event Propagation
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.events_propagation
Shadow DOM
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_dom
Event Best Practices
Creating Lightning Message Channel
Publishing on a Message Channel
https://developer.salesforce.com/docs/platform/lwc/guide/use-message-channel-publish.html
Subscribing & Unsubscribing from a Message Channel