Add/Remove Rows in Data table in LWC Salesforce

by Rijwan Mohmmed
how-to-add/remove-rows-in-data-table-in-lightning-web-component(lwc)-salesforce

Hello, Friends today we will learn to add new rows /remove and perform DML operations. Add/Remove Rows in Data table in LWC Salesforce

Key Highlights

  1. We create a LWC quick action on Account record
  2. Popup will display any existing contacts
  3. Ability to add new rows to add new contacts
  4. Ability to update existing contacts
  5. Ability to delete contacts (A delete icon in front of every row)
  6. Clicking on save button will invoke an apex method to Create, Update and Delete contacts in a single operation.
  7.  Display spinner on save operation
  8. Error handling

We will add/remove Rows in the Data table in LWC Salesforce in this video.

Check out this: Expand/Collapse Section in LWC

Lets start our code

AccountContactTable.Html :

<template>
    <lightning-quick-action-panel header="Manage Contacts">
        <div class="slds-p-around_none slds-m-top_x-small slds-m-bottom_medium slds-m-horizontal_none modalBodyy">
             <template if:true={isLoading}>
                <lightning-spinner alternative-text="Loading" size="medium" class="spinnerClass"></lightning-spinner>
             </template>
             <lightning-card>
                 <lightning-button label="Add Row" slot="actions" icon-name="utility:add" onclick={addRow}></lightning-button>
                <div class="slds-m-around_medium">
                    <table class="slds-table slds-table_cell-buffer slds-table_bordered" aria-labelledby="element-with-table-label other-element-with-table-label">
                        <thead>
                            <tr class="slds-line-height_reset">
                                <th class="" scope="col">
                                    <div class="slds-truncate" title="First Name">First Name</div>
                                </th>
                                <th class="" scope="col">
                                    <div class="slds-truncate" title="Last Name">Last Name</div>
                                </th>
                                <th class="" scope="col">
                                    <div class="slds-truncate" title="Email">Email</div>
                                </th>
                                <th class="" scope="col">
                                    <div class="slds-truncate" title="Stage">Action</div>
                                </th>
                            </tr>
                        </thead>
                        <tbody>
                            <template for:each={records} for:item="obj">
                                <tr class="inputRows" key={obj.Id}>
                                    <th data-label="Opportunity Name" scope="row">
                                        <lightning-input type="text" class="fields" variant="label-hidden" label="First Name" name="FirstName" value={obj.FirstName} data-id={obj.Id} onchange={updateValues}></lightning-input>
                                    </th>
                                    <td data-label="Account Name">
                                        <lightning-input type="text" class="fields" variant="label-hidden" label="Last Name" name="LastName" value={obj.LastName} data-id={obj.Id} onchange={updateValues}></lightning-input>
                                    </td>
                                    <td data-label="Close Date">
                                       <lightning-input type="text" class="fields" variant="label-hidden" label="Email" name="Email" value={obj.Email} data-id={obj.Id} onchange={updateValues}></lightning-input>
                                    </td>
                                    <td data-label="Prospecting">
                                        <lightning-button-icon icon-name="action:delete"  alternative-text="Delete" title="Delete" data-id={obj.Id} onclick={handleDeleteAction}></lightning-button-icon>
                                    </td>
                                </tr>
                            </template>
                        </tbody>
                    </table>
                </div>
            </lightning-card>
        </div>
        <div slot="footer">
            <lightning-button variant="neutral" label="Cancel" onclick={closeAction}></lightning-button> &nbsp;
            <lightning-button variant="brand" label="Save" onclick={handleSaveAction} disabled={isDisable}></lightning-button>
        </div>
    </lightning-quick-action-panel>
</template>

AccountContactTable.Js :

import { LightningElement, api, track, wire } from 'lwc';
import { CloseActionScreenEvent } from 'lightning/actions';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'
import fetchContacts from '@salesforce/apex/ContactController.fetchContacts';
import dmlOnContacts from '@salesforce/apex/ContactController.dmlOnContacts';
import { refreshApex } from '@salesforce/apex';

export default class AccountContactTable extends LightningElement {
    @api recordId;
    @track isLoading = true;
    @track records;
    wiredRecords;
    error;
    @track deleteConatctIds = '';

    //to close quick action
    closeAction(){
        this.dispatchEvent(new CloseActionScreenEvent());
    }

    //to add row
    addRow() {
        let randomId = Math.random() * 16;
        let myNewElement = {Email: "", FirstName: "", Id: randomId, LastName: "", AccountId: this.recordId};
        this.records = [...this.records, myNewElement];
    }

    get isDisable(){
        return (this.isLoading || (this.wiredRecords.data.length == 0 && this.records.length == 0));
    }

    //show/hide spinner
    handleIsLoading(isLoading) {
        this.isLoading = isLoading;
    }

    //update table row values in list
    updateValues(event){
        var foundelement = this.records.find(ele => ele.Id == event.target.dataset.id);
        if(event.target.name === 'FirstName'){
            foundelement.FirstName = event.target.value;
        } else if(event.target.name === 'LastName'){
            foundelement.LastName = event.target.value;
        } else if(event.target.name === 'Email'){
            foundelement.Email = event.target.value;
        }
    }

    //handle save and process dml 
    handleSaveAction(){
        this.handleIsLoading(true);

        if(this.deleteConatctIds !== ''){
            this.deleteConatctIds = this.deleteConatctIds.substring(1);
        }

        this.records.forEach(res =>{
            if(!isNaN(res.Id)){
                res.Id = null;
            }
        });
        
        dmlOnContacts({data: this.records, removeContactIds : this.deleteConatctIds})
        .then( result => {
            this.handleIsLoading(false);
            refreshApex(this.wiredRecords);
            this.updateRecordView(this.recordId);
            this.closeAction();
            this.showToast('Success', result, 'Success', 'dismissable');
        }).catch( error => {
            this.handleIsLoading(false);
            console.log(error);
            this.showToast('Error updating or refreshing records', error.body.message, 'Error', 'dismissable');
        });
    }

    //remove records from table
    handleDeleteAction(event){
        if(isNaN(event.target.dataset.id)){
            this.deleteConatctIds = this.deleteConatctIds + ',' + event.target.dataset.id;
        }
        this.records.splice(this.records.findIndex(row => row.Id === event.target.dataset.id), 1);
    }

    //fetch account contact records
    @wire(fetchContacts, {recordId : '$recordId'})  
    wiredContact(result) {
        this.wiredRecords = result; // track the provisioned value
        const { data, error } = result;

        if(data) {
            this.records = JSON.parse(JSON.stringify(data));
            this.error = undefined;
            this.handleIsLoading(false);
        } else if(error) {
            this.error = error;
            this.records = undefined;
            this.handleIsLoading(false);
        }
    } 

    showToast(title, message, variant, mode) {
        const event = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode
        });
        this.dispatchEvent(event);
    }

    updateRecordView() {
       setTimeout(() => {
            eval("$A.get('e.force:refreshView').fire();");
       }, 3000); 
    }
}

AccountContactTable.css :

.modalBodyy {
    position: relative;
}

accountContactTable.js-meta.xml :

<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
	<apiVersion>51.0</apiVersion>
	<isExposed>true</isExposed>
	<targets>
        <target>lightning__RecordAction</target>
        <target>lightning__RecordPage</target>
    </targets>
	<targetConfigs>
        <targetConfig targets="lightning__RecordAction">
            <actionType>ScreenAction</actionType>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

ContactController.cls :

public class ContactController {
    @AuraEnabled( cacheable = true )
    public static List<Contact> fetchContacts(String recordId) {
        return [SELECT Id, AccountId, FirstName, LastName, Email FROM Contact WHERE AccountId =:recordId LIMIT 100];
    }

    @AuraEnabled
    public static string dmlOnContacts(Object data, String removeContactIds) {
        List<Contact> updateContact = (List<Contact>) JSON.deserialize(JSON.serialize(data), List<Contact>.class);
        List<Contact> deleteContact = new List<Contact>();

        if(String.isNotBlank(removeContactIds)){
            List<Id> contactIds = removeContactIds.split(',');
            deleteContact = [SELECT Id FROM Contact WHERE Id IN :contactIds];
        }

        try {
            if(updateContact != null && !updateContact.isEmpty()){
                upsert updateContact;
            }

            if(deleteContact != null && !deleteContact.isEmpty()){    
                delete deleteContact;
            }
            return 'Success: Contact(s) upsert/delete successfully';
        }
        catch (Exception e) {
            String errorMsg = 'The following exception has occurred: ' + e.getMessage();
            throw new AuraHandledException(ErrorMsg);
        }
       // return '';
    }
}

What’s your Reaction?
+1
4
+1
1
+1
0
+1
0
+1
1
+1
1

You may also like

4 comments

kamila May 7, 2023 - 8:14 pm

Hello, i create a table with custom field and it´s not working, dont update fields. Can u help me?

Reply
Rijwan Mohmmed May 8, 2023 - 3:18 am

which object you are using of update.

Reply
Devyani August 24, 2023 - 2:39 pm

I want to customize this I dont want to display the default 3 rows how can i remove them?

Reply
Rijwan Mohmmed August 24, 2023 - 4:13 pm

Three rows showing according contact records. IN my case there are 3 contact record on account level.

Reply

Leave a Comment