I was fortunate enough to be on the Lightning beta and have spent a considerable amount of time building Lightning components over the past weeks. If you are familiar with JavaScript and possibly frameworks such as AngularJS and Backbone, then building Lightning components should be a snap.
Topcoder has teamed up with salesforce.com to help drive the development of Lightning components. We are currently running a number of development challenges at lightning.topcoder.com and have over $20,000 in prize money dedicated to build next generation UI components for Salesforce1. Head on over to lightning.topcoder.com and get started today! The challenges should be running for a number of months.
So let’s get started with some basic concepts. Lightning applications are composed of Lightning components. There’s not much difference the two with the exception that components must live inside an application. Before you proceed with this tutorial, I would suggest you take a look at the Lightning Components Quick Start which has a super simple guide to get a "hello world" up and running quickly. You might want to also check out the the Lightning Components Developer’s Guide for a great "Getting Started" expense tracker tutorial. We are going to build something a little more complex so let’s get started. All of the following code is available in this gist.
The application we are going to build is an Employee Store where users can redeem their "reward points" for products. From a high level, the app has a picker component that allows the user to choose the product to view from a Custom Object, a product view component that displays the currently selected product from the picker, a cart component that tracks products added to the cart and a small notification component that simply displays the name of the product added to the cart. The app emits an application level event whenever a product is added to the cart that is handled by the various components listening for that event (e.g., the cart and the notification panel).
A component is actually a bundle of a number of files. Only the component or application is actually required but you’ll typically use at least a client-side controller as well. Here’s a breakdown of what’s in a component:
Component or Application (MyComponent.cmp or MyApp.app) - This file contains the declarative markup for the component or app.
Controller (MyComponentController.js) - This file contains the client-side JavaScript controller methods to handle events fired and handled by the components.
CSS Styles (MyComponent.css) - The CSS styles scoped to the component.
Helper (MyComponentHelper.js) - This file contains JavaScript functions that can be called from any JavaScript code in a component’s bundle. Typically you’d move commonly called code from your controllers into a helper for reuse.
Components can also container custom renderers and documentation but that is beyond the scope of this tutorial.
To get started, the first thing you’ll need to do is signup for a new Winter '15 Developer org. Older orgs are not enabled with Lightning. Next follow steps #2 & #3 in the Lightning Components Quick Start. Make sure in step #3 you also check "Enable Debug Mode".
Now create the two Custom Objects to hold your data. We’ll create a Product__c object and a Product_Size__c object that looks like the following:
Go ahead and populate your objects with some data. I used snorgtees for some funny shirts. Now create the following "AwesomeProductController" Apex Controller that has methods to return a list of all products and a specific product by name:
Next, we’ll upload some CSS and JavaScript files that will be needed to make the app look pretty. Upload these three files and name the resources bootstrap, style and bootstrapjs.
In all of the your code for this tutorial, replace my namespace, aotpjd, with your own namespace.
OK, so now we have the "config" portion of our app done, let’s write some code. Currently you can only write Lightning components in Developer Console, so click your_name and then Developer Console. Click File -> New -> Lightning Application, enter the name as "CompanyStore" and click submit.
Now enter the following code for a simple "hello world":
When you open https://[your-pod].lightning.force.com/[your-namespace]/CompanyStore.app in your browser you should see "hello world".
Now replace "hello world" in your component with <aotpjd:ProductViewer/>. This component will display our product and provide the functionality we need.
Now we are going to create the main UI component for our application, ProductViewer. Click File -> New -> Lightning Component, enter the name as "ProductViewer", click submit and paste in the following code:
The first few lines in the component are really important so let's dive into them. In line #1, we define that our component will use the AwesomeProductController so that we’ll be able to use the two methods in it and retreive our records from the database.
Lines #2 & #3 are attributes of the component. Component attributes are like member variables in an Apex class. They are typed fields that are set on a specific instance of a component, and can be referenced from within the component's markup using an expression syntax. We’ll need the product attribute to reference the current product record we are displaying and the products attribute so we can display all of the products in the database in the select list.
In line #4 we specify a handler in the JavaScript controller that fires when the component initializes. This init handler triggers the doInit method in the client-side controller, which calls the Apex controller to populate the component with initial data. We’ll write this code in a second.
Line #5 uses aura:registerEvent to declare that the component may fire an event (in the JavaScript controller) called addToCartEvent. We’ll look at the configuration of that event in a second.
Most of this file is HTML markup but there are some other important pieces to point out. Lines #12-14 setup a select list with data from the database. This way you can easily switch and display different products (mostly for demo purposes). We use the builtin <aura:iteration> element to iterate the collection of product records returned from the database. The name of that attribute is products, while v. is the value provider for a component's attribute set, which represents the view. We also have an onchange handler that calls the change function in the JavaScript controller whenever the select list value changes.
Interspersed in the HTML we display values such as name, color and description from the database with the product attribute. Notice in line #29 we output standard fields with {!v.product.name} while custom fields use the namespace, {!v.product.aotpjd__photo__c}.
Lightning has a bunch of extremely useful core UI components for inputText, inputDate, InputCheckbox, button, etc. but in line #40 we use a simple onclick action to call the addToCart function in the JavaScript controller.
Events == #AWESOME
One of the core concepts of Lightning is the ability for components to fire and handle event that occur when, for instance, a user clicks a button or the underlying data of a component changes. In this tutorial we’ll just concern ourselves with user generated events. Events add an interactive layer to your app by enabling you to share data between components. This allows you to quickly assemble loosely coupled components that communicate and share data via events.
In line #5 of our ProductViewer component, we declared that the component may fire an event called addToCartEvent. Create this new event by clicking File -> New -> Lightning Event, entering the name "AddToCart", hitting submit and entering the following code:
The event itself contains an attribute called product which is a reference to a Product__c record. In a nutshell, the JavaScript controller will wrap up the product record in the event, fire the event and any component listening for that event will have access to the product record and can act on it accordingly.
An event can scoped either to a "component" or "application" level. According to the docs:
- A component event can be handled by a component itself or by a component that instantiates or contains the component.
- Application events follow a traditional publish-subscribe model. An application event is fired from an instance of a component. All components that provide a handler for the event are notified.
I’ve always used application events and don’t see the real difference between the two. I’ve had a number of conversations with the Dev Evangelism team and we couldn’t really come up with a good use case of when to use component over application events. Perhaps, if you want to tie a single event to a single component that listens and handles it? Perhaps someone smarter than me can clue me in.
Now we need to add the client-side JavaScript controller for the component which reacts to action from the UI. On the right side of the ProductViewer.cmp, click the "Controller" bar to create the ProductViewerController.js file for you. Paste in the following code:
The controller contains three functions. The first one, doInit, is called when the component initializes and loads the component with data. We’ve moved that code to a helper file to promote code reuse and we’ll look at that file in a second. The change function gets the value of the currently select picklist value (after onchange has been fired) and calls the helper function to actually change which product record from the database is currently being displayed in the UI. We’ll look at that code in a second as well ... it’s the same as above (code reuse!).
The meat of the controller is the AddToCart function which fires our AddToCart method that notifies our other components that a product has been added to our cart. Line #13 grabs a reference to the currently displaying product from the attribute and line #14 gets an instance of the AddToCart event. We then set the currently displaying product to the product param and then fire the event for all of the components to hear.
Our component helper, again, contains function that are used in multiple parts of our controller. You aren’t required to use a helper, everything can go in the controller, but it makes life easier. On the right side of the ProductViewer.cmp, click the "Helper" bar to create the ProductViewerHelper.js file for you. Paste in the following code:
We have two function in the helper, one to return all records in the database to the component (for the select list) and one to return a specific record to the component by name (the currently viewing product record). Lline #2 returns an instance of the server-side method getProducts in the Apex controller. Line #5, passes in a function to be called after the server responds. When the Apex controller returns the list of product records, line #6 sets the list to the value of the products attribute.
The getProduct function is similar but is passed the value of the record name and passes that to the Apex controller method so that it knows which record to return in the SOQL query.
Now we need to add the last major part of the application, the cart component that tracks which products the person wants to receive. Click File -> New -> Lightning Component, enter the name as "ShoppingCart", click submit and paste in the following code:
This component contains two attributes
- items - the array of current product items in the cart
- total - the current total of points for all items in the cart
Line #4 is the important part of this component. This handleAddToCartEvent event handler in the JavaScript controller (which we’ll write in a second) runs when the AddToCart application event you created is fired. In this component we display a standard shopping cart. The functionality in this component is similar enough from the other components that it doesn’t require much explanation. We do however, render the cart only in line #10 if there are at least one item in the cart.
The controller contains the handler that listens for our application event so let's create it. On the right side of the ShoppingCart.cmp, click the "Controller" bar to create the ShoppingCartController.js file for you. Paste in the following code:
The handleAddToCartEvent function fires whenever it receives an AddToCart application event. In line #3 it gets the product sent over with the event and gets the array of items from the component. Then starting in line #6, it adds the product to the array of items and updates the total number of items and items themselves in the component which rerenders the UI.
The final piece of the application is nothing special but I added it to demonstrate multiple components listening for events. Create the MessageArea component and its associated controller. This component simply listens for the AddToCart application event and displays the name of the added product. Nothing fantastically awesome.
So there’s your application that includes multiple components talking to each other with events and displaying data from an Apex controller. There is, of course, some functionality missing and some coding that could have been done differently but I thought this was the best route for a 'getting started' tutorial.