Friday 30 December 2016

JavaScript Promises in Lightning Components

JavaScript Promises in Lightning Components

Promise

Introduction

Promises have been around for a few years now, originally in libraries or polyfills but now natively in JavaScript for most modern browsers (excluding IE11, as usual!).

The Mozilla Developer Network provides a succinct definition of JavaScript promises:

A Promise is a proxy for a value not necessarily known when the promise is created

Promises can be in one of three states:

  • pending - not fulfilled or rejected (Heisenberg, for Breaking Bad fans!)
  • fulfilled  - the asynchronous code successfully completed
  • rejected - an error occurred executing the asynchronous code

For me, the key advantage with Promises is that they allow asynchronous JavaScript code to be written in a way that looks somewhat like synchronous code, and is thus easier for someone new to the implementation to understand.

Creating a Promise

var promise = new Promise(function(resolve, reject) {

  // asynchronous code goes here

  if (success) {
     /* everything worked as expected */
     resolve("Excellent :)");
  }
  else {
    /* something went wrong */
    reject(Error("Bogus :("));
  }
});
 

The Promise constructor takes a callback function as a parameter. This callback function contains the asynchronous code to be executed (to retrieve a record from the Salesforce server, for example). The callback function in turn takes two parameters that are also functions - note that you don’t have to write these functions, just invoke them based on the outcome of the asynchronous code:

  • resolve - this function is invoked if the asynchronous code executes successfully. Executing this function moves the Promise to the fulfilled state
  • reject - this function is invoked with an Error if anything goes wrong in the asynchronous code. Executing this function moves the Promise to the rejected state.

Handling the Result

Thus far all well and good, but pretty much all of the asynchronous code that I write is carrying out a remote activity and returning the result, so I need some way to be notified when the asynchronous code has completed. Enter the Promise.then() function:

promise.then(function(data) {
                alert('Success : ' + data);
             },
             function(error) {
                alert('Failure : ' + error.message);
             });

Promise.then() takes two functions as parameters - the first is a success callback, invoked if and when the promise is resolved, and the second is an error callback, invoked if and when the promise is rejected.

A Real World Example

When building Lightning Components, asynchronous interaction with the Salesforce server is typically carried out via an action. The following function takes an action and creates a Promise around it:

executeAction: function(cmp, action, callback) {
    return new Promise(function(resolve, reject) {
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var retVal=response.getReturnValue();
                resolve(retVal);
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        reject(Error("Error message: " + errors[0].message));
                    }
                }
                else {
                    reject(Error("Unknown error"));
                }
            }
        });
	$A.enqueueAction(action);
    });
}

the executeAction function instantiates a Promise that defines the action callback handler and enqueues the action. When the action completes, the callback handler determines whether to fulfil or reject the Promise based on the state of the response.

This function can then be used to create a Promise to retrieve an account: 

var accAction = cmp.get("c.GetAccount");
var params={"accountIdStr":accId};
accAction.setParams(params);
        
var accountPromise = this.executeAction(cmp, accAction);

and callback handlers provided to process the results:

accountPromise.then(
        $A.getCallback(function(result){
            // We have the account - set the attribute
            cmp.set('v.account', result);
        }),
        $A.getCallback(function(error){
            // Something went wrong
            alert('An error occurred getting the account : ' + error.message);
        })
     );

Note that the success and error callbacks are encapsulated in $A.getCallback functions as they are executed asynchronously, and therefore are outside of the Lightning Components lifecycle. Note also that if you forget to do this, quite a lot of the promise functionality will still work, which will make it difficult to track down what the exact problem is!

Chaining Promises

The Promise.then() function can return another Promise, thus setting up a chain of asynchronous operations that each complete in turn before the next one can start. Repurposing the above example to retrieve a Contact from the Account:

accountPromise.then(
        $A.getCallback(function(result){
            // We have the account - set the attribute
            cmp.set('v.account', result);

            // return a promise to retrieve a contact
            var contAction = cmp.get("c.GetContact");
            var contParams={"accountIdStr":accId};
            contAction.setParams(contParams);
            var contPromise=self.executeAction(cmp, contAction);
            return contPromise;
        }),
        $A.getCallback(function(error){
            // Something went wrong
            alert('An error occurred getting the account : ' + error.message);
        })
   )
   .then(
        $A.getCallback(function(result){
            // We have the contact - set the attribute
            cmp.set('v.contact', result);
        }),
        $A.getCallback(function(error){
            // Something went wrong
            alert('An error occurred getting the contact : ' + error.message);
        })
     );
    

However there is a side effect here - the second then() is executed regardless of the success/failure of the first. If the first Promise was rejected, the success callback for the second then() is executed with a null value. While this would be benign in the above example, it’s probably not behaviour that is desired in most cases. What would be better is that the second then() is only executed if the first one is successful. Enter the Promise.catch() function.

Catching Errors

The Promise.catch() function is invoked if a Promise is rejected, but the then() function didn’t provide an error callback:

promise.then(function(data) {
                alert('Success : ' + data);
             })
        .catch(function(error) {
                alert('Failure : ' + error.message);
             });

When chaining Promises, the catch() function becomes more powerful, as if a Promise is rejected and the then() function did not provide an error callback, control moves forward to either the next then() function that does provide an error callback, or the next catch() function.

Refactoring the example again:

accountPromise.then(
        $A.getCallback(function(result){
            // We have the account - set the attribute
            cmp.set('v.account', result);

            // return a promise to retrieve a contact
            var contAction = cmp.get("c.GetContact");
            var contParams={"accountIdStr":accId};
            contAction.setParams(contParams);
            var contPromise=self.executeAction(cmp, contAction);
            return contPromise;
        })
   )
   .then(
        $A.getCallback(function(result){
            // We have the contact - set the attribute
            cmp.set('v.contact', result);
        })
   .catch(
        $A.getCallback(function(error){
            // Something went wrong
            alert('An error occurred : ' + e.message);
        })
     ); 

The second then() function is now only executed if the account retrieval is successful. An error occurring retrieving either the account or the contact immediately jumps forward to the catch() function to surface the error.

Going back to the original point made in the introduction, I now have two asynchronous operations, with the second dependent on the success of the first, but coded in a readable fashion.

Further Reading

Promises are a tricky one to wrap your head around, and its certainly worth spending some time learning the basics and playing around with examples. I've found the following resources very useful:

Related Posts

 

 

Sunday 11 December 2016

Lightning Design System in Visualforce Part 1 - Getting Started

Lightning Design System in Visualforce Part 1 - Getting Started

Lightningall

Introduction

A short while ago I wrote a Medium post containing tips for Visualforce developers moving over to Lightning Components. A pre-requisite for developing Lightning Components is being able to write JavaScript to some level, which isn’t something that every existing Visualforce developer has, especially those that from a functional background that have gained enough of an understanding of Visualforce and Apex to become productive, but aren’t in a position to quickly train themselves up on a new programming language. 

If you are in this boat, the good news is that if you don’t have JavaScript you can still create a custom user interface matching the Lightning Experience look and feel - simply carry on building Visualforce pages but use the Salesforce Lightning Design System (LDS) to style them.

Get the LDS

LDS is automatically included in various Lightning Component scenarios, but to use it with Visualforce you still need a static resource. Previously it was possible just to download a zip file, but now a custom scope is required in the CSS so you have to generate your own download via the CSS customizer tool. I chose BBLDS - if you go with something else, remember to update the CSS style classes in the sample code. Once you’ve generated the file, upload it to Salesforce as a static resource - I’ve gone with the name BBLDS_W17, again if you change this remember to update the references in the sample code.

Create the Basic Page

I create a new Visualforce page, making sure to check the box so that my page is available in LEX:

Screen Shot 2016 12 10 at 18 22 54

and change the <apex:page /> tag to the following:

<apex:page showHeader="false" sidebar="false" standardStylesheets="false"
           standardController="Account" applyHTmlTag="false">

Note that I have to turn off all aspects of standard Visualforce styling - no header, no sidebar and no standard stylesheets. The example page displays some basic details of an Account so I specify the Account standard controller. Finally, the applyHtmlTag attribute is set to false - this gives me control over the <html>, <head> and <body> tags, which is necessary to use the SVG icons in the LDS. Next, I add the <html> tag, specifying the SVG namespace information, followed by the <body> tag:

<html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<body>

Next, I include the LDS - as noted above, if you’ve chosen a different name to BBLDS_W17, remember to change the name (and remember I die a little inside when my recommendations are rejected, but I accept that everyone has free will):

<apex:stylesheet value="{!URLFOR($Resource.BBLDS_W17,
                         'assets/styles/salesforce-lightning-design-system-vf.min.css')}" />

 Then I add the Lord of the Divs - one Div to rule them all - that wraps all content and specifies the LDS namespace. Again, if you haven’t used the same name as me (which hurts, it’s like a knife to my heart) remember to update the code:

<div class="BBLDS">

then I close everything off - the <div>, <body>, <html> and <page> tags.

Add LDS Components

The next task is to add some LDS components. Here I use the time-honoured approach of finding something in the LDS samples that looks appropriate, copying the sample and hacking it around, and I’d certainly recommend that approach when you first start out.

The first element I’ve added is the Record Home Page Header - I’m not going to cover the code in detail here, save for a couple of key points.

First - this is a Visualforce page so I can just drop in merge syntax to display details of the Account, for example:

<li class="slds-page-header__detail-block">
  <p class="slds-text-title slds-truncate slds-m-bottom--xx-small" title="Description">Description</p>
  <p class="slds-text-body--regular slds-truncate" title="{!Account.Description}">{!Account.Description}</p>
</li>

 gives me:

Screen Shot 2016 12 10 at 17 55 38

Second, SVG icons requires me to specify the location of the icon in the static resource file:

<div class="slds-media__figure">
  <svg aria-hidden="true" class="slds-icon slds-icon-standard-account">
    <use xlink:href="{!URLFOR($Resource.BBLDS_W17, 
'/assets/icons/standard-sprite/svg/symbols.svg#account')}">
</use>
</svg>
</div>

Screen Shot 2016 12 10 at 17 59 50

Third - I can still use standard component tags to generate formatted output:

<div class="slds-form-element slds-hint-parent slds-has-divider--bottom">
  <span class="slds-form-element__label">Created By</span>
  <div class="slds-form-element__control">
    <span class="slds-form-element__static">{!Account.CreatedBy.Name},
      &nbsp;<apex:outputField value="{!Account.CreatedDate}"/>
    </span>
  </div>
</div>

 

Screen Shot 2016 12 10 at 18 02 29

Note that I don’t use an output field tag to generate the details of the user that created the Account, as this has a side effect of adding a hover popup to the output link, which won’t be styled correctly as I’m not using any of the default Visualforce styling. Always consider the full effects of any standard component you use, and make sure you are happy with them - you don’t want to confuse your users with broken or half-styled elements.

Boring - where’s the final page?

Here’s  a screenshot of the final page - pretty simplistic, but definitely styled appropriately for LEX users:

Screen Shot 2016 12 10 at 18 07 33

Enough talk, show me the code

As usual with LDS posts, the code is in my LDS Samples Github Repository. There’s also an unmanaged package available to save wasting time copying and pasting - see the README.

In Conclusion

This post is in no way intended to suggest that you avoid learning Lightning Components in favour of making your Visualforce look like LEX. Lightning Components are the future and they are in and of LEX rather than iframed in from a different server, so you’ll be able to do a lot more with them in the future. I’d also imagine there isn’t a large team developing Visualforce nowadays, more likely a small team working on essential maintenance items, so you are unlikely to see much in the way of new features.

Related Posts