April 14, 2021

lightning-dual-listbox/lightning combo multi select using LWC in Salesforce

Sample Code:

Apex Class:

public with sharing class OpportunityContactRoleController {
    
    @AuraEnabled(cacheable=true)
    public static OpportunityContactRoleWrapper fetchOpptyCons( String strRecordId ) {

        OpportunityContactRoleWrapper objWrap = new OpportunityContactRoleWrapper();
        List < ContactWrapper > availableCons = new List < ContactWrapper >();
        Set < String > selectedCons = new Set < String >();

        try {

            Opportunity oppty = [ SELECT Id, AccountId FROM Opportunity WHERE Id =: strRecordId ];
            Set < Id > setConIds = new Set < Id >();

            for ( OpportunityContactRole objOCR : [ SELECT Id, ContactId, Contact.Name FROM OpportunityContactRole WHERE OpportunityId =: strRecordId ] ) {

                ContactWrapper objCW = new ContactWrapper();
                objCW.value = objOCR.ContactId;
                objCW.label = objOCR.Contact.Name;
                availableCons.add( objCW );
                selectedCons.add( objOCR.ContactId );
                setConIds.add( objOCR.ContactId );

            }

            for ( Contact objCon : [ SELECT Id, Name FROM Contact WHERE AccountId =: oppty.AccountId ] ) {

                if ( !setConIds.contains( objCon.Id ) ) {

                    ContactWrapper objCW = new ContactWrapper();
                    objCW.value = objCon.Id;
                    objCW.label = objCon.Name;
                    availableCons.add( objCW );

                }

            }
            
        } catch ( Exception e ) {
            throw new AuraHandledException(e.getMessage());
        }
        
        objWrap.selectedCons = new List < String > ( selectedCons );
        objWrap.availableCons = availableCons;
        return objWrap;

    }

    @AuraEnabled
    public static String addRemoveOppCons( String strRecordId, List < String > selectedCons, List < String > updatedCons ) {

        try {

            Map < Id, Id > mapConIdOCRId = new Map < Id, Id >();
            List < OpportunityContactRole > listInsertOCR = new List < OpportunityContactRole >();
            Set < Id > setOCRIds = new Set < Id >();

            for ( OpportunityContactRole objOCR : [ SELECT Id, ContactId FROM OpportunityContactRole WHERE OpportunityId =: strRecordId ] ) {

                mapConIdOCRId.put( objOCR.ContactId, objOCR.Id );

            }

            for ( String strConId : updatedCons ) {

                if ( !selectedCons.contains( strConId ) ) {

                    listInsertOCR.add( new OpportunityContactRole( ContactId = strConId, OpportunityId = strRecordId ) );

                }

            }

            for ( String strConId : selectedCons ) {

                if ( !updatedCons.contains( strConId ) )
                    setOCRIds.add( mapConIdOCRId.get( strConId ) );

            }
            
            if ( listInsertOCR.size() > 0 ) {
             
                insert listInsertOCR;

            }

            if ( setOCRIds.size() > 0 ) {

                Database.delete( new List < Id > ( setOCRIds ), false );

            }

            return 'Successful';
            
        } catch (Exception e) {
            throw new AuraHandledException( e.getMessage() );
        }

    }

    public class OpportunityContactRoleWrapper {

        @AuraEnabled
        public List < ContactWrapper > availableCons;
        @AuraEnabled
        public List < String > selectedCons;

    }

    public class ContactWrapper {

        @AuraEnabled
        public String value;
        @AuraEnabled
        public String label;

    }

}

HTML:
<template>
    <lightning-card>
        <lightning-dual-listbox
            class="slds-box"
            name="OppCons"
            label="Select/Remove Contacts"
            source-label="Available Contact(s)"
            selected-label="Selected Contact(s)"
            options={availableCons}
            value={selectedCons}
            onchange={handleChange}>
        </lightning-dual-listbox>
        <p slot="footer">
            <lightning-button
                variant="brand"
                label="Save"
                onclick={saveChanges}            
                disabled={diableBool}>
            </lightning-button>
        </p>
    </lightning-card>        
</template>

JavaScript:
import { LightningElement, api, wire } from 'lwc';
import fetchOpptyCons from '@salesforce/apex/OpportunityContactRoleController.fetchOpptyCons';
import addRemoveOppCons from '@salesforce/apex/OpportunityContactRoleController.addRemoveOppCons';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class OpportunityContactRole extends LightningElement {

    @api recordId;
    availableCons;
    selectedCons;
    updatedCons;
    diableBool = true;

    @wire( fetchOpptyCons, { strRecordId: '$recordId' })  
    wiredRecs( { error, data } ) {

        if ( data ) {

            console.log( 'Records are ' + JSON.stringify( data ) );
            this.availableCons = data.availableCons;
            this.selectedCons = data.selectedCons;

        } else if ( error ) {

            console.log( 'Error ' + JSON.stringify( error ) );

        }
        
    }

    handleChange( event ) {

        this.diableBool = false;        
        const selectedOptionsList = event.detail.value;
        console.log( 'Selected Options are ' + JSON.stringify( selectedOptionsList ) );
        this.updatedCons = selectedOptionsList;

    }

    saveChanges() {

        this.diableBool = true;
        addRemoveOppCons( { strRecordId : this.recordId, selectedCons : this.selectedCons, updatedCons : this.updatedCons } )
            .then( result => {

                console.log( 'Result ' + JSON.stringify( result ) );
                let message;
                let variant;

                if ( result === 'Successful' ) {

                    message = 'Successfully Processed!';
                    variant = 'success';

                } else {

                    message = 'Some error occured. Please reach out to your Admin';
                    variant = 'error';
                    
                }

                const toastEvent = new ShowToastEvent({
                    title: 'Opportunity Contact Add/Remove',
                    message: message,
                    variant: variant
                });
                this.dispatchEvent( toastEvent );

            } )
            .catch( error => {
                console.log( 'Error ' + JSON.stringify( error ) );
            } );
        this.selectedCons = this.updatedCons;
            
    }
    
}

js-meta.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>51.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>

To test, add the LWC to the Opportunity Lightning Record Page.
 

 

4 comments:

  1. Hello, what do you mean with the abbreviation Cons?

    ReplyDelete
    Replies
    1. For contacts, I have used Cons. You can use any variable name.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. You have to pass as wrapper. Dual List Box supports label and value.

      Delete