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
Showing posts with label Salesforce Lightning Design System. Show all posts
Showing posts with label Salesforce Lightning Design System. Show all posts

Tuesday, 21 July 2020

Salesforce LWC Tutorial Part 1 | ToDo App Project | Designing the markup in SLDS

Hello Trailblazers,

Welcome to the first tutorial in LWC Tutorial Series. We're focusing on the concept of Learning By Doing in this tutorial series and following that, it's time to get started with our first project in LWC which is a ToDo Application. In this tutorial, we'll be designing the markup in SLDS for our ToDo application and going forward in further tutorials, we'll be adding JavaScript in our application to make it working as shown below:-


So, let's start building the above application and while doing that, we're going to see from very basic, how we can built the HTML structure by looking at a design and we'll learn about various lightning tags and what's their usage on the go.

This tutorial is published on SFDC Stop YouTube Channel. So, if you want to learn in detail, have a look at the tutorial video down below. Otherwise, you can scroll down to have a look at the code gist with explanation where we created the markup of our ToDo Application

Tutorial Video


Code Gist with Explanation

I highly recommend you to have a look at the video to understand the thinking process like:- How I proceeded with creation of this component by just having a look at the design. However, I'll be explaining the code of the same below. So, let's have a look at the code using which we created our first ToDo App LWC component. My component name is todo and the code is given below:-

HTML Snippet (todo.html)

As you can see in the above code, I have first of all created a lightning-card with a title ToDo List. I added a lightning-button-icon in the actions slot of the card with the icon of sync. I used slds icons to find out which icon I should use for this purpose. Next, I created the card body using lightning-layout and also created a paragraph which I added to the footer slot of the card.

Let's learn in detail about the card body first, inside the lightning-layout, I added a lightning-layout-item with padding as around-small in order to give equal padding from the border to all the list items and I specified the size to 12 as each row in lightning-layout can be divided into 12 columns at max, so, I wanted the card body to take/cover the maximum width available.

After that, I created an unordered list by referring to the slds code shown here and under each list item, I again created a lightning-layout as I wanted to divide the list item into two parts, one for the task name and other for the delete button which will be used to delete the task. This lightning-layout has a vertical-align property of center so that the text (task name) is aligned middle in vertical direction to look good with delete button and also, I specified a horizontal-align property to spread as I want the items in the task row i.e. the task name and delete button to spread evenly in the width available.

As specified above, each lightning-layout under the list item has two lightning-layout-item one for task name and one for delete button. I have given the padding as horizontal-small to both these lightning-layout-item so that they're properly spaced from each other and also from the corners. I again used lightning-button-icon in order to create delete button for each task.

Moving onto the footer code, I again used lightning-layout but this time I specified a property named as pull-to-boundary as small in order to make sure that the text field and the button that I am going to create should stick with their boundaries irrespective of the width. I created two lightning-layout-item inside that. Again, these two are having a padding of horizontal-small so that they're placed away from each other and from the footer corners as well.

In the first lightning-layout-item, I added the property of flexibility as grow because I want the input field to grow as the width increases, I didn't add this property to the second lightning-layout-item where I am going to place the button because I want the button to have a fixed width as it shouldn't grow depending on the container width.

Finally, I added a lightning-input field to the first layout item with a placeholder as Enter new task so that it's shown as a hint and I also specified the variant as label-hidden as I didn't want a label for this field. I also made this field required as it's important to add a task name before adding it to the todo list. For the second layout item, I just added a simple lightning-button with variant as brand as I wanted it to be blue in color. I specified the label of this button as Add, as this button will be used to add elements to the todo list.

The output of the above code i.e. the final design is shown below:-



That's all for this tutorial. I hope you liked it. All the code used in this tutorial is available on GitHub and you can have a look at that by clicking here. Make sure to have a look at the video if you want to learn in detail and let me know your feedback in the comments down below.

Happy Trailblazing..!!

Saturday, 29 December 2018

Live Session on SLDS

Hello Trailblazer,

As suggested by some of the readers and viewers of SFDC Stop Blog and YouTube channel. I have scheduled a live session on SLDS (Salesforce Lightning Design System). This session will be online tomorrow at 4 P.M. IST on SFDC Stop YouTube Channel

You can join the session directly by clicking on the link given below and also set a reminder right now for the same:-

https://www.youtube.com/watch?v=Eb84xp1sJ8U

In this session, I'll walk you through the basics of SLDS and the most commonly used components in SLDS that we need to use while working with Salesforce Lightning like:- slds grid, slds table etc. In the last 15 minutes, there will be a question and answer session during which I'll answer your queries regarding SLDS. 

During and after the session, the video will also be available below:-

Hope to see you soon.

Happy Trailblazing...!!

Sunday, 26 August 2018

Embed your custom javascript application in Salesforce using Lightning Container

Hello Trailblazers, In this post I am going to tell you, how you can embed a custom javascript application made using any Js framework like:- React Js, Angular Js etc. within Salesforce Lightning Component. This post is a follow-up for my session at Jaipur Developer Fest 2018. I am going to give you all the resources required to get started with this concept as well as tell you a brief description about Lightning Container and it's implementation. As I have worked on React Js before switching onto Salesforce, I am going to make a sample React Application for the demo. Before moving ahead, let's have a look at presentation first:-

The lightning container component hosts your custom JavaScript application in an iframe within your lightning pages. As described before, you can embed any js application in salesforce lightning component using lightning container.

What is the need of Lightning Container ?

Traditional methods focus on using an iframe to embed a third party javascript application in salesforce and use that. Below are some common questions that arises using this traditional approach :-
  1. Why host that application onto some other server ? Use iframe to connect with that even if I need to use it within Salesforce Org only ?
  2. Why develop and use custom Salesforce APIs for data sync when I have other better methods to connect with Salesforce ?
Another common reason can be:- even with the evolution of Lightning, most Salesforce Developers use custom js frameworks to build UI and move towards a better looking visualforce page. However, I'll suggest you to move to Lightning because this is the best thing you can do but still Salesforce has now given you a platform to build your application in js framework of your choice and embed that in Lightning Component to use.

Features of Lightning Container

  1. Lightning Container allows you to use an application developed in 3rd party framework like:- Angular, React Js etc. in Lightning Component.
  2. Lightning Component can handle messages as well as errors from your embedded app in it's controller.
  3. In the same way as above, the embedded javascript application can also handle messages as well as errors, from your lightning component
  4. You can also call apex from your custom lightning:container application

Lightning Container Implementation

If you want to gain more theoretical knowledge about Lightning Container, kindly refer to the above presentation as now we're going to dig deep into code and embed a sample react application in Lightning Component. As we're going to develop a react application, there are some pre-requisites for your system:-
  1. You must have nodejs installed into your system, download it from here
  2. npm (node package manager) is automatically installed when you install nodejs.
  3. Download and install git from here.
I have made a full boilerplate code for you to get started with Lightning Container along with the sample implementation of all the methods available.

Once you have nodejs and npm installed, next step is to star my github repository so that you don't lose access to it in future and clone it using the command below github repository :-

Open Git Bash or cmd and use the above command to clone my boilerplate repository into your system. Once you have my repository cloned, switch to lightning-container-boilerplate directory and hit npm install command. (To switch in windows, use cd lightning-container-boilerplate and hit enter in the command line when you have cloned the repository successfully).

npm install command will install all the dependencies needed by our react boilerplate application and also the dependencies needed by our js app to connect with Salesforce. Salesforce has provided easy to use npm modules to use in your custom js app to establish a connection between both. A brief description about the npm modules is given below:-

  1. lightning-container:- This npm module is a must have to establish the communication between your javascript application and lightning component. It consists of methods like:- sendMessage, callApex, addMessageHandler, addErrorHandler etc.
     
  2. @salesforce-ux/design-system:- This module consists of all the slds classes that we can use in our custom js application to give it a proper look and feel of lightning.
Once you have cloned the github repository and installed the dependencies, you need to hit npm start command. Once you type this and hit enter, your react application will start running on port 3000 and you'll have something like given below:-


This is our react application that we're going to embed in lightning component using lightning:container. You'll see a build/ folder into your base lightning-component-boilerplate folder. Now, we have to zip this build folder and upload it as a static resource in Salesforce. While you're uploading it to your salesforce org, just make sure that you give the static resource name exactly as lightningcontainerboilerplate. This is because our lightning component can only find our application with this name. If you see in the base (lightning-component-boilerplate) folder, we have an .env file that consists of the below content:-

As you can see above, we're specifying our static resource name in the PUBLIC_URL of our application so that it can be easily embedded. If you look at the contents of the base folder, we also have an apex folder there. That apex folder consists of all the apex code that we need to add into our Salesforce Org. If you explore the apex folder more, you'll see the src folder in that in which further will consist of 2 folders:- aura and classes. In the aura folder, I have made a lightning component named lightningcontainerboilerplate and to test that, I have made a lightning application too named lightningcontainerboilerplateapp. In the classes folder, I have a single class named LightningContainerBoilerplate which is called in our custom js application directly as an apex call.

Now, before moving to the code in custom js application, we'll have a look at the Salesforce code first.

LightningContainerBoilerplate.cls

Starting from the apex class, let's have a look at the below code:-

As you can see above, we have a single function in this class which returns a String and has a @RemoteAction annotation that takes a single parameter called name and insert a contact with the LastName of contact equal to the name passed in the parameter. After the contact is inserted, we're returning a string consisting of message - Contact inserted successfully along with the contact id. This method must have a remote action annotation then only we'll be able to call it from our custom js application and it should be global and static too.

LightningContainerBoilerplateApp.app

Now let's have a look at our app, as you can see that we have just called our lightningcontainerboilerplate component and our app is extending force:slds so that we can easily apply and use slds in our lightning component.


LightningContainerBoilerplate.cmp

Let's have a look at the code below:-

As you can see above, I have made a simple lightning component that I can embed in lightning pages, I have made a single attribute named myMessage which is of type String and displays the message received from the custom js react application. Then I have made two lightning cards, one has a title Lightning Component and other has a title JavaScript Application.

Lightning Component

I have divided the Lightning Component card into 3 parts as follows:-
  1. In first part, I am displaying myMessage attribute using {!v.myMessage}. This is the message received from react application.
  2. In the second part, I am displaying an input field with a label of Input Message and an aura:id as inputMessage in which I can input the text to send to my react js application.
  3. In the third part, I have made a simple button with a label Send Message which is calling sendMessage controller function on click and is responsible to send the message to the react application.

JavaScript Application

In this card, I have simply called lightning:container tag in which I have given a class named containerClass which we'll define in css. We've given it an aura:id of jsContainer and in the src attribute, I need to give the reference to static resource. So, I have given it as {! $Resource.lightningcontainerboilerplate + '/index.html' }. There are two more attributes named as onmessage and onerror in which I need to call some controller functions as the onmessage attribute controller function will be called when a message is received from react app and onerror attribute controller function will be called when there is an error while receiving message from the js container. I have specified handleMessage controller function in the onmessage attribute and handleError controller function in the onerror attribute.

LightningContainerBoilerplate.css

As you can see above, I have defined the containerClass here used in lightning:container tag and specified a height of 400px for my custom js react application.

LightningContainerBoilerplateController.js

Now moving on to controller, let's have a look at the code below:-

As you can see above, handleMessage and handleError functions are responsible to handle the message and error from custom js application and were called from lightning:container attribute. Whereas, the sendMessage function takes the value of inputMessage (aura:id) input field and then send this value as a message to our react application using component.find('jsContainer').message(inputMessage); where jsContainer is the aura:id of lightning:container and message() is in the syntax to send any message to the container application.

React Js Application

Now, let's understand our sample react application that we used in this tutorial. This react application is made using create-react-app command. create-react-app is an npm module that you can install globally by using the command below:- 


You can find more information about this npm module here. You can create a new react application by using create-react-app as follows:-

create-react-app <application-name>

If you create any application using the above command and then move to that app's folder and run npm start, you'll see the below page:-


I have only modified the code in app.js file and the updated code is given below:-

As you can see in the above code,

  1. First of all, we've imported the lightning-container module and also the css file of slds from @salesforce-ux/design-system module
  2. In the constructor, I have initialized the state with two keys:- messageReceived and messageToSend. A state is basically a javascript object that we can use for interaction within a component. The messageReceived object mainly consist of a type and a value so that we can use the same object to display different types of messages received (success and error). I have also binded all the functions used in my application. It is a react syntax to bind all the functions to use them.
  3. In the componentDidMount() which is like an init method for react and is called as the component is mounted, I have called addMessageHandler and addErrorHandler methods from the LCC package and passed the handleMessage and handleError methods in them to specify the methods that'll be responsible to handle the message and error respectively coming from lightning component.
  4. The handleMessage method is receiving the message in a variable named successMessage, creating a javascript object with a type - success and value with the message received in successMessage variable.I am setting the messageReceived in state with the newly created messageReceived object using this.setState({ messageReceived: messageReceived }); 
  5. Similarly in the handleError method, I am setting the messageReceived in state in the same way, the only difference is here the object has a type of error.
  6. Next we have sendMessage() which is calling LCC.sendMessage() that can pass a javascript object as it's parameter. Here, our javascript object consists of two things, a name and a value where I have hardcoded name as MyMessage and value is this.state.messageToSend i.e. the value of this key in the component state. This value I am going to set as I take input in the input field.
  7. Now, let's move down to the HTML code returned inside render() It has slds applied and consists of simple title and then a form which consists of an input field for entering the Last Name and two buttons, one to send a message to lightning component and other to save contact. Both buttons use the input provided in the last name field.
  8. On change of input in the lastname field, I am calling this.handleInputChange. In this method, we're getting the value of input field using event.target.value and storing it in message variable and I am setting the messageToSend in state with that message variable using this.setState({ messageToSend: message }); 
  9. The Save Contact button is an input of type submit on click of which, the form is submitted and in the form tag, we're calling this.callApexController method onSubmit of that form. If you see the callApexController method, I am preventing the default behaviour of form submission using event.preventDefault and then I am using LCC.callApex() to call the apex controller directly from this JS application.
  10. In LCC.callApex I am passing 4 parameters, first is the class and method name LightningContainerBoilerplate.createNewContact, the second is this.state.messageToSend which I have set on change in Last Name input before. The third is this.handleApexReturnValue which is the callback method to be called when we receive a response from apex controller and the fourth is {escape:true} which is to maintain security to escape special characters so that no malicious js code is passed in response from apex.
  11. In the handleApexReturnValue method, we're getting two parameters as specified by salesforce, first is the result which is the value returned by our apex controller method and second is the event which is used to get the status of apex call. Next, I have created a variable named messageReceived and checking if the event.status is true then the messageReceived is equal to an object with type apexsuccess and value as result received from apex controller method. Otherwise if event.type is exception, then we have an object with type as apexerror and the value as event.message concatenated with event.where else we have an object with type as apexerror and value as event.message only as it is not an exception but any other unknown error. Finally, I am setting the messageReceived state with messageReceived object. 
  12. In the HTML code, below the form, we're checking the this.state.messageReceived.type and displaying different messages according to the state and then the value as this.state.messageReceived.value

If you make any changes, then to create a production build, you again open another terminal and move to the same lightning-container-boilerplate directory and run npm run build command. This will create a production build for application that we can upload as a static resource in our Salesforce Org and use it from there to embed in our lightning component. Once you've created a production build, you'll see a build/ folder into your base lightning-component-boilerplate folder. Now, we have to zip this build folder and upload it as a static resource in Salesforce.

Just make sure that, you append the above code in the manifest.json file generated in the build folder generated after creating a production build. If there is no such file, create one and add this code surrounded by { } which is the standard format of json file. This is required to specify that our custom js app is dealing with the apex controller which is mentioned in the code above and I have specified the apex-controller as LightningControllerBoilerplate which is the one I am using.

I hope you liked this blog and if you want this session to happen again or want a video about the same let me know in comments. Sharing a picture of my session at JDF below:-



Sharing my twitter moment below:-


And I am also embedding my first VLOG made at #JDF18. Do watch it and let me know if you liked it or any suggestions for further VLOGs :P



Happy Trailblazing..!!

Friday, 2 March 2018

Salesforce Lightning Tutorial - Part 5 | Adding Validations

Welcome to the 5th tutorial in the Salesforce Lightning Tutorial Series. In this post, you'll learn about how you can validations in custom lightning component. I'll be extending the code used in my previous posts so, if you are just starting or need to learn only about applying validations 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 create 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 begin by adding validations in our Lightning Component. Here our main focus is on client side validations only so all this validation part will be handled by making changes only in the Lightning Component and the Lightning Controller.

1. Lightning Component

Let's have a look at the code below and then I'll highlight the changes.
<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>
    <!-- Method to validate new contact -->
    <aura:method name="validateContact" action="{!c.validateContact}" />
    <!-- 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}" />
                        <!-- Validation added -->
                        <lightning:input aura:id="fieldToValidate" value="{!contact.LastName}" messageWhenValueMissing="Contact's Last Name is Mandatory" required="true"/>
                        <lightning:input type="email" value="{!contact.Email}" />
                        <!-- Validation added -->
                        <lightning:input aura:id="fieldToValidate" messageWhenPatternMismatch="Please enter the number in this pattern - (XXX) XXX-XXXX" 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">
                    <!-- Validation added -->
                    <lightning:input aura:id="formFieldToValidate" label="First Name" messageWhenValueMissing="Contact's First Name is Mandatory" required="true" value="{!v.contact.FirstName}" />
                    <!-- Validation added -->
                    <lightning:input aura:id="formFieldToValidate" label="Last Name" messageWhenValueMissing="Contacts's Last Name is Mandatory" required="true" value="{!v.contact.LastName}" />
                    <!-- Custom Validation added -->
                    <lightning:input aura:id="formFieldToValidate" label="Email" name="emailField" 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>
I have made changes in mainly the edit form and the new record form i.e. the modal which is used to create a new contact. The edit form is mainly the form with aura:id as recordEditForm. In the edit form, focus on the fields with <!-- Validation added --> comments, the first field is the Last name field, as the last name of a contact is required so I have set the required attribute to true and there is another attribute named messageWhenValueMissing i.e. this message will be displayed as a validation error when value of required attribute is missing. So, I have provided a message for the same as - Contact's Last Name is Mandatory. Another validation is for the phone field in which we added a pattern, when we created this form so we have to add that type of validation error message in the respective attribute. So, the attribute used here is messageWhenPatternMismatch and a friendly message is given that will be displayed when this attribute doesn't follow the pattern specified. One more thing to notice is that all the fields that are marked for validation has the same aura:id as fieldToValidate. <lightning:input> allows us to add validations in all fields at once, so I have given the same aura id to access all as an array. You'll see its usage when we move on to controller. The same procedure is applied to add validations in fields included in modal. In those fields, one field has <!-- Custom Validation added --> comment in which there is no particular message as we are going to use it to add custom validation. I have also added an aura:method attribute with name as validateContact and action as {!c.validateContact} which is the function that we have defined in controller. This function will be called from controller itself, that's why we need to specify it using aura:method tag to have it's definition in component so that it can be called using the component reference as component.validateContact(); | general syntax - component.<aura method name attribute>();

2. Lightning Controller

Moving on to the last part of our validation i.e. Lightning Controller, let's have a look at the code below before discussion.
({
    // 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') {
            // Getting the edit form fields to validate
            var contactFields = component.find("fieldToValidate");
            // Initialize the counter to zero - used to check validity of fields
            var blank=0;
            // If there are more than 1 fields
            if(contactFields.length!=undefined) {
                // Iterating all the fields
                var allValid = contactFields.reduce(function (validSoFar, inputCmp) {
                    // Show help message if single field is invalid
                    inputCmp.showHelpMessageIfInvalid();
                    // return whether all fields are valid or not
                    return validSoFar && inputCmp.get('v.validity').valid;
                }, true);
                // If all fields are not valid increment the counter
                if (!allValid) {
                    blank++;
                }
            } else {
                // If there is only one field, get that field and check for validity (true/false)
                var allValid = contactFields;
                // If field is not valid, increment the counter
                if (!allValid.get('v.validity').valid) {
                    blank++;
                }
            }
            // Call the helper method only when counter is 0
            if(blank==0) {
                // 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) {
        var isContactValid = component.validateContact(component, event, helper);
        if(isContactValid) {
           helper.insertContact(component, event, helper);
        }
    },

    // Function to validate new contact - Aura method used for the same
    validateContact: function(component, event, helper) {
        // Getting all fields and iterate them to check for validity
        var allValid = component.find('formFieldToValidate').reduce(function (validSoFar, inputCmp) {
            // Show help message if single field is invalid
            inputCmp.showHelpMessageIfInvalid();
            // Get the name of each field
            var name = inputCmp.get('v.name');
            // Check if name is emailField
            if(name=='emailField') {
                // Getting the value of that field
                var value = inputCmp.get('v.value');
                // If value is not equal to rahul@gmail.com, add custom validation
                if(value != 'rahul@gmail.com') {
                    // Focus on that field to make custom validation work
                    inputCmp.focus();
                    // Setting the custom validation
                    inputCmp.set('v.validity', {valid:false, badInput :true});
                }                
            }
            // Returning the final result of validations
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
        // Returning Validate contact result in boolen
        return allValid;
    }

})
In the above code, I have made some changes in the editContacts and createContact function, and also added a new function named validateContact for which we used the aura:method tag in the component. Starting with the editContacts  function, earlier in this, in the save section we called helper.saveContacts() and performed all the server related tasks there. But now we have to validate the fields first. In save section, First of all, we get all the fields with aura id fieldToValidate and store it in variable contactFields. Now, it may be possible that component.find return a single element too if there is only one field on which the validation is applied. So, if the contactFields is an array (checked by length!=undefined as an array should have a length). We are applying the reduce function to contactFields which is a javascript method in which we iterate each element of array and here we refer it by inputCmp.

The reduce function takes 2 parameters:- first the function whose result we have to return and 2nd the initial value or initial result. So, initially we have assumed that all fields are valid so we have given true as the 2nd parameter and in the first parameter, the function will take two parameters, first the result till now and 2nd the current element and the return value of this function will further be passed to the same function in next iteration in the first parameter (result till now). We have 2 variables validSoFar and inputCmp. If you want more detailed explaination for the reduce function(), I am sharing a link here. Inside the function which is passed as first argument to reduce, first we called inputCmp.showHelpMessageIfInvalid(); this is a predefined method that will show our error message when we click on save button if the particular inputCmp is invalid and then we simply returned our validSoFar && inputCmp.get('v.validity').valid this means there are 2 possibilities, either our result from the previous iteration was false so validSoFar is false and the result will be false again. If all the fields till now are valid then we and the current result with the validity of current inputCmp therefore, if the inputCmp is not valid, the result of AND operation will be false and this false result is passed to the next iteration as the validSoFar parameter. We have a counter named blank whose value is incremented if all fields are not valid. Similarly if the component.find() doesn't return an array, we checked it's valid attribute i.e. if it returns false, then we further increment the blank counter ( you can check this by removing the fieldToValidate aura id from one of the two input fields as this will lead to only one field left with that aura id and the condition will be executed ). Finally, if the blank counter has a value 0, this means that all our fields are valid. Therefore, saveContacts() method of helper is called and the contacts are saved. Till now we have worked on applying validations to the edit form which looks like this:-

Custom Validation

Moving on to our createContact() function, we called the validContact() function that is also defined in the controller itself using component.validContact(component, event, helper). This is possible only because we have added aura:method tag in our lightning component. So, let's explore our validateContact function now and see what's there. It's much similar to out previous solution, in this also, we are calling component.find() on input fields with id formFieldToValidate. If you remember in the lightning component, I have given formFieldToValidate aura id to inputs that were in the modal which is mainly used to create a new Contact. Here I am not checking for array or single element as I know I have more than one fields with same aura:id so definitely, component.find() will return an array. I again called the showHelpMessageIfInvalid(). Now, I have to add custom validation on field with name emailField so I get the name of field using inputCmp.get("v.name") and if name equals emailField I am going to show an error if it's value is not equal to rahul@gmail.com this is just an example of custom validation you can add any other condition. So, I get the value of field using inputCmp.get("v.value") and if this value is not equal to rahul@gmail.com I focused on the inputCmp using inputCmp.focus() and set the validity attribute of inputCmp to {valid:false, badInput :true}. The validity attribute looks like this:-


This means that I am making the validity attribute of that field invalid and the reason is badInput as I am making that true and finally I am returning the AND of  validSoFar and current input fields validity and the default value is true just like before. But if you notice the contactList component, in the modal email field, we haven't given any attribute and it's value like :- messageWhenBadInput="" so the default message will be displayed i.e. Enter a valid value.So, in this way, we can add custom validations in our lightning component and if you are wondering about that inputCmp.focus(); line so it's necessary to focus the particular field to show the custom validation error message to appear. You can remove this line and give it a try, in that case, you have to manually focus it to show the error message. Actually, the lightning:input tag is still in beta version so there maybe any further advancements possible to make custom validations more easier. If you want to learn more about the various validations we can apply and the respective attributes for lightning:input, you can find them in the official doc here. Just scroll down to the error messages part while viewing the same. Also, if you came across a better approach, feel free to share it in comments section below. We have applied custom validations in the modal section and it looks 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 use lightning events hopefully as most of the people were demanding for a tutorial on that. 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 validate branch to get the code specified here and not the future changes. Otherwise, directly go to the validate branch by clicking here.

Congratulations..!! You have completed all the 5 parts in Salesforce Lightning Basics Tutorial Series. Now before moving to the next step i.e. Salesforce Lightning Events Tutorial Series. Let's have a look at a small thank you gift - sObject Convertor that I have made for you for supporting SFDC Stop upto this level.

Happy Trailblazing..!!

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..!!

Saturday, 10 February 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:-


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 create new records using our lightning component and custom ui instead of calling standard create record global event. 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 delete branch to get the code specified here and not the future changes. Otherwise, directly go to the delete branch by clicking here.

Let's move on to Lightning Tutorial Part 4 now in which you'll learn how you can create a new record using your own custom lightning component and save the record to salesforce.

Happy Trailblazing..!!