Lightning Web Components: Multi Select Custom Lookup

Lightning Web Components (LWC) is current Trending technology in Salesforce Lightning Eco System. I have shared many post related to LWC which you can check to quickly get the idea. Today I will share Multi Select Custom Lookup in LWC.

Multi Select Custom lookup in lightning Web Component LWC

Previously I have shared Custom Lookup in Lightning web Component but in that we can only select one record, Sometimes there is requirement where we need to aloow users to select multiple records. We don’t have any standard component available for this functionality so I have created below component. As this is generic component so you can use it with any object.

Now we will check the code part.

lwcMultiLookup.html

<template>
    <lightning-card>
        <h3 slot="title">
            <lightning-icon icon-name="utility:connected_apps" size="small"></lightning-icon>
            Multi Select Custom Lookup in Lightning Web Components
        </h3>
        <div slot="footer">
                
        </div>
        <div>
            <div class="slds-form-element">
                
                <div class="slds-form-element__control">
                    <div class="slds-combobox_container">
                        
                        <div class={txtclassname} data-id="resultBox" aria-expanded="false" aria-haspopup="listbox" role="combobox">
                            <div class="slds-form-element__control slds-input-has-icon slds-input-has-icon slds-input-has-icon_left-right" role="none">
                                
                                <div>
                                    <span class="slds-icon_container slds-icon-utility-search slds-input__icon iconheight">
                                        <lightning-icon class="slds-icon slds-icon slds-icon_small slds-icon-text-default" icon-name={iconName} size="x-small" alternative-text="icon" ></lightning-icon>
                                    </span> 
                                </div>
                                <lightning-input required={required} data-id="userinput" label={Label} name="searchText" onchange={searchField} class="leftspace"></lightning-input>
                                <span class="slds-icon_container slds-icon-utility-search slds-input__icon slds-input__icon_right iconheight">
                                    <lightning-icon class="slds-icon slds-icon slds-icon_small slds-icon-text-default" icon-name="utility:search" size="x-small" alternative-text="icon" ></lightning-icon>
                                </span>
                            </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={selectedRecords} for:item="serecord">
                                    <span key={serecord.recId}>
                                    <lightning-pill label={serecord.recName} name={serecord.recId} onremove={removeRecord}>
                                            <lightning-icon icon-name={iconName} variant="circle" alternative-text={serecord.recName}></lightning-icon>
                                        </lightning-pill>
                                    </span>
                                </template>
                            </div>
                        
                            <!-- Second part display result -->
                            <div 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="serecord">
                                        <li role="presentation" class="slds-listbox__item" key={serecord.recId}>
                                            
                                            <div data-id={serecord.recId} data-name={serecord.recName} onclick={setSelectedRecord} class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta" role="option">
                                                <span class="slds-media__figure">
                                                    <span class="slds-icon_container slds-icon-standard-account">
                                                        <lightning-icon icon-name={iconName} class="slds-icon slds-icon slds-icon_small slds-icon-text-default" size="x-small"></lightning-icon>
                                                    </span>
                                                </span>
                                                <span class="slds-media__body">
                                                    <span class="slds-listbox__option-text slds-listbox__option-text_entity">{serecord.recName}</span>
                                                    <span class="slds-listbox__option-meta slds-listbox__option-meta_entity">{objectName} • {serecord.recName}</span>
                                                </span>
                                            </div>
                                        </li>
                                    </template>
                                </ul>
                            </div>
                            <div if:true={messageFlag}>
                                No result found.
                            </div>
                            <div if:true={LoadingText}>
                                Loading...
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </lightning-card>
</template>

Here we are displaying selected records on UI and also allowing users to select next record. User can easily cick on cancel icon to remove select records.

lwcMultiLookup.js

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

export default class LwcMultiLookup extends LightningElement {
    @api objectName = 'Account';
    @api fieldName = 'Name';
    @api Label;
    @track searchRecords = [];
    @track selectedRecords = [];
    @api required = false;
    @api iconName = 'action:new_account'
    @api LoadingText = false;
    @track txtclassname = 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click';
    @track messageFlag = false;
 
    searchField(event) {

        var currentText = event.target.value;
        var selectRecId = [];
        for(let i = 0; i < this.selectedRecords.length; i++){
            selectRecId.push(this.selectedRecords[i].recId);
        }
        this.LoadingText = true;
        getResults({ ObjectName: this.objectName, fieldName: this.fieldName, value: currentText, selectedRecId : selectRecId })
        .then(result => {
            this.searchRecords= result;
            this.LoadingText = false;
            
            this.txtclassname =  result.length > 0 ? 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open' : 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click';
            if(currentText.length > 0 && result.length == 0) {
                this.messageFlag = true;
            }
            else {
                this.messageFlag = false;
            }

            if(this.selectRecordId != null && this.selectRecordId.length > 0) {
                this.iconFlag = false;
                this.clearIconFlag = true;
            }
            else {
                this.iconFlag = true;
                this.clearIconFlag = false;
            }
        })
        .catch(error => {
            console.log('-------error-------------'+error);
            console.log(error);
        });
        
    }
    
   setSelectedRecord(event) {
        var recId = event.currentTarget.dataset.id;
        var selectName = event.currentTarget.dataset.name;
        let newsObject = { 'recId' : recId ,'recName' : selectName };
        this.selectedRecords.push(newsObject);
        this.txtclassname =  'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click';
        let selRecords = this.selectedRecords;
		this.template.querySelectorAll('lightning-input').forEach(each => {
            each.value = '';
        });
        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++){
            if(event.detail.name !== this.selectedRecords[i].recId)
                selectRecId.push(this.selectedRecords[i]);
        }
        this.selectedRecords = [...selectRecId];
        let selRecords = this.selectedRecords;
        const selectedEvent = new CustomEvent('selected', { detail: {selRecords}, });
        // Dispatches the event.
        this.dispatchEvent(selectedEvent);
    }
}

In Controller SearchField we are passing the current user txet in controller with the already selected record ids. We are passing existing record ids so that we can filter those records. Once user select any record from dropdown we fire the setSelectedRecord method and add the selected record in selectedRecords array. We are also sending an event to pass selected records to parent components. Once user click on remove icon we simply remove that reord from the array.

lwcMultiLookupController.cls

public with sharing class lwcMultiLookupController {
    public lwcMultiLookupController() {

    }
    @AuraEnabled(cacheable=true)
    public static List<SObJectResult> getResults(String ObjectName, String fieldName, String value, List<String> selectedRecId) {
        List<SObJectResult> sObjectResultList = new List<SObJectResult>();
        system.debug(fieldName+'-------------'+ObjectName+'---++----------'+value+'====='+selectedRecId);
        if(selectedRecId == null)
            selectedRecId = new List<String>();

        if(String.isNotEmpty(value)) {
            String query = 'Select Id,'+fieldName+' FROM '+ObjectName+' WHERE '+fieldName+' LIKE \'%' + value.trim() + '%\' and ID NOT IN: selectedRecId';
            system.debug(query);
            for(sObject so : Database.Query(query)) {
                String fieldvalue = (String)so.get(fieldName);
                sObjectResultList.add(new SObjectResult(fieldvalue, so.Id));
            }
        }
        return sObjectResultList;
    }
    
    public class SObjectResult {
        @AuraEnabled
        public String recName;
        @AuraEnabled
        public Id recId;
        
        public SObJectResult(String recNameTemp, Id recIdTemp) {
            recName = recNameTemp;
            recId = recIdTemp;
        }
        public SObJectResult() {
          
        }
    }
}

This is a simple apex controller we are making dynamic SOQL to get records based on user input and sending back to lwccontroller. We are using wrapper to pass data. Wrapper allow us to easily handle the data with any object.
We have created one custom app in lightning, using this app we are passing data from LWC to Lightning component. We have only created this app for demo purpose. You can get the selected records and can use in your existing components.

lwcMultiLookupDemo.app

<aura:attribute name="selectedRecords" type="lwcMultiLookupController.SObjectResult[]" description="text" ></aura:attribute>
<lightning:card title="">
    <c:lwcMultiLookup objectName="Account" fieldName="Name" 
                      iconName = "standard:account" onselected="{!c.selectedRecords}"/>

    <div class="slds-page-header">
        <div class="slds-media">
            <div class="slds-media__figure">
                <span class="slds-icon_container slds-icon-standard-opportunity" title="Account Record">
                </span>
            </div>
            <div class="slds-media__body">
                <h1 class="slds-page-header__title slds-truncate slds-align-middle" title="Account Record">Accounts</h1>
                <p class="slds-text-body_small slds-line-height_reset"></p>
            </div>
        </div>
    </div>
    <table class="slds-table slds-table_bordered slds-table_cell-buffer">
        <thead>
            <tr class="slds-text-title_caps">
                <th scope="col">
                    <div class="slds-truncate" title="Account Id">Account Id</div>
                </th>
                <th scope="col">
                    <div class="slds-truncate" title="Account Name">Account Name</div>
                </th>
            </tr>
        </thead>
        <tbody>
            <aura:iteration var="selectRec" items="{!v.selectedRecords}">
                <tr>
                    <th scope="row" data-label="Account Id">
                        <div class="slds-truncate" title="{!selectRec.recId}"><a id="{!selectRec.recId}">{!selectRec.recId}</a></div>
                    </th>
                    <td data-label="Account Name">
                        <div class="slds-truncate" title="{!selectRec.recName}">{!selectRec.recName}</div>
                    </td>
                </tr>
            </aura:iteration>
        </tbody>
    </table>
</lightning:card>

lwcMultiLokkupDemo.js

({
    selectedRecords : function(component, event, helper) {
        var selectRecName = event.getParam('selRecords');
        if(selectRecName != undefined) {
            component.set("v.selectedRecords", selectRecName);
        }
    }
})

Here I have share complete code for Multi Select Custom Lookup LWC. Code is pretty simple and I have just modify the Lookup code. Now we are filtering the selected records in SOQL and passing a list of array in parent Lightning Component.

In this post we have also covered how to use wrapper in Lightning or Lightning Web Components.

Did you liked this post or have any suggestions, let me know in comments. Happy programming 🙂

Advertisements

6 thoughts on “Lightning Web Components: Multi Select Custom Lookup

  1. Thanks Tushar, I have recently found out your blog and I am very pleased to say that it is one of the best sf blog. I really appreciate your knowledge sharing. Keep rocking bro !!

  2. Hi, wanted to understand where is the code to save the Account on the parent object. we are selecting multiple records but where are we saving these. also if i wanted this on the related list functionality, where users can select mutiple records and selected records should be the child records to the parent. For Example on Campaign object I want two related list and both for contact, lets say contact1 and contact2 and these shuld not allow user to create new records they must be able to multiselect contact and save them as related records to campaign.

  3. I love this. How much more of an effort would it take to select a range ( click 1 item then shift+click several items below to select the range)?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.