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(){
        
        if(this.handleCheckValidation()) {
            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); 
    }

    //added this method for validate input if any required
    handleCheckValidation() {
        let isValid = true;
        let inputFields = this.template.querySelectorAll('.fieldvalidate');
        inputFields.forEach(inputField => {
            if(!inputField.checkValidity()) {
                inputField.reportValidity();
                isValid = false;
            }
        });
        return isValid;
    }
}

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
9
+1
1
+1
1
+1
1
+1
1
+1
2

You may also like

8 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
Harsh January 2, 2024 - 12:37 pm

if a user click on Add row button but do not fill any data on cells and click on save button. so it will be problematic i think. can you please share with required fields on cells. means if we add row so we set a validation that “please fill the fields”. can you please share the updated code in this.

thanks..!!

Reply
Rijwan Mohmmed January 2, 2024 - 3:27 pm

Hi @Harsh, Sure I will fill this missing gap in my code.
Thanks for your suggestion.

Reply
Rijwan Mohmmed January 2, 2024 - 3:55 pm

Hi @Harsh, I updated the code for validation.

Reply
Harsh January 3, 2024 - 1:11 pm

Thanks, Rijwan
After checking in my org i will let you know..

thanks for your precious time!!

Reply

Leave a Comment