“Struck by lightning, struck by lightning!” J.R.R. Tolkien.
Our simple Salesforce 1 app from Part One and Part Two has been refactored into lightning like magic. In this post, we will examine the magic that is the source code to gain a better understanding of lightning applications, lightning components, and lightning events.
After we examine the source code, we will compare our refactored app and the underlying event-based architecture to our previous two solutions to illustrate a significant Salesforce Development paradigm shift that has begun and will soon bring together a growing community of 1,700,000 developers. I think that right now will be the most exciting time to be a developer in our lifetime.
Let’s ride the lightning!
If you struggle with any of the following code constructs then refer to the Lightning Developers Guide or the Lightning Cheat Sheet. And if you are still stuck, then reply to this blog so we can master the lightning together.
DisplayOpportunity – this is a lightning event that is composed of the EVENT.XML file. This event is fired when a user presses an opportunity in the MobileOpportunity3List lightning component. The event is handled by the MobileOpportunity3Product lightning component.
EVENT.XML
<aura:event type="APPLICATION" description="Event template" >
<aura:attribute name="opportunityId" type="String"/>
</aura:event>
DisplayOpportunityList – this is a lightning event that is composed of the EVENT.XML file. This event is fired when a user presses the back button on the MobileOpportunity3Product to return to the MobileOpportunity3List.
EVENT.XML
<aura:event type="APPLICATION" description="Event template" />
MobileOpportunity3List – this is a lightning component that is composed of the COMPONENT.XML, CONTROLLER.JS, HELPER.JS, and STYLE.CSS files. This component displays a list of opportunities and fires a DisplayOpportunityEvent when a user presses an opportunity.
COMPONENT.XML
<aura:component controller="jsonhammerle2.OpportunityController" implements="force:appHostable">
<aura:attribute name="visible" type="Boolean" default="true" />
<aura:attribute name="opportunities" type="Opportunity[]"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<aura:registerEvent name="DisplayOpportunityEvent" type="jsonhammerle2:DisplayOpportunity"/>
<aura:handler event="jsonhammerle2:DisplayOpportunityList" action="{!c.handleDisplayOpportunityList}"/>
<!-- Display Opportunity Data from Model -->
<aura:renderIf isTrue="{!v.visible}">
<div class="app-wrapper">
<div class="header">
<h1>Opportunities</h1>
</div>
<div class="list-view">
<aura:iteration items="{!v.opportunities}" var="opportunity">
<li>
<a href="{!'#' + opportunity.id}" onclick="{!c.displayOpportunity}" class="content">
<span class="icon">
<img src="data:image/png;base64,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" />
</span>
<h2><ui:outputText value="{!opportunity.Name}"/></h2>
<h3><ui:outputNumber value="{!opportunity.Amount}" format="¤#,##0.00;(¤#,##0.00)"/></h3>
</a>
</li>
</aura:iteration>
</div>
</div>
</aura:renderIf>
</aura:component>
CONTROLLER.JS
({
//Initialize the view and fetch the list of expenses on load
doInit : function(component, event, helper)
{
//Fetch the expense list from the Apex controller
helper.getOpportunityList(component);
},
displayOpportunity : function(component, event, helper)
{
// parese the ID from the DOM element
var opportunityId = event.currentTarget.hash.substring(1,19);
var evt = $A.get("e.jsonhammerle2:DisplayOpportunity");
evt.setParams({"opportunityId": opportunityId});
evt.fire();
component.set("v.visible", false);
},
handleDisplayOpportunityList : function(component, event, helper)
{
component.set("v.visible", true);
//Fetch the expense list from the Apex controller
helper.getOpportunityList(component);
},
})
HELPER.JS
({
//Fetch the expenses from the Apex controller
getOpportunityList: function(component)
{
//Set the action to invoke the Apex controller method
var action = component.get("c.getOpportunities");
//Set up the callback
var self = this;
action.setCallback(this, function(actionResult)
{
//Reset the value of the component list attribute with the records returned
component.set("v.Opportunities", actionResult.getReturnValue());
});
//Enque the action
$A.enqueueAction(action);
},
})
STYLE.CSS
.THIS .app-wrapper {
display: block;
position: absolute;
height: auto;
min-height: 100%;
width: 100%;
top: 0px;
left: 0px;
}
.THIS .header {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 58px;
background: #78a5ad;
}
.THIS .header h1 {
font-size: 165%;
font-weight: bold;
color: #e6f0f1;
display: block;
position: absolute;
top: 16px;
left: 0;
width: 100%;
text-align: center;
}
.THIS .list-view li {
display: block;
position: relative;
width: 100%;
height: 100%;
text-decoration: none;
border-bottom: 1px dotted #cccccc;
}
.THIS .list-view div {
vertical-align: baseline;
}
.THIS .list-view h2 {
font-size: 125%;
padding-left: 20px;
padding-top: 7px;
padding-bottom: 5px;
color: black;
}
.THIS .list-view h3 {
font-size: 110%;
padding-left: 20px;
padding-top: 5px;
padding-bottom: 5px;
color: black;
}
.THIS .list-view li:first-child {
padding-top: 59px;
}
.THIS .list-view li:last-child {
border-bottom: none;
}
.THIS .icon {
display: block;
position: absolute;
float: right;
right: 0;
height: 100%;
padding-top: 17px;
padding-right: 20px;
}
MobileOpportunity3Product – this is a lightning component that is composed of the COMPONENT.XML, CONTROLLER.JS, and STYLE.CSS files. This component displays a list of opportunities products and fires a DisplayOpportunityList event when a user presses the back button.
COMPONENT.XML
<aura:component controller="jsonhammerle2.OpportunityController" implements="force:appHostable">
<aura:attribute name="visible" type="Boolean" default="false" />
<aura:attribute name="OpportunityLineItems" type="OpportunityLineItem[]"/>
<aura:handler event="jsonhammerle2:DisplayOpportunity" action="{!c.handleDisplayOpportunity}"/>
<aura:registerEvent name="DisplayOpportunityEvent" type="jsonhammerle2:DisplayOpportunityList"/>
<aura:renderIf isTrue="{!v.visible}">
<div class="app-wrapper">
<div class="header">
<span class="icon">
<a href="#" onclick="{!c.displayOpportunityList}">
<img src="data:image/png;base64,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" />
</a>
</span>
<h1>OpportunityProducts</h1>
</div>
<div class="list-view">
<aura:iteration items="{!v.OpportunityLineItems}" var="opportunityLineItem">
<li>
<h2><ui:outputText value="{!opportunityLineItem.Name}"/></h2>
<h3><ui:outputNumber value="{!opportunityLineItem.TotalPrice}" format="¤#,##0.00;(¤#,##0.00)"/></h3>
</li>
</aura:iteration>
</div>
</div>
</aura:renderIf>
</aura:component>
CONTROLLER.JS
({
handleDisplayOpportunity : function(component, event, helper)
{
// Make component visible now that we have an opportunity to display
component.set("v.visible", true);
// Set the action to invoke the Apex controller method
var action = component.get("c.getOpportunityProducts");
action.setParams({ opportunityId : event.getParam("opportunityId") });
var self = this;
action.setCallback(this, function(actionResult)
{
//Reset the value of the component list attribute with the records returned
component.set("v.OpportunityLineItems", actionResult.getReturnValue());
});
//Enque the action
$A.enqueueAction(action);
},
displayOpportunityList : function(component, event, helper)
{
// make component visible now that we have an opportunity to display
component.set("v.visible", false);
var evt = $A.get("e.jsonhammerle2:DisplayOpportunityList");
evt.fire();
},
})
STYLE.CSS
.THIS .app-wrapper {
display: block;
position: absolute;
height: auto;
min-height: 100%;
width: 100%;
top: 0px;
left: 0px;
}
.THIS .header {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 58px;
background: #78a5ad;
}
.THIS .header h1 {
font-size: 165%;
font-weight: bold;
color: #e6f0f1;
display: block;
position: absolute;
top: 16px;
left: 0;
width: 100%;
text-align: center;
}
.THIS .list-view li {
display: block;
position: relative;
width: 100%;
height: 100%;
text-decoration: none;
border-bottom: 1px dotted #cccccc;
}
.THIS .list-view div {
vertical-align: baseline;
}
.THIS .list-view h2 {
font-size: 125%;
padding-left: 20px;
padding-top: 7px;
padding-bottom: 5px;
color: black;
}
.THIS .list-view h3 {
font-size: 110%;
padding-left: 20px;
padding-top: 5px;
padding-bottom: 5px;
color: black;
}
.THIS .list-view li:first-child {
padding-top: 59px;
}
.THIS .list-view li:last-child {
border-bottom: none;
}
.THIS .icon {
display: block;
position: absolute;
float: left;
left: 0;
height: 100%;
padding-top: 13px;
padding-left: 15px;
z-index:10;
}
MobileOpportunity3Connector – this lightning application enables the two decoupled components to communicate using APPLICATION events. I like to think of this as the metadata from the lightning application builder that represents an admin dragging and dropping components to build a composite application. This is just a guess as the lightning application builder has yet to be released. Perhaps someone in the know will verify or correct this assumption.
APPLICATION.XML
<aura:component>
<jsonhammerle2:MobileOpportunity3List/>
<jsonhammerle2:MobileOpportunity3Product/>
</aura:component>
MobileOpportunity3CompositeApp – this lightning application may be used to test the composite application in the FORCE.IDE.
APPLICATION.XML
<aura:application>
<jsonhammerle2:MobileOpportunity3Connector />
</aura:application>
And there you have the lightning!
I like this app because the Opportunity List is decoupled from the Opportunity Product List. This was achieved by replacing static references and calls with events and event handlers. This subtle difference allows the Salesforce administrators to declaratively create composite applications made up of lightning components using the Lightning App Builder. Moreover, the component developer is free to focus on developing the component without being too concerned about the complexities of the application. This is in contrast to our version from Part One where the Opportunity List, the Opportunity Product List, the business logic, the View, the Data Model, State Transitions, and the many references to external JavaScipt libararies and Cascading Style Sheets all reside in a single VisualForce page that glued it all together in one great big monolithic hot mess.
I also like this app ALOT because the components have no external references to JavaScript libraries nor Cascading Style Sheets which allowed me – the component developer – to fully understand every aspect of my component, how my component communicated with other components, and how my component communicated with Salesforce. This is in contrast with Angular version, which took control of the app except for when Angular called my injected methods.
With that said, I see similarities between AngularJS modules and Lightning Components in how they both seek to encapsulate logical and user interface aspects using different means, e.g. dependency injection versus event-architecture. We will explore how we can leverage the best of both later in this series.
I am not sure yet where we will go in the next version but Part Three is clearly the beginning of a very exciting ride.
– JSON
