Sunday, 5 August 2018

Putting Your TBODY on the Line

Putting Your TBODY on the Line



This week I’ve been working on a somewhat complex page built up from a number of Lightning components. One of the areas of the page is a table showing the paginated results of a query, with various sorting options available from the headings, and a couple of summary rows at the bottom of the page. The screenshot below shows the last few rows, the summary info and the pagination buttons.

Screen Shot 2018 08 04 at 17 34 06

The markup for this is of the following format:

<aura:iteration ...>
<th> _head_ </th>
</aura:iteration ...>
<aura:iteration ...>
<td> _data_ </td>
</aura:iteration ...>

So pretty much a standard HTML table with a couple of aura:iteration components to output the headings and rows. Obviously there’s a lot more to it than this, and styling etc, but for the purposes of this post those are the key details.

The Problem

Once I’d implemented the column sorting (and remembered that you need to return a value from an inline sort function, otherwise it’s deemed to mean that all the elements are equal to each other!), I was testing by mashing the column sort buttons and after a few sorts something odd happened:

Screen Shot 2018 08 04 at 17 35 10

The values inserted by the aura:iteration were sandwiched in between the two summary rows that should appear at the bottom.  I refreshed the page and tried again and this time it got a little worse:

Screen Shot 2018 08 04 at 17 33 32

This time the aura:iteration values appeared below both summary rows. I tested this on Chrome and Firefox and the behaviour was the same for both browsers. 

The Workaround

I’ve hit a few issues around aura:iteration in the past, although usually it’s been the body of that components rather than the surround ones, and I recalled that often the issue could be solved by separating the standard Lightning components with regular HTML. I could go with <tfoot>, but according to the docs this indicates that if the table is printed the summary rows should appear at the end of each page, which didn’t seem quite right.

I already had a <tbody>, but looking at the docs a table can have multiple <tbody> tags, to logically separate content, so another one of these sounded exactly what I wanted. Moving the summary rows into their own “section” as follows:

<aura:iteration ...>
<th> _head_ </th>
</aura:iteration ...>
<aura:iteration ...>
<td> _data_ </td>
</aura:iteration ...>

worked a treat. Regardless of how much I bounced around and clicked the headings, the summary rows remained at the bottom of the table as they were supposed to.

I haven’t been able to reproduce this with a small example component - it doesn’t appear to be related to the size of the list backing the table as I’ve tried a simple variant with several thousand members and the summary rows stick resolutely to the bottom fo the table. Given that I have a workaround I’m not sure how much time I’ll invest in digging deeper, but if I do find anything you’ll read about it here.

Related Posts


Saturday, 21 July 2018

Exporting Folder Metadata with the Salesforce CLI

Exporting Folder Metadata with the Salesforce CLI



In my earlier post, Exporting Metadata with the Salesforce CLI, I detailed how to replicate the CLI export command using the Salesforce CLI. One area that neither of these handle is metadata inside folders, so reports, dashboards and earl templates. Since then I’ve figured out how to do this, so the latest version of the CLIScripts Github repo has the code to figure out which folders are present, and includes the contents of each of these in the export.

Identifying the Folders

This turned out to be a lot easier than I expected - I can simply execute a SOQL query on the Folder sobject type and process the results:

let query="Select Id, Name, DeveloperName, Type, NamespacePrefix from Folder where DeveloperName!=null";
let foldersJSON=child_process.execFileSync('sfdx', 
        '-q', query, 
        '-u', this.options.sfdxUser,

Note that I’m not entirely sure what it means when a folder has a DeveloperName of null - I suspect it indicates a system folder, but as the folders I was interested in appeared, I didn’t look into this any further.

I then created a JavaScript object containing a nested object for each folder type:


and then parsed the resulting JSON, adding each result into the appropriate folder type object as a property named as the folder Id. The property contains another nested object wrapping the folder name and an array where I will store each entry from the the folder:

var foldersForType=this.foldersByType[folder.Type];
if (foldersForType) {
    if (!foldersForType[folder.Id]) {
        foldersForType[folder.Id]={'Name': folder.DeveloperName, 'members': []};

Retrieving the Folder Contents

Once I have all of the folders stored in my complex object structure, I can query the metadata for each folder type - the dashboards in this instance - and build out my structure modelling all the folders and their contents:

let query="Select Id, DeveloperName, FolderId from Dashboard";
let dashboardsJSON=child_process.execFileSync('sfdx', 
        '-q', query, 
        '-u', this.options.sfdxUser,

I then iterate the results and add these to the members for the specific folder:

var foldersForDashboards=this.foldersByType['Dashboard'];
var folderForThisDashboard=foldersForDashboards[dashboard.FolderId];
if (folderForThisDashboard) {

Adding to the Manifest

As covered in the previous post on this topic, once I’ve identified the metadata required, I have to add it to the manifest file - package.xml. 

I already had a method to add details of a metadata type to the package, so I extended that to include a switch statement to change the processing for those items that have folders. Using dashboards as the example again, I iterate all the folders and their contents, adding the appropriate entry for each:

case 'Dashboard':
    var dbFolders=this.foldersByType['Dashboard'];
    for (var folderId in dbFolders) {
        if (dbFolders.hasOwnProperty(folderId)) {
            var folder=dbFolders[folderId];
            for (var dbIdx=0; dbIdx<folder.members.length; dbIdx++) {
                var dashboard=folder.members[dbIdx];
                this.addPackageMember(folder.Name + '/' + dashboard.DeveloperName);

In the case of our BrightMedia appcelerator, the package.xml ends up looking something like this:


Exporting the Metadata

One thing to note is that the export is slowed down a bit as there are now six new round trips to the server - three for each of the folder types, and three more to retrieve the metadata for each type of folder. Exporting the metadata using the command:

node index.js export -u <username> -d output

creates a new output folder containing the zipped metadata. Unzipping this shows that the dashboard metadata has been retrieved as expected:

> ls -lR dashboards

total 24
drwxr-xr-x 5 kbowden staff 160 21 Jul 15:29 BG_Dashboard
-rw-r--r-- 1 kbowden staff 154 21 Jul 14:27 BG_Dashboard-meta.xml
drwxr-xr-x 5 kbowden staff 160 21 Jul 15:29 Best_Practice_Service_Dashboards
-rw-r--r-- 1 kbowden staff 174 21 Jul 14:27 Best_Practice_Service_Dashboards-meta.xml
drwxr-xr-x 6 kbowden staff 192 21 Jul 15:29 Sales_Marketing_Dashboards
-rw-r--r-- 1 kbowden staff 180 21 Jul 14:27 Sales_Marketing_Dashboards-meta.xml

total 48
-rw-r--r-- 1 kbowden staff 2868 21 Jul 14:27 BrightMedia.dashboard
-rw-r--r-- 1 kbowden staff 6917 21 Jul 14:27 BrightMedia_digital_dashboard.dashboard

total 88
-rw-r--r-- 1 kbowden staff 8677 21 Jul 14:27 Service_KPIs.dashboard

total 96
-rw-r--r-- 1 kbowden staff 10317 21 Jul 14:27 Sales_Manager_Dashboard.dashboard
-rw-r--r-- 1 kbowden staff 8046 21 Jul 14:27 Salesperson_Dashboard.dashboard

One more thing

I also fixed the export of sharing rules, so rather than specifying SharingRules as the metadata type, it specifies the three subtypes of sharing rule (SharingCriteriaRule, SharingOwnerRule, SharingTerritoryRule) required to actually export them!

Related Posts



Saturday, 14 July 2018

Lighting Component Attributes - Expect the Unexpected

Lighting Component Attributes - Expect the Unexpected



A couple of weeks ago my BrightGen colleague, Firoz Mirza, described some unexpected (by us at least) behaviour when dealing with Lightning Component attributes. I was convinced that there must be something else going on so spent some time creating a couple of simple tests, only to realise that he was absolutely correct and attributes work a little differently to how I thought, although if I’m honest, I’ve never really thought about it.

Tales of the Unexpected 1

The basic scenario was assigning a Javascript variable to the result of extracting the attribute via component.get(), then calling another method that also extracted the value of the attribute, but crucially also changed it and set it back into the component. A sample component helper that works like this is:

    propertyTest : function(cmp) {
	var testVar=cmp.get('v.test');
        alert('Local TestVar = ' + JSON.stringify(testVar));
        alert('Local TestVar after leaving it alone = ' + JSON.stringify(testVar));
    changeAttribute : function(cmp) {
        var differentVar=cmp.get('v.test');
        cmp.set('v.test', differentVar);

The propertyTest function gets the attribute from the component and assigns it to testVar. After showing the value, it then invokes changeAttribute, which gets the attribute and assigns it to a new variable, updates it and then sets it back into the component.

Executing this shows the value of the property is empty, as expected to begin with:

Screen Shot 2018 07 14 at 13 49 09

but after the attribute has been updated elsewhere, my local variable reflects this:

Screen Shot 2018 07 14 at 13 50 27


Tales of the Unexpected 2

After a bit more digging, I came across something that I expected even less - updating a local variable that stored the result of the component.get() for the attribute, changed the value in the component. So I updated my sample component to test this:

    propertyTest2: function(cmp) {
	var test2=cmp.get('v.test');
        alert('Local test2 = ' + JSON.stringify(test2));
    checkAttribute: function(cmp) {
        var otherVar=cmp.get('v.test');
        alert('Other var = ' + JSON.stringify(otherVar));

Executing this shows my local property value being updated as expected:

Screen Shot 2018 07 14 at 14 01 51

but then retrieving this via another variable showed that it appeared to have been updated in the component:

Screen Shot 2018 07 14 at 14 02 28

“Maybe it’s just because everything is happening in the same request” I thought, but after this request completed I executed the first test, so extracting the value in a separate request (transaction) showed that the attribute had indeed been updated in a way that persisted across requests:

Screen Shot 2018 07 14 at 14 03 44

Checking the Docs

Convinced that I must have missed this in the docs, I went back to the Lightning Components Developer Guide, Working with Attribute Values in JavaScript section, but only found the following:

component.get(String key) and component.set(String key, Object value) retrieves and assigns values associated with the specified key on the component.

which didn’t add anything to what I already knew. 

Asking the Experts

I then asked the question of Salesforce, with my sample components, and received the following response:

The behavior you’re seeing is expected. 

component.set() is a signal to the framework that you’ve changed a value. The framework then fires change handlers, rerenders, and so forth. 

The underlying value is a JavaScript object, array, primitive, or otherwise. JavaScript object’s and arrays are mutable. Aura’s model requires that you send that signal when you mutate an object or array.


So there you have it - a local variable assigned the result of component.get() gives you a live hook to the component attribute, and if it’s mutable then any changes you make to the local variable update the attribute regardless of whether you call component.set() or not. Good to know!

I think the docs could certainly use with some additional information to make this clear. It’s written down here, at least, so hopefully more people will know going forward!

Related Posts



Saturday, 7 July 2018

Exporting Metadata with the Salesforce CLI

Exporting Metadata with the Salesforce CLI



Regular readers of this blog are all too aware that I’m a big fan of the Salesforce CLI. I rarely use anything else to interact with Salesforce orgs, outside of the main UI. Prior to this I used the CLI, which is another excellent tool, though didn’t give me quite enough information about operations to switch to it exclusively.

One area where the CLI still beats the default functionality of the Salesforce CLI is the export command, which pull back most of the metadata from an org (except reports, dashboards, and email templates, where you have to jump through a few hoops to get folder names first). Switching back to the CLI to take a backup of an org started to get a bit repetitive, so I decided to write something to replicate the functionality using the Salesforce CLI.

To Plugin or Not to Plugin

After some consideration, I decided not to plugin. I need to do some work and then execute a deployment via the Salesforce CLI, and there’s currently no way to execute a command from a plugin. I could shell out of the plugin and execute the CLI as a new child process, but that seemed a bit clunky. Plus where does it end? We’d end up with plugins creating child sfdx commands that were plugins that created child sfdx commands - a house of cards made of processes and just as fragile. I’d also seen Dave Carroll call this approach an anti-pattern on twitter and I didn’t want him mad at me, so I went with a node script that wraps everything up into a single command from the users point of view.

The Node Code

As I plan to do more of this sort of thing, rather than having loads of different scripts to execute I went with one script that offers subcommands. Kind of like a CLI all of it’s own, but very lightweight.

In order to support subcommands I used the commander module - I’ve used this a few times in the past and it’s really straightforward. I just configure it with the subcommands and their options and it figures out which one to execute and parses the arguments for me:

var exportCmd=program.command('export')
		 .option("-u, --sfdx-user [which]", "Salesforce CLI username")
		 .option("-d, --directory [which]", "Output directory")
                 .action(function(options) {new ExportCommand(options).execute()});


The export command is responsible for generating a package.xml manifest file containing details of all the metadata to retrieve and then executing a metadata API retrieve.


It contains a list of the names metadata components to extract:

const allMetadata=[
... "ValidationRule", "Workflow" ];

The one wrinkle here is the standard objects. If I wildcard the CustomObject metadata type, this will just retrieve my custom objects. To include the standard objects, I have to explicitly name each of them. Luckily the Salesforce CLI provides a way for me to extract these - force:schema:sobject:list, so I can pull these back and turn them into a list of names:

var standardObjectsObj=child_process.execFileSync('sfdx', 
                        '-u', this.options.sfdxUser, 
                        '-c', 'standard']);

var standardObjectsString=standardObjectsObj.toString();

for (var idx=standardObjects.length-1; idx>=0; idx--) {
    if ( (standardObjects[idx].endsWith('__Tag')) ||
         (standardObjects[idx].endsWith('__History')) ||
         (standardObjects[idx].endsWith('__Tags')) ) {
         standardObjects.splice(idx, 1);

Then I can generate the package.xml by iterating the list of metadata names and adding a wildcard entry for each of them, and append the standard objects when the metadata name is CustomObject:

for (var idx=0; idx<allMetadata.length; idx++) {
    var mdName=allMetadata[idx];
    if (mdName=='CustomObject') {
        for (var soIdx=0; soIdx<standardObjects.length; soIdx++) {

Once the package.xml is written, I can then execute a force:mdapi:retrieve to pull back all of the available metadata:

                    '-u', this.options.sfdxUser, 
                    '-k', this.packageFilePath]);

Export all the Metadata!

(The full source code is available at the Github repo. If you want to try this out yourself, clone the repo, change into the directory containing the clone and run npm install to pull down the dependencies.)

To export the metadata I execute:

node index.js export -u -d output

Where -u specifies the username that I’ve previously authorised with the Salesforce CLI (I’m using the client dev org for my training system), and -d is the directory where the package.xml will be created and the metadata extracted to. If this directory doesn’t exist it will be created.

The output is just a couple of lines telling me it succeeded:

Exporting metadata
Metadata written to output/

The contents of the output subdirectory shows the package.xml and zip file retrieved from Salesforce:

$ ls -lrt
total 928
-rw-r--r-- 1 kbowden staff 20110 7 Jul 12:49 package.xml
-rw-r--r-- 1 kbowden staff 453236 7 Jul 12:50

unzipping the file creates an unpackaged directory containing the retrieved metadata:

$ unzip
inflating: unpackaged/labels/CustomLabels.labels

$ cd unpackaged
$ ls -l
total 48
drwxr-xr-x 16 kbowden staff 512 7 Jul 12:52 applications
drwxr-xr-x 4 kbowden staff 128 7 Jul 12:52 assignmentRules
drwxr-xr-x 7 kbowden staff 224 7 Jul 12:52 tabs
drwxr-xr-x 4 kbowden staff 128 7 Jul 12:52 triggers

and hey presto, my org is exported without having to revert back to the CLI.

Related Posts


Friday, 29 June 2018

Adding Signature Capture to a Lightning Flow

Adding Signature Capture to a Lightning Flow

Screen Shot 2016 11 07 at 18 36 19


As regular readers of this blog know, a few years ago I wrote a Lightning Component for the launch of the Component Exchange called Signature Capture which, in a shocking turn of events, allows a user to capture a signature image and attach it to a Salesforce record. Over the years it’s received various enhancements and when these are notable I write a blog post about it. The latest version satisfies this requirement as it adds support for inclusion in Lightning flows. Note that the rest of this post assumes that you are working with V1.31 of Signature Capture or higher.

The Scenario

I’m going for a pretty simple scenario - a custom button on the a Salesforce record starts a flow that contains just the Signature Capture component. Once the user is happy with the signature and saves it, finishing the flow returns them to the record that they started from.

Creating the Flow

If you are a metadata type, you can install the demo flow from the Signature Capture Samples Git repository. It’s called   don’t forget to activate it.

If not, carry out the following steps to create the flow:

  1. Open the flow designer
  2. Click on the ‘Resources’ tab and on the resulting list click ‘Variable'

    Screen Shot 2018 06 28 at 16 51 15

  3. Create a new variable named ‘recordId’ and click OK - this will contain the Id of the record that the user wishes to capture a signature against.

    Screen Shot 2018 06 28 at 16 47 32
  4. Click the ‘Palette’ tab and on the resulting list drag ‘Screen’ onto the canvas:FillFil

    Screen Shot 2018 06 28 at 16 52 41

  5. Fill in the ‘Name’ as desired - I went for ‘Signature Capture’ as I’m a creative type:

    Screen Shot 2018 06 28 at 16 56 54

  6. Click the ‘Add a Field’ tab and scroll to the bottom of the list and choose ‘Lightning Component’:

    Screen Shot 2018 06 28 at 16 57 27

  7. Click the ‘Field Settings’ and configure as shown below - note that I had to remove the entry that was automatically added to the ‘Outputs’ tab before I could save. Make sure to set the value of the 'Record Id' attribute to the 'recordId’ variable - this ensures that the captured signature is stored against the correct Salesforce record.

    Screen Shot 2018 06 28 at 16 58 23

  8. Set the screen as the start element
  9. Save the flow
  10. Activate the flow

And that’s it! Straightforward but the flow isn’t very useful as it’s not attached to anything.

Creating the Custom Button

Using the URL of the flow seems pretty clunky, but what can we do?

  1. Open the flow and copy the URL value:

    Screen Shot 2018 06 28 at 17 02 40

  2. Navigate to the setup page for the object you want to be able to launch the flow from (I’ve gone for Account) and click ‘Buttons, Actions, and Links’
  3. On the resulting page, click ‘New Button or Link'
  4. Configure the button as follows, replacing the ‘Button or Link URL’ with the concatenation of your flow URL (copied in step 1) and the appropriate record id for for the sobject type. The retURL parameter is the secret sauce to send the user back to the record when they finish the flow:

    Screen Shot 2018 06 28 at 17 19 11

  5. After saving, click the ‘Page Layouts’ link from the sObject setup page.
  6. Choose the layout to add the button to.
  7. On the resulting page builder, select the 'Mobile & Lightning Actions’ item from the palette:

    Screen Shot 2018 06 28 at 17 08 23

  8. And drag your new custom button on to the 'Salesforce Mobile and Lightning Experience Actions’ section:

    Screen Shot 2018 06 28 at 17 08 26

  9. Save the page layout

Enable Lightning Flow

Flows containing Lightning components require lightning flow to be enabled.

  1. Navigate to Setup -> Process Automation -> Process Automation Settings
  2. Check the ‘Enable Lightning runtime for flows’ box:

    Screen Shot 2018 06 28 at 17 12 53

  3. Save the settings.

Take it for a Spin

Once everything is in place you can launch the flow by navigating to a record of the appropriate sObject type and clicking the custom button. The video below shows my flow configured above being launched and completed from an account record:

Related Posts


Sunday, 24 June 2018

Sharing your Salesforce App on Github

Sharing your Salesforce App on Github



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.


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 IDE, where you can choose from a subset of metadata to retrieve
  • The 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 CLI, via the export and fetch commands.

In my view the 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)


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 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
  6. Verify the remote:
    $ git remote -v

    origin (fetch)
    origin (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 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 ( 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

* branch master -> FETCH_HEAD
Merge made by the 'recursive' strategy. | 2 ++
1 file changed, 2 insertions(+)
create mode 100644

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.
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



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 (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 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.