Hello friends, Today I am going to discuss Search Records and Show in Datatable In LWC. I will create 4 search fields and after filling and submitting, data will show in Datatable. In this way, we will show data according to the user’s perspective.
Highlights Points :
- Datatable have paginations so show data in chunk.
- Datatable data can be sort.
- Data table has url link column.
- Show error message in LWC toast.
- Show datatable data in each column based on the data type.
Step 1: In this step, we will create an apex class to fetch data whenever we feed the inputs on UI and hit the button. In this Apex Class, we create a static method that is annotated with Auraenabled. Also, we write a dynamic SOQL query according to UI inputs filters. Here we can fetch max 50000 records of a SOQL query put the limit at the end of the query.
SearchRecordsController.cls:
public class SearchRecordsController {
@AuraEnabled
public static String getUIThemeDescription() {
String theme = UserInfo.getUiThemeDisplayed();
return theme;
}
//Get Account Records
@AuraEnabled
public static wResult getAccounts(String pageSize, String pageNumber, string searchText, string sortDirection,string sortBy, string searchType){
List<WAccount> wAccList = new List<WAccount>();
String searchKey = searchText+ '%';
String query = 'SELECT Id, Name, BillingAddress, BillingStreet, BillingCity, BillingState, BillingCountry, BillingPostalCode, RecordType.Name, AccountNumber, type, Owner.Name, Owner.phone, Phone FROM Account where Name != null ';
if (String.isNotBlank(searchText)) {
if(searchType == 'Name'){
query += ' and (name like: searchKey)';
} else if(searchType == 'Phone'){
query += ' and (Phone LIKE: searchKey)';
} else if(searchType == 'Number'){
query += ' and (AccountNumber like: searchKey)';
}
}
query += ' limit 1000';
List<Account> lacc = Database.query(query);
for(Account a: lacc){
wAccList.add(new WAccount(a));
}
return new wResult(lacc.size(),wAccList);
}
//Get Account Records LWC
@AuraEnabled
public static wResult getAccountsLWC(string searchText, string searchType){
List<WAccount> wAccList = new List<WAccount>();
String searchKey = searchText+ '%';
String query = 'SELECT Id, Name, BillingAddress, BillingStreet, BillingCity, BillingState, BillingCountry, BillingPostalCode, RecordType.Name, AccountNumber, type, Owner.Name, Owner.phone, Phone FROM Account WHERE Name != null';
if (String.isNotBlank(searchText)) {
if(searchType == 'Name'){
query += ' and (name like: searchKey)';
} else if(searchType == 'Phone'){
query += ' and (Phone LIKE: searchKey)';
} else if(searchType == 'Number'){
query += ' and (AccountNumber like: searchKey)';
}
}
query += ' limit 1000';
List<Account> lacc = Database.query(query);
for(Account a: lacc){
wAccList.add(new WAccount(a));
}
return new wResult(lacc.size(),wAccList);
}
public class wResult{
@AuraEnabled public Integer totalAccount;
@AuraEnabled public List<WAccount> lstAccounts;
public wResult(Integer totalAccount, List<WAccount> lstAccounts){
this.totalAccount = totalAccount;
this.lstAccounts = lstAccounts;
}
}
public class WAccount{
@AuraEnabled public string Name;
@AuraEnabled public string Type;
@AuraEnabled public string Phone;
@AuraEnabled public string OwnerName;
@AuraEnabled public string OwnerId;
@AuraEnabled public string OwnerPhone;
@AuraEnabled public string AccountNumber;
@AuraEnabled public string BillingAddress;
@AuraEnabled public string BillingState;
public string makeAddress(Account Acc){
string address = '';
if(Acc.BillingAddress != null){
address = acc.BillingStreet+', '+acc.BillingCity+', '+acc.BillingState+', '+acc.BillingCountry+', '+acc.BillingPostalCode;
address = address.replace('null','');address = address.replace(', , ',',');
address = address.replace(', , ',',');
address = address.replace(',, ','');
}
return address;
}
public WAccount(Account acc){
this.name = (acc.name <> null) ? acc.name : '';
this.Type = (acc.Type <> null) ? acc.Type : '';
this.OwnerName = (acc.Owner.Name <> null) ? acc.Owner.Name : '';
this.OwnerId = acc.Owner.Id;
this.OwnerPhone = (acc.Owner.Phone <> null) ? acc.Owner.Phone : '';
this.Phone = acc.phone;
this.BillingAddress = makeAddress(acc);
this.AccountNumber = (this.AccountNumber <> null) ? this.AccountNumber : '';
this.Phone = (this.Phone <> null) ? this.Phone : '';
this.BillingState = (acc.BillingState <> null) ? acc.BillingState : '';
}
}
}
Step 2: In this step, we will create LWC Component. Here we are going to create a UI part so users can feed data and display data with appropriate search in Datatable. We create things one is input and the other is Datatable. We all know we can show the all data at one time but page loading time will increase so the solution is to show data in chunk form. For this we create pagination.
Also check this: Pagination In Salesforce Lightning Web Components
SearchRecordLWC.Html:
<template>
<!-- Search Fields Card Start -->
<lightning-card title="Search by one of the following Account fields.">
<template if:true={isDesktop}>
<div class="slds-p-horizontal_small">
<lightning-layout multiple-rows>
<lightning-layout-item size="3" padding="around-small">
<div style="font-weight:bold">
<lightning-input type="text" label="Account Name" value="" placeholder="Enter Account Name"
onchange={handleChange} name="Name" disabled={isNameDisabled} autocomplete="false" minlength="3" message-when-too-short="Minimum of 3 characters required to search" class="fieldvalidate"></lightning-input>
</div>
</lightning-layout-item>
<lightning-layout-item size="3" padding="around-small">
<div style="font-weight:bold">
<lightning-input type="text" label="Account Phone" value="" placeholder="Enter Account Phone"
onchange={handleChange} name="Phone" disabled={isPhoneDisabled} autocomplete="false" minlength="3" message-when-too-short="Minimum of 3 characters required to search" class="fieldvalidate"></lightning-input>
</div>
</lightning-layout-item>
<lightning-layout-item size="3" padding="around-small">
<div style="font-weight:bold">
<lightning-input type="text" label="Account Number" value="" placeholder="Enter Account Number"
onchange={handleChange} name="Number" disabled={isNumberDisabled} autocomplete="false" minlength="3" message-when-too-short="Minimum of 3 characters required to search" class="fieldvalidate"></lightning-input>
</div>
</lightning-layout-item>
<lightning-layout-item size="3" padding="around-small" class="slds-m-top_large">
<lightning-button variant="brand" label="Search" title="Search" icon-name="utility:search"
class="slds-m-left_x-small" onclick={searchAccount}></lightning-button>
</lightning-layout-item>
</lightning-layout>
</div>
</template>
<template if:true={isMobile}>
<div class="slds-p-horizontal_small">
<lightning-layout multiple-rows>
<lightning-layout-item size="12" padding="around-small">
<div style="font-weight:bold">
<lightning-combobox
name="searchType"
label="Search Type"
value=''
placeholder=""
options={searchOptions}
onchange={handleSearchTypeChange}>
</lightning-combobox>
</div>
</lightning-layout-item>
<lightning-layout-item size="12" padding="around-small">
<div style="font-weight:bold">
<lightning-input type="text" label="Search Text"
value={searchText} placeholder="Enter Search Text"
onchange={handleChange} name="SearchText"
disabled={isSearchTextDisabled} autocomplete="false"
minlength="3" message-when-too-short="Minimum of 3 characters required to search"
class="fieldvalidate">
</lightning-input>
</div>
</lightning-layout-item>
<lightning-layout-item size="12" padding="around-small" class="slds-m-top_large">
<lightning-button variant="brand" label="Search"
title="Search" icon-name="utility:search"
class="slds-m-left_x-small" onclick={searchAccount}>
</lightning-button>
</lightning-layout-item>
</lightning-layout>
</div>
</template>
<template if:true={data}>
<!-- Datatable with Pagination Start -->
<div class="slds-m-around_medium">
<div class="slds-page-header">
<div class="slds-page-header__row">
<div class="slds-page-header__col-title">
<div class="slds-media">
<div class="slds-media__body">
<div class="slds-page-header__name">
<div class="slds-page-header__name-title">
<h1>
<span class="slds-page-header__title slds-truncate" title="Accounts">Accounts</span>
</h1>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="height: 180px;">
<lightning-datatable key-field="id"
data={data}
columns={columns}
hide-checkbox-column="true"
show-row-number-column="true"
sorted-direction={sortDirection}
sorted-by={sortedBy}
onsort={onHandleSort}
onrowaction={handleRowAction}>
</lightning-datatable>
</div>
</div>
<!-- Pagination Buttons Start -->
<div class="slds-align_absolute-center">
<lightning-button label="Previous" icon-name="utility:chevronleft" onclick={previousHandler}
disabled={isPreviousDisable}>
</lightning-button>
<span class="slds-badge slds-badge_lightest"
style="margin-right: 10px;margin-left: 10px;">
Page {page} out of {totalPage}
</span>
<lightning-button label="Next" icon-name="utility:chevronright" icon-position="right" onclick={nextHandler}
disabled={isNextDisable}>
</lightning-button>
</div>
<!-- Pagination Buttons End -->
<!-- Datatable with Pagination End -->
</template>
</lightning-card>
<!-- Search Fields Card End -->
<!-- Show Spinner when data load -->
<div if:true={isLoading}>
<lightning-spinner
alternative-text="Loading..." variant="brand">
</lightning-spinner>
</div>
<!-- End Spinner Loading -->
<!-- Modal Pop-up Mobile view -->
<template if:true={showModal}>
<section aria-modal="true" class="slds-modal slds-fade-in-open">
<div class="slds-modal__container ">
<header class="slds-modal__header">
<h2 class="slds-text-heading_small">Account Details</h2>
<lightning-icon class="slds-modal__close" icon-name="utility:close" size="small" onclick={closeModal}></lightning-icon>
</header>
<div class="slds-modal__content slds-p-around_medium">
<p>Account Name : {modalFieldInfo.Name}</p></br></br>
<p>Account Owner : {modalFieldInfo.OwnerName}</p></br></br>
<p>Account No : {modalFieldInfo.AccountNumber}</p></br></br>
<p>Phone : {modalFieldInfo.Phone}</p></br></br>
<p>Address : {modalFieldInfo.address}</p></br></br>
</div>
<footer class="slds-modal__footer">
<lightning-button variant="Neutral" label="Close" onclick={closeModal} class="slds-p-around_x-small"></lightning-button>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open"></div>
</template>
<!-- End Modal Pop-up Mobile view -->
</template>
SearchRecordLWC.JS:
import { LightningElement, track } from 'lwc';
import retrieveAccounts from '@salesforce/apex/SearchRecordsController.getAccounts';
import getUIThemeDescription from '@salesforce/apex/SearchRecordsController.getUIThemeDescription';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
const columns = [
{label: 'Name', fieldName: 'Name', type: 'text', sortable : true , wrapText: true},
{label: 'Account ID', fieldName: 'AccountNumber', type: 'text',sortable : true , wrapText: true},
{label: 'Phone', fieldName: 'Phone', type: 'phone', sortable : true , wrapText: true},
{label: 'Owner', fieldName: 'linkName', type: 'url', sortable : true, wrapText: true,
typeAttributes: {label: { fieldName: 'OwnerName' }, target: '_blank' }},
{label: 'State', fieldName: 'BillingState', type: 'text', sortable : true , wrapText: true},
{label: 'Address', fieldName: 'BillingAddress', type: 'text', sortable : true , wrapText: true}
];
const columnsMobile = [
{label: 'Name', fieldName: 'Name', type: 'text',sortable : true , wrapText: true},
{label: 'Owner', fieldName: 'linkName', type: 'url',sortable : true, wrapText: true,
typeAttributes: {label: { fieldName: 'OwnerName' }, target: '_blank' }},
{label: 'Details', type: 'button', initialWidth: 105, typeAttributes: { label:'Details', name:'view', title: 'Click to View Details'}},
];
let i=0;
export default class SearchRecordLWC extends LightningElement {
@track page = 1; //initialize 1st page
@track items = []; //contains all the records.
@track data; //data displayed in the table
@track columns = columns; //holds column info.
@track startingRecord = 1; //start record position per page
@track endingRecord = 0; //end record position per page
@track pageSize = 15; //default value we are assigning
@track totalRecountCount = 0; //total record count received from all retrieved records
@track totalPage = 0; //total number of page is needed to display all records
@track searchText = '';
@track searchType = '';
@track isLoading = false;
@track sortDirection = 'asc';
@track isDesktop;
@track isMobile;
@track showModal = false;
@track modalFieldInfo;
@track sortedBy;
@track mydata;
@track currentPageSize = 0;
get searchOptions() {
return [
{ label: 'Select Search Type', value: '' },
{ label: 'Account Name', value: 'Name' },
{ label: 'Account Phone', value: 'Phone' },
{ label: 'Account Number', value: 'Number' }
];
}
connectedCallback() {
//get UI Info
this.getUIInfo();
}
getUIInfo(){
getUIThemeDescription({})
.then(result=>{
if(result == 'Theme4d'){
this.isDesktop = true;
this.isMobile = false;
this.columns = columns;
} else if(result == 'Theme4t'){
this.isDesktop = false;
this.isMobile = true;
this.columns = columnsMobile;
}
}).catch(error=>{
console.log(error);
});
}
//check field validation
handleCheckValidation() {
let isValid = true;
let inputFields = this.template.querySelectorAll('.fieldvalidate');
inputFields.forEach(inputField => {
if(!inputField.checkValidity()) {
inputField.reportValidity();
isValid = false;
}
});
return isValid;
}
searchAccount(){
if(this.handleCheckValidation()) {
if(this.searchText == ''){
this.page = 1;
this.sortDirection = 'asc';
this.sortBy = '';
this.data = [];
this.totalPage = 1;
this.totalRecountCount = 0;
return false;
}
this.isLoading = true;
retrieveAccounts({searchText : this.searchText, searchType : this.searchType})
.then(data=>{
console.log(data);
this.mydata = this.items = data.lstAccounts;
var parseData = data.lstAccounts;
parseData.forEach(record=>{
record.linkName = '/'+record.OwnerId;
});
this.items = parseData;
this.page = 1;
this.sortDirection = 'asc';
this.sortBy = '';
this.totalRecountCount = data.lstAccounts.length;
this.totalPage = Math.ceil(this.totalRecountCount / this.pageSize);
//here we slice the data according page size
this.data = this.items.slice(0, this.pageSize);
this.endingRecord = this.data.length;
this.error = undefined;
this.isLoading = false;
this.currentPageSize = this.endingRecord - this.startingRecord + 1;
}) .catch(error=>{
console.log(error);
this.error = error;
this.data = undefined;
this.isLoading = false;
this.showToast(this.error, 'Error', 'Error'); //show toast for error
})
}
}
handleSearchTypeChange(event){
this.searchType = event.detail.value;
}
handleChange(event){
if(event.target.name == 'SearchText'){
this.searchText = event.target.value;
}else{
this.searchText = event.target.value;
this.searchType = event.target.name;
}
}
closeModal(){
this.showModal = false;
}
handleRowAction(event) {
const actionName = event.detail.action.name;
const row = event.detail.row;
if(actionName == 'view'){
this.showModal = true;
this.modalFieldInfo = row;
}
}
get isNameDisabled(){
return (this.searchType !== 'Name' && this.searchText != '' ? true : false);
}
get isPhoneDisabled(){
return (this.searchType !== 'Phone' && this.searchText != '' ? true : false);
}
get isNumberDisabled(){
return (this.searchType !== 'Number' && this.searchText != '' ? true : false);
}
get isSearchTextDisabled(){
return this.searchType == '' ? true : false;
}
//press on previous button this method will be called
previousHandler() {
if (this.page > 1) {
this.page = this.page - 1;
this.displayRecordPerPage(this.page);
}
}
//press on next button this method will be called
nextHandler() {
if((this.page<this.totalPage) && this.page !== this.totalPage){
this.page = this.page + 1;
this.displayRecordPerPage(this.page);
}
}
get isPreviousDisable(){
return (this.page == 1 ? true : false);
}
get isNextDisable(){
return (this.page === this.totalPage || (this.page > this.totalPage)) ? true : false;
}
//this method displays records page by page
displayRecordPerPage(page){
this.startingRecord = ((page -1) * this.pageSize) ;
this.endingRecord = (this.pageSize * page);
this.endingRecord = (this.endingRecord > this.totalRecountCount)
? this.totalRecountCount : this.endingRecord;
this.data = this.items.slice(this.startingRecord, this.endingRecord);
//increment by 1 to display the startingRecord count,
//so for 2nd page, it will show "Displaying 6 to 10 of 23 records. Page 2 of 5"
this.startingRecord = this.startingRecord + 1;
//console.log(this.startingRecord);
//console.log(this.endingRecord);
this.currentPageSize = this.endingRecord - this.startingRecord + 1;
}
showToast(message, variant, title) {
const event = new ShowToastEvent({
title: title,
message: message,
variant: variant,
mode: 'dismissable'
});
this.dispatchEvent(event);
}
onHandleSort(event) {
this.sortedBy = event.detail.fieldName;
this.sortDirection = event.detail.sortDirection;
this.sortData(this.sortedBy, this.sortDirection);
}
sortData(fieldname, direction) {
if (fieldname == 'linkName') {
fieldname = 'OwnerName';
}
let parseData = [...this.items];
// Return the value stored in the field
let keyValue = (a) => {
return a[fieldname];
};
console.log(keyValue);
// cheking reverse direction
let isReverse = direction === 'asc' ? 1: -1;
// sorting data
parseData.sort((x, y) => {
x = keyValue(x) ? keyValue(x) : ''; // handling null values
y = keyValue(y) ? keyValue(y) : '';
// sorting values based on direction
return isReverse * ((x > y) - (y > x));
});
parseData.forEach(record=>{
record.linkName = '/'+record.OwnerId;
});
this.items = parseData;
this.displayRecordPerPage(this.page);
}
}
OutPut:
Reference:
1. LWC Datatable in Salesforce
2 comments
Hi Rijwan, would it be simple to allow multiple search inputs at once? Could you offer any help to accomplish that?
Yupp