Home » Custom DataTable in LWC
Custom DataTable in LWC

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.

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.data_table_custom_types


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. 

  1. Dispatching the Event 

  2. 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:



  1. Let's first create our custom Data Type(s) Table myCustomTypeDatatable:


We will create couple of files in below structure


  1.  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


  1. customCheckbox.html

<template>

    <lightning-input

        type="checkbox"

        checked={value}>

    </lightning-input>

</template>


  1. 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>


  1. 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.


  1. 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>


  1. 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

                }

            });

           

        }

    }

}



  1. 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() ) 


  1. 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

  1. myCheckboxComponent.html


<template>

    <lightning-input

        type="checkbox"

        checked={isChecked}

        label = {name}

        onchange={handleCheckboxEvent}>

    </lightning-input>

</template>


  1. 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.



  1. 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>


  1. 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();

    }

}



  1. 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”. 


  1. 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. 


  1. 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>


  1. 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

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.events_best_practices


Creating Lightning Message Channel

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_message_channel_intro


Publishing on a Message Channel

https://developer.salesforce.com/docs/platform/lwc/guide/use-message-channel-publish.html


Subscribing & Unsubscribing from a Message Channel

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_message_channel_subscribe