In this blog post, we will discuss on creating a lightning component to filter products based on Product Code and Product Name. User can type multiple codes or names separated by comma. Then, we will display the results based on the given search criteria using lightning data table.
We have accounts of type=Vendor and every product is linked to a Vendor account. We will have a Vendor picklist in the component and it gets populated with the distinct values from the Vendor Number column on the search results.
Apex Class:
public with sharing class SearchProductController {
@AuraEnabled public List<String> result{get;set;}
@AuraEnabled public List < Product2 > returnList{get;set;}
@AuraEnabled
public static SearchProductController fetchProductbyName(String searchKeyWord) {
SearchProductController obj = new SearchProductController();
String searchKey;
Set < String > returnSetVIds = new Set < String > ();
List < String > returnListVIds = new List < String > ();
try
{
if(searchKeyWord.contains(','))
{
List<String> searchKeyCode = searchKeyWord.split(',') ;
obj.returnList = [select Name,ProductCode,Voltumcon1__Vendor_Name__c, Voltumcon1__Vendor_Number__c
from Product2
where Name in: searchKeyCode AND IsActive=TRUE LIMIT 500
];
}
else
{
searchKey = searchKeyWord + '%';
obj.returnList = [select Name,ProductCode,Voltumcon1__Vendor_Name__c,Voltumcon1__Vendor_Number__c from Product2
where Name LIKE: searchKey AND IsActive=TRUE LIMIT 500];
}
for (Product2 prd: obj.returnList) {
//obj.returnList.add(prd);
returnListVIds.add(prd.Voltumcon1__Vendor_Number__c);
}
system.debug(returnListVIds);
returnSetVIds.addAll(returnListVIds);
system.debug(returnSetVIds);
obj.result = new List<String>();
obj.result.addAll(returnSetVIds);
return obj;
}
catch (Exception e) {
// "Convert" the exception into an AuraHandledException
throw new AuraHandledException('Something went wrong: '
+ e.getMessage());
}
}
@AuraEnabled
public static SearchProductController fetchProductbyCode(String searchKeyWord) {
SearchProductController objCode = new SearchProductController();
List<String> searchKeyCode;
Set < String > returnSetVIds = new Set < String > ();
List < String > returnListVIds = new List < String > ();
String searchKey;
try
{
if(searchKeyWord.contains(','))
{
searchKeyCode = searchKeyWord.split(',') ;
objCode.returnList = [select Name,ProductCode,Voltumcon1__Vendor_Name__c,Voltumcon1__Vendor_Number__c
from Product2
where ProductCode in: searchKeyCode AND IsActive=TRUE LIMIT 500
];
}
else
{
searchKey = searchKeyWord + '%';
objCode.returnList = [select Name,ProductCode,Voltumcon1__Vendor_Name__c,Voltumcon1__Vendor_Number__c
from Product2
where ProductCode LIKE: searchKey AND IsActive=TRUE LIMIT 500
];
}
for (Product2 prd: objCode.returnList) {
returnListVIds.add(prd.Voltumcon1__Vendor_Number__c);
}
returnSetVIds.addAll(returnListVIds);
objCode.result = new List<String>();
objCode.result.addAll(returnSetVIds);
return objCode;
}
catch (Exception e) {
// "Convert" the exception into an AuraHandledException
throw new AuraHandledException('Something went wrong: '
+ e.getMessage());
}
}
}
Please note that we have created 2 lists in the above class, one to hold the search results and the other one for holding Vendor Numbers.
Lightning Component:
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" controller="SearchProductController" >
<aura:attribute name="searchKeyword" type="String" description="search input"/>
<aura:attribute name="TotalNumberOfRecords" type="integer" default="0" description="display Total Number of records"/>
<aura:attribute name="selectedRowsCount" type="Integer" default="0"/>
<aura:attribute name="mycolumns" type="List"/>
<aura:attribute name="isLoading" type="Boolean" default="false"/>
<aura:attribute name="objClassController" type="SearchProductController"/>
<aura:handler name="init" value="{! this }" action="{! c.init }"/>
<!-- SHOW LOADING SPINNER-->
<lightning:spinner variant="brand" size="large" aura:id="Id_spinner" class="slds-hide" />
<div aria-labelledby="newSearchForm" class="slds-grid">
<!-- BOXED AREA -->
<fieldset class="slds-box slds-theme--default slds-container--small">
<label id="newSearchForm" class="slds-text-heading--small
slds-p-vertical--medium">
Search Products
</label>
<form class="slds-form--stacked">
<lightning:select aura:id="selectfilter" name="select1" label="Filter by" onchange="{! c.onChange }" required="true" >
<option value="Product Code">Product Code</option>
<option value="Product Name">Product Name</option>
</lightning:select>
<lightning:input value="{!v.searchKeyword}"
required="true"
placeholder="search Products.."
aura:id="searchField"
label="Enter Value"/>
<lightning:button onclick="{!c.Search}"
class="slds-m-top--medium"
variant="brand"
label="Search"
iconName="utility:search"/>
<br></br>
<div class="slds-form-element__control slds-grow" >
<lightning:select name="VendorId" label="Vendor Id" required="true" aura:id="VendorId"
messageWhenValueMissing="Select a valid value">
<option value="">Select</option>
<aura:iteration items="{!v.objClassController.result}" var="item">
<option value="{!item}" text="{!item}"></option>
</aura:iteration>
</lightning:select>
</div>
</form>
</fieldset>
</div>
<div class="slds-m-vertical_small">
<aura:if isTrue="{!v.objClassController.returnList.length >0}">
<h1 class="slds-m-vertical_small"><b>Total Rows: {! v.objClassController.returnList.length }</b></h1>
<aura:set attribute="else">
<h1 class="slds-m-vertical_small"><b>Total Rows: 0</b></h1>
</aura:set>
</aura:if>
<h1 class="slds-m-vertical_small"><b>Selected Rows: {! v.selectedRowsCount }</b></h1>
<lightning:datatable data="{! v.objClassController.returnList }"
columns="{! v.mycolumns }"
aura:id="avreltable"
onrowselection="{!c.updateSelectedText}"
onsort="{! c.updateColumnSorting }"
keyField="Id"/>
</div>
</aura:component>
Points to note in the above component:
a. We are using lightning:datatable to display search results. Data table is a table that displays columns of data, formatted according to type. This is one of the base component provided by the Lightning framework. Please refer this link for more details https://developer.salesforce.com/docs/component-library/bundle/lightning:datatable/example
b. We have created an attribute “objClassController” of apex class type to fetch the lists from the class to display the search results and Vendor numbers.
JS Controller:
({
init: function (cmp, event, helper) {
cmp.set('v.mycolumns', [
{ label: 'Product Name', fieldName: 'Name', sortable: true,type: 'text'},
{ label: 'Product Code', fieldName: 'ProductCode', sortable: true,type: 'text'},
{ label: 'Vendor', fieldName: 'Voltumcon1__Vendor_Name__c',sortable: true,type: 'text'},
{ label: 'Vendor Number', fieldName: 'Voltumcon1__Vendor_Number__c',sortable: true,type: 'text'}
]);
},
updateSelectedText: function (cmp, event) {
var selectedRows = event.getParam('selectedRows');
cmp.set('v.selectedRowsCount', selectedRows.length);
},
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();
}else{
// else call helper function
helper.SearchHelper(component, event);
}
},
onChange: function (cmp, evt, helper) {
cmp.set('v.searchKeyword',null);
},
updateColumnSorting: function (cmp, event, helper) {
cmp.set('v.isLoading', true);
// We use the setTimeout method here to simulate the async
// process of the sorting data, so that user will see the
// spinner loading when the data is being sorted.
setTimeout(function() {
var fieldName = event.getParam('fieldName');
var sortDirection = event.getParam('sortDirection');
cmp.set("v.sortedBy", fieldName);
cmp.set("v.sortedDirection", sortDirection);
helper.sortData(cmp, fieldName, sortDirection);
cmp.set('v.isLoading', false);
}, 0);
}
})
Helper function:
({
SearchHelper: function(component, event) {
// show spinner message
component.find("Id_spinner").set("v.class" , 'slds-show');
var filter = component.find("selectfilter").get('v.value');
if(filter == "Product Code")
{
var action = component.get("c.fetchProductbyCode");
}
else if(filter == "Product Name")
{
var action = component.get("c.fetchProductbyName");
}
action.setParams({
'searchKeyWord': component.get("v.searchKeyword")
});
action.setCallback(this, function(response) {
// hide spinner when response coming from server
component.find("Id_spinner").set("v.class" , 'slds-hide');
var state = response.getState();
if (state === "SUCCESS") {
var storeResponse = response.getReturnValue();
component.set('v.objClassController', storeResponse);
}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");
}
}
});
$A.enqueueAction(action);
},
sortData: function (cmp, fieldName, sortDirection) {
var data = cmp.get("v.objClassController.returnList");
var reverse = sortDirection !== 'asc';
data = Object.assign([],
data.sort(this.sortBy(fieldName, reverse ? -1 : 1))
);
cmp.set("v.objClassController.returnList", data);
},
sortBy: function (field, reverse, primer) {
var key = primer
? function(x) { return primer(x[field]) }
: function(x) { return x[field] };
return function (a, b) {
var A = key(a);
var B = key(b);
return reverse * ((A > B) - (B > A));
};
}
})
Output Screenshots:

Clicking on Search button without giving any value throws error:

Search on Product codes by entering them with comma separated values. Vendor Id got auto populated with the distinct Vendor Numbers from the search results:


Component will do a wildcard search for the entered value(in this case ‘SL’):

When the rows are selected, Selected rows will show the count of selected rows:

Wildcard search for the Product Name:

Exact search for the Product Name:

Please let me know for any questions. Thanks and stay safe!!