This LWC component will help you create a multi-select dropdown in your main LWC component. I created this component to create a searchable dropdown of products by name and add them to the record. The dropdown is dynamic here i.e. it updates using the search term entered. Below is the visual representation of the multi-select, dynamic search child LWC component.
multiSelectLookup HTML File
<template>
<div class="slds-form-element">
<label class='slds-m-around_x-small'><abbr class="slds-required">*</abbr>{labelName}</label>
<div class="slds-form-element__control">
<div class="slds-combobox_container">
<div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open"
aria-expanded="true" aria-haspopup="listbox" role="combobox">
<div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-has-focus"
role="none">
<lightning-input id="input"
value={searchInput} onchange={onchangeSearchInput} variant="label-hidden" aria-autocomplete="list" role="textbox"
autocomplete="off" placeholder="Search..." type="search">
</lightning-input>
</div>
<div class="slds-form-element__control slds-input-has-icon slds-input-has-icon slds-input-has-icon_left-right" role="none">
<template for:each={globalSelectedItems} for:item="selectedItem">
<span key={selectedItem.value}>
<lightning-pill label={selectedItem.label} name={selectedItem.value} data-item={selectedItem.value}
onremove={handleRemoveRecord}>
<lightning-icon icon-name={iconName} variant="circle"
alternative-text={selectedItem.label}></lightning-icon>
</lightning-pill>
</span>
</template>
</div>
<template if:true={isDisplayMessage}>
<lightning-card>No records found.</lightning-card>
</template>
<template if:false={isDisplayMessage}>
<template if:true={isDialogDisplay}>
<section aria-describedby="dialog-body-id-26" aria-label="Language Options"
class="slds-popover slds-popover_full-width" id="popover-unique-id-02" role="dialog">
<div class="slds-popover__body slds-popover__body_small" id="dialog-body-id-26">
<fieldset class="slds-form-element">
<lightning-checkbox-group name="Checkbox Group"
label={objectAPIName}
options={items}
value={value}
onchange={handleCheckboxChange}>
</lightning-checkbox-group>
</fieldset>
</div>
<footer class="slds-popover__footer slds-popover__footer_form">
<lightning-button label="Cancel" title="Cancel"
onclick={handleCancelClick} class="slds-m-left_x-small"></lightning-button>
<lightning-button variant="success" label="Done" title="Done"
onclick={handleDoneClick} class="slds-m-left_x-small"></lightning-button>
</footer>
</section>
</template>
</template>
</div>
</div>
</div>
</div>
</template>
JS File
import { LightningElement, api, track } from 'lwc';
import retrieveRecords from '@salesforce/apex/MultiSelectLookupController.retrieveRecords';
let i=0;
export default class MultiSelectLookup extends LightningElement {
@track globalSelectedItems = []; //holds all the selected checkbox items
//start: following parameters to be passed from calling component
@api labelName;
@api objectApiName; // = 'Contact';
@api fieldApiNames; // = 'Id,Name';
@api filterFieldApiName; // = 'Name';
@api iconName; // = 'standard:contact';
//end---->
@track items = []; //holds all records retrieving from database
@track selectedItems = []; //holds only selected checkbox items that is being displayed based on search
//since values on checkbox deselection is difficult to track, so workaround to store previous values.
//clicking on Done button, first previousSelectedItems items to be deleted and then selectedItems to be added into globalSelectedItems
@track previousSelectedItems = [];
@track value = []; //this holds checkbox values (Ids) which will be shown as selected
searchInput =''; //captures the text to be searched from user input
isDialogDisplay = false; //based on this flag dialog box will be displayed with checkbox items
isDisplayMessage = false; //to show 'No records found' message
//This method is called when user enters search input. It displays the data from database.
onchangeSearchInput(event){
this.searchInput = event.target.value;
if(this.searchInput.trim().length>0){
//retrieve records based on search input
retrieveRecords({objectName: this.objectApiName,
fieldAPINames: this.fieldApiNames,
filterFieldAPIName: this.filterFieldApiName,
strInput: this.searchInput
})
.then(result=>{
console.log('result'+JSON.stringify(result));
this.items = []; //initialize the array before assigning values coming from apex
this.value = [];
this.previousSelectedItems = [];
if(result.length>0){
result.map(resElement=>{
console.log(resElement);
//prepare items array using spread operator which will be used as checkbox options
this.items = [...this.items,{value:resElement.recordId,
label:resElement.recordName}];
/*since previously choosen items to be retained, so create value array for checkbox group.
This value will be directly assigned as checkbox value & will be displayed as checked.
*/
this.globalSelectedItems.map(element =>{
if(element.value == resElement.recordId){
this.value.push(element.value);
this.previousSelectedItems.push(element);
}
});
});
this.isDialogDisplay = true; //display dialog
this.isDisplayMessage = false;
}
else{
//display No records found message
this.isDialogDisplay = false;
this.isDisplayMessage = true;
}
})
.catch(error=>{
this.error = error;
this.items = undefined;
this.isDialogDisplay = false;
})
}else{
this.isDialogDisplay = false;
}
}
//This method is called during checkbox value change
handleCheckboxChange(event){
let selectItemTemp = event.detail.value;
//all the chosen checkbox items will come as follows: selectItemTemp=0032v00002x7UE9AAM,0032v00002x7UEHAA2
console.log(' handleCheckboxChange value=', event.detail.value);
this.selectedItems = []; //it will hold only newly selected checkbox items.
/* find the value in items array which has been prepared during database call
and push the key/value inside selectedItems array
*/
selectItemTemp.map(p=>{
let arr = this.items.find(element => element.value == p);
if(arr != undefined){
this.selectedItems.push(arr);
}
});
}
//this method removes the pill item
handleRemoveRecord(event){
const removeItem = event.target.dataset.item;
//this will prepare globalSelectedItems array excluding the item to be removed.
this.globalSelectedItems = this.globalSelectedItems.filter(item => item.value != removeItem);
const arrItems = this.globalSelectedItems;
//initialize values again
this.initializeValues();
this.value =[];
//propagate event to parent component
const evtCustomEvent = new CustomEvent('remove', {
detail: {removeItem,arrItems}
});
this.dispatchEvent(evtCustomEvent);
}
//Done dialog button click event prepares globalSelectedItems which is used to display pills
handleDoneClick(event){
//remove previous selected items first as there could be changes in checkbox selection
this.previousSelectedItems.map(p=>{
this.globalSelectedItems = this.globalSelectedItems.filter(item => item.value != p.value);
});
//now add newly selected items to the globalSelectedItems
this.globalSelectedItems.push(...this.selectedItems);
const arrItems = this.globalSelectedItems;
console.log('test54'+JSON.stringify(arrItems));
//store current selection as previousSelectionItems
this.previousSelectedItems = this.selectedItems;
this.initializeValues();
//propagate event to parent component
const evtCustomEvent = new CustomEvent('retrieve', {
detail: {arrItems}
});
this.dispatchEvent(evtCustomEvent);
}
//Cancel button click hides the dialog
handleCancelClick(event){
this.initializeValues();
}
//this method initializes values after performing operations
initializeValues(){
this.searchInput = '';
this.isDialogDisplay = false;
}
}
Meta XML File
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
</targets>
</LightningComponentBundle>
Apex Controller
public with sharing class MultiSelectLookupController {
//This method retrieves the data from database table. It search input is '*', then retrieve all records
@AuraEnabled
public static List<SObjectQueryResult> retrieveRecords(String objectName,
String fieldAPINames,
String filterFieldAPIName,
String strInput){
List<SObject> lstResult= new List<SObject>();
List<SObjectQueryResult> lstReturnResult = new List<SObjectQueryResult>();
if(strInput.equals('*')){
strInput = '';
}
boolean userActive=true;
String str = strInput + '%';
String recName = 'Person Account';
String strQueryField = '';
List<String> fieldList = fieldAPINames.split(',');
//check if Id is already been passed
if(!fieldList.contains('Id')){
fieldList.add('Id');
strQueryField = String.join(fieldList, ',');
}else {
strQueryField = fieldAPINames;
}
if(objectName=='Product__c'){
String strQuery = 'SELECT ' + String.escapeSingleQuotes(strQueryField)
+ ' FROM '
+ String.escapeSingleQuotes(objectName)
+ ' WHERE recordtype.name=\'Sample\' AND ' + filterFieldAPIName + ' LIKE \'' + str + '%\''
+ ' ORDER BY ' + filterFieldAPIName
+ ' LIMIT 50';
System.debug('strQuery=' + strQuery);
lstResult = database.query(strQuery);
}
else{
String strQuery = 'SELECT ' + String.escapeSingleQuotes(strQueryField)
+ ' FROM '
+ String.escapeSingleQuotes(objectName)
+ ' WHERE ' + filterFieldAPIName + ' LIKE \'' + str + '%\''
+ ' ORDER BY ' + filterFieldAPIName
+ ' LIMIT 50';
System.debug('strQuery=' + strQuery);
lstResult = database.query(strQuery);
}
return lstReturnResult;
}
public class SObjectQueryResult {
@AuraEnabled
public String recordId;
@AuraEnabled
public String recordName;
}
}
Parent LWC HTML to add the component
In this component, we have used the above multi-select lookup as a child component to add the products. “<c-multi-select-lookup” to embed the component. Understand that the ‘-‘ is used to segregate the capital letters in the name of the component “multiSelectLookup”.
<c-multi-select-lookup
label-name="Add Product"
object-api-name= "Product__c"
field-api-names="Id,Name"
filter-field-api-name="Name"
onvaluechange={handleChange}
onretrieve={selectItemEventHandler}
onremove={deleteItemEventHandler}
required="true">
</c-multi-select-lookup>
Parent JS to add the component
handleChange(event){
this.selectedBrand = event.detail;
var val=JSON.stringify(this.selectedBrand);
console.log('sval'+val);
}
selectItemEventHandler(event){
let args = JSON.parse(JSON.stringify(event.detail.arrItems));
this.displayItem(args);
if(event.detail.arrItems.length>5){
this.countBrandNames=true;
console.log('true'+ this.countBrandNames);
}
else {
this.countBrandNames=false;
console.log('false'+ this.countBrandNames);
}
}
displayItem(args){
this.values = []; //initialize first
args.map(element=>{
this.values.push(element.value);
});
this.isItemExists = (args.length>0);
this.selectedItemsToDisplay = this.values.join(',');
console.log('test123'+this.selectedItemsToDisplay);
}
//captures the remove event propagated from lookup component
deleteItemEventHandler(event){
let args = JSON.parse(JSON.stringify(event.detail.arrItems));
this.displayItem(args);
if(event.detail.arrItems.length>5){
this.countBrandNames=true;
console.log('true'+ this.countBrandNames);
}
else {
this.countBrandNames=false;
console.log('false'+ this.countBrandNames);
}
}
Check This Post: Dynamic Show and Hide Button in LWC
Also Refer: How to create a single select dropdown in LWC
Hope this post makes your day a tad bit easier. Lemme know in the comments below how you tweaked it.
You Have to add This Line of code in your multiSelectLookupController class file
/*
for (SObject record : lstResult) {
SObjectQueryResult resultItem = new SObjectQueryResult();
resultItem.recordId = String.valueOf(record.get(‘Id’));
resultItem.recordName = String.valueOf(record.get(filterFieldAPIName));
lstReturnResult.add(resultItem);
}
*/
because you are not convert the record in wrapper class so first you need to do first , If any other alternative solution please mention me .
public with sharing class multiSelectLookupController {
public multiSelectLookupController() {
}
//This method retrieves the data from database table. It search input is ‘*’, then retrieve all records
@AuraEnabled
public static List retrieveRecords(String objectName, String fieldAPINames, String filterFieldAPIName, String strInput){
List lstResult= new List();
List lstReturnResult = new List();
if(strInput.equals(‘*’)){
strInput = ”;
}
boolean userActive=true;
String str = strInput + ‘%’;
String recName = ‘Person Account’;
String strQueryField = ”;
List fieldList = fieldAPINames.split(‘,’);
//check if Id is already been passed
if(!fieldList.contains(‘Id’)){
fieldList.add(‘Id’);
strQueryField = String.join(fieldList, ‘,’);
}else {
strQueryField = fieldAPINames;
}
if(objectName==’Product__c’){
String strQuery = ‘SELECT ‘ + String.escapeSingleQuotes(strQueryField)
+ ‘ FROM ‘
+ String.escapeSingleQuotes(objectName)
+ ‘ WHERE recordtype.name=\’Sample\’ AND ‘ + filterFieldAPIName + ‘ LIKE \” + str + ‘%\”
+ ‘ ORDER BY ‘ + filterFieldAPIName
+ ‘ LIMIT 50’;
System.debug(‘strQuery=’ + strQuery);
lstResult = database.query(strQuery);
}
else{
String strQuery = ‘SELECT ‘ + String.escapeSingleQuotes(strQueryField)
+ ‘ FROM ‘
+ String.escapeSingleQuotes(objectName)
+ ‘ WHERE ‘ + filterFieldAPIName + ‘ LIKE \” + str + ‘%\”
+ ‘ ORDER BY ‘ + filterFieldAPIName
+ ‘ LIMIT 50’;
System.debug(‘strQuery=’ + strQuery);
lstResult = database.query(strQuery);
}
System.debug(‘lstResult ==== ‘ + lstResult);
System.debug(‘Before lstReturnResult === ‘ + lstReturnResult);
for (SObject record : lstResult) {
SObjectQueryResult resultItem = new SObjectQueryResult();
resultItem.recordId = String.valueOf(record.get(‘Id’));
resultItem.recordName = String.valueOf(record.get(filterFieldAPIName));
lstReturnResult.add(resultItem);
}
System.debug(‘After lstReturnResult === ‘ + lstReturnResult);
return lstReturnResult;
}
public class SObjectQueryResult {
@AuraEnabled
public String recordId;
@AuraEnabled
public String recordName;
}
}
//Please Check this code compare to your code , basically This code is all yours But there is missing block of code in your blog Apex code?
Thanks