Tuesday, July 17, 2018

Salesforce Lightning Tutorial - Part 3 | Delete Records from Server



This is the third post in Lightning Tutorial Series and in this post, you'll learn about how to delete the records displayed using a lightning component. In this post, I'll be extending the code used in my first and second post so, if you are just starting or need to learn only about deleting records only do have a look at my previous posts or at least the code by having a look at my blog posts starting from here or my github repository code in update branch here.

Let's start by looking at our code files one by one and doing changes to add a delete feature to our component. 

1. Apex Controller

As you know, till now we had only two methods in our Apex Controller getContactList - used to fetch contact list from server and saveContactList - which was used to save the records from our lightning component to salesforce. Let's have a look at the below code now:-

// Apex Controller for Contact List Lightning Component
public class ContactListController {
        
    @AuraEnabled
    public static List<Contact> getContactList(List<Id> accountIds) {
        // Getting the list of contacts from where Id is in accountIds
                List<Contact> contactList = [SELECT Id, FirstName, LastName, Email, Phone, AccountId FROM Contact WHERE AccountId in :accountIds];
                // Returning the contact list
        return contactList;
    }

    @AuraEnabled
    public static Map<String,String> saveContactList(List<Contact> contactList) {
        // Forming a  string map to return response
        Map<String,String> resultMap = new Map<String,String>();
        // Adding try catch for exception handling
        try {
                // Updating the Contact List
            update contactList;
            // Setting the success status and message in resultMap
            resultMap.put('status', 'success');
                resultMap.put('message', 'Contacts Updated Successfully');        
        }
        catch(Exception e) {
            // Setting the error status and message in resultMap
            resultMap.put('status', 'error');
                        resultMap.put('message',e.getMessage());
        }
        // Returning the result string map
        return resultMap;
    }
    
    @AuraEnabled
    public static Map<String,String> deleteContactList(List<Id> contactIds) {
        //Fetching Contacts
        List<Contact> contactsToDelete = [SELECT Id FROM Contact WHERE Id in :contactIds];
        // Forming a  string map to return response
        Map<String,String> resultMap = new Map<String,String>();
        // Adding try catch for exception handling
        try {
                // Deleting the Contacts
            delete contactsToDelete;
            // Setting the success status and message in resultMap
            resultMap.put('status', 'success');
                resultMap.put('message', 'Contacts Deleted Successfully');        
        }
        catch(Exception e) {
            // Setting the error status and message in resultMap
            resultMap.put('status', 'error');
                        resultMap.put('message',e.getMessage());
        }
        // Returning the result string map
        return resultMap;                
    }
}
As you can see above that I have added one more method in our controller namely -  deleteContactList which is responsible for deleting the contact list by getting the ids of contacts which we have to delete. So, as you can see, the deleteContactList method has a single parameter i.e. a list of contact ids and then it is using those list of contact ids to fetch the contacts from the database. I have initialized a map of key and value pairs both of type string and named it resultMap as done previously in the saveContactList method. As I am using the delete keyword to delete the contacts, it only accept an SObject or a SObject List that's why I have done a SOQL query to fetch the contacts related to the ids passed in the method. If you don't want to do the SOQL query, you can pass the list of contacts in the parameter as I have done in save method. But it will be heavier to pass more data to server that's why I have limited it to list of ids only. A more better approach is to use Database.delete method which we'll learn in upcoming tutorials. I have surrounded this query within a try catch so that it can catch any kind of exception and formed a result map in both the cases with key - value pairs of my choice and returned that resultMap as a response.

2. Lightning Component

We are done with the apex controller so let's move on to the lightning component in which we have to do very little changes to make our component capable of deleting record. Let's have a look at the code below:-

<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" controller="ContactListController" access="global" >
    <!-- Handler to call function when page is loaded initially -->
    <aura:handler name="init" action="{!c.getContactsList}" value="{!this}" />
    <!-- List of contacts stored in attribute -->
    <aura:attribute name="contactList" type="List" />
    <!-- Lightning card to show contacts -->
        <lightning:card title="Contacts">
        <!-- Body of lightning card starts here -->
        <p class="slds-p-horizontal_small">
            <!-- Aura iteration to iterate list, similar to apex:repeat -->
            <div aura:id="recordViewForm">
                <aura:iteration items="{!v.contactList}" var="contact">
                    <!-- recordViewForm to view the record -->
                    <lightning:recordViewForm recordId="{!contact.Id}" objectApiName="Contact">
                        <div class="slds-box slds-theme_default">
                                <!-- inputfield checkbox used to check wether to delete the contact or not -->                            
                                <lightning:input type="checkbox" value="{!contact.Id}" label="Mark for Deletion" aura:id="deleteContact" />
                            <br />
                            <!-- outputfield used to output the record field data inside recordViewForm -->
                            <lightning:outputField fieldName="FirstName" />
                            <lightning:outputField fieldName="LastName" />
                            <lightning:outputField fieldName="Email" />
                            <lightning:outputField fieldName="Phone" />
                        </div>
                    </lightning:recordViewForm>
                    <!-- Line break between two records -->
                    <br />
                </aura:iteration>
            </div>
            <div aura:id="recordEditForm" class="formHide">
                <aura:iteration items="{!v.contactList}" var="contact">
                    <div class="slds-box slds-theme_default">
                        <!-- inputfield used to update the record field data -->
                        <lightning:input value="{!contact.FirstName}" />
                        <lightning:input value="{!contact.LastName}" />
                        <lightning:input type="email" value="{!contact.Email}" />
                        <lightning:input type="tel" value="{!contact.Phone}" pattern="\([0-9]{3}\) [0-9]{3}-[0-9]{4}" />
                    </div>
                    <br />
                    <!-- Line break between two records -->
                </aura:iteration>
            </div>
        </p>
        <!-- Lightning card actions -->
        <aura:set attribute="actions">
            <!-- Delete button added -->
            <lightning:button variant="destructive" label="Delete" onclick="{!c.deleteContacts}" />
            <!-- New button added -->
            <lightning:button label="New" onclick="{!c.newContact}" />
            <!-- Edit/Save button added -->
            <lightning:button variant="brand" label="Edit" name="edit" onclick="{!c.editContacts}" />
        </aura:set>
    </lightning:card>
</aura:component>
As you can see, in the recordView form, I have added a lightning:input tag with the following attributes:-
  1.  type="checkbox"
  2. value="{!contact.Id}" ( As the input tag is under aura:iteration with eachrecord referred by var contact )
  3. label="Mark for Deletion"
  4. aura:id="deleteContact"
  5. checked ( has a type boolean and will be true if checkbox is checked. No need to define and give an initial value to this )
So, each record will display a checkbox with label as - Mark for Deletion that means you want to mark this contact for deletion or not ? and finally we add a button that will delete all the contacts that are marked for deletion on click of a delete button that I have added in the lightning card actions section with variant as destructive, label as delete and will call the controller method deleteContacts on clicking of this button.

3. Lightning Controller

Moving on to our lightning controller, as my button is calling the deleteContacts controller method on click, so I have to define the same in my controller. Here is the code for the lightning controller:- 

({
    // Function called on initial page loading to get contact list from server
        getContactsList : function(component, event, helper) {
        // Helper function - fetchContacts called for interaction with server
                helper.fetchContacts(component, event, helper);
        },

    // Function used to create a new Contact
    newContact: function(component, event, helper) {
        // Global event force:createRecord is used
        var createContact = $A.get("e.force:createRecord");
        // Parameters like apiName and defaultValues are set
        createContact.setParams({
            "entityApiName": "Contact",
            "defaultFieldValues": {
                "AccountId": component.get("v.recordId")
            }
        });
        // Event fired and new contact dialog open
        createContact.fire();
    },

    // Function used to update the contacts
    editContacts: function(component, event, helper) {
        // Getting the button element
        var btn = event.getSource();
        // Getting the value in the name attribute
        var name = btn.get('v.name');
        // Getting the record view form and the record edit form elements
        var recordViewForm = component.find('recordViewForm');
        var recordEditForm = component.find('recordEditForm'); 
        // If button is edit
        if(name=='edit') {
            // Hiding the recordView Form and making the recordEdit form visible
            $A.util.addClass(recordViewForm,'formHide');
            $A.util.removeClass(recordEditForm,'formHide');
            // Changing the button name and label
            btn.set('v.name','save');
            btn.set('v.label','Save');
        }
        else if(name=='save') {
            // Calling saveContacts if the button is save
            helper.saveContacts(component, event, helper);
        }
    },
    
    // Function used to delete the contacts
    deleteContacts: function(component, event, helper) {
        // Calling removeContacts Helper Function
        helper.removeContacts(component, event, helper);
    }
})

You can see above that the last function is deleteContacts that is only calling the helper's removeContacts method as all the server related work, I prefer to do in helper so here also on clicking of delete button all the operations take place in helper.

4. Lightning Helper

Moving on to out lightning helper now, let's have a look at the code first and then discuss things one by one:- 

({
    // Function to fetch data from server called in initial loading of page
        fetchContacts : function(component, event, helper) {
        // Assign server method to action variable
        var action = component.get("c.getContactList");
        // Getting the account id from page
        var accountId = component.get("v.recordId");
        // Setting parameters for server method
        action.setParams({
            accountIds: accountId
        });
        // Callback function to get the response
        action.setCallback(this, function(response) {
            // Getting the response state
            var state = response.getState();
            // Check if response state is success
            if(state === 'SUCCESS') {
                // Getting the list of contacts from response and storing in js variable
                var contactList = response.getReturnValue();
                // Set the list attribute in component with the value returned by function
                component.set("v.contactList",contactList);
            }
            else {
                // Show an alert if the state is incomplete or error
                alert('Error in getting data');
            }
        });
        // Adding the action variable to the global action queue
        $A.enqueueAction(action);
        },

    // Function to update the contacts on server
    saveContacts: function(component, event, helper) {
        // Getting the contact list from lightning component
        var contactList = component.get("v.contactList");
        // Getting the recordViewForm and recordEditForm component
        var recordViewForm = component.find('recordViewForm');
        var recordEditForm = component.find('recordEditForm'); 
        // Initializing the toast event to show toast
        var toastEvent = $A.get('e.force:showToast');
        // Defining the action to save contact List ( will call the saveContactList apex controller )
        var saveAction = component.get("c.saveContactList");
        // setting the params to be passed to apex controller
        saveAction.setParams({ contactList: contactList });
        // callback action on getting the response from server
        saveAction.setCallback(this, function(response) {
            // Getting the state from response
            var state = response.getState();
            if(state === 'SUCCESS') {
                // Getting the response from server
                var dataMap = response.getReturnValue();
                // Checking if the status is success
                if(dataMap.status=='success') {
                    // Remove the formHide class
                    $A.util.removeClass(recordViewForm,'formHide');
                    // Add the formHide class
                    $A.util.addClass(recordEditForm,'formHide');
                    // Getting the button element
                    var btn = event.getSource();
                    // Setting the label and name of button back to edit
                    btn.set('v.name','edit');
                    btn.set('v.label','Edit');
                    // Setting the success toast which is dismissable ( vanish on timeout or on clicking X button )
                    toastEvent.setParams({
                        'title': 'Success!',
                        'type': 'success',
                        'mode': 'dismissable',
                        'message': dataMap.message
                    });
                    // Fire success toast event ( Show toast )
                    toastEvent.fire();            
                }
                // Checking if the status is error 
                else if(dataMap.status=='error') {
                    // Setting the error toast which is dismissable ( vanish on timeout or on clicking X button )
                    toastEvent.setParams({
                        'title': 'Error!',
                        'type': 'error',
                        'mode': 'dismissable',
                        'message': dataMap.message
                    });
                    // Fire error toast event ( Show toast )
                    toastEvent.fire();                
                }
            }
            else {
                // Show an alert if the state is incomplete or error
                alert('Error in getting data');
            }
        });
        $A.enqueueAction(saveAction);
    },
    
    // Function to delete the contacts from server
    removeContacts: function(component, event, helper) {
        // Getting the deleteContact Component
        var contactsToDelete = component.find("deleteContact");
        // Initialize an empty array
        var idsToDelete = [];
        // Checking if contactsToDelete is an array
        if(contactsToDelete.length!=undefined) {
            // Iterating the array to get contact ids
            for(var i=0;i<contactsToDelete.length;i++) {
                // If contact has delete checkbox checked, add contact id to list of ids to delete
                if(contactsToDelete[i].get("v.checked"))            
                    idsToDelete.push(contactsToDelete[i].get("v.value"));
            }            
        } else {
            // if contactsToDelete is not an array but single object, 
            // check if delete checkbox is checked and push id to list of ids to delete
            if(contactsToDelete.get("v.checked"))            
                idsToDelete.push(contactsToDelete.get("v.value"));            
        }
        // Initializing the toast event to show toast
        var toastEvent = $A.get('e.force:showToast');
        // Defining the action to delete contact List ( will call the deleteContactList apex controller )
        var deleteAction = component.get('c.deleteContactList');
        // setting the params to be passed to apex controller
        deleteAction.setParams({
            contactIds: idsToDelete
        });
        // callback action on getting the response from server
        deleteAction.setCallback(this, function(response) {
            // Getting the state from response
            var state = response.getState();
            if(state === 'SUCCESS') {
                // Getting the response from server
                var dataMap = response.getReturnValue();
                // Checking if the status is success
                if(dataMap.status=='success') {
                    // Setting the success toast which is dismissable ( vanish on timeout or on clicking X button )
                    toastEvent.setParams({
                        'title': 'Success!',
                        'type': 'success',
                        'mode': 'dismissable',
                        'message': dataMap.message
                    });
                    // Fire success toast event ( Show toast )
                    toastEvent.fire();            
                        window.location.reload();
                }
                // Checking if the status is error 
                else if(dataMap.status=='error') {
                    // Setting the error toast which is dismissable ( vanish on timeout or on clicking X button )
                    toastEvent.setParams({
                        'title': 'Error!',
                        'type': 'error',
                        'mode': 'dismissable',
                        'message': dataMap.message
                    });
                    // Fire error toast event ( Show toast )
                    toastEvent.fire();                
                }
            }
            else {
                // Show an alert if the state is incomplete or error
                alert('Error in getting data');
            }            
        });
        $A.enqueueAction(deleteAction);
    }
})

As you can see above, I have added one method namely - removeContacts at the last which is responsible for deleting the marked contacts. First of all this method is getting the component which has an aura:id of deleteContact. If component.find() finds a single component with the aura:id given then it returns that component, otherwise it returns an array of components. So we have initialized an empty array namely idsToDelete which will store the ids that we have to pass to our apex controller to delete the contacts. Then we are checking that whether the returned value of our component.find or the contactsToDelete variable is array or not. So, in javascript we can always get the length of array using [].length and if the length is undefined that means that I have a single component (single contact record) not an array.

If I have a single contact record, the else condition will be executed and I'll check that whether my checked attribute is true or false and if it's true I'll add that component to the list of ids ( array ) which is to be passed to apex controller for deletion and if I have multiple contact records, I'll traverse through the array and do exactly the same thing. This was the whole logic and the rest part is same as in saveContacts method, I have initialized a toast, and then a deleteAction which is calling the deleteContactList apex controller's method. I am passing the idsToDelete array to the method's param contactIds . In the callback function I am checking the state, if the state is success and the dataMap has also the status of success, then fire a success toast and reload the page using window.location.reload and if the state is success but status is error, then we'll fire the error toast event with the exception or error message displayed from dataMap. Otherwise if the state is not success, then I have simply displayed an alert - error in getting data. Finally I have added this action to the global action queue using $A.enqueueAction().

Congratulations..!! You have added the functionality to delete the record in your lightning component too and your final component looks like this:-

Salesforce Lightning Tutorial - Part 2 | Update and Save Records



This is the second post in Lightning Tutorial Series and in this post, you'll learn about how to update the record displayed using a lightning component and save the record. In this post, I'll be extending the code used in my first post so, if you are just starting or need to learn about updating records only do have a look at my previous post or at least the code mentioned in that here.

There are some changes in our previous code too that I'll specify while explaining the additional work. As we have already fetched the data from apex controller and displayed in our lightning component, our next step is to provide functionality to update the records and save it back to salesforce within our lightning component itself.

1. Apex Controller

Starting with the apex controller again, up-till now we had only one method in our apex controller, which was used to fetch the contact list. Now we are going to add another method in which we'll pass the contact list to be updated as a parameter and our method will update that list.

// Apex Controller for Contact List Lightning Component
public class ContactListController {
        
    @AuraEnabled
    public static List<Contact> getContactList(List<Id> accountIds) {
        // Getting the list of contacts from where Id is in accountIds
                List<Contact> contactList = [SELECT Id, FirstName, LastName, Email, Phone, AccountId FROM Contact WHERE AccountId in :accountIds];
                // Returning the contact list
        return contactList;
    }

    @AuraEnabled
    public static Map<String,String> saveContactList(List<Contact> contactList) {
        // Forming a  string map to return response
        Map<String,String> resultMap = new Map<String,String>();
        // Adding try catch for exception handling
        try {
                // Updating the Contact List
            update contactList;
            // Setting the success status and message in resultMap
            resultMap.put('status', 'success');
                resultMap.put('message', 'Contacts Updated Successfully');        
        }
        catch(Exception e) {
            // Setting the success status and message in resultMap
            resultMap.put('status', 'error');
                        resultMap.put('message',e.getMessage());
        }
        // Returning the result string map
        return resultMap;
    }
}

As you can see in the above code, I have added another method named - saveContactList which is taking List<Contact> i.e. a list of contacts as a parameter. In this method, we are forming a map which has a key value pair both of string type Map<String,String>. I have named that map resultMap. This map will contain two key-value pairs in which one will tell that the update operation has been performed successfully or not and the second one will hold the message associated with the result of the operation. This resultMap is sent to the lightning component so that it can show the success or error message to the user accordingly.

In the method, I have added try catch that are similar to any programming language and are used to catch exception. In try, I am updating the contactList using the update keyword and if it is updated successfully, I am adding the specific values in resultMap whereas in case of any exception, I am adding e.getMessage() in the map where e is an instance of class Exception and the getMessage() give the exception message as a string. Finally, I am returning the resultMap.

I have made a change in existing code in the getContactList method, you can see that FirstName and LastName are added in query instead of Name as now we have to edit the data and contact Name can be edited as first-name and last-name separately. So, we have to fetch them separately.

2. Lightning Component

Moving on to the Contact List lightning component, have a look at the below code first :-

<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" controller="ContactListController" access="global" >
    <!-- Handler to call function when page is loaded initially -->
    <aura:handler name="init" action="{!c.getContactsList}" value="{!this}" />
    <!-- List of contacts stored in attribute -->
    <aura:attribute name="contactList" type="List" />
    <!-- Lightning card to show contacts -->
        <lightning:card title="Contacts">
        <!-- Body of lightning card starts here -->
        <p class="slds-p-horizontal_small">
            <!-- Aura iteration to iterate list, similar to apex:repeat -->
            <div aura:id="recordViewForm">
                <aura:iteration items="{!v.contactList}" var="contact">
                    <!-- recordViewForm to view the record -->
                    <lightning:recordViewForm recordId="{!contact.Id}" objectApiName="Contact">
                        <div class="slds-box slds-theme_default">
                            <!-- outputfield used to output the record field data inside recordViewForm -->
                            <lightning:outputField fieldName="FirstName" />
                            <lightning:outputField fieldName="LastName" />
                            <lightning:outputField fieldName="Email" />
                            <lightning:outputField fieldName="Phone" />
                        </div>
                    </lightning:recordViewForm>
                    <!-- Line break between two records -->
                    <br />
                </aura:iteration>
            </div>
            <div aura:id="recordEditForm" class="formHide">
                <aura:iteration items="{!v.contactList}" var="contact">
                    <div class="slds-box slds-theme_default">
                        <!-- inputfield used to update the record field data -->
                        <lightning:input value="{!contact.FirstName}" />
                        <lightning:input value="{!contact.LastName}" />
                        <lightning:input type="email" value="{!contact.Email}" />
                        <lightning:input type="tel" value="{!contact.Phone}" pattern="\([0-9]{3}\) [0-9]{3}-[0-9]{4}" />
                    </div>
                    <br />
                    <!-- Line break between two records -->
                </aura:iteration>
            </div>
        </p>
        <!-- Lightning card actions -->
        <aura:set attribute="actions">
            <!-- New button added -->
            <lightning:button label="New" onclick="{!c.newContact}" />
            <!-- Edit/Save button added -->
            <lightning:button variant="brand" label="Edit" name="edit" onclick="{!c.editContacts}" />
        </aura:set>
    </lightning:card>
</aura:component>
In the existing code, I have made a small change in lightning:recordViewForm in which I have added FirstName and LastName output fields instead of Name as we are fetching the first name and last name separately using the SOQL. You can see that I have added another aura:iteration tag and a div inside it which has the same slds-box slds-theme_default class as applied earlier in the div inside lightning:recordViewForm tag. Here, we don't need lightning:recordViewForm as we are going to use lightning:input tags to add an input field. You can see two wrapper divs  here in which one has an aura:id of recordViewForm and the second one has an aura:id of recordEditForm. These are used to hide/show one form at a time whose functionality we'll implement in the controller. The recordEditForm div also has a class of formHide which keep it hidden initially. I have also added another button in the lightning card actions which has a variant of brand (blue color) and a label of Edit and has a name attribute with value edit. This method is calling the editContacts function from the lightning controller specified as onclick="{!c.editContacts}".

One last thing I want to mention in this is that I have added a pattern attribute which is associated with lightning:input tag. The pattern attribute has a regular expression pattern="\([0-9]{3}\) [0-9]{3}-[0-9]{4}" which means that the phone number can be of the form ( <3 numbers between 0-9> ) <space> <3 numbers between 0-9> - <4 numbers between 0-9> which helps us to enter a valid phone number. I don't have api version 42.0 yet but if you have that in your org, I recommend you to use lightning:recordEditForm instead of using lightning:input. lightning:recordEditForm has a very similar syntax as lightning:recordViewForm. It is used to edit the form and it's much easier as you may not have to apply regular expression in that. Give it a shot and let me know in the result in the comments section.

3. Lightning CSS

You must be wondering about the formHide class added with wrapper div of edit component. As I have told earlier this css class is used to hide the edit form initially.

.THIS {
}
.THIS .formHide {
        display: none;
}

As you can see in the above code, In the formHide class, I have added display:none so that my element is not displayed where I have applied this css. That's all for css for now.

4. Lightning Controller

Our next step is to make a lightning controller that will show/hide the recordEditForm, take updated records from recordEditForm and will pass it to the helper so that it can be saved using saveContactList method int the apex controller.

({
    // Function called on initial page loading to get contact list from server
        getContactsList : function(component, event, helper) {
        // Helper function - fetchContacts called for interaction with server
                helper.fetchContacts(component, event, helper);
        },

    // Function used to create a new Contact
    newContact: function(component, event, helper) {
        // Global event force:createRecord is used
        var createContact = $A.get("e.force:createRecord");
        // Parameters like apiName and defaultValues are set
        createContact.setParams({
            "entityApiName": "Contact",
            "defaultFieldValues": {
                "AccountId": component.get("v.recordId")
            }
        });
        // Event fired and new contact dialog open
        createContact.fire();
    },

    // Function used to update the contacts
    editContacts: function(component, event, helper) {
        // Getting the button element
        var btn = event.getSource();
        // Getting the value in the name attribute
        var name = btn.get('v.name');
        // Getting the record view form and the record edit form elements
        var recordViewForm = component.find('recordViewForm');
        var recordEditForm = component.find('recordEditForm'); 
        // If button is edit
        if(name=='edit') {
            // Hiding the recordView Form and making the recordEdit form visible
            $A.util.addClass(recordViewForm,'formHide');
            $A.util.removeClass(recordEditForm,'formHide');
            // Changing the button name and label
            btn.set('v.name','save');
            btn.set('v.label','Save');
        }
        else if(name=='save') {
            // Calling saveContacts if the button is save
            helper.saveContacts(component, event, helper);
        }
    }
})
In the above code, you can see that I have added an editContacts function which is called by clicking on the edit action button in our component. So, first of all we are getting a reference to the button using the event.getSource() function, then we are getting the value in the name attribute of that button. We'll use this name value to hide/show the form accordingly. After this line, we are getting the reference to recordViewForm and recordEditForm respectively by using component.find() in which we have to pass the aura:id of the element whose reference we want to have.

After this, I have added a simple if-else which is checking the value of the name attribute if it is edit which it was initially in the component, then it will add formHide class to the recordViewForm attribute so that on clicking edit button, recordViewForm is hidden and it will remove the formHide class from the recordEditForm attribute so that it is displayed. This functionality is accomplished using $A.util.addClass() and $A.util.removeClass() in which $A is the global instance and util is the class containing addClass() and removeClass() methods. In case of edit, the button name is changed to save and label to Save. Whereas, if the button's name is save, then it will call the helper function named - saveContacts to save the list of contacts.

5. Lightning Helper

Moving on to the last part, our Lightning Helper in which I have added a single function to save the contacts. This function will call our apex controller's saveContactList method to save the contact's list. One great thing in lightning is that as you can see in the component's code that I have used contactList attribute to pass in aura:iteration in both view and edit form so that any change in record edit form will automatically effect the contactList attribute value too i.e. the records list and we can simply pick this list and pass to apex controller to save. Let's have a look at code now :- 

({
    // Function to fetch data from server called in initial loading of page
        fetchContacts : function(component, event, helper) {
        // Assign server method to action variable
        var action = component.get("c.getContactList");
        // Getting the account id from page
        var accountId = component.get("v.recordId");
        // Setting parameters for server method
        action.setParams({
            accountIds: accountId
        });
        // Callback function to get the response
        action.setCallback(this, function(response) {
            // Getting the response state
            var state = response.getState();
            // Check if response state is success
            if(state === 'SUCCESS') {
                // Getting the list of contacts from response and storing in js variable
                var contactList = response.getReturnValue();
                // Set the list attribute in component with the value returned by function
                component.set("v.contactList",contactList);
            }
            else {
                // Show an alert if the state is incomplete or error
                alert('Error in getting data');
            }
        });
        // Adding the action variable to the global action queue
        $A.enqueueAction(action);
        },

    // Function to update the contacts on server
    saveContacts: function(component, event, helper) {
        // Getting the contact list from lightning component
        var contactList = component.get("v.contactList");
        // Getting the recordViewForm and recordEditForm component
        var recordViewForm = component.find('recordViewForm');
        var recordEditForm = component.find('recordEditForm'); 
        // Initializing the toast event to show toast
        var toastEvent = $A.get('e.force:showToast');
        // Defining the action to save contact List ( will call the saveContactList apex controller )
        var saveAction = component.get("c.saveContactList");
        // setting the params to be passed to apex controller
        saveAction.setParams({ contactList: contactList });
        // callback action on getting the response from server
        saveAction.setCallback(this, function(response) {
            // Getting the state from response
            var state = response.getState();
            if(state === 'SUCCESS') {
                // Getting the response from server
                var dataMap = response.getReturnValue();
                // Checking if the status is success
                if(dataMap.status=='success') {
                    // Remove the formHide class
                    $A.util.removeClass(recordViewForm,'formHide');
                    // Add the formHide class
                    $A.util.addClass(recordEditForm,'formHide');
                    // Getting the button element
                    var btn = event.getSource();
                    // Setting the label and name of button back to edit
                    btn.set('v.name','edit');
                    btn.set('v.label','Edit');
                    // Setting the success toast which is dismissable ( vanish on timeout or on clicking X button )
                    toastEvent.setParams({
                        'title': 'Success!',
                        'type': 'success',
                        'mode': 'dismissable',
                        'message': dataMap.message
                    });
                    // Fire success toast event ( Show toast )
                    toastEvent.fire();            
                }
                // Checking if the status is error 
                else if(dataMap.status=='error') {
                    // Setting the error toast which is dismissable ( vanish on timeout or on clicking X button )
                    toastEvent.setParams({
                        'title': 'Error!',
                        'type': 'error',
                        'mode': 'dismissable',
                        'message': dataMap.message
                    });
                    // Fire error toast event ( Show toast )
                    toastEvent.fire();                
                }
            }
            else {
                // Show an alert if the state is incomplete or error
                alert('Error in getting data');
            }
        });
        $A.enqueueAction(saveAction);
    }    })
As you can see in the above code, first of all I am getting the contact's list and then the reference to viewForm and editForm in the same way as done earlier. Then I have initialized a toast event so that I can show a success or error toast with a proper message like we have in lightning. It is a global event that we get by passing e.force:showToast in the $A.get method. I have defined a saveAction js variable that is pointing to saveContactList apex controller method. Rest steps are same as we are getting the state and then the response from apex controller method. As I have added two keys in map returned from apex controller namely - status and message. So, I am checking if dataMap.status is error then I am setting the toast with params - type : error, mode:dismissable ( it will automatically dismiss after timeout or by clicking on X button ) and a suitable title and message. Then I fired the toast event to show the error message.

Whereas if my contactList is updated and success is there in status, then it will show the recordViewForm back again, hide the recordEditForm, set the button's label and name to Edit and edit respectively and finally fire a toast with a success message to tell the user that data is updated successfully.

You have added the update records functionality to your lightning component too. Your final component looks like this when you click on edit button :-


And when you save your record by clicking on save, it changes back to it's initial form:-