Saturday, 4 September 2021

Type ahead search component using Lightning web component(LWC)

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 configurable

Implementation

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
Lets drive into the code....

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.



Thank you for your attention and hope that this article was interesting for you.. !
Cheers ..!




No comments:

Post a Comment

Getting started with Heroku

I am familiar with the heroku for quite long time, have seen lots of people are interested with it but not sure from where its need to Start...