Sunday, 24 June 2018

Sharing your Salesforce App on Github

Sharing your Salesforce App on Github

Share

Introduction

Over the years there’s been a few ways to give away Salesforce apps. When I first started 10 years ago there was a Google Code Share accessible from the Developerforce site, but that star waned some time ago. There’s also unmanaged packages, but that typically requires someone to install your package into an org before they can look at the contents of the app, which is fine if they know they want to use it in it’s entirety, but less useful if they want to use your app as s starting point or example for something that they are doing.

Over the last few years, Github has become the de-facto way to share and collaborate on code. As well as providing (free for public) Git repository hosting, it has a bunch of other cool features to allow people to collaborate:

  • Pull requests, to allow changes to be reviewed, discussed and refined before they are merged.
  • Issue tracking, to capture ideas, feature requests, bugs
  • Releases, so that you can wrap and ship specific versions

A collection of useful applications in Github is also a signal to potential employers that you are passionate about app creation and I’m sure won’t do your career any harm. It’s also a great way to share a sample application after presenting at a user group or World Tour event.

I’m using my Summer 18 samples codebase as the example Salesforce application for this post. Note that this post assumes you have created a Github account and completed Git setup.

But Github is Microsoft now

It is, but under Satya Nadella we’re seeing a new Microsoft, particularly around open source. Even if you are nervous around what might happen to Github in the future, you are putting your application out there for everyone to use, so it’s not like it’s some big secret that Microsoft might help themselves to. I can see how a Microsoft competitor with a business account might be worried about security,  but for public applications it’s not something I’d be concerning myself with.

Metadata

The most common mechanism of storing an application outside of Salesforce is extracting the metadata via the Metadata API (SalesforceDX Scratch Org format is the new kid on the block, but at the moment is more likely to be of interest to ISVs in my opinion). There are various mechanisms for extracting metadata, requiring various degrees of setup, such as:

  • An IDE, such as the Force.com IDE, where you can choose from a subset of metadata to retrieve
  • The Force.com Migration Tool (ant), where you’ll need to define a manifest file (package.xml) describing the metadata you want to retrieve
  • The Salesforce CLI, via the force:mdapi:retrieve subcommand - again you’ll need a manifest file
  • The Force.com CLI, via the export and fetch commands.

In my view the Force.com CLI is still the easiest tool to use to export metadata - I wrote a blog post explaining how to do this a while ago.

Once you have your metadata exported you will have a structure similar to the following:

Screen Shot 2018 06 24 at 10 27 04

 

Creating a Local Repository

Github uses Git to manage the set of files that make up your application, stored in a data structure known as a repository. This enables tracking of changes to files (and much more besides). In order to create a repository, however, you need to extract your application from Salesforce.

Once your metadata is extracted from Salesforce it’s in a state where you can easily migrate it to other orgs, but it isn’t set up to be managed by Git. To turn your codebase into a local Git repository, you use the git init command:

$ git init .

Initialized empty Git repository in /Users/kbowden/GithubBlog/.git/

You can then run git status and see that you have indeed turned your app into a repository:

$ git status 

On branch master
No commits yet
Untracked files:

  (use "git add ..." to include in what will be committed)

       src/

nothing added to commit but untracked files present (use "git add" to track)

So you have a repository, but none of your applications files are being tracked by it. To achieve this, execute git add to stage the files and then git commit to commit them to the repository:

$ git add src

$ git commit
[master (root-commit) 35fa327] Created.
31 files changed, 202 insertions(+)
create mode 100644 src/aura/Navigator/Navigator.auradoc
create mode 100644 src/aura/Navigator/Navigator.cmp
      ...

create mode 100644 src/classes/UtilityController.cls
create mode 100644 src/classes/UtilityController.cls-meta.xml

Note that the git commit command will open an editor for your commit message.

Create the Remote Repository

Now that the code is being tracked by Git, the final step is to create the remote Github repository and push your local changes to it.

There are various ways to set up a remote repository, I use the following steps as it reminds me to set up the README.md file that serves as the “home page” for the repository on Github:

  1. Login to Github
  2. Click the + button on the top right and on the resulting dropdown menu, select New Repository

    Screen Shot 2018 06 24 at 11 02 08
  3. Fill out the resulting form, making sure to tick the box titled 'Initialize this repository with a README’, and click the Create Repository button

    Screen Shot 2018 06 24 at 11 03 45

  4. On the resulting page, click the ‘Clone or Download’ button and copy the Web URL:

    Screen Shot 2018 06 24 at 11 06 44

  5. Add the remote repository, using the Web URL copied in the step above. I’ve named the remote ‘origin’ as this is going to be the main repository going forward:
    $ git remote add origin https://github.com/keirbowden/GithubBlog.git
  6. Verify the remote:
    $ git remote -v

    origin https://github.com/keirbowden/GithubBlog.git (fetch)
    origin https://github.com/keirbowden/GithubBlog.git (push)

Pushing Local to Remote

The remote Github repository is now created and associated with the local repository. There’s nothing in the remote repository apart from the README.md file, so the next step is to push the contents of the local repository. Before this can be done, the contents of the remote repository must be merged in, as it has changes (README.md) that aren’t in the local repo.

The git pull command fetches the changes from the remote repository and merges them into the named branch in a single action. As the local and the remote repository have been created separately, use the '--allow-unrelated-histories' option to allow everything to be coerced into a single set of files:

$ git pull origin master --allow-unrelated-histories

From https://github.com/keirbowden/GithubBlog
* branch master -> FETCH_HEAD
Merge made by the 'recursive' strategy.
README.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 README.md

Finally, push the consolidated repository to Github via the git push command:

$ git push origin master

Counting objects: 30, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (24/24), done.
Writing objects: 100% (30/30), 4.71 KiB | 1.57 MiB/s, done.
Total 30 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
To https://github.com/keirbowden/GithubBlog.git
f956682..be01baf master -> master

All Done

Accessing the repository on Github shows the entire Salesforce application is now available:

Screen Shot 2018 06 24 at 11 28 15

Related Posts

 

Thursday, 14 June 2018

Building my own Learning System - Part 7

Building my own Learning System - Part 7

Learning

Introduction

Since I last blogged about my learning system I’ve started using it at BrightGen, for example to onboard developers onto our BrightMedia accelerator, where it has been generally well received. Not least by me, as it means that I don’t need to spend multiple hours with every developer, going through the same content which they may or may not be listening to. I’m also seeing some customers indicating that they’d like to pick up development tasks in BrightMedia projects. Thanks to my original design decision of distributing content across multiple endpoints, I can expose content to upskill the customers without giving away internal BrightGen content, or them even knowing that other content exists.

I’ve added in some features that are useful, to me at least, including:

  • Filtering by topic
  • Executing as a user (the github repo has the concept of running as a specific email address, as it’s unauthenticated)
  • Added an info modal, detailing the recent changes, which I keep forgetting to update, so if you use the unmanaged package to install it will likely be at least one version out of date :)

The great thing about building my own learning system though, is that I am learning by building it.

Learning by Building

As I’m adding features to my learning system I’m also learning more. Not just the Salesforce features that I expected to learn about, such as using the latest standard lightning components, but other areas that aren’t even Salesforce related.

Most recently I’ve been learning more about how to operate on Github. Up until now I’ve just typically created a repo when I wrote a blog post and added to it when I wrote another blog post on the same topic. Now that I’ve got a project that I’m trying to keep a bit more organised, I’m :

  • Adding details of fixes/features into CHANGELOG.md (I’m pretty good on this one - much better than my modal. I probably need to automate updating the modal from the change log).
  • Creating releases
  • Adding diffs to CHANGELOG.md that show all changes between releases 

This wasn’t something that I expected when I started working on my learning system and, once again, shows the value of side projects. I knew that I’d learn, but surprised even myself.

Related Information

All previous posts about the learning system have moved to a dedicated page on my blog. The code is available in the following repositories.

 

 

Tuesday, 12 June 2018

Accessing Lightning Component URL Parameters

Accessing Lightning Component URL Parameters

Introduction

One of the more common challenges when working with Lightning Components is how to access parameters passed on the URL. In a Lightning App it’s straightforward - simply define an attribute with the parameter name:

<aura:application >
    <aura:attribute name="param1" type="String" />
        Param1 = {!v.param1}
</aura:application>

and then add that parameter to the URL for the app:

    https://kabtutorial-developer-edition.lightning.force.com/KAB_TUTORIAL/UAApp.app?param1=test

and this is picked up and displayed in the resulting page:

Screen Shot 2018 06 11 at 08 07 30

However, Lightning Apps are rarely the solution - not least because they execute outside the Lightning Experience.

Accessing via JavaScript

In a Lightning Component’s JavaScript controller or helper, the parameters can be accessed directly from the window location, or with the locker service enabled for a component at API 40 and higher, the secure window location:

getURLParameter : function(param) {
    var result=decodeURIComponent
        ((new RegExp('[?|&]' + param + '=' + '([^&;]+?)(&|#|;|$)').
            exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
    console.log('Param ' + param + ' from URL = ' + result);
    return result;
}

In case you were wondering, I didn’t come up with the regular expression myself. If it weren’t for stack overflow I suspect I’d be using indexOf and working around errors for some time after.

There’s nothing wrong with this approach, but it feels a little .. clunky.

isUrlAddressable Interface

Summer 18 introduces the lightning:isUrlAddressable interface - I wrote a post about it’s navigational capability a short while ago. Near the bottom of the documentation for this component is an example of how to replace the deprecated force:navigateToComponent with a custom component implementing isUrlAddressable. This example includes an attribute that I hadn’t come across before - v.pageReference - which allows access to the parameters on the URL via the state property. 

I can now create a component that accesses the parameters as easily as an app:

<aura:component implements="flexipage:availableForAllPageTypes,lightning:isUrlAddressable" 
                access="global" >
    <lightning:card>
        Param1 = {!v.pageReference.state.param1}
    </lightning:card>
</aura:component>

Really? That easy?

Yes and no. It’s that easy if you are navigating directly to a component that implements the isUrlAddressable interface, but not if you are:

  • Opening a Lightning Application Page created with the Lightning App Builder
  • Inside another component, even if that component implements the isUrlAddressable interface

In these situations, and any other where the component accessing the v.pageReference attribute isn’t that component that you navigated to, the v.pageReference attribute will be undefined and you’ll have to fall back on accessing via JavaScript, as shown earlier. 

So definitely a step in the right direction, but not the silver bullet I’m still looking for.

Related Posts

 

Sunday, 27 May 2018

Building a Salesforce CLI Plugin

 

Introduction

Plug

As regular readers of this blog will know, I’m a big fan of the command line in general and the Salesforce CLI in particular. Up until now I’ve been layering functionality onto the CLI by wrapping it in bash or node scripts, so I was interested to read about the Plugin Development beta program as this seemed like a neater way to extend it.

The examples I’ve seen to date are mainly focused on extracting information from the org, or performing local processing on files that have already been generated by another CLI command, so I wanted to push things a little further and change something in the org. In the command line tool that I’ve written to manage our BrightMedia projects we update the org with a bunch of information, including the Git commit id, after a successful deployment, and this felt like a good challenge to start with

Creating the Plugin

Simply following the instructions from the plugin generator Github repo gave me the initial version of the plugin. This creates fairly hefty folder structure, but the most interesting bit is the src/commands folder - anything I put in here becomes a command for my plugin and the folder structure from here down gives me the topic/subtopic etc.

I have the structure :

Screen Shot 2018 05 27 at 15 08 58

which gives me the topic bbuzz and the command gitstamp. This concluded the simple part of creating my plugin.

More to Learn

The example plugin that the generator creates is coded in TypeScript, a typed superset of JavaScript that compiles down to plain old JavaScript. I hadn’t used this before, but it’s not too different to JavaScript so I was able to get going reasonably quickly, with the compiler telling me what I’d missed or broken.

The Command

A plugin command needs to extend SfdxCommand - I inadvertently left the name of mine as Org, which is the name for the command created by the plugin generator, but thus far it hasn’t caused me any problems.

export default class Org extends SfdxCommand {

and as I will be connecting to an org I’ll need to enable that. I also want this to be executed from a SalesforceDX project, as the name of the custom setting and the specific field to write the commit id to can be overridden through the sfdx-project.json file:

 protected static requiresUsername = true;
 protected static requiresProject = true;

I then fetch the configuration from the project configuration file, supplying defaults if no config is found:

const projectJson = await this.project.retrieveSfdxProjectJson();
const settingConfig = _.get(projectJson.get('plugins'),'bb.gitSetting');
const settingName=settingConfig.settingName || 'Git_Info__c';
const commitIdField=settingConfig.commitIdField || 'Commit_Id__c';

I then query the setting from the org, as if there is already one I need to update it with it’s id:

const query = 'Select Id, SetupOwnerId, ' + commitIdField + ' from ' + settingName + ' where SetupOwnerId = \'' + orgId + '\'';

interface Setting{
  Id? : string,
  SetupOwnerId : string,
  [key: string] : string
}

// Query the setting to see if we need to insert or update
const result = await conn.query<Setting>(query);

let settingInstance : Setting;
settingInstance={"SetupOwnerId": orgId};

if (result.records && result.records.length > 0) {
  settingInstance.Id=result.records[0].Id
}

Note that I define a typescript interface for the custom setting. As I don’t know the name of the field that the commit id will be stored in, I just define the generic [key:string] rather than a specific name.

Then I get the current commit id and fill that in on the settingInstance:

const util=require('util');
const exec=util.promisify(require('child_process').exec);

const {error, stdout, stderr} = await exec('git rev-parse HEAD');

const commitId=stdout.trim();
settingInstance[commitIdField]=commitId;

Then I had to figure out how to insert or update the custom setting record. Checking the Org class of the Salesforce DX Core library didn’t show me many options, but after a short while I realised that the core library is built on top of jsforce, and sure enough Connection extends jsforce.Connection which had the CRUD commands that I needed:

let opResult;
if (settingInstance.Id) {
  opResult=await conn.sobject(settingName).update(settingInstance);
}
else {
  opResult=await conn.sobject(settingName).insert(settingInstance);
}

this.ux.log('Done');

if (!opResult.success) {
  this.ux.log('Error updating commit id' + JSON.stringify(opResult.errors));
}
else {
  this.ux.log('Stamped the Git commit id in the org');
}

and finally I output a JSON format result string so that if I need to use it in a script I can:

return { orgId: this.org.getOrgId(), 
         commitId: commitId, 
         success: opResult.success, 
         errors: opResult.errors };

Something I wasn’t expecting was that every time I made code changes, simply executing the plugin caused it to be recompiled, which was a real time saver.

Publishing

Once I had my plugin working, I then had to figure out how to publish to the npm.js registry - once again I hadn’t done this before so it was another opportunity to learn.  Luckily this is well documented so it wasn’t as hard as I was expecting. There’s a little bit of hoop jumping around creating the releases on Github, but I’d recently been through at least this aspect with my training system.

Take it for a Spin

To try out the plugin, follow the instructions on the npm page. If you’d like to dig into the code, it's available at the Github repo.

Related Posts

 

Saturday, 19 May 2018

Lightning Utility Bar Click Handling in Summer 18

Lightning Utility Bar Click Handler in Summer 18

Bar

Introduction

As I’ve written about before, I’m a big fan of the lightning utility bar.It allows me to add components to an application that persist on the screen regardless of which tab/record the user is working in/on. One area that has always been a bit of a let down is that I have no way of knowing that the user has opened the utility bar item. In our BrightMEDIA appcelerator I have some utility items that can be added to applications that can change during the application’s lifecycle as components register and deregister. While I can hook in to the original rendering, the item doesn’t get fire a rerender when it is opened, so there is nowhere for me to latch on. In this case I usually put a message up that the information may be out of date and a ‘Refresh’ button - very familiar if the user has viewed Salesforce dashboards in the past.

Enter Summer 18

This problem goes away in Summer 18 (assuming this feature goes live, forward looking statement, safe harbor etc) as the lightning;utilityBar API gets a new event handler - onUtilityClick(). As per the docs, this allows you to register an event handler that is called whenever the utility is clicked.The utility bar API is a headless service component, which is the direction Salesforce are clearly heading for this kind of functionality. The only slightly tricksy aspect to this is that the headless component needs to be rendered in the utility before it can be accessed and a handler can be registered. I tried initialising the utility component at startup ,via the standard checkbox, but everything went null. For this reason I register the event handler from a custom renderer when my utility component handles the render event.

There’s a Sample, right?

As usual I’ve created a sample component to demonstrate this new functionality - this is a utility item that shows the total open opportunity value for my pre-release org user. It executes a server side method whenever the utility is clicked to make it visible. The short video below shows how the utility refreshes itself as when it is visible and displays the correct amount, even if I’ve just edited the opportunity I’m on.

The component includes the lightning utility API :

<lightning:utilityBarAPI aura:id="utilitybar" />

Which is then located by the rendered function to register the handler (a function from the helper) :

var utilityBarAPI = component.find("utilitybar");
var eventHandler = function(response){
    helper.handleUtilityClick(component, response);
};

and the helper method checks if the component is visible, via the panelVisible function of the response parameter passed to it by the platform, and makes a call to the server to recalculate the open opportunity value and display it:

handleUtilityClick : function(cmp, response) {
    if (response.panelVisible) {
        this.recalculate(cmp);
    }
},
recalculate : function(cmp) {
    cmp.set('v.message', 'Calculating ...');
    var action = cmp.get("c.GetOpenOpportunityValue"); 

    self=this;
    action.setCallback(self, function(response) {
        var result = response.getReturnValue();
        cmp.set('v.message', 'Open opps = ' + result);
    });
    $A.enqueueAction(action);
}

You can access the entire component, and it’s Apex controller, at the 

What else can this API do?

Check out Andy Fawcett’s blog for more details.

Related Posts

 

 

Friday, 11 May 2018

Lightning Component Navigation in Summer 18

Lightning Component Navigation in Summer 18

Introduction

In my last but one post (Toast Message from a Visualforce Page) I explained how I needed to solve the problem due to the force:createRecord event not respecting overrides, instead always opening the standard modal-style form and there was no GA mechanism to navigate to a component, force:navigateToComponent being still in beta.

A couple of days after, the Summer 18 release notes came out in preview and towards the end I saw that there was finally a solution for this.

lightning:isUrlAddressable Interface

The new lightning:isUrlAddressable interface allows me to expose a Lightning Component via a dedicated URL. I simply implement this on my component and my component is available at the (current) URL :

   https://<instance>.lightning.force.com/lightning//cmp/<namespace>__<component name>

However, one thing we can be certain of with Lightning Components is change, so rather than hardcoding the URL to my component, there’s a way to get the platform to dynamically generate it.

lightning:navigation Component

The new lightning:navigation component is a headless (i.e. does not have a user interface aspect) service component that exposes methods to help with navigation. The method that I’m interested in is navigate(PageReference). Using this I can create a PageReference JavaScript object that describes the location I want to navigate to and leave it up to the lightning:navigation component to figure out the appropriate URL to hit. 

The beauty of using this mechanism is that my components are decoupled from the URL format. If the Lightning Components team decide the change this, it has no impact on me - the Lightning Navigation component gets updated and everything still works as it always did.

Example

Note - as these features are part of Summer 18, at the time of writing they aren’t available outside of pre-release orgs, which was where I created my example.

I’ve created a component (WebinarOverride)that implements lightning:actionOverride, so that I can use it to override the default new action, just to show that it has no effect, and lightning:isUrlAddressable so that I can navigate to it.

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,force:hasRecordId,lightning:actionOverride,lightning:isUrlAddressable">
	<lightning:card iconName="standard:event" title="Override">
	    <lightning:formattedText class="slds-p-left_small" 
value="This is the webinar override component."/> </lightning:card> </aura:component>

Note that it doesn’t do anything in terms of functionality, it’s just intended to obviously identify that we’ve ended up at this component.

I then have a component that can navigate to the override called, creatively, NavigationDemo:

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes">
    <lightning:navigation aura:id="navService"/>
    <lightning:button label="Force Navigate" onclick="{!c.forceNavigate}" />
    <lightning:button label="Lightning Navigate" onclick="{!c.lightningNavigate}" />
</aura:component>

That this has two buttons - one that uses force:createRecord:

var createRecordEvent = $A.get("e.force:createRecord");
createRecordEvent.setParams({
    "entityApiName": "Webinar__c"
});
createRecordEvent.fire();

and one that uses the lightning:navigation component:

var navService = cmp.find("navService");
var pageReference = {
    "type": "standard__component",
    "attributes": {
        "componentName": "c__WebinarOverride" 
    }, 
    "state": {}
};

navService.navigate(pageReference); 

Note that as the navigation aspect is provided by a headless service component, I have to locate the components via cmp.find() and then execute the method on that. Note also my pageReference object, which has a type of standard__component (even though this is a custom component - go figure) and supplies the component name as an attribute.

I’ve overridden the Lightning Experience actions for the Webinar custom object with my WebinarOverride component:

Screen Shot 2018 05 06 at 10 14 56

So clicking the New button on the webinar home page takes me to the override. If I now open my NavigationDemo component:

Screen Shot 2018 05 06 at 10 18 27

clicking the Force Navigate button is disappointing as always:

Screen Shot 2018 05 06 at 10 18 37

however, clicking on the Lightning Navigate button takes me to my WebinarOverride component:

Screen Shot 2018 05 06 at 10 18 55

You can find the full component code at the Github repository.

Conclusion

The lightning:navigate component also retires the force:navigateToComponent() function, although it’s not clear if there was a WWE-style ladder match to determine who retired. This function has had a troubled existence, leaking out before it was ready and then being withdrawn, causing wailing and gnashing of teeth in the developer community, then entering a short beta before being deprecated. Farewell little buddy, you gave it your best shot but couldn’t cut it.

The headless component mechanism feels like the way that new functionality like this will be added, rather adding functions/events etc to the framework. I guess this keeps the framework as lightweight as it can be and doesn’t force functions on applications that don’t care about them.

One additional point to note - if you forget the lightning:isUrlAddressable interface on your target component the lightning:navigation function will still happily return the URL, but when you navigate to it you will get a not particularly helpful error message that the page isn’t supported in the Lightning Experience.

Finally, I’d still prefer force:createRecord to respect overrides. While I can make my new object component configurable via a custom setting or custom metadata type, from a low code perspective I don’t want to have to change two places to use a different one. Still, we’re in a better place that we have been for a couple of years.

Related Posts

 

 

Thursday, 3 May 2018

Lightning Application Events and Caching

Lightning Application Events and Caching

Spidey

Introduction

The lightning component framework has two types of event for intra-component communication:

  • Component
    Component events can be handled by the component that fired it, or any component above it in the containment hierarchy, so essentially any ancestor.

  • Application
    Application events can be handled by any ancestor, but also have an additional propagation phase that allows any component to receive the events, assuming they’ve already registered. 

I typically use application events in a broadcast scenario, where one of my components fires an event and a bunch of other components in my app receive it and take action. For example, when a user selects a value in a picklist and other components need to react to it. Which all worked fine until I had some cached pages.

Lightning Experience Caching

By default, the Lightning Experience will cache up to 5 last visited pages in order to speed up the browser back button. If I create a page that manages an account, and access this with the account id for Universal Contains and then AW Computing, both of these will be cached in the browser. This really speeds up performance, but has an unexpected side effect when using application events,

Even though the page managing Universal Containers is not visible, the components on it are still “live”. If my page has a custom component on it that receives application events, it will continue to receive application events while cached and not visible. This may not sound that bad, but in my case the component was receiving an event asking it to validate its contents and responding with another event to indicate the status. Both the component for AW Computing (that was visible) and the component for Universal Containers (that was not visible) received the event and responded to it. 

Once this happened I was caught in a classic race condition - whichever one responded first would be taken as the current state of my component. As always seems to happen with race conditions, it was the worst outcome that prevailed most of the time. I would be working on the AW Computing page, in which the component was in an invalid state, but the Universal Containers component, which was in a valid state, would respond first and processing would proceed when it should have been blocked. What it looked like at the time was that duplicate events were being fired - tracking down what was actually happening was rather a challenge.

What to do?

Sadly there was no way for the component to know that it had been cached, everything about the visible one was the same as the invisible one. I did try a solution where I destroyed the component once the user navigated off the page, but that meant that if I used the back button the page was cached but the component missing, which meant the user had to refresh the page - also not a great experience.

The actual fix was to move away from application events in this case and use component events. As the communication was between a custom component and it’s parent, it all worked fine. If that hadn’t been the case then I’d probably have MacGyvered something where (un)rendering the component updated a window expando to indicate which version is actually “live” - it would work but I’d feel a bit dirty for having to resort to such tricks.

Related Posts

 

Saturday, 21 April 2018

Toast Message from a Visualforce Page

Toast Message from a Visualforce Page

Introduction

Pretty much everything I do now around the user interface is via Lightning Components, typically added to a Lightning page so that I can also include standard components. There’s one aspect that continues to frustrate me - when I need to send the user to a custom new record page programatically, when they press a button in a custom component, for example.

  • I can’t use the force:createRecord standard event because, as the docs say, "This event presents a standard page to create a record. That is, it doesn’t respect overrides on the object’s create action.”.
  • I can’t use a Lightning page as these aren’t URL-addressable and there’s no way to navigate to them.
  • I can’t use force:navigateToComponent as that’s still in beta and, while I’m pretty sure it will be supported in the future, there’s no guarantee it will ever go GA.
  • I can use a Lightning app (.app bundle) as these are URL addressable, but when I do they take me outside the Lightning Experience.

So I use Lightning Out for Visualforce and force:navigateToURL to send the user there. 

Events

As my Lightning components are inside the Visualforce iframe, I have no access to the standard Lightning events, such as force:navigateToURL and, to the point of this post, force:showToast. If I try to instantiate one of these events in a Lightning component:

var evt=$A.get("e.force:navigateToURL");

I’ll get null returned.

For many events this isn’t the end of the world though, as I can add a handler in my Visualforce page when I dynamically create the component:

$A.eventService.
        addHandler({
                    event: 'force:showToast',
                    handler: function(event) { 
                                 // take some action
                    }
});

Then when I instantiate the force:showToast event I’ll get one, and when I fire the event my handler function will be invoked I’m still in Visualforce though, in an iframe and not executing in the one.app container, so it doesn’t help that much so far.

One really important aspect I always forget when adding the custom event handler in Lightning Out for Visualforce - my Lightning app that is the bridge between Visualfoce and the underlying Lightning components needs to declare a dependency for the event, otherwise nothing works. Another few hours of my life I won’t get back and even more annoying as it’s about the fifth time I’ve fallen for it:

<aura:dependency resource="markup://force:showToast" type="EVENT"/>

Posting a Message

Visualforce pages can communicate with Lightning components, via the window.postMessage() JavaScript function. It’s slightly clunky in that I need to identify the target origin that I want to send the message to, but I can pull the message from the toast event and send it on wrapped in a JavaScript object:

var lexOrigin = "https://kabtutorial-developer-edition.lightning.force.com";
var message={type: "EventFromVF",
             message: event.getParams().message};
parent.postMessage(message, lexOrigin);

So now all I need is a Lightning component that can receive this and raise it’s own toast event to actually display the message to the user. I called mine ToastReceiver and  a reduced version of the code is shown below :

window.addEventListener("message", function(event) {
    if ( (event.data.type) && (event.data.type=='EventFromVF') )
        var toastEvent = $A.get('e.force:showToast');
	toastEvent.setParams({
    	        type: 'info',
        	    message: event.data.message
	        });
    	toastEvent.fire();
    }
}, false);

My first thought was to wrap my Visualforce page in a Lightning component that iframes it in. This worked perfectly, but left me in exactly the same situation as before - I didn’t have a supported (GA) mechanism to programmatically navigate to that component. It seems that I’m stuck between a rock and a hard place - I need to navigate to a Visualforce page, but somehow that Visualforce page needs to message a Lightning component on the same browser page.

Enter the Lightning Utility Bar

The Lightning Utility Bar allows me to include the same set of Lightning components across every page of my app, so I added one to my Sales app and added my ToastReceiver to it, remembering to check the box that loads the component in the background when the app opens, otherwise my toast messages only appear after I’ve opened the utility bar element.

The diagram below shows the flow:

Screen Shot 2018 04 21 at 16 59 03

I enter a message in my Lightning component, which fires a toast event (1), this is picked up by the event handle in the Visualforce JavaScript, which posts a message (2) that is received by the Lightning component in the utility bar. This fires it’s own toast event (3) that, as it is executing in the one.app container, displays a toast message to the user.

Note that while my toast message originated from a Lighting component, this can also be used to display toast messages from regular Visualforce pages inside the Lightning experience, just post a message. All standard functionality and all generally available. Not a bad result for a Saturday afternoon’s work, even if I say so myself.

Demo

In the short video below my browser is initially on a Lightning page and clicking the button programmatically sends me to a Visualforce page via the force:navigateToURL event, containing a Lightning component with a message input and a button. Clicking the button starts off the flow described above.

Related Posts

 

Saturday, 14 April 2018

Managing a List of New Records with Lightning Components

Managing a List of New Records with Lightning Components

Nearly 7 years ago I wrote a blog post on Managing a list of New Records in Visualforce, and I thought it would be interesting to recreate this using Lightning Components and compare  the two.

The basic concept is that I have a collection of new account records and I want to be able to enter details, add or remove rows and save once I’m happy. In Lightning I’ve created a couple of components to manage this - one that looks after the collection of records (NewAccounts) and one that captures the input for a single account (NewAccount).  In the following screenshot each row equates to the NewAccount component:

Screen Shot 2018 04 14 at 16 45 14

One interesting aspect is that the list of records is managed outside of the rows, but each row has a button to allow it to be deleted. While I could try to juke around with the styling to line things up, this is an excellent use case for a Component Facet. A Component Facet is an attribute passed to a contained component that is itself a collection of components. As it is defined in the outer component, it can reference aspects of the outer component. In my case it defines the controller function called when the user clicks the delete button and includes the index of of the element in the collection of records in the button name, so that I can easily locate and remove the element:

<c:NewAccount account="{!account}" index="{!index}">
  <aura:set attribute="buttons">
    <div class="slds-form-element">
      <label class="slds-form-element__label">Actions</label>
      <div class="slds-form-element__control">
        <lightning:button label="Delete" onclick="{!c.deleteRow}"
                          name="{!'btn' + index}" />
      </div>
    </div>
  </aura:set>
</c:NewAccount>

The first major difference is that in Visualforce I have to create my collection of Account records server side, in the constructor of the page controller, while in Lightning my NewAccounts component creates these in it’s init handler:

init : function(component, event, helper) {
    var accounts=[];
    for (var idx=0; idx<5; idx++) {
        accounts.push({sobjectType:'Account'});
    }
    component.set('v.accounts', accounts);
}

The only field that I’m defining when I create each account record is the sobjectType - I don’t think that I actually need this, as on the server side I use a strongly typed array of Account records, but I find it’s a great habit to get into. In terms of the user experience there’s probably not a lot to choose here though - the Visualforce page will take a short while to be created and returned, and Lightning pages are hardly .. lightning fast.

However, all that changes when the user adds or deletes rows. In Visualforce I have to send the list of records back to the server and then carry out the appropriate action. In my lightning component, this is handled in the JavaScript controller. for example when deleting a row:

deleteRow : function(component, event, helper) {
    var name=event.getSource().get("v.name");
    var index=name.substring(3);
    var accounts=component.get('v.accounts');
    accounts.splice(index, 1);
    component.set('v.accounts', accounts);
}

I get the index from the name which has the format ‘btn<index>’ so I just use the array substring prototype function to strip off the ‘btn’ and then use the Array.splice prototype function to remove the element at that position.

In Visualforce I’d probably show some kind of spinner to let the user know that something has happened, whereas in Lightning this happens so quickly there’s no chance for me to get in between and show something. If I really want to draw the users attention, I’d use CSS to highlight the element that was created or do some kind of slow motion hiding of the element before removing it. 

When the user decides to save is the place where I have to do more work in Lightning. In Visualforce I would simply bind the button to a server side action, insert the updated accounts property, and set a page message, maybe after some checking of how many were populated etc. In Lightning I have to figure out the populated records, instantiate a controller action, add my records as s property, hand over to apex and then process the results. While it sounds like a fair bit to do, it actually isn’t that bad, especially if I create a utility function to process the response that all of my components can utilise, which I do every time for production code.

saveRows : function(component, event, helper) {
    var accounts=component.get('v.accounts');
    var toSave=[];
    for (var idx=0; idx<accounts.length; idx++) {
        if ( (null!=accounts[idx].Name) && (''!=accounts[idx].Name) ) {
            toSave.push(accounts[idx]);
        }
    }
    var accAction = component.get("c.SaveAccounts");
    var params={"accountsStr":JSON.stringify(toSave)};
    accAction.setParams(params);
    accAction.setCallback(this, function(response) {
        var state = response.getState();
        if (state === "SUCCESS") {
            var toastEvent=$A.get("e.force:showToast");
            if (toastEvent) {
                toastEvent.setParams({
                        "type":'success',
                        "title":'Success',
                        "message":'Accounts saved'
                });
                        toastEvent.fire();
            } 
        }
        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(accAction); 
}

Note that I’m sending my list of records back as a JSON string, a habit I got into when the Lightning Components framework had problems with array parameters. I still use it occasionally so that my controller methods can handle multiple types of parameters. I’m always in two minds as to whether this is a good thing - it makes the code more flexible, but more difficult to understand what is going on without appropriately typed parameters. 

There’s not a lot more to the code, but if you want the full picture it’s available on Github.

Related Posts

 

Saturday, 31 March 2018

Building My Own Learning System - Part 6

Building My Own Learning System - Part 6

Versions

Introduction

In previous posts in this series I covered the initial idea and development through to sharing the code with installation, configuration etc instructions. Now that I have the basics working I’ve started to iterate and add a few features. Oddly the first feature I added was the one that I most recently thought of - the ability to display a custom message to the user when they complete a path, retrieved from the path itself. I’m thinking that this will allow me to carry use the system for more interesting challenges - complete a path to get a keyword, or a location, something like that anyway,. I did say I’d just thought of it, not that I’d thought it through!  It was only a few lines of code to implement this, just a change to the PassStep method so that it returned a tuple of a completed state and message to display, A few tweaks to the unit tests and I carried out an sfdx deployment to my sample endpoint and verified with my updated client code that all was working as expected and my sample Bob Buzzard character path was displaying the custom message:

Screen Shot 2018 03 31 at 16 14 48

Then it hit me - anyone that didn’t have my new client would get errors when accessing the sample endpoint, and they’d have to dig into the debug logs to figure out why. Suddenly it was important to add another feature. (Looking at my sample endpoint it appears that there’s only me using it at the moment anyway, so I guess if you are going to break things then the earlier the better!).

Versioning

Like most things in the tech world, there are loads of different ways to handle versioning. I considered the Salesforce route of having different endpoints for each version and discounted it. I’m not convinced that I want to be supporting older versions and I don’t particularly want to get into managing a number of classes representing historic versions. Salesforce can do this because they have more than one person maintaining things in their downtime, and if I ever become a multi-billion dollar company I’ll reconsider.

The route I ended up going was defining the version for each of the client and server in code, and having the client send its version with every request to an endpoint. The endpoint then compares this to its version and decides if it can handle the request. This gives me the option of supporting older versions if I want to, without committing me to any level of service! If the endpoint can't handle the request it throws an exception indicating what needs to happen - either upgrade the client or ask the admin of the endpoint to upgrade that. The updated client displays the error message to the user who can jump on any required action.

Installing the Latest ... Version

The current version of the system is now V1.0, and I’ve created github releases for both the endpoint and client, both of which have been tagged as V1.0. 

The unmanaged package the client V1.0 release is : <Salesforce instance>/packaging/installPackage.apexp?p0=04t0O000001IqIm

Related Information

As I plan to continue with these posts as I add new features or learn that I’ve made a terrible mistake should have done things differently, I’ve moved the list of posts into a dedicated page on this blog. 

 

Saturday, 24 March 2018

Building My Own Learning System - Part 5

Building My Own Learning System - Part 5

Backend server theres something very familiar about all this

Introduction

In Part 1 of this blog series I covered the problem I was trying to solve (on-boarding/accrediting internal/external staff using common content, but without opening up everything to the entire world) and the data model I was using to manage this. Part 2 was around the fledgeling user interface and a fake service to prove confidence in the method. Part 3 covered the backend, or at least the initial implementation of this - as long as there is a local interface implementation to connect to it, the concrete backend can live anywhere. Part 4 walked through the front end setup and configuration and shared the code.

In part 5 I’ll cover installing the backend code, setup of a remote endpoint, creating a training path and configuring the new endpoint for your client to access. I’ll also share the code.

Code First

 The back end code lives at : https://github.com/keirbowden/bbtrnremote

Installation

As the back end manages the data, an unmanaged package isn’t an option as it would mean recreating all training paths etc each time there was an update. As I mentioned in Part 4, I don’t think a private managed package is the right thing for something that people might install in orgs with real data, so the back end is intended to be installed as a discrete set of components. For example, using the Salesforce CLI you could deploy from the cloned repository directory using 

    sfdx force:mdapi:deploy -d src -u <username>

where <username> is from an org that you’ve previously authorised. If you are using something other than the Saleforce CLI then best of luck - I’d switch to the CLI myself ;)

Configuration

There’s a bit more to the back end compared to the front end:

  1. Enable MyDomain (there is a Lighting action and overrides, although not as many as I’d like)
  2. Assign the Training Admin permission set to your user
  3. Create a tab for Training Paths - everything else is reachable from that
  4. Create a Training Path, including at least one step and at least one question in that step. You can also create a badge to go with it if you want -  the code will handle it either way.
  5. Create a Force.com site and note down it’s address.
  6. Add the Training Site permission set to the Guest User for the site (via Public Access Settings -> View Users)

Then switch over to your client org and configure the endpoint:

  1. Add a Training Endpoint custom metadata entry - name and label as you desire and the following fields populated:

    Hostname: https://<site address>
    Path: /services/apexrest/TrainAPI
    Rewrite Image Links: Checked

  2. Add the site address to the remote site settings

And away you go. If you get any errors, have a look at the debug logs. Typically errors will be data related and I find that the stack trace in the client logs shows me what the problem is.

Caveat Emptor

Same as with the front end, the error handling is pretty basic, I just let the errors make their way back to the client. If you are authoring a training path, make sure you have a test front end to try it out on before you make it available to your users.

Same as the front end again, nothing is labels. 

Creating a training path and most of the associated data is via the regular Salesforce object pages, so be prepared to traverse a bit. The exception to this is when creating a question. The New Question action on the Training Step page will create a new question and take you to a Lightning page that allows you to manage the question and all of it’s associated answer on a single page. Over time more of this type of assistance will be added. I haven’t really focused on it yet as this is the kind of thing that admins rather than users will be accessing as a rule.

Conclusion

If you hit problems, raise an issue in the appropriate Github repo:

I’m not sure what will be in the next instalment. I might go through some of the code in more detail, or there might be new features to talk about. Stay tuned.

Related Posts

 

 

Saturday, 17 March 2018

Building my own Learning System - Part 4

Building my own Learning System - Part 4

Teach

Introduction

In Part 1 of this blog series I covered the problem I was trying to solve (on-boarding/accrediting internal/external staff using common content, but without opening up everything to the entire world) and the data model I was using to manage this. Part 2 was around the fledgeling user interface and a fake service to prove confidence in the method. Part 3 covered the backend, or at least the initial implementation of this - as long as there is a local interface implementation to connect to it, the concrete backend can live anywhere.

Now that I’ve been through the building blocks, it’s time to get into the code and also mention a couple of interesting features that I’ve put in place and share the front end code.

Show me the code!

The front end code lives at https://github.com/keirbowden/bbtrn

Installation

If you want to try this out yourself, here’s the approach I’d recommend. 

First configure MyDomain - you can’t use lightning components without this.

While you could just deploy the front end code using the Salesforce CLI (or one of the legacy tools, such as ant) I’d recommend using the unmanaged packages. There are two of these, containing the following items:

  • The custom metadata types to configure the endpoints and the implementation of the service
    <salesforce URL>/packaging/installPackage.apexp?p0=04t0O000001Ehcd

  • Everything else - UI lightning components, data accessor, service implementation
    <salesforce URL>/packaging/installPackage.apexp?p0=04t0O000001IqIm

I’ve split these into two packages because the configuration should be static, so ideally that will be installed once and only the contents of custom metadata types will change. The package containing everything else will change as new features are added. While as an unmanaged package this can’t be upgraded, as the data is stored elsewhere (the training content endpoints) uninstalling the old version and installing the new one doesn’t lose anything so seems like a reasonable approach. Why an unmanaged package I hear you ask? Mainly because this is unlikely to hit the app exchange so I’d be asking everyone to trust me and install code that they couldn’t see in their orgs. While I’m a trustworthy guy, this didn’t feel like the right thing to do

The backend code doesn’t really lend itself to an unmanaged package, as there will be plenty of data to recreate, and I didn’t want to use a managed package for the reason mentioned above, so I’d recommend using the Salesforce CLI or similar to deploy via metadata.

Of course you can always install the code in your own packaging org and build your own package (managed or unmanaged) from it. Worst case is you might have to do some jiggery pokery when I push new features, as I won’t be taking that into account. 

Configuration

To begin with, I’d recommend configuring things to use my example endpoint via the following steps:

  1. Create a new instance of the Training_Config custom metadata type with the following settings:
    Label/Name : Default
    Service Implementation : TrainingServiceRemoteImpl

  2. Create a new instance of the Training_Endpoint metadata type with the following settings:
    Label: Bob Buzzard
    Name: Bob_Buzzard
    Hostname: https://trainrem-developer-edition.eu8.force.com
    Path: /services/apexrest/TrainAPI
    Rewrite Image Links: Checked

  3. Add the training endpoint hostname https://trainrem-developer-edition.eu8.force.com to your remote sites, otherwise you’ll get errors when attempting to callout

  4. Edit the Training lightning app page and make it available for your profile

Then navigate to said page and away you go.

Note:t the front end sends your email address to the back end - this is purely used to identify your requests, but you are trusting me not to spam you (I won’t because what’s in it for me?).

Interesting Features

  • Restricting Access to Paths

    As you may want to beta test content, a training endpoint has the concept of opening up a training path to a selected group of users. In the sample back end we only know about the user’s email address, so this is how it is controlled. You can create a Candidate Restrictions sobject instance, which defines a domain and the addresses with that domain that are or are not allowed access, and then link this to a Training Path via the Training Path Candidate Restriction junction object, If there are no restrictions, a training path is open for anyone to access. Not that this shouldn’t be consider any kind of secure authorisation system, it’s purely a simple way to stop people being presented with a path before you are read for them to see it. If you need to lock things down, protect the endpoint via authentication

  • Wait Your Turn

    If you specify the Hours Between Attempts field on a training path and a user answers the questions incorrectly, they will be made to wait until at least that number of hours have elapsed before trying again. Hopefully this will cut down on the number of people guessing their way through paths. Probably not, but you can only go so far without reinventing web assessor!

Caveat Emptor

The error handling is fairly basic, mainly because the errors are typically down to bad data/setup at the remote endpoint, so I usually catch them before users do. 

 Nothing is labels yet - that’s on my todo list, but it’s all hardcoded English strings for now.

Conclusion 

If you hit any problems, raise an issue in the appropriate git repo. I’ve done quite a bit of testing, but if there’s one thing 30+ years in the software industry has taught me, it’s that as soon as I let anyone loose on my stuff it gets broken. I may just take it down your issue on my invisible typewriter and file it in the bin, but equally I might fix it, so it’s worth rolling the dice. 

In the next instalment of this series, I’ll share the backend code and what you need to do to create your own training endpoint and paths.

Related Posts

 

Sunday, 11 March 2018

Turning on the Lightning Locker Service

Turning on the Lightning Locker Service

Nuke

Introduction

This week I turned on the Locker service for an application that I wrote several years ago. It’s a few “pages” built from a fairly large number of custom Lightning components with a lot of JavaScript business logic. The application itself works fine without the Locker service, but there’s more and more standard components and features that I’d like to use, but that are only available in API 40+. I also have a JavaScript library that some of the components interact with, so I needed to upgrade all of my components at the same time, or risk some of them using a different window object.

I’ve made various attempts at this in the past, but always been defeated by weird errors that I was unable to isolate or reproduce. aura:if was often in the vicinity though, so it’s always my prime suspect. The last attempt was about 6 months ago and I’d created quite a few applications running on the latest API in that time, and I was hopeful that nth time is the charm, so I ran my script to update all of the meta-xml files to API 41 (there are 300 of them, so not really something that can be done manually) and deployed the app to my dev org. Here’s what I found.

Issues

Deployment Time

The first attempt failed with 19 errors, including the following:

  • Invalid Aura API - $A.util.json.encode. 
    This shows how long this application has been around - some of the very early examples in the Lightning Components Developers Guide etc used this method, but it’s been advised against for a while. I thought I’d cleared them all out, but had obviously missed one or two. This is a simple fix, just use JSON.stringify instead.

  • Invalid Aura API - $A.util.format.
    This is around replacing tokens such as {0} in strings/labels etc and can be replaced with the String.format standard JavaScript function, so rather than:

        $A.util.format(<string>, val);

    you would have

        <string>.replace(‘{0]’, val);

  • Invalid Aura API - this was thrown from a configuration item being passed to a JavaScript library function containing the text ‘onSelect’. I’m pretty sure that this was a false positive, but as this was something that I’d created an alternative pure Lightning version of, I don’t think I’ll be needing it going forward so sacked it off.

The biggest issue was : Invalid SecureWindow API, top was blacklisted. Even though this is a Lightning Experience application and uses LEX navigation, there’s still one place where I surface part of the application through Lightning Out, when creating a child object. Even though I’ve overridden the new action, this isn’t respected so I end up in the regular modal style new component.Thus I navigate to a Visualforce page instead and use Lightning Components for Visualforce to surface the contents. This is all fine until I want to get back to the man page for the app. I can’t use force:navigateTo methods by default, as these are only available in LEX, and if I set the window.location to a new value, that just changes the Visualforce iFrame embedded in the page. Thus I used window.top.location, as this changes the URL for the outermost window of the app.

There is a solution to this though - I can create my own handler for force:navigateToSObject event in the Visualforce page, and as there is no locer service in Visualforce I’m free to tinker with the outer window location to my heart’s content. Make sure that you add the dependency reference to the event to your Lightning app though, e.g.

    <aura:dependency resource="markup://force:navigateToSObject" type="EVENT"/>

I didn’t to begin with and spent a lot of time trying to figure out why it wasn’t working!

Run Time

I only hit two issues at runtime, and the biggest hurdle was actually getting the errors surfaced - I ended up taking a binary chop approach to find the problem component - commenting out half of the functionality at a time until i was able to narrow things down, then surrounding lots of code with try/catch exception handlers.

  1. Missing ‘var’ when using a variable, e.g.

        for (i=0; i<len; i++)

    Without the locker service, this will try to find a variable named ‘i’ against through the scope chain and, if it doesn’t find one, create it in the global scope. Almost certainly not what is required and in my cases definitely not. With the locker service, ES5 strict mode is enabled and this generates a reference error

  2. Getting non-existant attributes, e.g.

        var prop=cmp.get(‘prop’)

    Spot the problem? Missing the ‘v.’ namespace for the attribute, although in one case this was there but the attribute hadn’t been declared in the markup.

  3. Breaking encapsulation
    In this case, I was programmatically finding a component in the ‘ui’ namespace and changing an attribute. This is exactly what the locker service was created to stop, so no surprises there was an error. I’d completely forgotten it was there - it was a workaround to a bug with the standard select component where I couldn’t dynamically set the multi attribute based on one of my component attributes. It’s fixed now, and probably was years ago, so I just removed the offending code.

In my app, these are genuine bugs and it’s good to get them out of the way. Things obviously still work at the moment, but are fragile - in the first case, if there is an existing variable in the scope chain I’ll overwrite it’s value, which never ends well.

Conclusion

While I haven’t exhaustively tested every aspect of my app, the weird errors that I’ve seen in the past aren’t appear this time around. I’m sure that they weren’t all down to the locker service - I’ve fixed plenty of issues in my app over the years, but the locker service definitely made things more difficult to track down and proved to have plenty of gaps when used in anger. But for my purposes, it’s ready for prime time.

Related Posts

Saturday, 3 March 2018

Building my own Learning System - Part 3

Building my own Learning System - Part 3

NewImage

Introduction

In Part 1 of this blog series I covered the problem I was trying to solve (on-boarding/accrediting internal and external users with the same content, but without opening up all my content to everyone) and the data model to support this. Part 2 covered the user interface and a faker to allow me to check that my idea had legs without building the whole thing - if I’m going to fail, I like to get it over with as quickly as possible. This wasn’t the case though, so I then proceeded to build out the backend.

There are any number of ways to implement the backend, both on Salesforce and elsewhere. As I’ve created an Apex interface that the front end works against, any mechanism can be supported simply by creating a new implementation of the interface that knows how to talk tot the remote endpoint. For the purposed of my sample implementation I went with e REST endpoint surfaced via a Force.com site, as this means I don’t have to worry about authentication, can focus on the business problem and can easily use hurl.it to test. As to whether this is appropriate for a real training endpoint is a topic for discussion - it depends on how sensitive the information is and whether there is any issue with someone getting hold of it. I decide for each endpoint based on these and other factors.

REST API

Again in the interests of simplicity, I went with a REST API that exposes a single POST method that acts as a dispatcher. The body of the request contains the underlying “method” that should be invoked, and any parameters required for that method. While this might not please the purists, as I don’t have an endpoint per object, I didn’t want to have to create a new Apex class to implement each method that I added, especially for a sample implementation.

The API implements much the same interface as my faker, but with additional parameters to identify the candidate taking the training. As I’m not using authentication, I identify users by their email address. Note that this is about identification rather than authentication or authorisation, as anyone can choose any email address.

Client

The client makes use of a new implementation of the same API as the faker, that simply sends a request to the remote REST API. As I want to support multiple endpoints, these are configured via a custom metadata type in the client org. This means that a user has a number of training histories and badge totals, one per endpoint,

In my sample REST API, the training step details are retrieved from rich text area fields on sObjects. Any images that have been added via the rich text editor will be returned with a relative URL which will obviously point to nothing in the client org. To fix this, the custom metadata for an endpoint has a checkbox to indicate if images should be rewritten. If this is checked then the endpoint hostname is prepended to the image links, which works a treat.

As nothing is now stored in the client org, a user can install the training metadata components in any Salesforce org and, as long as they reuse the same email address that they’ve previously taken training with and configure the endpoints appropriately, they can pick up where they left off. This was a key feature for me as I really didn’t want to have to install the training paths along with the client software, as it makes things must more resistant to change. 

That’s it for part 3 - in part 4 I’ll go through the installation and configuration and, all things being equal, share the code too.

Related Posts