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
Apex controller for the lightning component has two @auraenabled functions for fetching search results and getting field properties. There is wrapper class (ColumnDefinition) to structure the field properties to be returned for the lightning datatable.
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>
CustomObjectSearchCmp.design
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!
Why Isit not showing for number fields?
ReplyDelete