Blog

Global Records Search Using LWC and SOSL

gr_blog_banner

In this blog we are about to see a global records search feature implemented using SOSL.

This is something similar to the standard Salesforce search where we can search the records by using the SOSL query along with LWC or Aura components.

We can specify the required objects in SOSL and add the criteria to restrict the records in
dropdown.

Here we have implemented the search using SOSL and LWC.

Use case :

To perform the global record search and to get the record ID.

Sample SOSL Query to perform global search

FIND {test} IN ALL FIELDS RETURNING
Account(NAME WHERE NAME Like ‘Test Account%’ Order by LastViewedDate DESC),
Contact(NAME WHERE NAME Like ‘Test Contact%’ Order by LastViewedDate DESC),
Opportunity(NAME WHERE NAME Like ‘Test Opportunity%’ Order by LastViewedDate
DESC)

We can specify the objects with criteria in the SOSL query to extract the records list.

GlobalRecordSearchController.apxc

public class GlobalRecordSearchController {
    
    @AuraEnabled(cacheable = true)
    public static List<ObjectWrapper> searchRecords(String searchTerm, string objectName) {
        
        List<ObjectWrapper> wrapperList = new List<ObjectWrapper>();
        
        if(objectName == 'All' && searchTerm != null){
            String myQuery1 = 'FIND \'' + searchTerm + '\'  IN ALL FIELDS RETURNING Account(NAME WHERE NAME Like  \'%' + searchTerm + '%\' Order by LastViewedDate DESC), Contact(NAME WHERE NAME Like  \'%' + searchTerm + '%\' Order by LastViewedDate DESC), Opportunity(NAME WHERE NAME Like  \'%' + searchTerm + '%\' Order by LastViewedDate DESC) LIMIT 5';
            List<List<sObject>> searchList = System.Search.query(myQuery1);
            List <Account> searchAccounts = searchList.get(0);
            List <Contact> searchContact = searchList.get(1);
            List <Opportunity> searchOpportunity = searchList.get(2);
            for(Account acc : searchAccounts) {
                ObjectWrapper objWrapper = new ObjectWrapper();
                objWrapper.Id = acc.Id;
                objWrapper.Name = acc.Name;
                objWrapper.Type = 'Account';
                objWrapper.Icon = 'standard:account';
                wrapperList.add(objWrapper);
            }
            for(Contact cont : searchContact) { 
                ObjectWrapper objWrapper = new ObjectWrapper();
                objWrapper.Id = cont.Id;
                objWrapper.Name = cont.Name;
                objWrapper.Type = 'Contact';
                objWrapper.Icon = 'standard:contact';
                wrapperList.add(objWrapper);
            }
            for(Opportunity opp : searchOpportunity) {
                ObjectWrapper objWrapper = new ObjectWrapper();
                objWrapper.Id = opp.Id;
                objWrapper.Name = opp.Name;
                objWrapper.Type = 'Opportunity';
                objWrapper.Icon = 'standard:opportunity';
                wrapperList.add(objWrapper);
            }
        }
        else{
            if(objectName != null && objectName != 'All' && searchTerm != null){
                String myQuery;
                myQuery = 'Select Id, Name from '+objectName+' Where Name Like  \'%' + searchTerm + '%\' LIMIT  5';
                List<sObject> lookUpList = database.query(myQuery);
                for(sObject myobj: lookUpList){
                    ObjectWrapper objWrapper = new ObjectWrapper();
                    objWrapper.Id = myobj.Id;
                    objWrapper.Name = String.valueOf(myobj.get('Name'));
                    objWrapper.Type = objectName;
                    if(objectName =='Account')
                        objWrapper.Icon = 'standard:account';
                    else if(objectName =='Contact')
                        objWrapper.Icon = 'standard:contact';
                    else if(objectName =='Opportunity')
                        objWrapper.Icon = 'standard:opportunity';
                    wrapperList.add(objWrapper);
                }
            }
        }
        return wrapperList;
    }
    public class ObjectWrapper {
        @AuraEnabled
        public String Id{get;set;}
        @AuraEnabled
        public String Name{get;set;}
        @AuraEnabled
        public String Type{get;set;}
        @AuraEnabled
        public String Icon{get;set;}
    }
}

globalRecordSearch.lwc

<template>
    <h2 id="modal-heading-02" class="slds-text-heading_medium slds-hyphenate slds-align_absolute-center">Record Search</h2>
    <div class="slds-col slds-size_11-of-12 slds-align_absolute-center slds-p-top_small">
        <div style="margin-left: 3%;">
            <template for:each="{options}" for:item="item">
                <fieldset key="{item.value}" style="display: block; float: left;">
                    <div class="slds-form-element__control">
                        <span class="slds-radio">
                            <input name="radiogroup" id="{item.value}" type="radio" value="{item.value}" onchange="{handleRadioChange}" />
                            <label class="slds-radio__label" for="{item.value}">
                                <span class="slds-radio_faux"></span>
                                <span class="slds-form-element__label">{item.label}</span>
                            </label>
                        </span>
                    </div>
                </fieldset>
            </template>
            <br />
            <br />
        </div>
    </div>
    <div class="slds-form-element slds-align_absolute-center">
        <div class="slds-form-element__control">
            <div class="slds-combobox_container">
                <div id="box" class="{boxClass}" aria-expanded="true" aria-haspopup="listbox" role="combobox">
                    <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">
                        <template if:true="{isValueSelected}">
                            <span class="slds-icon_container slds-combobox__input-entity-icon"> <lightning-icon icon-name="{selectedIcon}" size="x-small" alternative-text="icon"></lightning-icon></span>
                            <input type="text" id="combobox-id-1" value="{selectedName}" class="slds-input slds-combobox__input slds-combobox__input-value" style="padding-left: 36px;" readonly />
                            <button class="slds-button slds-button_icon slds-input__icon slds-input__icon_right" title="Remove selected option">
                                <lightning-icon icon-name="utility:close" size="x-small" alternative-text="close icon" onclick="{handleRemovePill}"></lightning-icon>
                            </button>
                        </template>
                        <template if:false="{isValueSelected}">
                            <div class="slds-p-top_none">
                                <lightning-input
                                    class="{inputClass}"
                                    type="search"
                                    id="input"
                                    value="{searchTerm}"
                                    onclick="{handleClick}"
                                    onblur="{onBlur}"
                                    onchange="{onChange}"
                                    variant="label-hidden"
                                    autocomplete="off"
                                    placeholder="{searchPlaceholder}"
                                >
                                </lightning-input>
                            </div>
                        </template>
                    </div>
                    <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-with-icon-7 slds-dropdown_fluid" role="listbox">
                        <ul class="slds-listbox slds-listbox_vertical" role="presentation">
                            <template if:true="{records}">
                                <template for:each="{records}" for:item="record">
                                    <li key="{record.Id}" onclick="{onSelect}" data-id="{record.Id}" role="presentation" data-name="{record.Name}" data-type="{record.Type}" onmouseout="{hideData}" onmouseover="{showData}">
                                        <span class="slds-lookup__item-action slds-lookup__item-action--label" role="option">
                                            <lightning-icon class="slds-icon slds-icon--small" icon-name="{record.Icon}" alternative-text="{record.Type}" size="small"></lightning-icon>
                                            <span class="slds-media__body">
                                                <span class="slds-listbox__option-text slds-listbox__option-text_entity">{record.Name}</span>
                                                <span class="slds-listbox__option-meta slds-listbox__option-meta_entity"> •{record.Type} </span>
                                            </span>
                                        </span>
                                    </li>
                                </template>
                            </template>
                            <template if:false="{records}">
                                <span class="slds-lookup__item-action slds-lookup__item-action--label" role="option">
                                    <span class="slds-truncate"><lightning-spinner alternative-text="Loading" size="small"></lightning-spinner></span>
                                </span>
                            </template>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

globalRecordSearch.js

import {
	LightningElement,
	api,
	track,
	wire
} from 'lwc';
import search from '@salesforce/apex/GlobalRecordSearchController.searchRecords';
const DELAY = 300;
export default class GlobalRecordSearch extends LightningElement {
	@track isLoading = false;
	@api objName;
	@api iconName;
	@api searchPlaceholder = 'Search Records';
	@track selectedId;
	@track selectedName;
	@track selectedType;
	@track selectedIcon;
	@track records;
	@track isValueSelected;
	@track blurTimeout;
	searchTerm;
	@track ranger;
	@track left;
	@track top;
	@track objRecordId;
	@api recordId;
	value = 'All';
	label;
	get options() {
		return [{
				label: 'All',
				value: 'All'
			},
			{
				label: 'Account',
				value: 'Account'
			},
			{
				label: 'Contact',
				value: 'Contact'
			},
			{
				label: 'Opportunity',
				value: 'Opportunity'
			},
		];
	}
	//css
	@track boxClass = 'slds-combobox slds-dropdown-trigger
	slds - dropdown - trigger_click slds - has - focus ';
	@track inputClass = '';
	handleClick(event) {
		this.searchTerm = event.target.value;
		this.records = null;
		this.getvalues();
		this.inputClass = 'slds-has-focus';
		this.boxClass = 'slds-combobox slds-dropdown-trigger
		slds - dropdown - trigger_click slds - has - focus slds - is - open ';
	}
	onBlur() {
			this.blurTimeout = setTimeout(() => {
					this.boxClass = 'slds-combobox
					slds - dropdown - trigger slds - dropdown - trigger_click slds - has - focus '}, 300);
				}
				onSelect(event) {
					let selectedId = event.currentTarget.dataset.id;
					let selectedName = event.currentTarget.dataset.name;
					this.isValueSelected = true;
					this.selectedName = selectedName;
					this.selectedId = selectedId;
					for (let i = 0; i < this.records.length; i++) {
						if (this.records[i].Id == this.selectedId) {
							this.selectedIcon = this.records[i].Icon;
						}
					}
					alert('Selected Record Id--' + this.selectedId);
					if (this.blurTimeout) {
						clearTimeout(this.blurTimeout);
					}
					this.boxClass = 'slds-combobox slds-dropdown-trigger
					slds - dropdown - trigger_click slds - has - focus ';
					const value = event.currentTarget.dataset.name;
				}
				handleRemovePill() {
					this.isValueSelected = false;
					this.searchTerm = '';
				}
				onChange(event) {
					this.searchTerm = event.target.value;
					this.getvalues();
				}
				handleRadioChange(event) {
					this.searchTerm = '';
					this.value = event.target.value;
					console.log(this.value);
					this.isValueSelected = false;
				}
				getvalues() {
					if (this.searchTerm.length >= 2) {
						search({
								searchTerm: this.searchTerm,
								objectName: this.value
							})
							.then(result => {
								this.isLoading = true;
								this.records = result;
							})
					}
					if (this.searchTerm.length <= 0) {
						this.records = null;
					}
				}
			}

Image 1

Global Records Search

Image 2

Global Records Search

Image 3

Global Records Search

Image 4

Global Records Search

Image 5

Global Records Search

Image 6

Global Records Search