Getting Started with Salesforce Lightning Development

Here at Appiphony, we’ve had the opportunity to be an early adopter of developing for Salesforce’s new Lightning Experience on the Lightning Framework. We built Lightning Demo Apps that were featured at Dreamforce and took what we learned to launch our own component library — Lightning Strike. While working with the Lightning Framework has brought in exciting new design and architecture concepts, we found that, like anything bleeding edge, the initial learning curve was steep. Out of that experience, we came up with a guide to share what we’ve learned and help introduce what developing in Lightning is like. This guide is for any developer that is brand new to Lightning.

Intro to Lightning Components

The Lightning Component framework is a javascript front-end framework specifically designed for building custom functionality on Salesforce in the Lightning Experience. Components can be surfaced:

  • By Drag & Drop on the Lightning App Builder
  • By Drag & Drop on a Lightning Record Home Page
  • In code as subcomponents of a Lightning Component or App
<aura:component>
<c:NestedComponent />
</aura:component>

It is event driven architecture along with OOP baked in at its core. The idea behind it is to develop loosely coupled, fully encapsulated components that communicate with each other through events and work together in a larger ecosystem.

Each component contains its own HTML, CSS & JS and because the details of a component’s implementation are encapsulated, the developer can innovate and make changes to the component in a contained environment. Development lifecycle is further streamlined by utilizing a set of prebuilt components. Components interact with their environment through events.

Setting up your development environment

We believe the best way to develop is with the latest version of the Force.com IDE.

The quickest way to get started is with the Salesforce Developer Console.

Other options include using Sublime or Atom as the text editor, and Mavensmate plugin running in the background to sync up with Salesforce. Follow the steps below to install and configure your dev environment.

The following editors also work with an additional Salesforce integration app:

Atom:

  1. Install atom.io then download Mavensmate application
  2. In Atom, go to Packages > Setting View > Install Packages/Themes and install the Mavensmate plugin
  3. Open the Mavensmate application → Settings
  4. Setup your workspace
  5. Set Salesforce API version
  6. When you’re ready to get started, create a new project and connect to your Salesforce org (password must be password+Security Token)
  7. For settings, we use spaces set to a length of 4 with Soft Wrap enabled

Sublime:

  1. Install Sublime Text 3
  2. Download the setup .dmg file
  3. Open the setup file and drag Sublime to Applications
  4. Install the package control manager
  5. Open Sublime and open the console using Control + ‘ or View > Show Console
  6. From the installation guide, copy/paste the Python snippet into the console
  7. Restart Sublime
  8. Install Mavensmate Plugin for Sublime
  9. Open search by using ‘Control + Shift + P’ and type “install”
  10. Select “Package Control: Install Package”
  11. Then search for “mavensmate” then select to install
  12. Install Mavensmate Desktop Application
  13. The desktop application must be running together with Sublime
  14. From the desktop application, click on the hamburger menu → Settings
  15. Configure your Workspaces (mm_workspace)
  16. [
     “/Users/{user}/Dropbox/Workspace/Atom”, “/Users/{user}/Dropbox/Workspace/Sublime”
    ]

The Component Bundle

When you create a new Lightning component you create what Salesforce calls a bundle. Below is an image of a bundle generated in the Developer Console and a brief explanation describing the purpose of each file.

({
handleSelectedSObject: function(component, event, helper) {
helper.processObjectHelper(component, event, helper);
},
  handleCustomEvent: function(component, event, helper {
helper.processEventHelper(component, event, helper);
}
})
  • HELPER: This contains functions that contain business logic and reusable code. It is best practice to pass in component, event and helper but it is not necessary.
({
processObjectHelper: function(component, event, helper) {
var customAttribute = component.get(‘v.customAttribute’);
},
  processEventHelper: function(component, event, helper) {
component.set(‘v.customAttribute’, ‘new value’);
}
})
  • STYLE: This applies encapsulated CSS to the component
.THIS {
background-color: white;
}
.THIS.customClass {
background-color: red;
}
  • DOCUMENTATION: This is an optional file containing additional information
  • RENDERER: This is an optional file used to customize the default rendering behavior of the framework
({
render: function(component, helper, event) { },
rerender: function(component, helper, event) { },
afterRender: function(component, helper, event) { },
unrender: function(component, helper, event) { }
})
  • DESIGN: This is a configuration file is used to expose attributes to the setup section of the Lightning App Builder
<design:component label=”Example Component”>
<design:attribute name=”configurableAttribute” label=”Configurable Attribute” description=”Configurable Attribute” /></design:component>
  • SVG: Used to store custom SVG used for the component

The simplest components only need the component file. If you want to add custom JS code, you’ll need a controller and helper.

Component File:

The component file contains template markup. In general, Lightning component markup consists of HTML and additional custom XML specific to framework functionality. The example below shows the first line of markup every component needs.

<aura:component implements=”flexipage:availableForAllPageTypes,force:appHostable, force:hasRecordId” controller=”ApexControllerName” access=”global”>
</aura:component>

The root-level tag of every component is an <aura:component> where you define the base type. This is where you can add attributes to define the functionality of your component. In the example above, the attribute “implements” is used to add functionality from native Salesforce interfaces. The most commonly used interfaces are:

  • flexipage:availableForAllPageTypes exposes the component in the App Builder or Lightning page
  • force:appHostable interface makes the component available as a custom tab
  • force:hasRecordId exposes the recordId of the record the component is on
  • The record ID attribute is created in memory at run time due to the presence of force:hasRecordId. However, it is good practice to add it explicitly to the markup for clarity

The “controller” attribute is also defined at the root-level tag. This exposes back-end code defined as @AuraEnabled Apex methods to the component.

The “access” attribute is set to “global” at the root-level. This grants developers and the Lightning App Builder access to components that are part of a managed package.

Attributes and Expressions:

Lightning Components render dynamic data and layouts through the use of attributes and expressions in the component markup. Attributes are similar to variables and are used to dynamically get and set values in a component. Expressions allow you to render attributes and make calculations. In the example below, an attribute was defined, and then rendered in the markup using an expression.

<aura:attribute name=”customAttribute” type=”String” access=”global” description=”attribute description”/> <! — Attribute ->
<div>
{!v.attributeName} <! — Expression ->
</div>
  • All attributes must have a name and a type.
  • Data types can be both primitive and complex.
  • A default value can be set and you can mark it as required
  • Render the value of an attribute using {!v.attributeName} syntax, “v” standing for “value”.
  • It is best practice to provide a description that surfaces in aura:docs

We’ll dive into more advanced uses of expressions later. Once basic dynamic data is rendering on the component, a developer can use events to set attributes and react to their changes.

Events:

Events are used by the framework to communicate actions between components. A component can register an event to declare that it will emit it and define handlers to run custom code when an event is fired. The Lightning Framework provides System Events, Standard Events and UI Events that can be handled by components out-of-the-box. The example below defines handlers on the “init” and “change” System Events.

<aura:attribute name=”customAttribute” type=”String” access=”global” description=”attribute description”/> <! — Attribute ->
<aura:handler name=”init” value=”{!this}” action=”{!c.doInit}” /> <! — Handling the standard “init” event ->
<aura:handler name=”change” value=”{!v.customAttribute}” action=”{!c.handleAttributeChange}” /> <! — Handling the standard “change” event ->

The “init” event is used so we can run custom code when the component is ready. The custom code is defined by passing in an expression {!c.doInit}. Instead of using “v”, the expression uses “c”, which stands for “controller”. This tells the component to look for a function on the Controller file and run it.

The “change” event works the same way but runs when an attribute on the component changes.

Every function that is called on the controller is passed “component”, “event” and “helper” as parameters. “Component” represents itself, “event” is what triggered the controller code and helper is a reference to the Helper file.

Controller file:

({
handleSelectedSObject: function(component, event, helper){
helper.processObjectHelper(component, event, helper);
},
  handleCustomEvent: function(component, event, helper){
helper.processEventHelper(component, event, helper);
}
})

Helper file:

({
processObjectHelper: function(component, event, helper){
var customAttribute = component.get(‘v.customAttribute’);
},
  processEventHelper: function(component, event, helper){
component.set(‘v.customAttribute’, ‘new value’);
}
})

While initialization and change events allow for custom code to run at key moments, a developer can also tie events to user interaction, leverage standard events, and create custom events to fire in code.

UI events from the browser are handled by JavaScript action functions in a component’s controller.

The action function is bound to named attributes such as “onclick”, “change” or “press” using an expression inside a Lightning Component’s markup. The example below binds the controller function “handleClickSubmit” to the lightning:button via an “onclick” attribute.

<lightning:button label=”Submit” onclick=”{!c.handleClickSubmit}” /> <! — Base component with a defined event handler →

Standard events come out-of-the box and send/receive messages related to general UX in the Lightning Experience. Components can fire standard events to signal for navigation, file preview and record saving amongst others. Handlers can be created to run custom code when an object is selected or a validation error occurs. It is recommended to try solving a UX problem with standard events before creating a custom events. The example below registers the component to fire and handle the standard “ltng:selectSObject” event. The advantage here is that now the component can interact with native Salesforce UI that also registers and handles the event.

<aura:registerEvent name=”SObjectSelected” type=”ltng:selectSObject”/> <! — Registering a standard application event -->
<aura:handler event=”ltng:selectSObject” action=”{!c.handleSelectedSObject}”/> <! — Handling a standard application event -->

When a standard event does not send the message you want, custom events can be created and used with components. An event file needs to be created to declare it and its attributes. The name of this file becomes the name of the event. The attributes defined become parameters that can be passed from the event emitter and the handler. The screenshot below is of the event file “CustomComponentEvent.evt”.

<aura:event type=”Component” description=”Event template”>
<aura:attribute name=”recordId” Type=”String” />
</aura:event>

Now components can register and handle an event with type “CustomComponentEvent” and pass in the attribute “recordId”.

<aura:handler event=”ltng:selectSObject” action=”{!c.handleSelectedSObject}”/> <!— Handling a standard application event -->
<aura:handler event=”c:CustomComponentEvent” action=”{!c.handleCustomEvent}”/> <!— Handling a custom component event -->

Custom Events can have the type of “Component” and “Application”. A Component Event communicates within the component that fired the event and to child components. An Application Event sends a message to any instantiated component in the DOM.

Application Events are especially useful when communicating across different components on a Record Home Page or a Lightning App Page. Keep in mind that even instantiated components that are not in the viewport respond to Application Events.

Now that we have dynamic data rendering on the DOM and events to facilitate communication across components or through user interaction, let’s look at how to get data from the server.

Server Calls:

Calls to the server are made through an Apex Controller. An Apex Controller is connected to a component at the root level with the “controller” attribute. The example below allows the component to communicate with “ApexControllerName”, specifically to invoke “@AuraEnabled” Apex methods.

<aura:component implements=”flexipage:availableForAllPageTypes, force:appHostable, force:hasRecordId” controller=”ApexControllerName” access=”global”>

Now a component controller or helper, can access the method

getRecords: function(component, event, helper) {
var action = component.get(‘c.ApexMethod’);
}

Define its parameters

action.setParams({
recordId: component.get(‘v.recordId’)
});

Set a callback function

action.setCallback(this, function(response) {
if (response.getState() === ‘SUCCESS’) {
if (response.isValid() && response != null) {
var results = response.getReturnValue();
component.set(‘v.records’, JSON.parse(results));
}
} else if (response.getState() === ‘ERROR’) {
component.set(‘v.error’, ‘An error has occurred’);
}
});

And queue the request. When server calls are entered into a queue, they can be potentially batched into a single execution context for more efficiency.

$A.enqueueAction(action);

The complete function looks like:

getRecords: function(component, event, helper) {
var action = component.get(‘c.ApexMethod’);
action.setParams({
recordId: component.get(‘v.recordId’)
});

action.setCallback(this, function(response) {
if (response.getState() === ‘SUCCESS’ && response.isValid() && response != null) {
var results = response.getReturnValue();
component.set(‘v.customAttribute’, results[0]);
} else if (response.getState() === ‘ERROR’) {
component.set(‘v.error’, ‘An error has occurred’);
}
});
  $A.enqueueAction(action);
}

One thing you can do with data returned from the server is set a component attribute to the results. Doing so in the Controller or Helper will update the expression value in markup. This can be done with the “component.set” method.

var results = response.getReturnValue();
component.set(‘v.customAttribute’, results[0]);

If you need to use attribute values in Controller or Helper code, you can use “component.get” to retrieve the bound value.

We gather all of the code we wrote throughout this guide in the three files below.

Component file:

<aura:component implements=”flexipage:availableForAllPageTypes, force:appHostable, force:hasRecordId” controller=”ApexControllerName” access=”global”>
<aura:attribute name=”customAttribute” type=”String” access=”global” description=”attribute description”/> <! — Attribute -->
<aura:handler name=”init” value=”{!this}” action=”{!c.doInit}” /> <! — Handling the standard “init” event -->
<aura:handler name=”change” value=”{!v.customAttribute}” action=”{!c.handleAttributeChange}” /> <! — Handling the standard “change” event -->
<aura:registerEvent name=”SObjectSelected” type=”ltng:selectSObject”/> <! — Registering a standard application event -->
<aura:registerEvent name=”RegisteredComponentEvent” type=”c:CustomComponentEvent”/> <! — Registering a custom component event -->
<aura:handler event=”ltng:selectSObject” action=”{!c.handleSelectedSObject}”/> <! — Handling a standard application event -->
<aura:handler event=”c:CustomComponentEvent” action=”{!c.handleCustomEvent}”/> <! — Handling a custom component event -->
  <div>
{!v.attributeName} <! — Expression -->
<lightning:button label=”Submit” onclick=”{!c.handleClickSubmit}” /> <! — Base component with a defined event handler -->
</div>
</aura:component>

Controller file:

({
handleSelectedSObject: function(component, event, helper){
helper.processObjectHelper(component, event, helper);
},
  handleCustomEvent: function(component, event, helper){
helper.processEventHelper(component, event, helper);
}
})

Helper file:

({
processObjectHelper: function(component, event, helper){
var customAttribute = component.get(‘v.customAttribute’);
},
  processEventHelper: function(component, event, helper){
component.set(‘v.customAttribute’, ‘new value’);
},
  getRecords: function(component, event, helper) {
var action = component.get(‘c.ApexMethod’);

action.setParams({
recordId: component.get(‘v.recordId’)
});
    action.setCallback(this, function(response) {
if (response.getState() === ‘SUCCESS’) {
if (response.isValid() && response != null) {
var results = response.getReturnValue();
component.set(‘v.records’, JSON.parse(results));
}
} else if (response.getState() === ‘ERROR’) {
component.set(‘v.error’, ‘An error has occurred’);
}
});
    var results = response.getReturnValue();
component.set(‘v.customAttribute’, results[0]);
}
})

You’re now able to create a simple component and add basic markup with dynamically bound data. Through events, you’re able to call controller methods to run Javascript and call Apex methods. To leverage the power of Lightning Components, we recommend breaking down functionality into smaller, self-contained blocks. Salesforce has out-of-the-box components and our open source Lightning Strike library has components for functionality that the native ones don’t yet cover. Feel free to try them out, see how they were built and contribute!