Saturday, 14 January 2017

Salesforce Platform Cache - Expect the Unexpected

Platform Cache - Expect the Unexpected

Cache

Introduction

When the Salesforce Platform Cache appeared, this allowed a lot of home made caching code to be retired. Up until the the only data that was cached was custom settings. (This is in terms of not requiring a trip to the database, there are obviously all sorts of caches at play in the Salesforce platform - reports involving large amounts of data often succeed on the second or third try as some of the data has made it’s way closer to the front end). While custom settings speed up the retrieval of data and save SOQL calls, it was a pretty basic solution, requiring DML to push information into the ‘cache’, and the type of data that could be stored was pretty basic, so caching and rehydrating a complex object meant you had to handle the transformation yourself. 

In this post I won’t be going into the details of getting started with the cache, as the Trailhead module does an excellent job of this already.

Seek, and you may not find it

A key feature of the platform cache is that just because you put something in the cache, it doesn’t mean that it will still be there at a later date, thus you have to be prepared to deal with cache misses. Thus if I store an opportunity keyed by it’s id in a Blue Peter style partition I created earlier:

Cache.OrgPartition oppsPart=Cache.Org.getPartition('Opportunities');
Opportunity opp=[select id, Name, CloseDate from Opportunity where id='0060Y000003AWjG'];
oppsPart.put('0060Y000003AWjG', opp);

I can try to retrieve it in a different request:

Cache.OrgPartition oppsPart=Cache.Org.getPartition('Opportunities');
Opportunity oppFromCache=(Opportunity) oppsPart.get('0060Y000003AWjG');
System.debug('Opp = ' + oppFromCache);

and all things being equal I'll get the following debug output:

12:27:52:158 USER_DEBUG [3]|DEBUG|Opp = Opportunity:{Name=Edge Installation,
Id=0060Y000003AWjGQAW, CloseDate=2014-11-02 00:00:00}

If the opportunity has been booted from the cache for any reason (expired, an evil co-worker removes it) then I'll receive a null rather than the opportunity, and can fetch it from the database.

You have to know what you are asking for

Based on the above, if you are doing a lot of work with opportunities matching a particular criteria, it’s tempting to try to use the cache as a database replacement, storing all of them in there keyed by id:

Cache.OrgPartition oppsPart=Cache.Org.getPartition('Opportunities');
List<Opportunity> opps=[select id, Name, CloseDate from Opportunity];
for (Opportunity opp : opps)
{
	oppsPart.put(opp.id, opp);
}

I can then retrieve these by iterating all of the keys in the partition:

Cache.OrgPartition oppsPart=Cache.Org.getPartition('Opportunities');
List<Opportunity> opps=[select id, Name, CloseDate from Opportunity];
for (Opportunity opp : opps)
{
	oppsPart.put(opp.id, opp);
}

which gives me the following output:

12:42:40:176 USER_DEBUG [6]|DEBUG|Opp = Opportunity:{Name=Edge SLA,
Id=0060Y000003AWjHQAW, CloseDate=2014-11-02 00:00:00}
12:42:40:180 USER_DEBUG [6]|DEBUG|Opp = Opportunity:{Name=United Oil
Installations, Id=0060Y000003AWjFQAW, CloseDate=2014-11-02 00:00:00}
...
12:42:40:261 USER_DEBUG [6]|DEBUG|Opp = Opportunity:{Name=Burlington Textiles
Weaving Plant Generator, Id=0060Y000003AWjOQAW, CloseDate=2014-11-02 00:00:00}

At first glance this looks fine, but there is a huge issue with this approach - I have no idea whether this is the full collection of opportunities that I cached. Entries might have been evicted, even while I was iterating the opportunities I’d queried if the partition filled up, or an evil co-worker may remove a couple and then add them back later so that I have no idea why they weren’t processed.

Unlike executing  SOQL query, all I can be sure of is that I’ve retrieved all of the opportunities that remain in the cache, which may bear very little relation to the contents of the database. Clearly another approach is required. 

Cache collections as a single entry

In this scenario, rather than storing each object as an entry the solution is to store the entire collection as a single entry:

Cache.OrgPartition oppsPart=Cache.Org.getPartition('Opportunities');
List<Opportunity> opps=[select id, Name, CloseDate from Opportunity];
oppsPart.put('all', opps);

retrieving it in a separate request as follows:

Cache.OrgPartition oppsPart=Cache.Org.getPartition('Opportunities');
List<Opportunity> oppsFromCache=(List<Opportunity>) oppsPart.get('all');
for (Opportunity opp : oppsFromCache)
{
	System.debug('Opp = ' + opp);
}

In this case, if my opportunities have been evicted from the cache I’ll receive a null response and can re-query them from the database. Of course this doesn’t stop an evil co-worker from overwriting the ‘all' entry in the cache with a different collection of opportunities - if this continues to be a problem then a session cache partition, which is tied to a specific user, is probably a better option.

More Information

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

 

Sunday, 27 November 2016

Force CLI Part 5 - Node Wrapper

Force CLI Part 5 - Node Wrapper

Json

Introduction

In earlier posts in this series I shared the odd example bash script that carried out some combination of commands in conjunction with the Force CLI to achieve things like creating a zip backup of Salesforce metadata. The reason I went with bash scripts is that I know the syntax really well and was able to quickly build some tools for managing deployment of our BrightMedia Full Force solution. In essence it allowed us to get big quick.

However, there are a couple of downsides to this approach:

  • Not that many people know bash these days - I spent many years building systems on Linux so I’m pretty familiar with it, but I didn’t want to be a bottleneck
  • Bash isn’t the easiest tool to process XML or JSON format data. You either end up running the files through additional commands such as XMLStarlet, which gives you a whole new syntax/configuration to learn, or you start parsing it using tools like sed and awk, which works for the basics but quickly becomes unusable with complex data.
I’m pushing most of our people towards JavaScript now, with the advent of lightning components, so JavaScript running on the Node platform seemed like the obvious choice to rewrite the scripts in. I then found out that the CLI for SalesforceDX will be built in JavaScript on Node, and will have a plugin architecture to allow us to extend it, which more than justified my choice. 

JSON takes Salesforce Data

While my code behind the tools is now a lot more flexible and easier to maintain, the key use case I’ve found is when extracting Salesforce record data for processing - e.g. carrying out a data fix as part of a release. The Force CLI can output the results of a query formatted as a JSON string, and JavaScript can parse this into an object using the built in JSON.parse() function.

The following example reflects a real-world scenario I encountered a few weeks ago. I was bitten by a known issue with the Winter 17 release of Salesforce, which meant that I couldn’t login to the Lightning Experience. I needed to flip the users in this org to the classic interface, which I started doing via the data loader before creating a script to do the same thing with the Force CLI wrapped in a Node script.

Getting Started

First, download and install Node from the NodeJS org website.

Then create a new directory for the project - in this example a subdirectory named cli.

  • mkdir cli
  • cd cli
Next, create a Node package file for the project:
  • npm init

Accept the defaults for everything and add a description if you so desire.

Note that you don’t strictly need to do this, for the simple example in this post, but it’s a really good habit to get into for when you need to include dependencies on other packages.

Show Me the Code

Save the following code into a file named index.js :

var child_process=require('child_process');

var instance=process.argv[2];
if (instance===undefined) {
    instance='login';
}
var loginOpts=['login', '-i=' + instance];
child_process.execFileSync('force', loginOpts);

// extract the user records as JSON
var queryOpts=['query', 'select id, Username, UserPreferencesLightningExperiencePreferred from User',
               '--format:json'];

var uJSON=child_process.execFileSync('force', queryOpts);

// turn into a JS object
var users=JSON.parse(uJSON);

// dump the users out to the console
for (var idx=0, len=users.length; idx<len; idx++) {
    var user=users[idx];
    console.log('User = ' + JSON.stringify(user, null, 4));
}

 Executing this produces the following (edited) output: 

kbowden@Keirs-MacBook-Air cli $ node index.js
User = {
    "Id": "00524000003vqIgAAI",
    "UserPreferencesLightningExperiencePreferred": true,
    "Username": "ltg.test@bgmctest.brightgen.com",
    "attributes": {
        "type": "User",
        "url": "/services/data/v36.0/sobjects/User/00524000003vqIgAAI"
    }
}
User = {
    "Id": "00524000000FctAAAS",
    "UserPreferencesLightningExperiencePreferred": true,
    "Username": "keirbowden@bgmctest.brightgen.com",
    "attributes": {
        "type": "User",
        "url": "/services/data/v36.0/sobjects/User/00524000000FctAAAS"
    }
}

So I can see that a number of users are setup for the Lightning Experience.

Explain Yourself

Breaking down the code a little, the first line loads the child_process module, which allows a script to spawn other processes:

var child_process=require('child_process');

The next lines figure out the correct Salesforce instance to connect to - the default is login, but you can override this by passing an additional argument after index.js, such as test to connect to the sandbox login endpoint. Once this is done, a child process is spawned to execute the login. 

var instance=process.argv[2];
if (instance===undefined) {
    instance='login';
}
var loginOpts=['login', '-i=' + instance];
child_process.execFileSync('force', loginOpts);

Note that the chid process function takes two parameters - the first is the command to execute (force in this case) and the second an array of parameters to pass to the command. Note also that I use the execFileSync variant to execute the command synchronously and return the output as the result which I promptly ignore!

Next, a query is executed via the Force CLI to extract details of all users in JSON format:

// extract the user records as JSON
var queryOpts=['query', 'select id, Username, UserPreferencesLightningExperiencePreferred from User',
               '--format:json'];

var uJSON=child_process.execFileSync('force', queryOpts);

and this is converted to an array of JavaScript objects:

var users=JSON.parse(uJSON);

Updating the Users

Once I verified that I could extract the users successfully, I could then update each user to use Salesforce Classic, by replacing the final loop with the following code which executes the Force CLI record update command for each returned user:

for (var idx=0, len=users.length; idx<len; idx++) {
    var user=users[idx];
    if (user.UserPreferencesLightningExperiencePreferred) {
        console.log('Reverting user ' + user.Username + ' to classic');
        var updateOpts=['record', 'update', 'user', user.Id,
                        'UserPreferencesLightningExperiencePreferred:false'];
        child_process.execFileSync('force', updateOpts);
    }
}

Running the revised script gives the following (edited) output:

kbowden@Keirs-MacBook-Air cli $ node index.js login
Reverting user ltg.test@bgmctest.brightgen.com to classic Reverting user keirbowden@bgmctest.brightgen.com to classic

Logging in after executing this script confirms that the users have been updated correctly as I am taken straight to the classic UI rather than a sad snowman with an error message! 

Related Posts

 

 

Thursday, 10 November 2016

Lightning Component Actions with Signature Capture Part 2

Lightning Component Actions with Signature Capture Part 2

Introduction

In the first part of this blog I covered how to set up a quick action using the Lightning Component Action in the Signature Capture package. In this post I’ll show how to create a custom lightning component action that wraps the Signature Capture component and allows overriding of the default text and labels.

Messaging

The following list shows the Signature Capture message/label attributes and their default values:

  • startMsg - “Click the ‘Capture Signature’ button to begin 
  • enterMsg - “Sign here please"
  • completeMsg - “Here is the captured signature"
  • captureButtonLabel - “Capture Signature"
  • saveButtonLabel - “Save”     (new in Winter 17)
  • clearButtonLabel - “Clear”    (new in Winter 17)

Text Literals

The packaged component action, SignatureCaptureAction, leaves the default attribute values as-is. A custom action, on the other hand, allows some or all of these to be overridden with different text literals:

<BGSIGCAP:SignatureCapture recordId="{!v.recordId}"
          startMsg="Let's Go - Click the button to start” />

 

Screen Shot 2016 11 10 at 08 11 42

Custom Labels

Attributes may also be defined as custom labels, which have the added advantage of automatically picking up the correct translation for the user:

Screen Shot 2016 11 10 at 08 16 03

<BGSIGCAP:SignatureCapture recordId="{!v.recordId}"
         saveButtonLabel="{!$Label.c.SigCapSave}" />

Screen Shot 2016 11 10 at 08 25 01

 

Example Custom Action

Here’s a couple of screenshots from an example custom component with all text content overridden :

Screen Shot 2016 11 10 at 08 33 41

Screen Shot 2016 11 10 at 08 33 53

Screen Shot 2016 11 10 at 08 34 03

The source for this can be found in the Signature Capture Samples GITHUB repository.

Related Posts

 

Monday, 7 November 2016

Lightning Component Actions with Signature Capture

Lightning Component Actions with Signature Capture

Screen Shot 2016 11 07 at 18 36 19

Introduction

As some readers of this blog may be aware, a year or so ago I wrote a Lightning Component for the Component Exchange that was soon to be launched at Dreamforce. The component is called Signature Capture and unsurprisingly allows a user to capture a signature image and attach it to a Salesforce record. Up until the Winter 17 release of Salesforce, the only way to associate this with a record declaratively was to use the Lightning App Builder to include it in a custom record home page. This works fine but is a slightly odd user experience, as the component is always present even after the user has captured the signature. This all changed once Winter 17 was live thanks to Lightning Component Actions.

Lightning Component Actions

As the name suggests, Lightning Component Actions are custom actions that invoke a lightning component. Many lightning components are called, but few are chosen for custom actions. Specifically those that implement one of two interfaces:

  • force:LightningQuickAction
  • force:LightningQuickActionWithoutHeader

The difference being that if you implement force:LightningQuickActionWithoutHeader the entire user interface is down to you.

You can use Lightning Component Actions in Salesforce1 and the Lightning Experience.

SignatureCaptureAction

The latest version of Signature Capture package contains a simple component that implements the force:LightningQuickAction interface and includes the Signature Capture component itself. To create a quick action that uses this is a matter of a few clicks. 

Setting Up

First choose which sObject type you want to associate the quick action with - in the screenshots below I’m using account.

Navigate to the Buttons, Links, and Actions section of the setup for the sObject and choose New Action. Select ‘Lightning Component’ as the action type and ‘BGSIGCAP:SignatureCaptureAction' as the Lightning Component. I find that a height of 500px works best, but your mileage may vary. Then enter a Label and Name and click the Save button:

Screen Shot 2016 11 07 at 18 14 31

Once the quick action is defined, it must be added to the page layout in the Salesforce1 and Lightning Experience Actions section (if you haven’t added any quick actions to this section before you’ll need to override the default via the link provided):

Screen Shot 2016 11 07 at 18 24 09

Take it for a Spin

After setup is complete, navigate to any account record in the Lightning Experience and you will see the quick action in the set of buttons on the top right:

Screen Shot 2016 11 07 at 18 26 04

Clicking the button opens the action in its own dialog, allowing a signature to be drawn via mouse, trackpad etc:

Screen Shot 2016 11 07 at 18 27 45

To my mind this is a much better user experience, as the component only appears on demand, so there is no confusion around a component that is always present. The slight downside is that once the signature has been captured the user still has to close the dialog via the standard Cancel button, but that’s a minor drawback to my mind.

Mobility Included

Adding the quick action to the page layout also makes it available to the Salesforce1 application on mobile devices. A word of warning around this - the locker service currently doesn’t surface touch events to a component, so if you enable the locker service the Signature Capture component won’t work on tablets or phones.

Repo Man

The Signature Capture component has its own github repo with sample apps (or app at the time of writing!). This is also where feature requests are tracked, so if there’s anything you’d like to see added, let me know and I’ll see if it can be accommodated. Just to set expectation, this is something that I work on mainly in my spare time (I’m currently on vacation, hence having time for this post) so I may take a while to respond.

Related Posts

 

 

 

Saturday, 29 October 2016

Lightning Components - New Tab, New You

Lightning Components - New Tab, New You

Lctab

Introduction

This is something I’ve been meaning to blog about for a while now, but things like Dreamforce, preparing for Dreamforce and the Winter 17 release kept getting in the way. I didn’t get as far as setting up a nice example, due to the sheer volume of code involved, so I haven’t been back and tried it out recently. Therefore it’s possible that you’ll never have the same experience I did, but as it’s not exactly onerous it seemed worth putting it out there anyway. Your mileage may vary.

Signature move

While building out the MVP for our BrightMedia Lightning booking page to demonstrate at Dreamforce, I needed to change the signature of one of my Apex controller methods, so to get a simple example from something like:

public static Account GetAccount(String idStr)

to

public static Account GetAccount(String idStr, String name)

and changed the parameters that the Lightning Component passed on the action from:

var params={idStr:'0018000001QtQVA', name:'Blog Post Account'};

That went well!

Once I’d made the various changes and saved all the artefacts, I refreshed the page and starting seeing some unexpected behaviour. The name parameter came through as null regardless of what I set it to. Debug statements in the lightning component showed that the name had a value (it wasn’t a hardcoded string unlike the example above!), but some time after I added it to the action it went bad. Making it a hardcoded string didn’t help, nor did juking around the other parameters. I even tried adding more parameters that definitely didn’t exist server side, but nothing helped.

After a some time and about and thousand debug statements later, I found that while I might be adding the new parameter to my params object, when that was sent to the server it had disappeared, hence the server always reported null.

Cache is king

I then tried what I should have as soon as this manifested itself, the browser equivalent of turning it off and on again. I closed the browser tab and opened a new one. Lo and behold everything worked as expected. We all know that the lightning framework aggressively caches on the front end to improve performance, and it looks like the client retained a metadata view of the server side signature after I changed it.

Now I need to stress that for once I’m not complaining about the behaviour of the framework! This is exactly the kind of thing that I’d expect when caching metadata. It’s not always possible to check everything in a cache is still valid - you quickly start to lose the benefit if you go back and check the source on every request. 

The key takeaway here is that when your application involves caching and you change something which doesn’t work as you expected, open a new tab (or window), or even a new browser. It only takes a few seconds and will save time and heartache in the long run.

Related posts