Search Records and Show in Datatable In LWC

by Rijwan Mohmmed
2 comments
search-records-and-show-in-datatable-in-lwc-techdicer

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.

search-records-and-show-in-datatable-in-lwc-techdicer
search-records-and-show-in-datatable-in-lwc-techdicer

Highlights Points :

  1. Datatable have paginations so show data in chunk.
  2. Datatable data can be sort.
  3. Data table has url link column.
  4. Show error message in LWC toast.
  5. 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:

search-records-and-show-in-datatable-in-lwc-techdicer
search-records-and-show-in-datatable-in-lwc-techdicer

Reference:

1. LWC Datatable in Salesforce

What’s your Reaction?
+1
2
+1
0
+1
0
+1
0
+1
0
+1
0

You may also like

2 comments

Brad Bazemore July 6, 2023 - 12:19 am

Hi Rijwan, would it be simple to allow multiple search inputs at once? Could you offer any help to accomplish that?

Reply
Rijwan Mohmmed July 7, 2023 - 11:42 am

Yupp

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.