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 ..!
