Salesforce LWC: Multi-Record Creation using Lightning Web Component


In this blog we will learn about one very common business use case. Most of the time we get requirement to create a table kind of view from which we can see most of the record's information and also we can manage creating updating and deleting record from same view. 
So in this blog we will see how to create such kind of UI using Lightning Web Component. We will take one use case and will implement the same.

Use Case: 
There will be one input field for Account selection. Once user select any account then user can see all related Contacts. Also user should have capability to add multiple new contacts for selected Account and user can delete existing Contacts.

Solutions:
To implement above solution first we need one input field where user can select Account Name. For this we can utilize Lightning Data Service. Once User will select Account we can pass the Account Id to Apex Class for getting related Contacts and show it on UI using HTML table.
Lets see some code for the UI:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<template>
    <lightning-card  title="">
        <div class="slds-p-horizontal_small">
            <lightning-record-edit-form object-api-name="Contact">
                <lightning-layout multiple-rows>
                    <lightning-layout-item size="12" small-device-size="6" medium-device-size="4" large-device-size="6" padding="around-small">
                        <lightning-input-field field-name="AccountId" onchange={handleAccountId}></lightning-input-field>
                    </lightning-layout-item>
                </lightning-layout>
            </lightning-record-edit-form><br/>
           <template if:true={selectedAccount}>
                <div>
                    <table class="slds-table slds-table_cell-buffer slds-table_bordered">
                        <thead>
                        <tr class="slds-line-height_reset">
                            <th class="" scope="col">
                                <div class="slds-truncate" title="Select Contact">Select Contact</div>
                            </th>  
                            <th class="" scope="col">
                            <div class="slds-truncate" title="Contact First Name">Contact First Name</div>
                            </th>
                            <th class="" scope="col">
                            <div class="slds-truncate" title="Contact Last Name">Contact Last Name</div>
                            </th>
                            <th class="" scope="col">
                            <div class="slds-truncate" title="Account Name">Account Name</div>
                            </th>
                            <th class="" scope="col">
                                <div class="slds-truncate" title="Action">Action</div>
                            </th>
                        </tr>
                        </thead>
                        <tbody>
                            <template if:true={contactDataWrp}>
                                <template for:each={contactDataWrp} for:item="con" for:index="index">
                                    <tr class="slds-hint-parent" key={con.Id}>
                                        <td data-label="Select Contact">
                                            <div class="slds-truncate" title="Select Contact">
                                                <lightning-input type="checkbox" disabled={disabledCheckbox} name="input1"></lightning-input>
                                            </div>
                                        </td>
                                        <td data-label="Contact First Name">
                                            <div class="slds-truncate" title="First Name">{con.FirstName}</div>
                                        </td>
                                        <td data-label="Contact Last Name">
                                            <div class="slds-truncate" title="Last Name">{con.LastName}</div>
                                        </td>
                                        <td data-label="Account Name">
                                            <div class="slds-truncate" title="Account Name">{accountName}</div>
                                        </td>
                                        <td data-label="Action">
                                            <div class="slds-truncate" title="Action">
                                                <lightning-button label="Delete" variant="neutral" value={index} onclick={deleteRecord}></lightning-button>
                                            </div>
                                        </td>
                                    </tr>
                                </template>
                            </template>
                            <template if:true={blankRow}>
                                <template for:each={blankRow} for:item="con" for:index="ind">
                                    <tr class="slds-hint-parent" key={con.Id}>
                                        <td data-label="Select Contact">
                                            <div class="slds-truncate" title="Select Contact">
                                                <lightning-input type="checkbox" name={ind} value={con.isChecked} onchange={setCheckBox}></lightning-input>
                                            </div>
                                        </td>
                                        <td data-label="Contact First Name">
                                        <lightning-input type="text" value={con.FirstName} placeholder="Contact First Name" name={ind} onchange={setFirstName}></lightning-input>
                                        </td>
                                        <td data-label="Contact Last Name">
                                            <lightning-input type="text" value={con.LastName} placeholder="Contact Last Name" name={ind} onchange={setLastName}>></lightning-input>
                                        </td>
                                        <td data-label="Account Name">
                                            <lightning-record-view-form record-id={selectedAccount} object-api-name="Account">
                                                <lightning-output-field field-name="Name" variant="label-hidden"></lightning-output-field>
                                            </lightning-record-view-form>
                                        </td>
                                        <td data-label="Action">
                                            <lightning-button label="Remove" variant="neutral" value={ind} onclick={removeRow}></lightning-button>
                                        </td>
                                    </tr>
                                </template>
                            </template>
                        </tbody>
                    </table>
                </div>
            </template>
        </div>
        <div slot="footer">
            <template if:true={selectedAccount}>
                <lightning-button name="multipleRowSave" label="Save Conacts" class="slds-p-around_xx-small" variant="brand-outline" onclick={saveData}></lightning-button>
                <lightning-button name="multipleRowAddition" label="Add Row" class="slds-p-around_xx-small" variant="brand" onclick={addRow}></lightning-button>
                <lightning-button name="multipleRowRemoval" class="slds-p-around_xx-small" label="Remove Row" variant="destructive" onclick={removeRow}></lightning-button>
            </template>
        </div>
    </lightning-card> 
</template>

If you see above code, you will find it has two separate iteration over list of data. This is because first one is showing current Contacts of Account and second one is showing new Contacts which are not inserted yet into Salesforce.
Lets move to the Java Script Logic: 

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import { LightningElement, track, wire, api} from 'lwc';
import contactRecords from '@salesforce/apex/MultiRecordCreationHandler.getRelatedContacts';
import deleteContactHandler from '@salesforce/apex/MultiRecordCreationHandler.deleteContactHandler';
import insertContactData from '@salesforce/apex/MultiRecordCreationHandler.saveContactData';
import { getRecord } from 'lightning/uiRecordApi';
import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';

export default class MultiRecordCreation extends LightningElement {
    @track contactDataWrp;
    @track blankRow = [];
    @track disabledCheckbox = true;
    @track index = 0;
    @track selectedAccount;
    @track accountName;


    @wire(getRecord, { recordId: '$selectedAccount', fields: [ ACCOUNT_NAME_FIELD ] })
    savedRecordIdWire({error,data}) {
        if(data){
            this.accountName = data.fields.Name.value;
        }else if(error){
            window.alert(JSON.stringify(error));
        }
    }

    handleAccountId(event){
        let accountId = event.detail.value[0];
        if(accountId !== undefined){
            this.selectedAccount = accountId;
            this.contactDataWrp = [];
           contactRecords({accId : accountId}).then(result => {
                this.contactDataWrp = result;
                this.index = result.length;
            }).catch(error => {
                console.log(error);
            })
        }else{
           this.blankRow = []; 
           this.index = 0;
           this.contactDataWrp = [];
        }
    }

    deleteRecord(event){
        const selectedContact = this.contactDataWrp[event.target.value];
        window.alert(JSON.stringify(this.contactDataWrp) + ' & ' + event.target.value + ' & ' + JSON.stringify(selectedContact));
        deleteContactHandler({conId: selectedContact.Id, accId: selectedContact.AccountId}).then(result => {
            this.contactDataWrp = result;
        }).catch(error => {
            window.alert(JSON.stringify(error));
        })
    }

    addRow(event){
        this.index++;
        let i = this.index;
        let newContact = new Object();
        let blankRow = this.blankRow;
        newContact.Id = i;
        newContact.isChecked = false;
        blankRow.push(newContact);
        this.blankRow = blankRow; 
    }

    removeRow(event){
        const eventName = event.target.name;
        let blankRow = this.blankRow;
        if(eventName === 'multipleRowRemoval'){
            for(let i = 0; i < blankRow.length; i++){
                if(blankRow[i].isChecked){
                    blankRow.splice(i, 1);
                    i--;
                }
            }
        }else{
            blankRow.splice(event.target.value, 1);
        }
        this.blankRow = blankRow;
    }

    setFirstName(event){
        const eventName = event.target.name;
        let blankRow = this.blankRow;
        blankRow[eventName].FirstName = event.target.value;
        this.blankRow = blankRow;
    }

    setLastName(event){
        const eventName = event.target.name;
        let blankRow = this.blankRow;
        blankRow[eventName].LastName = event.target.value;
        this.blankRow = blankRow;
    }

    saveData(event){
        let blankRow = this.blankRow;
        let contactDataList = [];
        for(let i = 0; i < blankRow.length; i++){
            if(blankRow[i] !== undefined && blankRow[i].isChecked){
                let conData = new Object();
                conData.AccountId = this.selectedAccount;
                conData.FirstName = blankRow[i].FirstName;
                conData.LastName = blankRow[i].LastName;
                contactDataList.push(conData);
            }
        }
        if(contactDataList.length > 0){
            insertContactData({contactDataString: JSON.stringify(contactDataList)}).then(result => {
                let newContactList = this.contactDataWrp;
                for(let i = 0; i < result.length; i++){
                    if(result[i] !== undefined){
                        let contactRecord = {'sobjectType' : 'Contact'};
                        contactRecord.Id = result[i].Id;
                        contactRecord.FirstName = result[i].FirstName;
                        contactRecord.LastName = result[i].LastName;
                        contactRecord.AccountId = this.selectedAccount;
                        newContactList.push(contactRecord);
                    }
                }
                this.contactDataWrp = newContactList;
                this.blankRow = []; 
                this.index = newContactList.length;
            }).catch(error => {
                window.alert('Please contact system admin: ' + JSON.stringify(error));
            })
        }else{
            window.alert('Please select any row to insert data.');
        }
    }

    setCheckBox(event){
        let blankrow = this.blankRow;
        if(blankrow[event.target.name].isChecked){
            blankrow[event.target.name].isChecked = false;
        }else{
            blankrow[event.target.name].isChecked = true;
        }
        this.blankRow = blankrow;
    }
}

Let us talk about each method. 
  1. wire Service: The purpose of wire service is to get selected Account information.
  2. handleAccountId: In this method once we select any Account then it will make server side call and get all related Contacts.
  3. deleteRecord: This method will delete selected record.
  4. addRow & removeRow: These two methods are used to add/remove new row for new contact creation.
  5. saveData: This method is used for save new records and after saving it will be part of existing contact records for selected Account.
  6. setCheckBox: This checkbox method is used for selecting multiple rows for performing actions.
Apex Class Logic: 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public with sharing class MultiRecordCreationHandler {
    @AuraEnabled
    public static List<Contact> getRelatedContacts(Id accId){
        return [Select Id, FirstName, LastName, Account.Name from Contact where AccountId = :accId];
    }

    @AuraEnabled
    public static List<Contact> deleteContactHandler(Id conId, Id accId){
        Database.delete(conId);
        return [Select Id, FirstName, LastName, AccountId, Account.Name from Contact where AccountId = :accId];
    }

    @AuraEnabled
    public static List<Contact> saveContactData(String contactDataString){
        List<Contact> contactList = (List<Contact>)System.JSON.deserializeStrict(contactDataString, List<Contact>.Class);
        Database.insert(contactList);
        return contactList;
    }
}

Demo: 



Code Reference:




References:


Other Useful blogs:
  • https://www.sfprompt.com/
  • https://manish-porwal.blogspot.com/2019/11/draw-chart-in-lwc-using-chartjs.html

Comments

Followers

Popular posts from this blog

Salesforce LWC: Basic Drag and Drop Functionality

Salesforce Lightning Web Component: Dynamic Column Selection in Lightning Datatable