inline edit data table

Custom Data Table With Inline Editing In Salesforce Lightning Component – Sample

Lightning Component

Hi Guys,

Today in this post we are going to create a custom lightning data table with inline edit functionality. like standard inline editing.

Output :

inline editing
Output : Lightning Component

 

Component Blue Print :
inline editing salesforce lightning component
Component Blueprint

 

Component Functionality :

  • Support inline editing by clicking on pencil icon OR double click on row column.
  • Make name field as required on data table.
  • Save and Cancel functionality, for records update.
Step 1 : Create Apex Controller : inlineEditCtrl.apxc

From Developer Console >> File >> New >> Apex Class

/* sfdcmonkey.com || Date 12/07/2017 || API Version 41.0 */
public with sharing class inlineEditCtrl {
  
    // method for fetch account records list  
    @AuraEnabled
    public static List < account > fetchAccount() {
        
        List < Account > returnList = new List < Account > ();
        List < Account > lstOfAccount = [select id, Name, Rating, website from account LIMIT 5];
        
        for (Account acc: lstOfAccount) {
            returnList.add(acc);
        }
        return returnList;
    }
    
  // method for update records after inline editing  
    @AuraEnabled
    public static List < account > saveAccount(List<Account> lstAccount) {
        update lstAccount;
        return lstAccount;
    }
    
  // method for fetch picklist values dynamic  
    @AuraEnabled
    public static List < String > getselectOptions(sObject objObject, string fld) {
        system.debug('objObject --->' + objObject);
        system.debug('fld --->' + fld);
        List < String > allOpts = new list < String > ();
        // Get the object type of the SObject.
        Schema.sObjectType objType = objObject.getSObjectType();
        
        // Describe the SObject using its object type.
        Schema.DescribeSObjectResult objDescribe = objType.getDescribe();
        
        // Get a map of fields for the SObject
        map < String, Schema.SObjectField > fieldMap = objDescribe.fields.getMap();
        
        // Get the list of picklist values for this field.
        list < Schema.PicklistEntry > values =
            fieldMap.get(fld).getDescribe().getPickListValues();
        
        // Add these values to the selectoption list.
        for (Schema.PicklistEntry a: values) {
            allOpts.add(a.getValue());
        }
        system.debug('allOpts ---->' + allOpts);
        allOpts.sort();
        return allOpts;
    }
}
  • see code comments.
Step 2 : Create Child Lightning Component  : inlineEditRow.cmp

From Developer Console >> File >> New >> Lightning Component

<!-- sfdcmonkey.com || Date 12/07/2017 || API Version 41.0 || Child Component-->
<aura:component controller="inlineEditCtrl">
  <!-- on component load, fetch picklist values dynamically from apex controller -->   
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <!--declare aura attributes-->
    <aura:attribute name="objInfoForPicklistValues" type="account" default="{sobjectType : 'Account'}" description="object information to fetch picklist values"/>
    <aura:attribute name="ratingPicklistOpts" type="string[]" description="store picklist options values"/> 
    <aura:attribute name="showSaveCancelBtn" type="boolean"/>
    <aura:attribute name="showErrorClass" type="boolean" default="false"/>
    <aura:attribute name="sNo" type="string" />
    <aura:attribute name="singleRec" type="sobject" default="{'sobjectType' : 'account',
                                                               'Name' : '',
                                                               'Website' : '',
                                                               'AnnualRevenue' :'',
                                                               'Rating': ''
                                                               }"/>
    <aura:attribute name="nameEditMode" type="boolean" default="false" />
    <aura:attribute name="ratingEditMode" type="boolean" default="false" />
    
    <!--Table Row Start-->  
    <tr>
        <td><div class="slds-truncate">{!v.sNo}</div></td>
       
        <td ondblclick="{!c.inlineEditName}" class="{! v.showErrorClass == true ? 'slds-cell-edit slds-has-error' : 'slds-cell-edit'}">
            <span class="slds-grid slds-grid_align-spread">
                <!-- show input and output section based on boolean flag --> 
                <aura:if isTrue="{!v.nameEditMode == false}">
                    <span class="slds-truncate" title="Name">{!v.singleRec.Name}</span>
                    <button onclick="{!c.inlineEditName}" class="slds-button slds-button_icon slds-cell-edit__button slds-m-left_x-small" tabindex="0" title="Edit Name">
                      <lightning:icon iconName="utility:edit" size="xx-small" alternativeText="edit"/>
                    </button>
                   
                    <!-- Inline Edit Section in else case-->  
                    <aura:set attribute="else">
                        <section  tabindex="0" class="slds-popover slds-popover_edit" role="dialog" style="position: absolute; top: 0px">
                            <div class="slds-popover__body">
                                <div class="slds-form-element slds-grid slds-wrap">
                                    <div class="slds-form-element__control slds-grow">
                                        <ui:inputText class="slds-input inputFieldWidth"
                                                      labelClass="slds-form-element__label slds-form-element__label_edit slds-no-flex"
                                                      aura:id="inputId"
                                                      blur="{!c.closeNameBox}"
                                                      change="{!c.onNameChange}"
                                                      required="true"
                                                      label="Name"
                                                      value="{!v.singleRec.Name}" />
                                    </div>
                                </div>
                            </div>
                            <span id="form-end" tabindex="0"></span>
                        </section>  
                    </aura:set>  
                </aura:if> 
            </span>
        </td>
        
        <td>
            <div class="slds-truncate">{!v.singleRec.Website}</div>
        </td>
        
        <td ondblclick="{!c.inlineEditRating}" class="slds-cell-edit">
            <span class="slds-grid slds-grid_align-spread">
                <!-- show input and output section based on boolean flag --> 
                <aura:if isTrue="{!v.ratingEditMode == false}">
                    <span class="slds-truncate" title="Rating">{!v.singleRec.Rating}</span>
                    <button onclick="{!c.inlineEditRating}" class="slds-button slds-button_icon slds-cell-edit__button slds-m-left_x-small" tabindex="0" title="Edit Rating">
                        <lightning:icon iconName="utility:edit" size="xx-small" alternativeText="edit"/>
                    </button>
                    
                    <!-- Inline Edit Section in else case-->  
                    <aura:set attribute="else">
                        <section  tabindex="0" class="slds-popover slds-popover_edit" role="dialog" style="position: absolute; top: 0px">
                            <div class="slds-popover__body">
                                <div class="slds-form-element slds-grid slds-wrap">
                                    <div class="slds-form-element__control slds-grow">
                                        <label class="slds-form-element__label">Rating</label>
                                        <ui:inputSelect aura:id="accRating"
                                                        class="slds-select inputFieldWidth"
                                                        blur="{!c.closeRatingBox}"
                                                        change="{!c.onRatingChange}"
                                                        value="{!v.singleRec.Rating}"
                                                        />
                                    </div>
                                </div>
                            </div>
                            <span id="form-end" tabindex="0"></span>
                        </section>  
                    </aura:set>  
                </aura:if> 
            </span>
        </td> 
    </tr>
</aura:component>
  • see code comments.
JavaScript Controller – inlineEditRowController.js
({
    
    doInit: function(component, event, helper) {
      // call the fetchPickListVal(component, field_API_Name, aura_attribute_name_for_store_options) -
      // method for get picklist values dynamic   
        helper.fetchPickListVal(component, 'Rating', 'ratingPicklistOpts');
    },
    
    inlineEditName : function(component,event,helper){   
        // show the name edit field popup 
        component.set("v.nameEditMode", true); 
        // after the 100 millisecond set focus to input field   
        setTimeout(function(){ 
            component.find("inputId").focus();
        }, 100);
    },
    
    inlineEditRating : function(component,event,helper){   
        // show the rating edit field popup 
        component.set("v.ratingEditMode", true); 
        // after set ratingEditMode true, set picklist options to picklist field 
        component.find("accRating").set("v.options" , component.get("v.ratingPicklistOpts"));
        // after the 100 millisecond set focus to input field   
        setTimeout(function(){ 
            component.find("accRating").focus();
        }, 100);
    },
    
     onNameChange : function(component,event,helper){ 
        // if edit field value changed and field not equal to blank,
        // then show save and cancel button by set attribute to true
        if(event.getSource().get("v.value").trim() != ''){ 
            component.set("v.showSaveCancelBtn",true);
        }
    },

    onRatingChange : function(component,event,helper){ 
        // if picklist value change,
        // then show save and cancel button by set attribute to true
        component.set("v.showSaveCancelBtn",true);
    },     
    
    closeNameBox : function (component, event, helper) {
      // on focus out, close the input section by setting the 'nameEditMode' att. as false   
        component.set("v.nameEditMode", false); 
      // check if change/update Name field is blank, then add error class to column -
      // by setting the 'showErrorClass' att. as True , else remove error class by setting it False   
        if(event.getSource().get("v.value").trim() == ''){
            component.set("v.showErrorClass",true);
        }else{
            component.set("v.showErrorClass",false);
        }
    }, 
    
    closeRatingBox : function (component, event, helper) {
       // on focus out, close the input section by setting the 'ratingEditMode' att. as false
        component.set("v.ratingEditMode", false); 
    },
    
   
})
  • see code comments
JavaScript Helper – inlineEditRowHelper.js
({
   // fetch picklist values dynamic from apex controller 
    fetchPickListVal: function(component, fieldName, picklistOptsAttributeName) {
        var action = component.get("c.getselectOptions");
        action.setParams({
            "objObject": component.get("v.objInfoForPicklistValues"),
            "fld": fieldName
        });
        var opts = [];
        action.setCallback(this, function(response) {
            if (response.getState() == "SUCCESS") {
                var allValues = response.getReturnValue();
 
                if (allValues != undefined && allValues.length > 0) {
                    opts.push({
                        class: "optionClass",
                        label: "--- None ---",
                        value: ""
                    });
                }
                for (var i = 0; i < allValues.length; i++) {
                    opts.push({
                        class: "optionClass",
                        label: allValues[i],
                        value: allValues[i]
                    });
                }
                component.set("v." + picklistOptsAttributeName, opts);
            }
        });
        $A.enqueueAction(action);
    },
})
Also Checkout  : How to Fetch Picklist value from sObject and Set in ui:inputSelect
style – inlineEditRow.css
.THIS .inputFieldWidth {
    width:85%;
}
Step 3 : Create Parent Lightning Component  : inlineEditTable.cmp

From Developer Console >> File >> New >> Lightning Component

<!-- sfdcmonkey.com || Date 12/07/2017 || API Version 41.0 || Parent Component-->
<aura:component controller="inlineEditCtrl" implements="flexipage:availableForAllPageTypes,force:hasRecordId,force:appHostable">   
   <!--Init handler which is call initRecords js function on component Load-->  
    <aura:handler name="init" value="{!this}" action="{!c.initRecords}"/>
    
    <!--declare aura attributes-->
    <aura:attribute name="AccountList" type="account[]" description="store account records list"/>
    <aura:attribute name="showSaveCancelBtn" type="boolean" default="false" description="flag for rendered save and cancel buttons in aura:if "/>
    
    <div class="slds-m-around_large">
        <!-- use aura:if for show/hide buttons --> 
        <aura:if isTrue="{!v.showSaveCancelBtn}">
            <!--button for save and cancel Record after Inline Edit-->
            <lightning:buttonGroup class="slds-m-around_medium">
                <lightning:button label="Cancel" onclick="{!c.cancel}"/>
                <lightning:button label="Save" onclick="{!c.Save}" variant="success"/>
            </lightning:buttonGroup>
        </aura:if> 

        <!--Data Table-->     
        <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="Id">S.No</div></th> 
                  <th scope="col"><div class="slds-truncate" title="Account Name">Account Name</div></th>
                  <th scope="col"><div class="slds-truncate" title="Website">Website</div></th>
                  <th scope="col"><div class="slds-truncate" title="Rating">Rating</div></th>
                </tr>
            </thead>
            
            <tbody>
             <!--### display all records of AccountList attribute one by one by aura:iteration ###-->
                <aura:iteration items="{!v.AccountList}" var="acc" indexVar="sNo">
                   <!-- Child Lightning Component --> 
                    <c:inlineEditRow singleRec="{!acc}"
                                     showSaveCancelBtn="{!v.showSaveCancelBtn}"
                                     sNo="{!sNo + 1}" />
                </aura:iteration>
            </tbody>
        </table>
    </div>
</aura:component>
  • see code comments
JavaScript Controller – inlineEditTableController.js
({
    initRecords: function(component, event, helper) {
      // call the apex class method and fetch account list  
         var action = component.get("c.fetchAccount");
             action.setCallback(this, function(response) {
              var state = response.getState();
              if (state === "SUCCESS") {
                  var storeResponse = response.getReturnValue();
                  console.log(JSON.stringify(storeResponse));
               // set AccountList list with return value from server.
                  component.set("v.AccountList", storeResponse);
            }
        });
        $A.enqueueAction(action);
    },
    
    Save: function(component, event, helper) {
      // Check required fields(Name) first in helper method which is return true/false
        if (helper.requiredValidation(component, event)){
              // call the saveAccount apex method for update inline edit fields update 
               var action = component.get("c.saveAccount");
                  action.setParams({
                    'lstAccount': component.get("v.AccountList")
                  });
            action.setCallback(this, function(response) {
                var state = response.getState();
                if (state === "SUCCESS") {
                    var storeResponse = response.getReturnValue();
                    // set AccountList list with return value from server.
                    component.set("v.AccountList", storeResponse);
                    // Hide the save and cancel buttons by setting the 'showSaveCancelBtn' false 
                    component.set("v.showSaveCancelBtn",false);
                    alert('Updated...');
                }
            });
            $A.enqueueAction(action);
        } 
    },
    
    cancel : function(component,event,helper){
       // on cancel refresh the view (This event is handled by the one.app container. It’s supported in Lightning Experience, the Salesforce app, and Lightning communities. ) 
        $A.get('e.force:refreshView').fire(); 
    } 
    
})
$A.get(‘e.force:refreshView’).fire(); This event is handled by the one.app container. It’s supported in Lightning Experience, Salesforce app, and Lightning communities.
JavaScript Helper- inlineEditTableHelper.js
({
    requiredValidation : function(component,event) {
        // get all accounts.. 	
        var allRecords = component.get("v.AccountList");
        var isValid = true;
        // play a for loop on all account list and check that account name is not null,   
        for(var i = 0; i < allRecords.length;i++){
            if(allRecords[i].Name == null || allRecords[i].Name.trim() == ''){
                alert('Complete this field : Row No ' + (i+1) + ' Name is null' );
                isValid = false;
            }  
        }
        return isValid;
    },
})
  • see code comments
Add Lightning Components as Custom Tabs in Lightning Experience :
https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/aura_add_cmp_lex.htm 

Related Resource:

Like our facebook page for new post updates. & Don’t forget to bookmark this site for your future reference.

if you have any suggestions or issue with it, you can share your thoughts  in comment box.

Happy Learning 🙂

16 comments

  • Hi Admin,

     

    I am getting below errors

    This page has an error. You might just need to refresh it. Action failed: c:inlineEditRow$controller$closeASPProposedBox [event.getSource(…).get(…).trim is not a function] Failing descriptor: {c:inlineEditRow$controller$closeASPProposedBox}

  • Hi, its really great article.

    I use this for some custom implementation where i need to use ui:inputDate but inline editing is not working for ui:input date.

  • Hi thanks for this great post.

     

    I am having issues with cancel. its not doing anything on click.  Can u pl help?

     

  • Hi,

    Thanks for the great post!! your page is helping a lot with lightning development.

    I have issues with the below

     

    Cancel button is not working
    inline edit value box is going out of the name window.

    Pl let me know how can i fix those both.

     

     

     

  • I need to display same list data using group…example I need to displayed data group by website.
    If there are multiple account with same website ..all should displayed under on website..and website name should ne displayed as row header.
    Need lightening code ASAP

  • Hello, I am trying to show the edit option to few rows for ex: if Account Industry = ‘Education’, only then update the corresponding Account_Date__c field, else the Account Date field should not be editable.

    Is it possible to achieve this with inline editing on datatable through LWC?

    Thanks.

    • Hi Paridhi,
      You could do this in the Account_Date_c change method by simply rejecting any attempt when the account = ‘Education’.

      Using the above code as an example and rejecting edit for a particular company name, the change method would look like this:-

      inlineEditRating : function(component,event,helper){

      var cName = component.get(“v.singleRec.Name”);
      if (cName == ‘Company 5’){
      alert(‘You can\’t change the Rating on Company 5’);
      }else{
      // show the rating edit field popup
      component.set(“v.ratingEditMode”, true);
      // after set ratingEditMode true, set picklist options to picklist field
      component.find(“accRating”).set(“v.options” , component.get(“v.ratingPicklistOpts”));
      // after the 100 millisecond set focus to input field
      setTimeout(function(){
      component.find(“accRating”).focus();
      }, 100);
      }//endif
      },//inlineEditRating

      There’s an example LWC inline edit at https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.data_table_inline_edit

  • Fantastic post, Piyush. Thank you so much.

    Here’s a test class…

    /* https://sfdcmonkey.com/2017/12/08/lightning-data-table-inline-editing/     */
    /* test class for this component's apex || Date 18/08/2021 || API Version 52.0 */
    @isTest(SeeAllData=false)
    public class inlineEditCtrlTest {
        
        
        @isTest static void testInlineEdit() {
    				
            List<Account> accs = inlineEditCtrl.fetchAccount();
            system.debug('accs size = '+accs.size());
            List<Account> savedAccs = inlineEditCtrl.saveAccount(accs);
            sObject objObject = new Account(Name='TestObj');
            string fld = 'Rating';
            List < String > lstPicks = inlineEditCtrl.getselectOptions(objObject, fld);
            system.debug('Pick List of Options = '+lstPicks);
        }//testInlineEdit
        
        @testSetup
        static void setup() {
            
            Account anAcc1 = new Account(name='test1', rating='Hot',website='www.sfdcmonkey.com');
            insert anAcc1;
            Account anAcc2 = new Account(name='test2', rating='Cold',website='www.sfdcmonkey.com');
            insert anAcc2;
            Account anAcc3 = new Account(name='test3', rating='Warm',website='www.sfdcmonkey.com');
            insert anAcc3;
            Account anAcc4 = new Account(name='test4', rating='Hot',website='www.sfdcmonkey.com');
            insert anAcc4;
            Account anAcc5 = new Account(name='test5');
            insert anAcc5;
         }//setup
      
    }//inlineEditCtrlTest

     

  • Please enlighten me on the following:

    In the method fetchAccount, why do you not simply return lstOfAccount from the query? Why transfer the data into another list before returning it?

    Also, in saveAccount, it is unnecessary to return the lstAccount – it could be a void. Do you do that for debugging at the java controller level?

  • Another suggestion:

    Move the helper.fetchPickListVal(component, ‘Rating’, ‘ratingPicklistOpts’); from the row component up to the table component. That way it is only called once instead of for every account.

    The call to the row component then becomes:

    <!-- Child Lightning Component --> 
    <c:inlineEditRow singleRec="{!acc}"
             showSaveCancelBtn="{!v.showSaveCancelBtn}"
             sNo="{!sNo + 1}"
             ratingPicklistOpts="{v.ratingPicklistOpts"} />

     

  • What if you can’t iterate over the component, how then would you dynamically edit fields? It is not efficient in some instances with a lot of data and doInit attribute in the child component to call it multiple times. I am passing the entire list to the child component and letting the child component iterate through it but now I’m unable to use the inline edit function described here because it will open edit on the entire column instead of single cell in the column

Leave a Reply