SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel

Wednesday 21 February 2018

Salesforce Lightning Tutorial - Part 4 | Create new Records

Welcome to the 4th tutorial in the Salesforce Lightning Tutorial Series. In this post, you'll learn about how you can create a new record using our custom lightning component and save it to salesforce. I'll be extending the code used in my previous posts so, if you are just starting or need to learn only about creating new 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 delete branch here so that you can understand the progress till now and the further additions that I'll do in this post.

So, let's start coding in each and every file one by one and make our lightning component more amazing.

1. Apex Controller

As usual, starting from the apex controller, I have created a new function to insert new contact.
// 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;                
    }

    @AuraEnabled
    public static Map<String, String> createContactRecord(Contact newContact) {
        // Forming a  string map to return response
        Map<String,String> resultMap = new Map<String,String>();
        // Adding try catch for exception handling
        try {
            // Inserting the Contact
            insert newContact;
            // Setting the success status and message in resultMap
            resultMap.put('status', 'success');
            resultMap.put('message', 'Contact Inserted 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;        
    }
}
You can see in the  above code, the last function is the latest one that I added named createContactList which is very similar to the other functions above. It take a single contact as a parameter and insert that contact as we are going to insert only one contact at a time. It has a @auraenabled annotation so that this method can be called from lightning component. I am returning a resultMap as a response which is a map of <String, String>. If the contact is inserted, the status key is success and if there is any exception caught by try catch, the status key is error and message key has a value accordingly. We return this resultMap so that correct response can be shown to our user from lightning component.

2. Lightning Component

Moving on to our lightning component, we are going to create a modal now which will be called containing the form to create a new contact. Let's have a look at the 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" />
    <!-- New Contact Object -->
    <aura:attribute name="contact" type="Contact"
    default="{
        'SObjectType': 'Contact',
        'FirstName': '',
        'LastName': '',
        'Email': '',
        'Phone': ''
    }">            
    </aura:attribute>
    <!-- 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">
            <!-- New contact modal button added -->
            <lightning:button name="contactModal" label="New Contact" onclick="{!c.openModal}" />
            <!-- 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>
    <!-- Contacts Modal Section -->
    <div>
        <section aura:id="contactModal" role="dialog" tabindex="-1" aria-labelledby="contactModalHeading" aria-modal="true" aria-describedby="contactModalBody" class="slds-modal">
            <!-- Modal Container -->
            <div class="slds-modal__container">
                <!-- Modal Header ( consists of close button and heading of modal ) -->
                <header class="slds-modal__header">
                    <lightning:buttonIcon class="slds-modal__close" alternativeText="Close" iconName="utility:close" onclick="{!c.closeModal}" variant="bare-inverse" size="large"></lightning:buttonIcon>
                    <h2 id="contactModalHeading" class="slds-text-heading_medium slds-hyphenate">New Contact</h2>
                </header>
                <!-- Modal Body ( consists of form ) -->
                <div class="slds-modal__content slds-p-around_medium" id="contactModalBody">
                    <lightning:input label="First Name" value="{!v.contact.FirstName}" />
                    <lightning:input label="Last Name" required="true" value="{!v.contact.LastName}" />
                    <lightning:input label="Email" value="{!v.contact.Email}" />
                    <lightning:input label="Phone" value="{!v.contact.Phone}" />
                </div>
                <!-- Modal Footer ( consists of cancel and save buttons ) -->
                <footer class="slds-modal__footer">
                    <lightning:button onclick="{!c.closeModal}" variant="neutral">Cancel</lightning:button>
                    <lightning:button onclick="{!c.createContact}" variant="brand" >Save</lightning:button>
                </footer>
            </div>
        </section>
        <!-- Modal Backdrop -->
        <div aura:id="contactModalBackdrop" class="slds-backdrop"></div>
    </div>
</aura:component>
As you can see, I have added a new attribute whose name is contact and type is also Contact and it has a default value that consists of all the fields, with empty value as I am going to bind value to these fields using my modal fields and pass this contact object as an attribute to the ContactListController function that we just created to create new contact. I have added a new buttons in the lightning card actions section. The button has a label of New Contact and will call the openModal function from our lightning controller.

Apart from all this, I have added a modal which will popup when we click on New Contact button and we can fill in the details of the new contact and save it to the server. The modal code is present after the lightning card.The modal container mainly consists of 3 sections:- Modal Header, Modal Body and Modal Footer. The header mainly consists of a close button and a heading of New Contact. The body consists of 4 input fields for firstname, lastname (kept as required) , phone and email of contact respectively in which the value is binded with the contact attribute fields like in case of FirstName, the value is {!v.contact.FirstName} and the footer consists of cancel and save buttons. The whole code for the modal is referred from slds. I have made the necessary changes like:- converting html buttons to lightning buttons and adding input fields. There is also a separate div with aura id of contactModalBackdrop which is used to add a backdrop to the page when we open the modal.

3. 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);
    },

    // Function used to open the contact modal
    openModal: function(component, event, helper) {
        var modal = component.find("contactModal");
        var modalBackdrop = component.find("contactModalBackdrop");
        $A.util.addClass(modal,"slds-fade-in-open");
        $A.util.addClass(modalBackdrop,"slds-backdrop_open");
    },

    // Function used to close the contact modal
    closeModal: function(component, event, helper) {
        var modal = component.find("contactModal");
        var modalBackdrop = component.find("contactModalBackdrop");
        $A.util.removeClass(modal,"slds-fade-in-open");
        $A.util.removeClass(modalBackdrop,"slds-backdrop_open");
    },

    // Function used to create new contact
    createContact: function(component, event, helper) {
        helper.insertContact(component, event, helper);
    }
})
In the lightning controller, I have added 3 functions mainly to open the contact modal, close the contact modal and create a new contact. If you see, I am toggling two classes, first is slds-fade-in-open  which is used to display the modal when this class is applied to the modal element and will hide the modal when this class is missing. The same case is with slds-backdrop_open class which is mainly used in backdrop to show/hide it when modal is opened or closed. Apart from these, the createContact function is simply calling our insertContact helper function which will communicate with the apex controller to insert the contact.

4. Lightning Helper

So, the last thing is Lightning Helper. Let's have a look at the code first, I have added a new function at last named insertContact :-
({
    // 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');
            }
        });
        // Adding the action variable to the global action queue
        $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');
            }            
        });
        // Adding the action variable to the global action queue
        $A.enqueueAction(deleteAction);
    },

    // Function to create new contacts on server
    insertContact: function(component, event, helper) {
        var contact = component.get("v.contact");
        contact.AccountId = component.get('v.recordId');
        // Initializing the toast event to show toast
        var toastEvent = $A.get('e.force:showToast');
        var createAction = component.get('c.createContactRecord');
        createAction.setParams({
            newContact: contact
        });
        createAction.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');
            }
        });
        // Adding the action variable to the global action queue
        $A.enqueueAction(createAction);
    }
})
As you can see, first of all we get the contact from the contact's attribute. Then we assign the AccountId of the contact with the records id as this lightning component is going to be embedded within an account's detail page and the contact we create should be associated with that account. Then we initialize a toast and a createAction which is going to call createContactList method of apex controller.We set the parameter as the contact which we get by our lightning component. If the state of response is success we check the status and show the message using toast and if the response is not success, we simply show an alert with the message error in getting data following the previous procedure as we have done till now. Finally we add the action to the global action queue.

We have added another interesting feature that helps us to create a new contact using our lightning component, and our final output is like this:-

Tired of reading or just scrolled down..!! Don't worry, you can watch the video too.

If you liked this post then do follow, subscribe, comment your views or any feedback and share it with everyone in your circle so that they can also get benefit by this. Hope to see you next time too when we'll learn how to add client side validations using JavaScript to our lightning component. For the whole code at one place, please refer to my github repository here. You can fork this repo and do your own changes. However please switch to create branch to get the code specified here and not the future changes. Otherwise, directly go to the create branch by clicking here.

Let's move on to Lightning Tutorial Part 5 now in which you'll learn how you can add validations to the form which is displayed in custom lightning component.

Happy Trailblazing..!!

8 comments:

  1. Replies
    1. Hi, I recommend you to send me a DM on twitter instead because I am not regular in responding to emails. Please let me know if you still need email. On twitter you can find me here :- https://www.twitter.com/rahulcoder

      Delete
  2. Hi. But how to set a condition for a field in a modal window and, if it is not executed, show a message about incorrect data?

    ReplyDelete
    Replies
    1. You can handle it in javascript on form submit. Depends on which type of form you've made and are using.

      Delete
  3. Hi. I made an analogous code according to your sample, but the success message is not showing. What could be the reason?

    ReplyDelete
    Replies
    1. Hi Yasperson, check out your console if you've received any error there. Also, add a console.log('test data'); to the js code before the toast to confirm whether you're actually reaching there or not. Make sure you've initialized the toast properly before calling out.

      Delete
  4. Hi. I made a similar code according to your example, but the success message is not displayed. What could be the reason?

    ReplyDelete