Saturday 17 March 2018

Building my own Learning System - Part 4

Building my own Learning System - Part 4



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


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. 


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
    Path: /services/apexrest/TrainAPI
    Rewrite Image Links: Checked

  3. Add the training endpoint hostname 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.


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



  1. If you want to take a good deal from this
    piece of writing then you have to apply such methods to your won website.

  2. Hi Bob,
    How are you? I am getting an error while loading training page. Error in remote action : An error occurred accessing the external training service

    1. Check the debug logs on both sides - there's a variety of reasons you could see this. A common one is not giving access to the remote domain. It's also possible that something in Winter 21 has impacted things - I'll have a play with my exemplar site and see if that's the case.