Overview
In this tutorial we are going to build a simple TypeAhead Search component using LWC which creates suggestions as soon as someone begins typing into a text box. We will architect the component in such a way that the final product will be configurableImplementation
To implement Type-ahead Searcher we will use lightning datatable to display the search result. in this scope of article i will not explain how to search record within salesforce database.
below 4 parameters you can pass though dynamically using lightning app builder which is defined in metadata xml file.
<targetConfig targets="lightning__RecordPage">
<property name="header" type="String"/>
<property name="object" type="String"/>
<property name="fields" type="String"/>
<property name="viewable" type="String"/>
</targetconfig
- header: name of the section
- object: API name of object
- fields: comma separated API name of fields
- viewable: name of exact label for comma separated fields
Parent component
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <template> <lightning-card title={header} icon-name="custom:custom63"> <div class="slds-m-around_medium"> <c-search-console object={object} fields={fields} ontypeahead={handleObjects}> </c-search-console> <c-mydata-table objects={objects} viewable={viewable}> </c-mydata-table> </div> </lightning-card> </template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* eslint-disable no-console */ import { api, LightningElement, track } from 'lwc'; export default class RummageView extends LightningElement { @api header = '' @api object = '' @api viewable; @api fields = [] @track objects; handleObjects(e){ this.objects = e.detail } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>47.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__RecordPage</target> <target>lightning__AppPage</target> <target>lightning__HomePage</target> <target>lightningCommunity__Page</target> <target>lightningCommunity__Default</target> </targets> <targetConfigs> <targetConfig targets="lightning__RecordPage"> <property name="header" type="String"/> <property name="object" type="String"/> <property name="fields" type="String"/> <property name="viewable" type="String"/> </targetConfig> <targetConfig targets="lightning__AppPage"> <property name="header" type="String"/> <property name="object" type="String"/> <property name="fields" type="String"/> <property name="viewable" type="String"/> </targetConfig> </targetConfigs> </LightningComponentBundle> |
Child Component 1
1 2 3 4 5 6 7 8 9 10 11 | <template> <lightning-input label="Search" value={query} type="search" onchange={handleKeyChange} class="slds-m-bottom_small"> </lightning-input> </template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | /* eslint-disable no-console */ import { api, LightningElement, track, wire } from 'lwc' import search from '@salesforce/apex/searchcontroller.search' /** The delay used when debouncing event handlers before invoking Apex. */ const DELAY = 500; export default class RummageBar extends LightningElement { @api object = ''; @api fields = ''; @api objects = []; @track query = ''; base = ''; fieldArray = []; @wire(search, { fieldArray: '$fieldArray', base: '$base', query: '$query' }) wiredResult(objects) { console.log( 'RAN WIRE objects =>' ); console.log( JSON.parse( JSON.stringify( objects ) ) ); if (objects.data) { this.objects = [] this.error = false this.dispatchEvent(new CustomEvent('typeahead', { detail: objects.data })) } else if (objects.error) { this.error = objects.error this.dispatchEvent(new CustomEvent('whytherum', { detail: objects.error })) } } handleKeyChange(event) { // Debouncing this method: Do not update the reactive property as long as this function is // being called within a delay of DELAY. This is to avoid a very large number of Apex method calls. window.clearTimeout(this.delayTimeout) const query = event.target.value console.log('query..>'+query); console.log(this.fields) console.log(this.object) if(!query){ this.objects = [] } if(!this.base){ this.base = `SELECT ${this.fields} FROM ${this.object} ` console.log('base url..>'+this.base); } if( ! this.fieldArray.length ){ this.fieldArray = this.fields.split(',') } if(!query || !this.fieldArray.length || !this.base){ return false } // eslint-disable-next-line @lwc/lwc/no-async-operation this.delayTimeout = setTimeout(() => { this.query = query console.log('THIS>QUERY => ${this.query}') }, DELAY) return true } } |
Child component 2
1 2 3 4 5 6 7 8 9 10 11 12 | <template> <template if:true={isdataFound}> <lightning-datatable key-field="id" data={data} columns={columns} max-row-selection="1"> </lightning-datatable> </template> </template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /* eslint-disable no-console */ import { api, LightningElement, track } from 'lwc'; export default class ReactiveTable extends LightningElement { @api viewable @track data =[] @track columns = [] @track isdataFound=false @api get objects(){ return this.data } set objects(data){ console.log('data==>'+JSON.stringify(data)); if(data==null||Object.keys(data).length==0){ this.isdataFound=false; this.data = data return } console.log('length-->'+Object.keys(data).length); this.isdataFound=true; const [ record ] = data const viewable = this.viewable.split(',').map(x => x.trim()) console.log('viewable..'+viewable); const fields = Object.keys(record).filter(x => viewable.includes(x)) console.log('fields..'+fields); const columns = fields.map(x => ({ label: this.labeler(x), fieldName: x })) console.log('fields..'+columns); //console.dir( 'ReactiveTable.set.object data => ' ) console.log( JSON.parse( JSON.stringify( {data} ) ) ) this.columns = columns this.data = data } labeler(raw){ const s = raw.replace('__c', '').replace(/_/gi, ' ') return s } } |
Apex Controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public with sharing class searchcontroller { @AuraEnabled(cacheable=true) public static List<sObject> search(List<String> fieldArray, String base, String query) { system.debug('fieldArr'+fieldArray); system.debug('query'+query); system.debug('base'+base); if(base == '' || fieldArray.size() == 0 || query == ''){ return null; } Integer LMT = 10; // -1 for no limit String key = '%' + query + '%'; String search = base; // Build where clause, skip if invalid for(String field : fieldArray){ if('ID' == field.trim().toUpperCase()){ continue; } search += search.indexOf(' WHERE') == -1 ? ' WHERE '+field+' LIKE :key ' : ' OR '+field+' LIKE :key '; } search += LMT > -1 ? ' LIMIT '+LMT+' ' : ''; // System.debug(base); System.debug(fieldArray); System.debug(query); System.debug(search); return Database.query( search ); } } |
Demo Time:
for demo purpose i did pass following parameters within Account Record page as below.
Let's search account and see the result as below.
Cheers ..!