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

Sunday 1 April 2018

Salesforce Lightning Events Part 2 - Building Component Events

Now, we are going to see component event in action and for this, let's start by creating a Lightning App to calculate the total income out of all income records in the table. To make this app let's have a look at the code below:-
<!-- Lightning Application extending slds -->
<aura:application extends="force:slds">
    <!-- Calling the container component -->
    <c:LightningEventsCompContainer />
</aura:application>
You can see above that our application is extending the force:slds library so that we can use the Lightning Tags following slds styling and I have invoked a single component in this app whose name is - LightningEventsCompContainer. This is our main container component which will contain 1 or more components. We'll read about this below in the post but for now you only need to know that this component is calling another Lightning Component named - LightningEventsComp1 which we are going to discuss now:-

LightningEventsComp1.cmp - Lightning Component

Our Lightning Component will look like this:-


This lightning component consists of the below code:-
<!-- Source Lighntning Component -->
<aura:component>
    <!-- Attributes for lightning:datatable -->
    <aura:attribute name="incomes" type="Object"/>
    <aura:attribute name="mycolumns" type="List"/>
    <!-- Registering the Lightning Event -->
    <aura:registerEvent name="totalIncomeComponentEvent" type="c:LightningComponentEvent"></aura:registerEvent>
    <!-- Defining a handler for lightning event -->
    <aura:handler name="totalIncomeComponentEvent" event="c:LightningComponentEvent"  action="{!c.handleRegisteredComponentEvent}"></aura:handler>
    <!-- Defining init handler for lightning component -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <!-- Lightning card section begins -->
    <lightning:card title="SFDC Stop Income Calculator" iconName="standard:account">
        <!-- Lightning card actions -->
        <aura:set attribute="actions">
            <!-- Action to toggle display of income form -->
            <lightning:button label="Show/Hide Income Form" onclick="{!c.toggleIncomeForm}" />
        </aura:set>
        <!-- Body of Lightning card -->
        <p class="slds-p-horizontal_small">
            <!-- Income form to add new income record to the table -->
            <div aura:id="incomeForm" class="hide">
                <lightning:Input aura:id="source" name="source" required="true" label="Income source"></lightning:Input>
                <lightning:Input aura:id="amount" name="amount" required="true"  label="Amount"></lightning:Input>
                <br />
                <lightning:button name="addIncome" label="Add Income" onclick="{!c.addIncome}"  variant="brand" ></lightning:button>
            </div>
            <br />
            <!-- Lightning datatable used to show income records -->
            <lightning:datatable data="{! v.incomes }" 
                columns="{! v.mycolumns }" 
                keyField="sno"
            />
            <br />
            <!-- Button used to fire the event that will pass total income to the application root -->
            <lightning:button name="addIncome" label="Calculate Total Income" onclick="{!c.fireTotalIncomeComponentEvent}"  variant="brand" ></lightning:button>
        </p>
    </lightning:card>
</aura:component>
As you can see in the above code, I have created two attributes, first attribute has the name income and is of type Object whereas the second attribute has the name mycolumns and is of type List. In this tutorial am going to make a simple table which will consist of the income source and it's amount.

Registering the Event

I have used the aura:registerEvent tag. This tag is used to register a new event and the name of the event is given as totalIncomeComponentEvent, this name should be same wherever we are going to reference this event. For the type, we have to refer to the actual event file, so I  have given the type of event as c:LightningComponentEvent as I am going to create a file with this name to define the event.

Handling the Event

The next tag is aura:handler which is used to handle the event we defined above. It's name is same as the name given in the registerEvent tag, the event attribute will consists of the reference to actual event file which is c:LightningComponentEvent and finally, the action attribute consists of the handleRegisteredComponentEvent function which is defined in the Lightning Controller and will be called as the event is fired. The next tag consists of the init handler which as we know will call it's action function as the component is initialized.

For this component, I have made a lightning:card with a suitable header and standard account icon as you can see in the image too. This card consists of a single action button which is used to toggle Income Form visibility by calling the lightning controller function toggleIncomeForm. In the card body, we have an income form which is hidden initially so, it is associated with a class hide that we'll define in css file. The income form consists of 2 lightning:input fields and 1 lightning:button and below the Income Form, we have a lightning:datatable tag which is used to make a table of income entries and finally a button named Calculate Total Income which is used to fire event to calculate the total income.

The two fields in the income form, are used to enter the income source and amount respectively and the button in the income form with label Add Income is used to call the addIncome function in controller and will add the income specified in the form to our datatable. The Calculate Total Income button below the table is used to fire the event we have registered and this task is done by calling fireTotalIncomeComponentEvent function of lightning controller. Below is the small introduction to lightning:datatable if you already know about this, you can skip ahead to event part.

lightning:datatable 

As I have used lightning:datatable in my component, I want to give a brief introduction to that. lightning:datatable tag is used to make a table by providing the table data in the compatible format. It is designed to deal and show the data of any sObject and has a number of features we can use. As in this section we are not going to deal with any apex controller, so I am going to pass static data to this table and update it accordingly. As you can see, in the datatable tag, I have given 3 attributes:-
  1. data -  This should be of type object and consists of data that is to be displayed in the table. Each record consists of key value pairs in which key is the fieldName same as defined in columns of table and value is the value of that field for that particular record.
  2. columns -  This should be of type list and consists of all columns with label along with their type and fieldName attributes.
  3. keyField - This is a required attribute to render the table and also helps in making the performance better. It associates each row with a unique id.
So, that's all with the lightning component, before moving to the controller let's have a look at defining a lightning event.

LightningComponentEvent.evt - Lightning Event

A lightning event can be considered as an envelope of information that you can pass from one component to another. Let's have a look at the code below:-
<!-- Defining a component event in Salesforce Lightning -->
<aura:event type="COMPONENT">
    <!-- Defining the totalIncome attribute of event with type decimal -->
    <aura:attribute name="totalIncome" type="Decimal"></aura:attribute>
</aura:event>
So, as you can see above, I have used aura:event tag to define an event. Each event has a type attribute in which we have to specify that whether it's a component or application event. I have given the attribute value as COMPONENT as we are dealing with component events now. To keep it simple. I have used a single attribute in this event. It's name is totalIncome and it's type is Decimal. This means that this event can carry one decimal value with it and traverse through it's path between components.

So, we have defined our event. Let's move on to lightning controller now in which we'll fire this event and define handler for this event.

LightningEventsComp1Controller.js - Lightning Controller

Moving on to the controller, before discussing, let's have a look at the code below:-
({
    // Init handler used to initialize lightning:datatable
    doInit : function(component, event, helper) {
        // Defining the columns to be used in lightning:datatable
	component.set('v.mycolumns', [
            {label: 'SNo.', fieldName: 'sno', type: 'number'},
            {label: 'Name of Source', fieldName: 'source', type: 'text'},
            {label: 'Amount', fieldName: 'amount', type: 'number'}
        ]);
        // Setting the incomes object with initial data to be included in datatable
        // Note tht fieldName in columns above are used as key values while inserting records
        component.set('v.incomes', [{
            sno: 1,
            source: 'Regular Job',
            amount: 10000
        },
        {
            sno: 2,
            source: 'Part Time Job',
            amount: 2000
        }]);
    },
	
    // Function to handle Lightning Event fired from this component itself.
    handleRegisteredComponentEvent: function(component, event, helper) {
	alert('Event handler at source component that fired the event.');
    },

    // Function to toggle Income Form visibility
    toggleIncomeForm: function(component, event, helper) {
	// Getting the income form reference
	var incomeForm = component.find('incomeForm');
	// Toggling the class of incomeForm to show/hide the form
	$A.util.toggleClass(incomeForm, 'hide');
    },

    // Function to add new income in the existing table data
    addIncome: function(component, event, helper) {
    	// Getting all the income records currently present
    	var incomes = component.get('v.incomes');
    	// Forming a new income record by taking values from the income form
    	var newIncome = {
	    sno: incomes.length + 1,
	    source: component.find('source').get('v.value'),
	    amount: parseFloat(component.find('amount').get('v.value'))
	};
	// Check if the new income record has value in each field if yes, push it to the list of existing income records
	if(newIncome.source!='' && newIncome.amount!='' && newIncome.source!=null && newIncome.amount!=null)
            incomes.push(newIncome);
	// Set the income table records attribute with the new value
	component.set('v.incomes', incomes);
        // Empty the fields in the income form
	component.find('source').set('v.value','');
        component.find('amount').set('v.value','');
    },

    // This method is used to fire the income lightning event
    fireTotalIncomeComponentEvent: function(component, event, helper) {
	// Getting all the income records from the list
	var incomes = component.get('v.incomes');
	var totalIncome = 0;
	// Calculating the total income by adding the amount of each income record
	for(var i=0;i<incomes.length;i++) {
	    totalIncome += incomes[i].amount;
	}
	/* Getting the event defined by using component.getEvent() and passing the
	same value as in the name attribute of registerEvent tag */
	var totalIncomeComponentEvent = component.getEvent('totalIncomeComponentEvent');
	// Setting the attribute of event using setParams()
	totalIncomeComponentEvent.setParams({
       	    totalIncome: totalIncome
	});
	// Firing the event
	totalIncomeComponentEvent.fire();
    }
})
As you can see above, we have a total of 5 functions in our controller file. Let's discuss each function one by one:-

  1. doInit:- This function is called initially when the component is loaded. In our component, when it is initially loaded, our lightning:datatable is pre-filled with some data so here, I am going to define the columns and initial data which will be used by my table. To set columns, we need mainly 3 things, the label, the fieldName (each column is refered by fieldName while entering data) and the type (data type) for the column. This is really useful when we deal with real sObject data as we can directly specify the column data type according to the data type of field. So, if you remember, mycolumns attribute in our lightning component was of type list, So I have made an array of columns and that array is stored in the mycolumns attribute. Similar format is used for the incomes attribute, an array with initial data is passed so that this data is displayed when the table is initially loaded. Notice that in case of data, the fieldName act as key and the value is the actual value for that column in a particular row.
     
  2. handleRegisteredComponentEvent:- This function simply sets an alert - Event handler at the source component that fired the event. It is simply used to learn about the order in which the event handlers are called as in bubble phase this function will be called in the beginning as the source itself will handle the event first and then move forward whereas in the capture phase this function will be called at last as the event handling starts from root. This function is called when LightningComponentEvent is fired.
     
  3. toggleIncomeForm:- This function is mainly used to toggle the income form visibility. First we are getting the incomeForm div by it's aura:id and then we are toggling the hide class so that the form is hidden if it was previously visible and vice-versa. This function is called when we click on Show/Hide Income Form button in the lightning component.
     
  4. addIncome:- In this function, first of all we are getting the income records that are already present in the data table.Then we form the newIncome object that consists of the serial number incremented by 1 and the source and amount value is taken from the incomeForm. Finally, we push the newIncome to the existing incomes array if all the fields have value and we set the incomes attribute with the updated list in our lightning component and set the source and amount fields to blank in incomeForm.
     
  5. fireTotalIncomeComponentEvent:- This function is responsible for firing our LightningComponentEvent and is called when we click on the Calculate Total Income button. It gets the table data from component consisting of income records, iterate them and calculate the total income. Finally we get our event defined in LightningComponentEvent file by referring it by it's name i.e. totalIncomeComponentEvent we set the param totalIncome by using the setParams() function which take the attribute name defined in the event file and it's value. At the end, we fire this event by calling fire() function on the variable that is referring the event so that the event is fired can be handled by it's respective handlers.
Our Component event and controller is ready, but css related to this component is yet to be defined which will show/hide our incomeForm. So, let's define our css file.

LightningEventsComp1.css

.THIS {
}
/* Class used to toggle the income form display */
.THIS .hide {
    display: none;
}
You can see above, I have defined a hide class that consists of only a single statement i.e. display:none; as I am toggling this class in the incomeForm on click of Show/Hide Income Form button which is calling the toggleIncomeForm function of controller responsible for toggling this class in the incomeForm by default incomeForm is hidden when we open the page as this class is applied in code.

Now, we are done with our component. If you remember, I have invoked single component in my Lightning App named:- LightningEventsCompContainer. This is the wrapper component which will consists of our LightningEventsComp1 component and also other components in the upcoming posts. So, let's have a look at this simple parent component now.

LightningEventsCompContainer.cmp

<!-- Wrapper/Parent component called directly from the Lightning Application -->
<aura:component>
    <!-- Attribute to store total income coming through the event -->
    <aura:attribute name="totalIncome" type="decimal" default="0" ></aura:attribute>
    <!-- Handler defined to handle 'totalIncomeComponentEvent' name same as used in registerEvent tag -->
    <aura:handler name="totalIncomeComponentEvent" event="c:LightningComponentEvent"  action="{!c.handleTotalIncomeComponentEvent}"></aura:handler>
    Outer Component
    <!-- Inner component section with border -->
    <div class="innerComponent">
    	Inner Component Section Begin
    	<!-- Calling the source component -->
    	<c:LightningEventsComp1 />
    	Inner Component Section End
    </div>
    <!-- Section to show total income -->
    <span class="totalIncome">Total Income = {!v.totalIncome}</span>
</aura:component>
As you can see in the above code, I have defined an attribute named totalIncome which is of type decimal with default value as 0. This parent component will mainly handle the event fired by the child component and as we know that the event carry the total income, that value will be used by this parent component and component will set this value in this totalIncome attribute. So, to capture the event, I have defined a handler with the same name as given while registering the event i.e. totalIncomeComponentEvent. This handler is for LightningComponentEvent so I have given this in the event attribute and is calling handleTotalIncomeComponentEvent function which I'll define in the lightning controller of this parent component.

Then I have written a text named Outer Component to make sure that this is the outer component and called the LightningEventsComp1 component in a separate div with class innerComponent. I have called this component by using a tag :- <c:LightningEventsComp1 />. Finally, I have displayed the total income attribute value in a separate div with class totalIncome.

LightningEventsCompContainerController.js

Moving on to the controller for this component, let's have a look at the code below:-
({
    // Function to handle Lightning Event
    handleTotalIncomeComponentEvent : function(component, event, helper) {
    	alert('Event handler at the parent component');
    	// Getting the value of totalIncome attribute of event using event.getParam()
    	var totalIncome = event.getParam('totalIncome');
    	// Setting the totalIncome attribute of component with the event's totalIncome attribute value.
    	component.set('v.totalIncome', totalIncome);
    }
})
As you can see, in the above code, I have only a single function named:- handleTotalIncomeComponentEvent which is called automatically by our event handler tag when the event is captured by this component. Now if you remember, out event consists of the total income as the sum of all income amounts present in the table in it's totalIncome attribute. So, as the event is received, first of all an alert is there saying Event handler at the parent component and then I get the value of event attribute using event.getParam('totalIncome'); as the attribute name is totalIncome in event and finally I set my totalIncome attribute present in the parent component so that the total income which is displayed in the lower div in parent component is updated.

Now, our whole code is ready except the css for parent component. So, let's have a look at the css file below:-

LightningEventsCompContainer.css

.THIS {
}
/* class for the totalIncome text size and padding */
.THIS.totalIncome {
    font-size: 1.5em; 
    padding-left: 1em;	
}
/* Class to add border to inner/source component */
.THIS.innerComponent {
    border: 1px solid black;
}
As, you can see  that our css consists of only two classes, totalIncome -  to give a proper font-size and padding to the div where total income is displayed and innerComponent which is giving a border to the innerComponent div calling LightningEventsComp1 component.

This is our final application that we made:-

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

Video of Single Component (Event registration and handling in source component) :-


Extending previous video to include Container Component (Event registration and handling in source component as well as container component) :-



Congratulations..!! You recently code your own Component Event. Now, this time it's your turn. Checkout the application you made and as you know, by default the events traverse in bubble phase so check which event handler is called first, the parent component or the source component itself and which event handler is called later. You can also change these event handlers to work in capture phase by adding another attribute as phase="capture" in the aura:handler present in both the components to handle our event. Try this and let me know what you have noticed like in both the cases, which alert came first and why ?

If you need the whole code for this tutorial, you can visit the componenteventbubblephase branch of my github repo here.

In the next post, I'll be using the same code to discuss about both - bubble and capture phase showcasing the proper working and path followed by event in both the cases. Till then stay tuned, subscribe and share this blog in your network if you liked it.
Happy Trailblazing..!!

32 comments:

  1. Rahul Sir,
    Thank You For This Blog.

    ReplyDelete
    Replies
    1. You're welcome Aman. Actually due to some internet problem, I am not able to upload video.. will fix that and upload asap.

      Delete
    2. No issue Sir, The way you write to explain is quite efficient.

      Delete
    3. Happy to know about that Aman :-) Share in your network too so that everyone can make the best use of it.

      Delete
  2. Very well explained! Looking forward to more. Thank you.

    ReplyDelete
    Replies
    1. Thanks for the feedback Alo :-) Share in your network so that others can get Benefit too.

      Delete
  3. ({
    doinit : function(component,event,helper){
    console.log('@@@@ Record List Value outside setCallback'+' '+JSON.stringify(component.get("v.Records"))); // 3rd Line
    var action = component.get("c.getRecords");
    action.setCallback(this,function(response){
    var state = response.getState();
    if(state === "SUCCESS"){
    var storeResponse = response.getReturnValue();
    component.set("v.Records",storeResponse);
    console.log('#### Record List Value inside setCallback'+' '+JSON.stringify(component.get("v.Records"))); // 10th Line
    }
    });
    $A.enqueueAction(action);
    }
    })

    ReplyDelete
  4. Rahul sir,
    In the above code , 3rd Line Print =>@@@@ Record List Value outside setCallback []
    &
    10th Line Print => #### Record List Value inside setCallback [{"FirstName":"Aman","LastName":"Sort","Phone":"123654899","Id":"0037F00000VmDJhQAN"},{"FirstName":"aman","LastName":"sort","Phone":"1258562148","Id":"0037F00000VmDJiQAN"},

    ReplyDelete
  5. Inside setCallback value of the Variable get displayed.but outside the same variable displayed no value.I used 3rd Line after the 10th Line but still same result.Sir, whenever you get free time please have a look on this problem

    ReplyDelete
    Replies
    1. Hi Aman, yeah you'll get the same result even if you place the third line after the 10th line because call to server is asynchronous and js doesn't wait while executing code for your response it'll execute the further lines of code and will execute the code inside callback whenever a response is captured.

      Delete
  6. Rahul Sir,You are awesome.Thank you.

    ReplyDelete
  7. Rahul sir,

    How to add amounts based on selected check box in the above component

    thanks,
    prakash

    ReplyDelete
    Replies
    1. Hi Prakash, there is an attribute named onrowselection in lightning datatable which will call a function in lightning controller as a particular row is selected, so you can addup the value of incomes on selection of row in that function. However, for more information in detail about lightning datatable, you can visit the official doc:- https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/aura_compref_lightning_datatable.htm

      Delete
  8. Hi Rahul Sir,
    Thanks for the blog. Happy to see the way you explained.

    ReplyDelete
    Replies
    1. Happy to see that you liked it. Do share it among others as well :-)

      Delete
  9. Hi Rahul Sir,
    We are building a generic component and we want to save list of sobject records without any interference of apex logic. We are trying to do it using lightning:recordEditFor. Is there any way that we can do it in js. Looking forward to hear from you.

    Thanks

    ReplyDelete
    Replies
    1. Hi, Actually the lightning:recordEditForm which is updated to lightning:recordForm in this release is only useful to deal with single record. For multiple records, you need to make a custom UI and deal with multiple records at once using your own apex controller. You can have a look at this sample for updating multiple records at once:- https://sfdcstop.blogspot.com/2018/02/salesforce-lightning-tutorial-part-2.html

      Delete
  10. Thanks. I will surely try this.

    ReplyDelete
  11. Hi Rahul.Your videos helped alot for getting the things easily. From the above video in the controller, we are inserting the data to the datatable. so how we can display the data to the datatable if we fetch from database like apex server side controller?

    ReplyDelete
    Replies
    1. Hi Lokesh, for that you need to arrange your data in the same format so that it can be easily recognized by data table and then your datatable wil be able to display that.

      Delete
  12. Hi Rahul,
    Thanks for amazing video.
    Do you give training on Lightning? Please let me know.
    Thanks

    ReplyDelete
    Replies
    1. Hi Madhuri, great to see that you liked it. Do share it among others :-)

      I don't give training as of now but give free sessions on salesforce meetups. Another one is coming up on IndiaDreamin 2018. Make sure to join if you're around NCR.

      Delete
  13. Hi Rahul,
    Thanks for awesome stuff on events.
    Actually when iam using type='number' in lightning datatable, 'sno' column and 'amount' columns are
    not alligning properly with data, they are alligning to the right corner of each column.Is there any work around for this issue?
    Thanks

    ReplyDelete
    Replies
    1. Hi Srinivas,

      I haven't tried modfying the cell css myself but datatable is actually rendered as an html, So, you can try to modify that behaviour by applying "text-align": "center" css to the td cell of the rendered data table.

      Delete
  14. very good blog Rahul. I came to know that there are two phases in Lightning Components.

    ReplyDelete
    Replies
    1. Thanks Mahesh :-) Make sure to share it in your network too

      Delete
  15. Very good blog Rahul. Great effort.

    ReplyDelete
    Replies
    1. Happy to see that you liked it Mahesh, thanks for your feedback :-)

      Delete
  16. Very good blog Rahul.Great effort😊

    ReplyDelete
    Replies
    1. Thank you for reading :-) Make sure to share it in your network too..!!

      Delete
  17. Nice and quiet descriptive video. Thanks

    ReplyDelete