I am a PluralSight Author!

I am delighted to announce that I have published my first on-line course through PluralSight, ‘Play by Play: Prototyping UX Solutions with Lightning Components’.

The course home page is available at https://app.pluralsight.com/library/courses/play-by-play-prototyping-ux-solutions-lightning-components/table-of-contents. The trailer can be viewed for free and PluralSight subscribers can access the 3 hour course that focuses on Lightning Component development. The associated github repo is available here and the wiki I work through is available here.

Don Robins (left) and I talk through the course scenario in 'Play by Play: Prototyping UX Solutions with Lightning Components'

Don Robins (left) and I talk through the course scenario in ‘Play by Play: Prototyping UX Solutions with Lightning Components’

The course is part of a suite of new ‘Play by Play’ styled Salesforce Courses available on PluralSight organised by Salesforce Instructor (and my very good friend) Don Robins. Don is bringing many experts in the Salesforce world to talk and present courses on administration, development and architecture – the work he has done here is phenomenal.

The Course

In this course, I am challenged to show how to effectively approach working with customers and project stakeholders to design, prototype, validate and architect a set of Lightning Components required for a desired solution.

I demonstrate the Salesforce Lightning Design System “Starter Kit” — a rapid prototyping environment using the Salesforce Lightning Design System, HTML, CSS and Node.js. It allows a developer to jumpstart a prototype Lightning project, and easily deploy it to Heroku.

Then, you’ll see me incorporate these mock HTML pages into temporary “Composite” Lightning Components, to surface his prototype inside the Lightning Experience for stakeholder validation.

The initial screen of the final solution showing a list of Questionnaires to be completed.

The initial screen of the final solution showing a list of Questionnaires to be completed.

I then transform the mock pages into a cohesive component based architecture, converting prototype markup and behaviour into fully functional Lightning Components, designed for an ‘Instant Experience’ allowing users maximum ease of use, with minimal clicks.

The User input screen of the final solution providing for questions for answering.

The User input screen of the final solution providing for questions for answering.

And you’ll learn about reusability patterns and development practices that bring the application to life, such as Component Inheritance, Utility Functions, Data Validation, Error Handling and combining the use of Apex controllers along with the Lightning Data Service.

By the time we’re done, you’ll be able to use my project code and Wiki instructions to apply what you’ve learned to your own Lightning Component projects.

Thank Yous

I want to thank Kevin Pesch and all at PluralSight for the opportunity to make this course. And a huge thank you to the very patient and indefatigable Don Robins who urged me to create the course with him and have a grand adventure doing it.

Other Play-by-play Salesforce Lightning Courses have just been released where Don challenges Christian Menzinger and Steve Drucker. Also a shout out to Matt Morris who was my recording buddy for the day – his course on Diagramming Salesforce Solutions is well worth checking out.

Londons Calling 2017

I am honoured to presenting a session at London’s Calling 2017 – a European Salesforce.com event independently organised by the community. The event takes place in London on the 10th February 2017.

The day-long event is bigger than last year’s event – full of amazing sessions and keynotes and a chance to meet and chat with many MVPs from the Developer Community and Developer Evangelists from Salesforce. My congratulations to Simon Goodyear, Francis Pinar, Jodi Wagner and Kerry Townsend for their incredible efforts in organising the event.

London's Calling 2016

London’s Calling 2017

My own session is at 10.00am in the ‘Shift’ room and in it I discuss the planning and delivering of customer requirements with Lightning Components – and the lessons I have learned over the last year, most of which I have spent developing Lightning Components for Blue Wave’s customers.

The slide deck for the session is available here. The checkbox group component is available in my github account.

I have also recorded a webinar where I go through the Lightning Design System Starter Kit to augment the session.

See you at #LC2017!

See you at #LC2017!

Summer 16: Lightning Components for Record Home Pages

Salesforce Summer 16 Release Logo

Salesforce will release it’s Summer 16 upgrade in the weekends of June 4-5 and June 11-12 – and its a very exciting one in the evolution of Lightning Components. Lightning Experience Record Home Pages can be customised and custom Lightning Components can be added to these home pages.

Lightning Component Interfaces

To surface a custom Lightning Component on a Lightning Experience Record Page, the Component must implement the interface flexipage:availableForRecordHome. The Component will also implement a second interface force:hasRecordId that ensures that it receives the ID of the currently displayed record.

Implementing the interface force:hasSObjectName (along with flexipage:availableForRecordHome) allows a Lightning Component to receive the object name (String) of the currently displayed record.

Example: Displaying an Opportunity record’s List of Quotes

Lightning Experience - Opportunity showing a List of Quotes (Custom Lightning Components)

Lightning Experience – Opportunity showing a List of Quotes (Custom Lightning Components)

In this example, a Lightning Component retrieves Quote records for an associated OpportunityId when it is rendered. The Lightning Component implements the interfaces to make it available for Lightning Experience Home Pages and receive the Opportunity ID.

The force:hasRecordId interface will automatically populate an aura:attribute with a name of ‘recordId’ with the id of, in this case, the Opportunity. The force:hasSObjectName interface will populate an aura:attribute named ‘sObjectName’ with ‘Opportunity’.

<aura:component controller="OpportunityLightningController" 
                implements="flexipage:availableForRecordHome,
                            force:hasRecordId,force:hasSObjectName" 
                access="global" >

  <aura:attribute name="recordId" type="String" />
  <aura:attribute name="sObjectName" type="String" />
  <aura:attribute name="quotes" type="Quote[]" />
    
  <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    
  <table class="slds-table slds-table--bordered">
  <thead>
   <tr class="slds-text-heading--label">
    <th class="slds-is-sortable" scope="col">
     <div class="slds-truncate">Quote Name</div>
    </th>
    <th class="slds-is-sortable" scope="col">
      <div class="slds-truncate">Status</div>
    </th>
    <th class="slds-is-sortable" scope="col">
     <div class="slds-truncate slds-text-align--right">
        Sub Total ({!$Locale.currency})</div>
    </th>
    <th class="slds-is-sortable" scope="col">
     <div class="slds-truncate slds-text-align--right">
        Discount (%)</div>
    </th>
    <th class="slds-is-sortable" scope="col">
     <div class="slds-truncate slds-text-align--right">
        Total Price ({!$Locale.currency})</div>
    </th>
    <th class="slds-is-sortable" scope="col">
     <div class="slds-truncate slds-text-align--right">
        Tax ({!$Locale.currency})</div>
    </th>
    <th class="slds-is-sortable" scope="col">
     <div class="slds-truncate slds-text-align--right">
        Shipping ({!$Locale.currency})</div>
    </th>
    <th class="slds-is-sortable" scope="col">
     <div class="slds-truncate slds-text-align--right">
        Grand Total ({!$Locale.currency})</div>
    </th>
   </tr>
  </thead>
  <tbody>
   <aura:iteration items="{!v.Quotes}" var="quote">
    <tr class="slds-hint-parent">
     <th class="slds-truncate" scope="row" data-label="Quote Name">
      <a href="{! '/' + quote.Id }" target="_blank">{! quote.Name }</a>
     </th>
     <td class="slds-truncate" scope="row" data-label="Status">
      {! quote.Status }
     </td>
     <td class="slds-text-align--right" data-label="Sub Total">
      <ui:outputCurrency value="{!quote.Subtotal}" format="0,000.00" />           
     </td>
     <td class="slds-text-align--right" data-label="Discount (%)">
      <ui:outputNumber value="{!quote.Discount}" />           
     </td>
     <td class="slds-text-align--right" data-label="Total Price">
      <ui:outputCurrency value="{!quote.TotalPrice}" format="0,000.00" /> 
     </td>
     <td class="slds-text-align--right" data-label="Tax">
      <ui:outputCurrency value="{!quote.Tax}" format="0,000.00" />           
     </td>
     <td class="slds-text-align--right" data-label="Shipping">
      <ui:outputCurrency value="{!quote.ShippingHandling}" format="0,000.00" /> 
     </td>
     <td class="slds-text-align--right" data-label="Sub Total">
      <ui:outputCurrency value="{!quote.GrandTotal}" format="0,000.00" />           
      </td>
     </tr>
    </aura:iteration>
    </tbody>
  </table>

</aura:component>

The ‘quotes’ aura:attribute is populated by the Javascript controller function ‘doInit’ in the line component.set(‘v.Quotes’, response.getReturnValue());.

Component Controller

({
    doInit : function(component, event, helper) {
        // create a one-time use instance of the serverEcho action
        // in the server-side controller
        var action = component.get("c.getQuotes");
        action.setParams({ OpportunityId : component.get("v.recordId") });

        // Create a callback that is executed after 
        // the server-side action returns
        action.setCallback(this, function(response) {
            var state = response.getState();
			// The action executed successfully
            if (state === "SUCCESS") {
                // Quote records have been returned (if any exist)
                // The Quotes component can be set with the Quotes returned
                component.set('v.Quotes', response.getReturnValue());
            }
            else if (state === "NEW") {
				// The action was created but is not in progress yet
            }            
            else if (state === "RUNNING") {
				// The action is in progress
            }            
            else if (state === "ABORTED") {
				// The action was aborted
            }            
            else if (state === "INCOMPLETE") {
                // The server didn't return a response. 
                // The server might be down or the client might be offline. 
                // The framework guarantees that an action's callback is always invoked as 
                // long as the component is valid. If the socket to the server is never 
                // successfully opened, or closes abruptly, or any other network 
                // error occurs, the XHR resolves and the callback is invoked 
                // with state equal to INCOMPLETE.
            }
            else if (state === "ERROR") {
	            // The server returned an error
                // generic error handler
                var errors = response.getError();
                if (errors) {
                    $A.log("Errors", errors);
                    if (errors[0] && errors[0].message) {
                        throw new Error("Error: " + errors[0].message);
                    }
                } else {
                    throw new Error("Unknown Error");
                }
            }
        });

        // A client-side action could cause multiple events, 
        // which could trigger other events and 
        // other server-side action calls.
        // $A.enqueueAction adds the server-side action to the queue.
        $A.enqueueAction(action);
    }
})

The Quote records are retrieved using the following Apex Class.

public class OpportunityLightningController {
    
    @AuraEnabled
    public static List<Quote> getQuotes(String OpportunityId) {
        // Make sure we're not seeing something naughty
        if(OpportunityId == null) {
                throw new AuraHandledException('No OpportunityId received.');            
        }
        
        List<Quote> quoteList = [SELECT id, Name, Tax, SubTotal, TotalPrice, 
                                 Discount, GrandTotal, Status, ExpirationDate,
                                 ShippingHandling
                                 FROM Quote 
                                 WHERE OpportunityId = :OpportunityId];
        return quoteList;
    }
    
}

Here is the associated test class. Note that I am throwing an AuraHandledException where I intercept a possible error in the OpportunityLightningController class – I am testing for this below.

@isTest
private class OpportunityLightningController_TEST {
    
    @TestSetup
    static void setUpQuote() {
        Account a = new Account(Name='Test Inc');
        insert a;
                
        Pricebook2 pbk1 = new Pricebook2 (Name='Test Pricebook Entry 1',Description='Test Pricebook Entry 1', isActive=true);
        insert pbk1;
        
        Product2 prd1 = new Product2 (Name='Test Product Entry 1',Description='Test Product Entry 1',productCode = 'ABC', isActive = true);
        insert prd1;
        
        Id pricebookId = Test.getStandardPricebookId();
        
        PricebookEntry pbe1 = new PricebookEntry (Product2ID=prd1.id,Pricebook2ID=pricebookId,UnitPrice=50, isActive=true);
        insert pbe1;
        
        
        Opportunity opp1 = new Opportunity (Name='Opp1',StageName='Stage 0 - Lead Handed Off',CloseDate=Date.today(),Pricebook2Id = pbe1.Pricebook2Id, AccountId = a.id);
        insert opp1;
        
        
        OpportunityLineItem lineItem1 = new OpportunityLineItem (OpportunityID=opp1.id,PriceBookEntryID=pbe1.id, quantity=4, totalprice=200);
        insert lineItem1;
        
        Quote quttest = new Quote (Name = 'qoutetest' , OpportunityId = opp1.id , Pricebook2Id = pricebookId );
        insert quttest ;

        List<QuoteLineItem> listval = new   List<QuoteLineItem>();
        QuoteLineItem qutlineitemtest = new QuoteLineItem ();
        qutlineitemtest = new QuoteLineItem(QuoteId = quttest.id , Quantity = 3.00 , UnitPrice = 12 , PricebookEntryId = pbe1.id);
       insert qutlineitemtest;

    }
    
    @isTest
    static void getQuotesTest() {
        
        Opportunity opp1 = [SELECT id FROM Opportunity LIMIT 1];
        System.assertEquals(1, OpportunityLightningController.getQuotes(opp1.id).size());
    }
    
   	@isTest
    static void throwAnError_TEST() {
        try {
	        OpportunityLightningController.getQuotes(null);
            System.assertEquals(1,0);	// line should not run            
        } catch(Exception e) {
            System.assertEquals('Script-thrown exception', e.getMessage());
            System.assertEquals('System.AuraHandledException', e.getTypeName());
        }
    }
    
}

System Administrators can add the Component to the Opportunity Record Home Page by clicking the ‘Edit Page’ option in the settings menu. This will open the Lightning App Builder to edit the page layout.

Lightning Experience - Editing Opportunity Record Home Page

Lightning Experience – Editing Opportunity Record Home Page

A Custom Tab can be added to the main panel by clicking on the panel and using the associated options on the Right Hand Side of the App Builder interface. Note that the default tab can also be changed.

Lightning Experience Opportunity Home Page - Adding a Custom Tab

Lightning Experience Opportunity Home Page – Adding a Custom Tab

Any component can be dragged into the new tab. The Custom Component showing the List of Quotes (I called it ‘playWithOpportunityId’) is available in the list on the left hand side.

Lightning Experience Opportunity Home Page - Adding a Custom Lightning Component - 'playWithOpportunityId' - showing the list of Quotes for the Opportunity

Lightning Experience Opportunity Home Page – Adding a Custom Lightning Component – ‘playWithOpportunityId’ – showing the list of Quotes for the Opportunity

The Lightning App Builder creates or updates a Lightning Page when saved. This Lightning Page must then be ‘Activated’ using the available button to override the current Lightning Experience Record home page.

It is interesting that I did not have to add Salesforce Lightning Design System to the Lightning Component – presumably it was applied to the custom component by Lightning Experience.

After being added to the Lightning Page, any changes to the Lightning Component is not immediately applied to the Lightning Page. The Page must be re-opened and saved in the App Builder to apply new changes. This is a little inconvenient and takes a little getting used to.

I believe that this capability will be a real eye-opener for customers, managers, administrators and users to the capability of Lightning Components. And provide a new world of possibilities for developers.

Connecting Lightning Components and Visualforce

It’s a busy time. Since January I have been preparing to teach the new ‘Programming Lightning Components‘ course (DEV601) for Salesforce University. It is an excellent in-depth 5 day course. I have taught it twice over the last 5 weeks and am due to teach it again next week. Feedback from attendees has been very positive.

As you interact on these courses you always learn new things, and an interesting add on to Lightning out for Visualforce is worth sharing.

Lightning Out for Visualforce

Lightning Out for Visualforce allows for the embedding of Lightning Components via javascript in a Visualforce page. In order to use it, a visualforce page requires

  • a platform provided javascript library ‘/lightning/lightning.out.js’. There is alternatively a new visualforce tag ‘<apex:includeLightning />’ that can be used.
  • the Lightning Components must be added as an aura dependency to a Lightning Application. Many Lightning Components can be used in this application.
  • The Lightning Application must extend an interface ‘ltng:outApp’.

Currently, the only way to use a Lightning Component in Salesforce Classic (Aloha) is through Lightning Out for Visualforce.

Visualforce Interacting with a Lightning Component

It is possible to call javascript functions in the Lightning Component from Visualforce. The example below uses a built-in callback function provided in the $Lightning.createComponent function to save a reference to the component in the context of the JavaScript in the page.

In the below example, the Visualforce page will display a Lightning Component called ‘lightSabre’ (via a Lightning Application called ‘LightSabreApp’) which as a button that plays a sound. Once the Component is created in the ‘lightSabreDiv’ DOM element, the callback function (which takes a parameter of the component being created) can be used to apply the component reference to a javascript variable in the Visualforce page.

<apex:page >    
    <script src="/lightning/lightning.out.js"></script>
    

<div id="lightSabreDiv"></div>


    <a href="#" onclick="pressButton()">PRESS (to call function within c:LightSabre Controller)</a>

    <script>
        var component;

    	function pressButton() {
            component.helper.playSound(component);
        }
    
        $Lightning.use("c:LightSabreApp", function() {
            $Lightning.createComponent(
                "c:LightSabre",
                {},
                "lightSabreDiv",
                function(cmp) {
                    component = cmp;
                }
            );            
        });
    
    </script>
    
</apex:page>

Lightning Application Markup:

<aura:application access="GLOBAL" extends="ltng:outApp">
    <aura:dependency resource="c:LightSabre" />        
</aura:application>

The page can then call functions within the component. In this case the ‘pressButton()’ javascript function is directly calling a function in the helper of the Lightning Component that plays the sound (in this case a static resource).

Component Markup:

<aura:component description="Linking Visualforce to Lightning">


<div class="centered">
      <ui:button label="Light Sabre!!" press="{!c.playbackBegin}" />
      <audio aura:id="audiofile" src="/resource/lightsaber"></audio>
</div>


</aura:component>

Component Controller:

({
    playbackBegin : function(component, event, helper) { 
        var audioEl = component.find('audiofile').getElement();
        audioEl.play();
    }
})

Component Helper:

({
    playSound : function(component) { 
        var audioEl = component.find('audiofile').getElement();
        audioEl.play();
    },
})

Lightning Out for Visualforce went GA in the Spring 16 release. Lightning Out will also be available for any external site (with authentication) and through the Mobile SDK – these features are in pilot at time of writing and will be available in a future release.

#LondonsCalling: Lightning Single Page Application

Until I put together a full blog post on it, here is the slidedeck for my LDNs Calling 2016 presentation.

The sessions at the fantastic event are being recorded and the videos and slidedecks will be posted to http://www.londonscalling.net/.

Updates:

  1. I have added all of the code to my github repo.
  2. The recording of all presentations given at Londons Calling is now available at the London’s Calling Youtube Channel. My own presentation is here:

London’s Calling – Feb 5th 2016

I am looking forward to presenting a session at London’s Calling – the first European Salesforce.com event independently organised by the community. The event takes place in London on the 5th February 2016.

The day-long event is a full of amazing sessions and keynotes from Salesforce.com’s Peter Coffee and Erica Kuhl – not to mention the chance to meet and chat with many MVPs from the Developer Community and Developer Evangelists from Salesforce.
London's Calling 2016

My own session is at 10.55am in the ‘Alt’ room (the rooms are called CMD, CTRL, ALT and TAB – love it). The session is a sequel to my session at Dreamforce 15 where I created a complex single page application (SPA) with a Visualforce page and no apex was used. The sequel asks the question – how can I create a similar spa in a Lightning Application with Lightning Components and the Lightning Design System – and what is the experience like?

Visualforce SPA to Lightning SPA

Visualforce SPA to a Lightning SPA

I will post the slide deck for the session here the day before the event – and you might even get free code here too!

Kudos to the team who are organising the event and my thanks for getting the opportunity to speak at it.

My photo - See you all there!

See you all there!

Lightning components require My Domain

Hands up who’s gotten this message when running Lightning Applications since the Winter 16 release?

“Lightning components require My Domain. Please contact your system administrator for more information.”

To fix this, follow these steps

  1. Setup | Administer | Domain Management | My Domain
  2. Enter a domain name of your chosen. In the below screen shot, I am using a developer org which is why ‘-dev-ed’ is added to the end.Setting My Domain
  3. Check to see if  the domain used is available.
  4. Agree with the Terms & Conditions
  5. Click ‘Register Domain’
  6. It may take some time for Salesforce to register the domainDomain Registration Pending
  7. Once the domain is registered, re-open the ‘My Domain’ page to see new options:Click here to Login
  8. Click the ‘Click here to login’ button. This will login into Salesforce using the domain name selected.
  9. Click ‘Deploy to Users’ and agree with the alertDeploy to Users alert
  10. Now open your Lightning applications and all should work!My Lightning App is working again!