Reusable Custom LWC Multi-Select Lookup

by Rijwan Mohmmed
reusable-custom-lwc-multi-select-lookup

Hello folks, today we will discuss creating Reusable Custom LWC Multi-Select Lookup in Salesforce. We all know that single-select lookup is available in Salesforce but multi-select lookup not. So We will make this by custom code and use this.

Also, check this: Verify Phone HTTP Request in LWC Salesforce

reusable-custom-lwc-multi-select-lookup-output

Key Highlights :

  1. Multi Record Section (multi-select lookup).
  2. Prevent duplicate record selection.
  3. Reusable.
  4. Can query dynamic fields.
  5. Pills will be shown of selected records.

Code :

First, we create an Apex class where we search for the object records.

ReusableMultiSelectLookupCtrl.cls :

public class ReusableMultiSelectLookupCtrl {
    @AuraEnabled(cacheable=true)
    public static List<sObject> retriveSearchData(String ObjectName, String fieldName, String value, List<String> selectedRecId) {
        List<sObject> sObjectResultList = new List<sObject>();
        if(selectedRecId == null)
            selectedRecId = new List<String>();

        if(String.isNotEmpty(value)) {
            String query = 'Select '+fieldName+' FROM '+ObjectName+' WHERE Name LIKE \'%' + value.trim() + '%\' and ID NOT IN: selectedRecId order by createdDate DESC LIMIT 5';
            for(sObject so : Database.Query(query)) {
                sObjectResultList.add(so);
            }
        }
        return sObjectResultList;
    }
}

Now our next part is to create a Reusable LWC Custom multiselect lookup so we can use this in parent

reusableMultiSelectLookup.HTML :

<template>
	<div class="slds-form-element" onmouseleave={toggleResult} data-source="lookupContainer">
		<div class="slds-combobox_container slds-has-selection">
			<label class="slds-form-element__label" for="combobox-id-1">{label}</label>
			<div class="lookupInputContainer slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click"
				aria-expanded="false" aria-haspopup="listbox" role="combobox">
				<!--------------- lookup field ---------------->
				<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_left-right" role="none">
					<div class="searchBoxWrapper">
						<lightning-input type="search" data-id="userinput" name="searchText"
							is-loading={isSearchLoading} value={searchKey} variant="label-hidden"
							placeholder={placeholder} onchange={handleKeyChange} onclick={toggleResult} data-source="searchInputField"></lightning-input>
					</div>
				</div>
				<!--------------- lookup field ---------------->
				<!-- lookup Selected record pill container start-->
				<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={selectedRecords} for:item="serecord">
						<span key={serecord.Id}>
                        <lightning-pill label={serecord.Name} name={serecord.Id} onremove={removeRecord}>
                                <lightning-icon icon-name={iconName} variant="circle" alternative-text={serecord.Name}></lightning-icon>
                            </lightning-pill>
                        </span>
					</template>
				</div>
				<!------ /lookup Selected record pill container end ----------->
				<!------ lookup search result part start------------>
				<div style="margin-top:0px" id="listbox-id-1"
					class="slds-dropdown slds-dropdown_length-with-icon-7 slds-dropdown_fluid" role="listbox">
					<ul class="slds-listbox slds-listbox_vertical" role="presentation">
						<template for:each={searchRecords} for:item="obj">
							<li key={obj.Id} role="presentation" class="slds-listbox__item">
								<div data-id={obj.Id} data-name={obj.Name} onclick={setSelectedRecord}
									class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta"
									role="option">
									<span style="pointer-events: none;" class="slds-media__figure slds-listbox__option-icon" >
                                        <span class="slds-icon_container" >
                                            <lightning-icon icon-name={iconName} size="small" alternative-text="icon" ></lightning-icon>  
                                        </span>
									</span>
									<span style="pointer-events: none;" class="slds-media__body" >
                                        <span  class="slds-listbox__option-text slds-listbox__option-text_entity">{obj.Name}</span>
									<span class="slds-listbox__option-meta slds-listbox__option-meta_entity">Account • {obj.Name}</span>
									</span>
								</div>
							</li>
						</template>
						<!-- if there is no records..-->
						<template if:true={messageFlag}>
							<li class="slds-listbox__item" style="text-align: center; font-weight: bold;">No Records
								Found...</li>
						</template>
					</ul>
				</div>
				<!------ /lookup search result part start------------>
			</div>
		</div>
	</div>
</template>

reusableMultiSelectLookup.JS :

import { LightningElement, api, track } from 'lwc';
import retriveSearchData from '@salesforce/apex/ReusableMultiSelectLookupCtrl.retriveSearchData';

export default class ReusableMultiSelectLookup extends LightningElement {
    @api objectname = 'Account';
    @api fieldnames = ' Id, Name ';
    @api Label;
    @track searchRecords = [];
    @track selectedRecords = [];
    @api iconName = 'standard:account'
    @track messageFlag = false;
    @track isSearchLoading = false;
    @api placeholder = 'Search..';
    @track searchKey;
    delayTimeout;

    searchField() {
        var selectedRecordIds = [];

        this.selectedRecords.forEach(ele=>{
            selectedRecordIds.push(ele.Id);
        })

        retriveSearchData({ ObjectName: this.objectname, fieldName: this.fieldnames, value: this.searchKey, selectedRecId: selectedRecordIds })
            .then(result => {
                this.searchRecords = result;
                this.isSearchLoading = false;
                const lookupInputContainer = this.template.querySelector('.lookupInputContainer');
                const clsList = lookupInputContainer.classList;
                clsList.add('slds-is-open');

                if (this.searchKey.length > 0 && result.length == 0) {
                    this.messageFlag = true;
                } else {
                    this.messageFlag = false;
                }
            }).catch(error => {
                console.log(error);
            });
    }

    // update searchKey property on input field change  
    handleKeyChange(event) {
        // Do not update the reactive property as long as this function is
        this.isSearchLoading = true;
        window.clearTimeout(this.delayTimeout);
        const searchKey = event.target.value;
        this.delayTimeout = setTimeout(() => {
            this.searchKey = searchKey;
            this.searchField();
        }, 300);
    }

    // method to toggle lookup result section on UI 
    toggleResult(event) {
        const lookupInputContainer = this.template.querySelector('.lookupInputContainer');
        const clsList = lookupInputContainer.classList;
        const whichEvent = event.target.getAttribute('data-source');

        switch (whichEvent) {
            case 'searchInputField':
                clsList.add('slds-is-open');
                this.searchField();
                break;
            case 'lookupContainer':
                clsList.remove('slds-is-open');
                break;
        }
    }

    setSelectedRecord(event) {
        var recId = event.target.dataset.id;
        var selectName = event.currentTarget.dataset.name;
        let newsObject = this.searchRecords.find(data => data.Id === recId);
        this.selectedRecords.push(newsObject);
        this.template.querySelector('.lookupInputContainer').classList.remove('slds-is-open');
        let selRecords = this.selectedRecords;
        this.template.querySelectorAll('lightning-input').forEach(each => {
            each.value = '';
        });

        console.log(this.selectedRecords);
        const selectedEvent = new CustomEvent('selected', { detail: { selRecords }, });
        // Dispatches the event.
        this.dispatchEvent(selectedEvent);
    }

    removeRecord(event) {
        let selectRecId = [];
        for (let i = 0; i < this.selectedRecords.length; i++) {
            console.log('event.detail.name--------->' + event.detail.name);
            console.log('this.selectedRecords[i].Id--------->' + this.selectedRecords[i].Id);
            if (event.detail.name !== this.selectedRecords[i].Id)
                selectRecId.push(this.selectedRecords[i]);
        }

        this.selectedRecords = [...selectRecId];
        console.log('this.selectedRecords----------->' + this.selectedRecords);
        let selRecords = this.selectedRecords;
        const selectedEvent = new CustomEvent('selected', { detail: { selRecords }, });
        // Dispatches the event.
        this.dispatchEvent(selectedEvent);
    }
}

Now we can use the above LWC component in our parent component and send dynamic parameters

LWCMultiSelectLookup.HTML :

<template>
	<!--------card header ----------------->
	<div class="slds-tabs_card">
		<div class="slds-page-header">
			<div class="slds-page-header__row">
				<div class="slds-page-header__col-title">
					<div class="slds-media">
						<div class="slds-media__figure">
							<span class="slds-icon_container slds-icon-standard-opportunity">
								 <lightning-icon icon-name="standard:recipe" alternative-text="recipe" title="recipe"></lightning-icon>
                            </span>
						</div>
						<div class="slds-media__body">
							<div class="slds-page-header__name">
								<div class="slds-page-header__name-title">
									<h1>
										<span>Reusable Custom LWC Multi Select Lookup</span>
										<span class="slds-page-header__title slds-truncate" title="Recently Viewed">TechDicer</span>
									</h1>
								</div>
							</div>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div> <br/>
	<!--------/card header end ----------------->
    <!------ lookup card ------------------------->
	<lightning-card variant="Narrow" title="Reusable Custom LWC Multi Select Lookup" icon-name="standard:apps">
		<div class="slds-p-horizontal_small">
			<c-reusable-multi-select-lookup objectname="Account" fieldnames=" Id, Name, Type, AccountNumber "
				onselected={handleselectedCompanyRecords} icon-name="standard:account" placeholder="Lookup Company...">
			</c-reusable-multi-select-lookup>

			<template if:true={selectedRecordsLength}>
				<div class="slds-m-top_medium">
					<lightning-datatable key-field="id" data={selectedRecords} hide-checkbox-column columns={columns}>
					</lightning-datatable>
				</div>
			</template>
		</div>
	</lightning-card>
    <!------ /lookup card ------------------------->
</template>

LWCMultiSelectLookup.JS:

import { LightningElement, track } from 'lwc';

const columns = [
    { label: 'Name', fieldName: 'Name' },
    { label: 'Type', fieldName: 'Type', type: 'text' },
    { label: 'Account Number', fieldName: 'AccountNumber'}
];

export default class LWCMultiSelectLookup extends LightningElement {
    @track selectedRecords = [];
    columns = columns;
    @track selectedRecordsLength;

    handleselectedCompanyRecords(event) {
        this.selectedRecords = [...event.detail.selRecords]
        this.selectedRecordsLength = this.selectedRecords.length;
    }
}

Output :

reusable-custom-lwc-multi-select-lookup-output
reusables-custom-lwc-multi-select-lookup-output

Reference :

  1. Create Reusable Custom Lookup in (LWC) Salesforce
What’s your Reaction?
+1
2
+1
0
+1
0
+1
1
+1
2
+1
0

You may also like

Leave a Comment