A lightning-datatable component displays tabular data based on data rows and columns provided. We required this component on many pages in the project.
Instead of putting this component on every page. We can create a reusable and configurable data table component.
This blog will create a generic data table component that has a below configurable setting. It will populate data based on below requirement
- Based on the object and fields provided.
- Based on the apex method and fields provided.
- Based on provided records and fields
It will have a configuration setting for enabling features like
- Pagination
- Editing
- Deleting
- Single Row Selection
- Sorting on Fields
- Field data searching
Let us create a generic component based on the above requirement. We will go to each requirement step by step. First, let us handle the requirement to get data from Salesforce Org to show based on assigned properties.
1. Based on the object and fields provided.
As we will get the object name and field name as a parameter from the container/parent component. Let us get data from that object by dynamically creating SOQL.
Let us create base class to create dynamic SOQL. Records should be fetched based on user’s access.
Let us create a generic data selector class which will get data based on the provided object name and columns. SOQL will be created at runtime based on passed fields and object names. It has a cached and non-cached method to retrieve data. Call method based on your use case.
Let us call this apex method in Lightning Web Component to get object records. We have to pass all setting variables to get the required data.
fetchRecords() {
return new Promise((resolve, reject) => {
this.handleSpinner(true);
let fields = this.getFields();
if (fields) {
this.sortBy = fields[0];
fields = fields.join(',');
}
var field=fields;
if(fields.slice(-1)===',')
{
field=fields.slice(0, -1);
}
const params = {
objectName: this.objectName,
fields: field,
sortBy: this.sortBy,
sortAsc: this.sortAsc,
whereCondition: this.whereCondition,
limitRecords: this.limit
};
if (this.cacheable) {
getCachedRecords({params:params})
.then(map => resolve(this.getResolve(map.records)))
.catch(error => reject(this.getReject(error)));
} else {
getRecords({params:params})
.then(map => resolve(this.getResolve(map.records)))
.catch(error => {console.error(error)});
}
});
}
2. Based on the apex method and fields
The above approach will work when we need to get data from one object but what if we need to show data from multiple objects. In that case, we need an apex method to retrieve data. We have to call those apex methods imperatively. For this, we have to import all methods in the lightning web component.
For the generic concept, we have to call a specific apex method based on the passed apex method parameter. For this specific apex method, we have to import that method in our LWC component but that will be difficult. To handle that in a generic way, we can use ICallable interface or we can use Repository class. In both approach, apex method will be called based on parameter.
We will go with Repository class pattern for this use case.
I have used AccountSelector class only for this post but if you need to call any other class code then you have to call that in repository class.
Call this repository class from Lightning Web Component.
Import repository class to call apex method
import apexExecute from '@salesforce/apex/DataRepository.execute';
Call this apex method using below code.
fetchRecordFromApex=()=>
{
if (this.apexName !== '') {
apexExecute({action:this.apexName,params:this.param})
.then(result => {
this.recordsInPage = result;
})
.catch(error => {
this.error = error;
console.log(JSON.stringify(error));
});
}
}
3. Based on provided records and fields
In some scenario, we already have data in our component or we are passing data from flow then we just need to show that in datatable. In that use case, this option will work. We just have to pass records and column detail to components.
Configurable Features:
Now we are ready with data retrieval, let us create different configuration to generate data table with proper feature like pagination support, single row selection. Let is go each feature creation one by one.
1. Pagination
Datatable can return 100’s of records so we should support pagination. Right now this component supporting client-side pagination only. For pagination we need total record count and page record size. We can configure the page record size also in this component. Navigation buttons will be added to navigate back and next on the page.
//Pagination Supported
@api showPagination = false;
//Pagination Size
@api paginationSize=10;
// page Size property
@api
get pageSize() {
if (!isNotBlank(this._pageSize)) this._pageSize = 10;
return parseInt(this._pageSize, 10);
}
set pageSize(value) {
this._pageSize = value;
}
//Show Page Information
get pageNumberInfo() {
if (this._records && this._records.length > 0) {
this._paginationInfo.currentPage = (((this._startFromIndex + 1) / this.pageSize) - (((this._startFromIndex + 1) % this.pageSize) / this.pageSize) + ((((this._startFromIndex + 1) % this.pageSize) === 0) ? 0 : 1));
return 'Page ' + this._paginationInfo.currentPage + ' of ' + this._paginationInfo.totalPages;
}
return 'Page 0 of 0';
}
2. Editing
Editing is supported when we are retrieving data using objects. Lightning data service will be used to save edited records. Edit form will be opened in the dialog box. Dialog box code is added below.
//Suport edit
@api editable = false;
handleRowAction=event=>
{
const action = event.detail.action;
const row = event.detail.row;
if(row && this.objectName)
{
this.recordId=row.Id;
//in case edit button clicked. Open selected record in dialog box to edit
if(action.name==='edit')
{
if(this.template.querySelector('c-common-dialog'))
{
this.template.querySelector('c-common-dialog').openmodal();
}
}
//in case delete button clicked. delete selected record if(action.name==='delete')
{
this.deleteRecordById();
}
}
}
3. Deleting
Deletion is supported in case data is shown using the object method. Using rowselection event we will know the currently selected row and then that record will be deleted using Lightning data service. See the above code to call below delete method on the delete icon of data row.
//Deleting selected record id
deleteRecordById=()=>
{
var index = findRowIndexById(this._records, this.recordId);
if (index !== -1) {
deleteRecord(this.recordId)
.then(() => {
showMessage(this,'Notification','Record deleted','success');
this._records = this._records
.slice(0, index)
.concat(this._records.slice(index + 1));
this.refresh(this._records);
})
.catch(error => {
showMessage(this,'Notification','Error deleting record','error');
console.error(error);
});
}
}
4. Single Row Selection
We can restrict single row selection in data table. See our blog Single Row Selection in Lightning Datatable
5. Sorting on Fields
Sorting is configurable and each field is sorted on the basis of ascending and descending order of field click.
handleSortdata(event) {
this.sortedBy = event.detail.fieldName;
let fieldValue = row => row[this.sortedBy] || '';
let reverse = this.sortedDirection === 'asc' ? 1 : -1;
this.sortedDirection = this.sortedDirection === 'asc'?'desc':'asc';
this._records = [...this._records.sort(
(a, b) => (a = fieldValue(a), b = fieldValue(b), reverse * ((a > b) - (b > a)))
)];
}
6. Field data searching
Searching is configurable and it will show combo box with column list. Searching is done on the basis of field selected and text criteria.
var result=arrayContainsValue(this.tempRecords,this.searchFieldVal,inp.value);
console.log('result:' + result);
if(inp.value==='' || inp.value==undefined)
{
result=this.tempRecords;
}
this.refresh(result);
Let see complete Code:
All apex code is already added on top.
LWC Code:
1. Common Library Component
2. Dialog Box Component to show edit form
3. Custom Datatable component
How to use this datatable component
1. Show data using object Name and Columns
We are populating records from Contact object and showing id, name fields.
Html Template:
<lightning-card title="Generic Datatabel Demo with Object" icon-name="custom:custom63">
<div style="height: 300px;">
<c-dynamic-data-table columns={columns} onrowselection={clickMe}
object-name='Contact' editable=true can-delete=true show-pagination=true pagination-size='20'></c-dynamic-data-table>
</div>
</lightning-card>
JS Code:
const columns = [
{label: 'Id', fieldName: 'Id', type: 'text',sortable : true},
{label: 'Name', fieldName: 'Name', type: 'text',sortable : true, editable: true}
];
export default class GenericDataTableTest extends LightningElement {
columns = columns;
where='Name=\'Dhanik Sahni\'';
param = {
acctid: '0012v00002pK5mfAAC'
};
clickMe()
{
alert('clickMe');
}
}
2. Show data using Apex method
We are populating records from apex method getById and showing id, name fields.
Html Template:
<lightning-card title="Generic Datatabel with Apex" icon-name="custom:custom63">
<div style="height: 200px;">
<c-dynamic-data-table columns={columns}
apex-name='getById' param={param}></c-dynamic-data-table>
</div>
</lightning-card>
JS Code:
const columns = [
{label: 'Id', fieldName: 'Id', type: 'text',sortable : true},
{label: 'Name', fieldName: 'Name', type: 'text',sortable : true, editable: true}
];
export default class GenericDataTableTest extends LightningElement {
columns = columns;
}
3. Show data using records and columns
We are populating records from array available in component and showing id, name fields.
Html Template:
<lightning-card title="Generic Datatabel with Records and Columns" icon-name="custom:custom63">
<div style="height: 200px;">
<c-dynamic-data-table columns={columns} records={records}></c-dynamic-data-table>
</div>
</lightning-card>
JS Code:
import { LightningElement } from 'lwc';
const columns = [
{label: 'Id', fieldName: 'Id', type: 'text',sortable : true},
{label: 'Name', fieldName: 'Name', type: 'text',sortable : true, editable: true}
];
export default class GenericDataTableTest extends LightningElement {
columns = columns;
records=[
{
'Id':'00001',
'Name':'Dhanik Sahni'
},
{
'Id':'00002',
'Name':'Hanu Singh'
},{
'Id':'00003',
'Name':'Ram Singh'
},
]
}
References:
https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/example
Other Post for datatable
Single Row Selection in Lightning Datatable
Extend lightning-datatable with file upload element in Lightning Web Component
7 comments
HI Dhanik,
Your Custom data table is super but facing some issue , in the pagination of first page selected one row, the same row is selected in other page as well. could you please help me out, how to rectify this issue.
–
Thanks,
Zakeer.
Hello Zakeer,
I have update commonDatatable.html and commonDatatable.js code. Check now with updated code.
Thank You,
Dhanik
Thanks Dhanik. Still the issue exist. When i selected the first row on the first page, the same row is selected on the other page.
Please help me on the below point
1) Selected of rows
2) restrict the end user, to select only one row
3) when iam passing the data to the data table, it shows all the data in the first page itself, when i click on the next page , at that time i see 10 records per page.
Thanks Dhanik. Still the issue exist. When i selected the first row on the first page, the same row is selected on the other page.
Please help me on the below point
1) When i selected the first row on the first page, the same row is selected on the other page.
2) restrict the end user, to select only one row
3) when iam passing the data to the data table, it shows all the data in the first page itself, when i click on the next page , at that time i see 10 records per page.
Sir is there any way to get record count in org by using lwc when selecting object and field whether the field is used in filling the records.
Hello Nagaratna,
You can use Apex Count() in SOQL to get record count and display in fields. Ping me on linkedin if you need to discuss something related to this topic.
Thank You,
Dhanik
[…] Generic DataTable in Lightning Web Component […]