+ Start a Discussion
sheila srivatsavsheila srivatsav 

Inline editing not working in datatable

I am using google crome browser and performing inline editing on lightning datatable.

I am able to display the field values but when I edit Opportunity Name and click save its not saving.

Please let me know the issue.
 
public class DataTableController {

    @AuraEnabled
    public static List<Opportunity> getOpportunities()
    {
        List<Opportunity> oppList=new List<Opportunity>();
        
        oppList=[select Name,Account.Name,StageName,CloseDate,Amount from Opportunity
                 where Amount != NULL];
        
        return oppList;
    }
    
    @AuraEnabled
    public static void updateOpportunities(List<Opportunity> oppsList){
        try
        {
            Database.update(oppsList);
            
          }
        catch(Exception ex)
        {
            system.debug(ex.getMessage());
        }
    }
}

<aura:component implements="force:appHostable,force:hasRecordId,flexipage:availableForAllPageTypes" 
                controller="DataTableController"
                access="global">
	
        <!-- Three important parts of datatable is Key , data and columns
         so we need attribute for data and columns(metatadata)-->
    <!-- attributes -->
    <aura:attribute name="data" 
                    type="Object"/>
    
    <aura:attribute name="columns" 
                    type="List"/>
    
<aura:attribute name="updatedRecord" type="Object[]" />

    <!-- handlers-->
    <aura:handler name="init" 
                  value="{!this}" 
                  action="{!c.doInit}"/>
    
    <lightning:card title="OpportunityDatatable">
        
        <lightning:datatable aura:id="opportunitydatatable"
                             keyField="id"
                             data="{!v.data}"
                             columns="{!v.columns}"
                             onsave="{!c.handleSaveOpps}"
                             hideCheckboxColumn="true"/>
        
</lightning:card>
    
    {!updatedRecord}

</aura:component>

({
	doInit : function(component, event, helper) {
        
        helper.queryColumns(component,event,helper);
        
        helper.queryContacts(component,event,helper);
  	},
    
    handleSaveOpps: function (component, event, helper) {
        debugger;    
        var draftValues = event.getParam('draftValues');
console.log(draftValues);
        var action = component.get('c.updateOpportunities');
        action.setParams({ 
            "oppsList": draftValues
        });
        action.setCallback(this, $A.getCallback(function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                helper.fireSuccessToast(component);  
                helper.fireRefreshEvt(component);
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
                helper.fireFailureToast(component);  
            }
        }));
        $A.enqueueAction(action);
    }
     
})


({
	queryColumns : function(component,event,helper) {
        
		  component.set('v.columns', [
            {label: 'Opp Name', fieldName: 'Name', editable : 'true', type: 'text'},
            {label: 'Acc Name', fieldName: 'AccountName', type: 'text'},
            {label: 'StageName', fieldName: 'StageName', type: 'text'},
            {label: 'CloseDate', fieldName: 'CloseDate', type: 'date'},
            {label: 'Amount', fieldName: 'Amount', type: 'currency', cellAttributes: { alignment: 'left' }} 
        ]);
	},
    
    queryContacts : function(component,event,helper) {
        
        var action=component.get('c.getOpportunities');
        
        action.setParams({
        });
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                
                  //the following code will display the parent account name
                 var rows = response.getReturnValue();
                 for (var i = 0; i < rows.length; i++) {
                    var row = rows[i];
                    if (row.Account) row.AccountName = row.Account.Name;
                }
                //component.set("v.data", response.getReturnValue());
                 component.set("v.data", rows);
            }
        });
        $A.enqueueAction(action);
    },
    
     fireSuccessToast : function(component) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ 
            'title' : 'Success', 
            'message' : 'Opportunities updated sucessfully.' ,
            'type':'success'
        }); 
        toastEvent.fire(); 
    },
    
    fireFailureToast : function(component) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ 
            'title' : 'Failed', 
            'message' : 'An error occurred. Please contact your administrator.',
            'type':'error'
        }); 
        toastEvent.fire(); 
    },
    
    fireRefreshEvt : function(component) {
        var refreshEvent = $A.get("e.force:refreshView");
        if(refreshEvent){
            refreshEvent.fire();
        }
    }
       
})

 
Best Answer chosen by sheila srivatsav
Khan AnasKhan Anas (Salesforce Developers) 
Hi Sheila,

Greetings to you. It is a pleasure to be in touch with you again.

You need to use editable: true like this:
{label: 'Opp Name', fieldName: 'Name', editable: true, type: 'text'},

Also, lightning is case-sensitive. So, use Id in keyField instead of id
keyField="Id"

And you need below handler in component in order to refresh your datatable:
<aura:handler event="force:refreshView" action="{!c.doInit}" />

Below is the final code you can refer:

Component:
<aura:component controller="DataTableTestC"
                implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
    
    <!-- Three important parts of datatable is Key , data and columns
         so we need attribute for data and columns(metatadata)-->
    <!-- attributes -->
    <aura:attribute name="data" 
                    type="Object"/>
    
    <aura:attribute name="columns" 
                    type="List[]"/>
    
    
    <!-- handlers-->
    <aura:handler name="init" 
                  value="{!this}" 
                  action="{!c.doInit}"/>

    <aura:handler event="force:refreshView" action="{!c.doInit}" />
    
    <!-- the container element determine the height of the datatable -->
    <div style="height: 300px">
        
        <lightning:datatable aura:id="opportunitydatatable"
                             
                             keyField="Id"
                             data="{!v.data}"
                             columns="{!v.columns}"
                             hideCheckboxColumn="true"
                             onsave="{!c.handleSaveOpps}"/>
        
    </div>
</aura:component>

Controller:
({
    doInit : function(component, event, helper) {
        
        helper.queryColumns(component,event,helper);
        
        helper.queryContacts(component,event,helper);
    },
    
    handleSaveOpps: function (component, event, helper) {
        debugger;    
        var draftValues = event.getParam('draftValues');
        console.log('draftValues-> ' + JSON.stringify(draftValues));
        var action = component.get('c.updateOpportunities');
        action.setParams({"oppsList": draftValues});
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                helper.fireSuccessToast(component);  
                helper.fireRefreshEvt(component);
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
                helper.fireFailureToast(component);  
            }
        });
        $A.enqueueAction(action);
    }
})

I hope it helps you.

Kindly let me know if it helps you and close your query by marking it as solved so that it can help others in the future. It will help to keep this community clean.

Thanks and Regards,
Khan Anas

All Answers

Khan AnasKhan Anas (Salesforce Developers) 
Hi Sheila,

Greetings to you. It is a pleasure to be in touch with you again.

You need to use editable: true like this:
{label: 'Opp Name', fieldName: 'Name', editable: true, type: 'text'},

Also, lightning is case-sensitive. So, use Id in keyField instead of id
keyField="Id"

And you need below handler in component in order to refresh your datatable:
<aura:handler event="force:refreshView" action="{!c.doInit}" />

Below is the final code you can refer:

Component:
<aura:component controller="DataTableTestC"
                implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
    
    <!-- Three important parts of datatable is Key , data and columns
         so we need attribute for data and columns(metatadata)-->
    <!-- attributes -->
    <aura:attribute name="data" 
                    type="Object"/>
    
    <aura:attribute name="columns" 
                    type="List[]"/>
    
    
    <!-- handlers-->
    <aura:handler name="init" 
                  value="{!this}" 
                  action="{!c.doInit}"/>

    <aura:handler event="force:refreshView" action="{!c.doInit}" />
    
    <!-- the container element determine the height of the datatable -->
    <div style="height: 300px">
        
        <lightning:datatable aura:id="opportunitydatatable"
                             
                             keyField="Id"
                             data="{!v.data}"
                             columns="{!v.columns}"
                             hideCheckboxColumn="true"
                             onsave="{!c.handleSaveOpps}"/>
        
    </div>
</aura:component>

Controller:
({
    doInit : function(component, event, helper) {
        
        helper.queryColumns(component,event,helper);
        
        helper.queryContacts(component,event,helper);
    },
    
    handleSaveOpps: function (component, event, helper) {
        debugger;    
        var draftValues = event.getParam('draftValues');
        console.log('draftValues-> ' + JSON.stringify(draftValues));
        var action = component.get('c.updateOpportunities');
        action.setParams({"oppsList": draftValues});
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                helper.fireSuccessToast(component);  
                helper.fireRefreshEvt(component);
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
                helper.fireFailureToast(component);  
            }
        });
        $A.enqueueAction(action);
    }
})

I hope it helps you.

Kindly let me know if it helps you and close your query by marking it as solved so that it can help others in the future. It will help to keep this community clean.

Thanks and Regards,
Khan Anas
This was selected as the best answer
sheila srivatsavsheila srivatsav
Hi Khan
Thanks for giving prompt reply to my posts and remembering me.

Ur code works.

I have a doubt

a. assume I make StageName as editable, so with this code I have to enter the picklist value manually, is there a way i can select the picklist value
   directly from the dropdown ?

b. How 2 different handllers can have the same action {!c.doInit} ?

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/> 

<aura:handler event="force:refreshView" action="{!c.doInit}" />

c. Is it possible to have delete option in datatable?

can u explain pls?

thanks
sheila
 
Khan AnasKhan Anas (Salesforce Developers) 
Hi Sheila,

1. Currently, NO. It is an idea to include picklist in datatable: https://success.salesforce.com/ideaView?id=0873A000000PZJ4QAO
Picklist fields are not part of lightning datatable but you can use the workaround. I suggest you please refer to below links for more information on the workaround:

https://salesforce.stackexchange.com/questions/224094/is-there-a-way-to-achieve-lightningdatatable-with-picklist-field

http://www.sfdcpanda.com/lightning-datatable-with-picklist-select-powered-in-edit-mode/


2. Yes, we can use the same action method for multiple handlers. Here, you just need to refresh the datatable. So, it can be done by calling doInit method which will initialize the component again. The init handler is defined to execute some code when the component is initialized. 


3. Yes, we can delete the records in datatable. We need to remove hideCheckboxColumn="true" as we need to display checkboxes to select records for deletion and we have to use onrowselection attribute in lightning:datatable. 
onrowselection: The action triggered when a row is selected. Returns the selectedRows object.

Please try the below code, I have tested in my org and it is working fine.

Component:
<aura:component controller="DataTableTestC"
                implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
    
    <!-- Three important parts of datatable is Key , data and columns
         so we need attribute for data and columns(metatadata)-->
    <!-- attributes -->
    <aura:attribute name="data" 
                    type="Object"/>
    
    <aura:attribute name="columns" 
                    type="List[]"/>
    
    <aura:attribute name="delIds" type="List"/>
    
    <!-- handlers-->
    <aura:handler name="init" 
                  value="{!this}" 
                  action="{!c.doInit}"/>
    
    <aura:handler event="force:refreshView" action="{!c.doInit}" />
    
    <!-- the container element determine the height of the datatable -->
    <div class="slds-grid slds-grid--align-end"> 
         <button class="slds-button slds-button--brand" onclick="{!c.doDelete}">Delete</button>
    </div>
    <div style="height: 300px">
        
        <lightning:datatable aura:id="opportunitydatatable"
                             
                             keyField="Id"
                             data="{!v.data}"
                             columns="{!v.columns}"
                             onsave="{!c.handleSaveOpps}"
                             onrowselection="{! c.handleRowAction }"/>
        
    </div>
</aura:component>

Controller:
({
    doInit : function(component, event, helper) {
        
        helper.queryColumns(component,event,helper);
        
        helper.queryContacts(component,event,helper);
    },
    
    handleSaveOpps: function (component, event, helper) {
        debugger;    
        var draftValues = event.getParam('draftValues');
        console.log('draftValues-> ' + JSON.stringify(draftValues));
        var action = component.get('c.updateOpportunities');
        action.setParams({"oppsList": draftValues});
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                helper.fireSuccessToast(component);  
                helper.fireRefreshEvt(component);
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
                helper.fireFailureToast(component);  
            }
        });
        $A.enqueueAction(action);
    },
    
    handleRowAction : function(component, event, helper){
        var selRows = event.getParam('selectedRows');
        component.set("v.delIds",selRows);
    },
    
    doDelete : function(component, event, helper){
        var delIdList = component.get("v.delIds");
        var action = component.get('c.deleteForm');
        action.setParams({lstId : delIdList});
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                helper.fireSuccessToast(component);  
                helper.fireRefreshEvt(component);   
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                    errors[0].message);
                    }
                } 
                else {
                    console.log("Unknown Error");
                }
            }
        });
        $A.enqueueAction(action);
    }
})

Helper:
({
    queryColumns : function(component,event,helper) {
        
        component.set('v.columns', [
            {label: 'Opp Name', fieldName: 'Name', editable: true, type: 'text'},
            {label: 'Acc Name', fieldName: 'AccountName', type: 'text'},
            {label: 'StageName', fieldName: 'StageName', editable: true, type: 'text'},
            {label: 'CloseDate', fieldName: 'CloseDate', type: 'date'},
            {label: 'Amount', fieldName: 'Amount', type: 'currency', cellAttributes: { alignment: 'left' }}  
        ]);
    },
    
    queryContacts : function(component,event,helper) {
        
        var action=component.get('c.getOpportunities');
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                var rows = response.getReturnValue();
                for (var i = 0; i < rows.length; i++) {
                    var row = rows[i];
                    if (row.Account) row.AccountName = row.Account.Name;
                }
                component.set("v.data", rows);
                
            }
        });
        $A.enqueueAction(action);
    },
    fireSuccessToast : function(component) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ 
            'title' : 'Success', 
            'message' : 'Opportunities updated sucessfully.' ,
            'type':'success'
        }); 
        toastEvent.fire(); 
    },
    
    fireFailureToast : function(component) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ 
            'title' : 'Failed', 
            'message' : 'An error occurred. Please contact your administrator.',
            'type':'error'
        }); 
        toastEvent.fire(); 
    },
    
    fireRefreshEvt : function(component) {
        var refreshEvent = $A.get("e.force:refreshView");
        if(refreshEvent){
            refreshEvent.fire();
        }
    }
    
})

Apex:
public class DataTableTestC {
    
    @AuraEnabled
    public static List<Opportunity> getOpportunities()
    {
        List<Opportunity> oppList=new List<Opportunity>();
        
        oppList=[select name,Account.Name,StageName,CloseDate,Amount from Opportunity
                 where StageName != NULL
                 AND
                 Amount != NULL
                 order by Account.Name];
        
        return oppList;
    }
    
    @AuraEnabled
    public static void updateOpportunities(List<Opportunity> oppsList){
        try
        {
            Database.update(oppsList);
            
        }
        catch(Exception ex)
        {
            system.debug(ex.getMessage());
        }
    }
    
    @AuraEnabled
    public static void deleteForm(List<Opportunity> lstId) {
        List<Opportunity> rf = [SELECT Id FROM Opportunity WHERE Id IN :lstId];
        delete rf;
    }
}


Also, if you want to delete single record using row action button then you can use {type: "button", typeAttributes: { . . .}} in datatable columns and you need to use onrowaction in lightning:datatable attribute.

onrowaction: The action triggered when a row action is clicked. By default, it also closes the row actions menu. Returns the eventDetails object.

Below is the code for the same.

Component:
<aura:component controller="DataTableTestC"
                implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
    
    <!-- Three important parts of datatable is Key , data and columns
         so we need attribute for data and columns(metatadata)-->
    <!-- attributes -->
    <aura:attribute name="data" 
                    type="Object"/>
    
    <aura:attribute name="columns" 
                    type="List[]"/>
    
    <aura:attribute name="delIds" type="List"/>
    
    <!-- handlers-->
    <aura:handler name="init" 
                  value="{!this}" 
                  action="{!c.doInit}"/>
    
    <aura:handler event="force:refreshView" action="{!c.doInit}" />
    
    <!-- the container element determine the height of the datatable -->
    <div style="height: 300px">
        
        <lightning:datatable aura:id="opportunitydatatable"
                             
                             keyField="Id"
                             data="{!v.data}"
                             columns="{!v.columns}"
                             onsave="{!c.handleSaveOpps}"
                             hideCheckboxColumn="true"
                             onrowaction="{!c.deleteRecord}"/>
        
    </div>
</aura:component>

Controller:
({
    doInit : function(component, event, helper) {
        
        helper.queryColumns(component,event,helper);
        
        helper.queryContacts(component,event,helper);
    },
    
    handleSaveOpps: function (component, event, helper) {
        debugger;    
        var draftValues = event.getParam('draftValues');
        console.log('draftValues-> ' + JSON.stringify(draftValues));
        var action = component.get('c.updateOpportunities');
        action.setParams({"oppsList": draftValues});
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                helper.fireSuccessToast(component);  
                helper.fireRefreshEvt(component);
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
                helper.fireFailureToast(component);  
            }
        });
        $A.enqueueAction(action);
    },
    
    
    deleteRecord : function(component, event, helper) {
        var recId = event.getParam('row').Id;
        console.log('recId-> ' + recId);
        var actionName = event.getParam('action').name;
        console.log('actionName-> ' + actionName);
        if ( actionName == 'Delete') {
            var action = component.get('c.deleteOneRec');
            action.setParams({lstId1 : recId});
            action.setCallback(this, function(response) {
                var state = response.getState();
                if (state === "SUCCESS") {
                    $A.get('e.force:refreshView').fire();
                    //helper.fireSuccessToast(component);  
                    //helper.fireRefreshEvt(component);   
                }
                else if (state === "ERROR") {
                    var errors = response.getError();
                    if (errors) {
                        if (errors[0] && errors[0].message) {
                            console.log("Error message: " + 
                                        errors[0].message);
                        }
                    } 
                    else {
                        console.log("Unknown Error");
                    }
                }
            });
            $A.enqueueAction(action);
        }
    }
})

Helper:
({
    queryColumns : function(component,event,helper) {
        
        component.set('v.columns', [
            {label: 'Opp Name', fieldName: 'Name', editable: true, type: 'text'},
            {label: 'Acc Name', fieldName: 'AccountName', type: 'text'},
            {label: 'StageName', fieldName: 'StageName', editable: true, type: 'text'},
            {label: 'CloseDate', fieldName: 'CloseDate', type: 'date'},
            {label: 'Amount', fieldName: 'Amount', type: 'currency', cellAttributes: { alignment: 'left' }},
            {type: "button", typeAttributes: {
                label: 'Delete',
                name: 'Delete',
                title: 'Delete',
                disabled: { fieldName: 'isActive'},
                value: 'delete',
                iconPosition: 'left'
            }}
        ]);
    },
    
    queryContacts : function(component,event,helper) {
        
        var action=component.get('c.getOpportunities');
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                var rows = response.getReturnValue();
                for (var i = 0; i < rows.length; i++) {
                    var row = rows[i];
                    if (row.Account) row.AccountName = row.Account.Name;
                }
                component.set("v.data", rows);
                
            }
        });
        $A.enqueueAction(action);
    },
    fireSuccessToast : function(component) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ 
            'title' : 'Success', 
            'message' : 'Opportunities updated sucessfully.' ,
            'type':'success'
        }); 
        toastEvent.fire(); 
    },
    
    fireFailureToast : function(component) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ 
            'title' : 'Failed', 
            'message' : 'An error occurred. Please contact your administrator.',
            'type':'error'
        }); 
        toastEvent.fire(); 
    },
    
    fireRefreshEvt : function(component) {
        var refreshEvent = $A.get("e.force:refreshView");
        if(refreshEvent){
            refreshEvent.fire();
        }
    }
    
})

Apex:
public class DataTableTestC {
    
    @AuraEnabled
    public static List<Opportunity> getOpportunities()
    {
        List<Opportunity> oppList=new List<Opportunity>();
        
        oppList=[select name,Account.Name,StageName,CloseDate,Amount from Opportunity
                 where StageName != NULL
                 AND
                 Amount != NULL
                 order by Account.Name];
        
        return oppList;
    }
    
    @AuraEnabled
    public static void updateOpportunities(List<Opportunity> oppsList){
        try
        {
            Database.update(oppsList);
            
        }
        catch(Exception ex)
        {
            system.debug(ex.getMessage());
        }
    }
    
    
    @AuraEnabled
    public static void deleteOneRec(String lstId1) {
        Opportunity op = [SELECT Id FROM Opportunity WHERE Id = :lstId1];
        delete op;
    }
}

I hope it helps you :)

Kindly let me know if it helps you and close your query by marking it as solved so that it can help others in the future. It will help to keep this community clean.

Regards,
Khan Anas
sheila srivatsavsheila srivatsav
Hi Khan

I went through the code
Its working
thanks very much