top of page
Search
  • Shreyas Dhond

Reusable SObject Specific Lightning Search Component

Background

In a recent project we had the requirement to provide search functionality on standard and custom objects within other lightning components, lightning pages (flexipages), and flows. As a developer your first instinct is that there must be a standard out of the search component for reuse. But alas that's not the case so I decided to write a reusable search component that can be leveraged and used in all the above scenarios. Below are the specifics to the solution.


CustomObjectSeachCmp (Github)


Demo



Solution

The solution is a simple lightning component that relies on SOSL to do the search and a lightning datatable to display the results. The main components are highlighted below.


CustomObjectSearchCmpLtngCtrl

The apex controller for the lightning component has two @auraenabled functions for fetching search results and getting field properties. There is a wrapper class (ColumnDefinition) to structure the field properties to be returned for the lightning data table.


public with sharing class CustomObjectSearchCmpLtngCtrl {
    @AuraEnabled public static List<SObject> getSearchList(String searchKeyWord,
            String objectApiName, String fields) {

        String searchQuery = 'FIND \'' + searchKeyWord + '\' IN ALL FIELDS ' +
                'RETURNING ' + objectApiName + '(' + fields + ')';
        System.debug('searchQuery: ' + searchQuery);
        List<List<SObject>> results = Search.query(searchQuery);
        return results[0];
    }

    @AuraEnabled public static List<ColumnDefinition> getColumnDefinitions(String objectApiName, String fields) {
        System.debug('getColumnDefinitions: ' + objectApiName + ' ' + fields);
        List<String> fieldList = fields.replaceAll('\\s+', '').split(',');
        List<ColumnDefinition> columnDefinitions = new List<ColumnDefinition>();

        //Get Object API information
        Schema.DescribeSObjectResult objectDescribe = Schema.describeSObjects(new String[]{objectApiName}).get(0);

        //Get field API information from object field map
        Map<String, Schema.SObjectField> fieldMap = objectDescribe.fields.getMap();
        for(String field : fieldList) {
            if(fieldMap.containsKey(field)) {
                Schema.DescribeFieldResult fieldDescribe = fieldMap.get(field).getDescribe();
                ColumnDefinition cd = new ColumnDefinition(fieldDescribe.getLabel(), field,
                        String.valueOf(fieldDescribe.getType()));
                System.debug('cd: ' + cd);
                columnDefinitions.add(cd);
            }
        }

        return columnDefinitions;
    }

    public class ColumnDefinition {
        @AuraEnabled
        public String label;
        @AuraEnabled
        public String fieldName;
        @AuraEnabled
        public String type;

        public ColumnDefinition(String label, String fieldName, String type) {
            this.label = label;
            this.fieldName = fieldName;
            this.type = type;
        }
    }
}

Lightning Component (CustomObjectSearchCmp)

The lightning component shows the search results in a lightning datatable and has a couple of configuration fields to specify the object to search on and the fields to return in the results.


CustomObjectSearchCmp.cmp

<aura:component controller="CustomObjectSearchCmpLtngCtrl"
                implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome"
                access="global">
    <aura:attribute name="objectName" type="String" access="public"/>
    <aura:attribute name="fields" type="String" access="public"/>

    <aura:attribute name="searchKeyword" type="String"/>
    <aura:attribute name="searchResult" type="List"/>
    <aura:attribute name="columns" type="List"/>
    <aura:attribute name="Message" type="boolean" default="false"/>
    <aura:attribute name="showSpinner" type="Boolean" default="false" />

    <!--Handlers-->
    <aura:handler name="init" value="{!this}" action="{!c.onInit}"/>

    <article class="slds-card slds-is-relative">
        <aura:if isTrue="{!v.showSpinner}">
            <lightning:spinner />
        </aura:if>
        <div class="slds-m-around_medium">
            <!-- SEARCH INPUT AND SEARCH BUTTON-->
            <lightning:layout>
                <lightning:layoutItem size="3" padding="around-small">
                    <lightning:input value="{!v.searchKeyword}"
                                     required="true"
                                     placeholder="search .."
                                     aura:id="searchField"
                                     label="Search"/>
                </lightning:layoutItem>
                <lightning:layoutItem size="2" padding="around-small">
                    <lightning:button onclick="{!c.search}"
                                      variant="brand"
                                      label="Search"
                                      iconName="utility:search"/>
                </lightning:layoutItem>
            </lightning:layout>

            <!-- ERROR MESSAGE IF NOT RECORDS FOUND-->
            <aura:if isTrue="{!v.Message}">
                <div class="slds-notify_container slds-is-relative">
                    <div class="slds-notify slds-notify_toast slds-theme_error" role="alert">
                        <div class="slds-notify__content">
                            <h2 class="slds-text-heading_small">No Records Found...</h2>
                        </div>
                    </div>
                </div>
            </aura:if>

            <!-- TABLE CONTENT-->
            <div style="height: 300px">
                <lightning:datatable
                        columns="{!v.columns}"
                        data="{!v.searchResult}"
                        keyField="Id"/>
            </div>
        </div>
    </article>
</aura:component>

The design file just exposes the "objectName" and "fields" properties on the component to be configurable on a lightning page (flexipage).

<design:component>
    <design:attribute name="objectName" label="Object" description="Enter SObject API Name here"
                      default=""/>
    <design:attribute name="fields" label="Fields" description="Enter field list comma separated to include in results"
                      default=""/>
</design:component>

CustomObjectSearchCmpController.js

({
    onInit : function(component, event, helper) {
        helper.getFieldDefinitions(component, event);
    },

    search : function(component, event, helper) {
        var searchField = component.find('searchField');
        var isValueMissing = searchField.get('v.validity').valueMissing;
        // if value is missing show error message and focus on field
        if(isValueMissing) {
            searchField.showHelpMessageIfInvalid();
            searchField.focus();
        } else {
            // else call helper function
            helper.getSearch(component, event);
        }
    }
})

CustomObjectSearchCmpHelper.js

({
    getFieldDefinitions : function(component, event) {
        // show spinner
        component.set("v.showSpinner" , true);
        var action = component.get("c.getColumnDefinitions");
        action.setParams({
            'objectApiName': component.get("v.objectName"),
            'fields': component.get("v.fields")
        });
        action.setCallback(this, function(response) {
            //hide spinner when response coming from server
            component.set("v.showSpinner" , false);
            var state = response.getState();
            if (state === "SUCCESS") {
                var columnDefinitions = response.getReturnValue();
                component.set("v.columns", columnDefinitions);
            } else if (state === "INCOMPLETE") {
                alert('Response is Incompleted');
            } else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        alert("Error message: " +
                                    errors[0].message);
                    }
                } else {
                    alert("Unknown error");
                }
            }
            //hide spinner
            component.set("v.showSpinner", false);
        });
        $A.enqueueAction(action);
    },

    getSearch : function(component, event) {
        // show spinner
        component.set("v.showSpinner" , true);
        var action = component.get("c.getSearchList");
        action.setParams({
            'searchKeyWord': component.get("v.searchKeyword"),
            'objectApiName': component.get("v.objectName"),
            'fields': component.get("v.fields")
        });
        action.setCallback(this, function(response) {
            //hide spinner when response coming from server
            component.set("v.showSpinner" , false);
            var state = response.getState();
            if (state === "SUCCESS") {
                var searchResults = response.getReturnValue();
                console.log("searchResults: " + JSON.stringify(searchResults));
                // if storeResponse size is 0 ,display no record found message on screen.
                if (searchResults.length == 0) {
                    component.set("v.Message", true);
                } else {
                    component.set("v.Message", false);
                }
                // set searchResult list with return value from server.
                component.set("v.searchResult", searchResults);
            } else if (state === "INCOMPLETE") {
                alert('Response is Incompleted');
            } else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        alert("Error message: " +
                                    errors[0].message);
                    }
                } else {
                    alert("Unknown error");
                }
            }
            //hide spinner
            component.set("v.showSpinner", false);
        });
        $A.enqueueAction(action);
    }
})

Hopefully this component can be of assistance to other lightning developers that are looking for SObject specific search functionality. Feel free to suggest any improvements and reuse and modify the code to suit your needs. Cheers and Happy Coding!

3 views0 comments

Comments


bottom of page