Passing platform configuration to an AngularJS app

Running a JavaScript client-side MVC app such as an AngularJS app in Salesforce presents the problem of how to obtain configuration information from the platform. Most of the app is best located in a static resource zip file as server-side Visualforce processing isn’t needed. Using relative URLs between the various files in the zip then avoids any dependency on the absolute URL of the zip. (That absolute URL includes a timestamp and also a namespace prefix if a managed package is involved so the fewer references to it the better.)

But there are still a few configuration parameters that are easiest to obtain using Visualforce. The index Visualforce page – that dynamic page content is inserted into – is a good single place to obtain that information and make it available to the rest of the app through JavaScript via Angular’s constant mechanism:

<apex:page showHeader="false" sidebar="false"
        standardStylesheets="false" applyHtmlTag="false">
<html lang="en" ng-app="eepApp" ng-controller="AppController">
<head>...</head>
<body>...

<script src="{!URLFor($Resource.appzip, 'lib/angular/angular.min.js')}"></script>
<script src="{!URLFor($Resource.appzip, 'js/app.js')}"></script>
<script src="{!URLFor($Resource.appzip, 'js/controllers.js')}"></script>
<script src="{!URLFor($Resource.appzip, 'js/filters.js')}"></script>
<script src="{!URLFor($Resource.appzip, 'js/services.js')}"></script>

<script>
(function() {
    var parts = '{! $CurrentPage.Name }'.split('__');
    var namespace = parts.length == 2 ? parts[0] : null
    var restPrefix =  '{! $Site.CurrentSiteUrl }services/apexrest'
            + (namespace ? '/' + namespace : '');
    var pagePrefix = 'https://{! $Site.Domain }';
    var serverUrls = {
        namespacePrefix: namespace ? namespace + '__' : '',
        configRest: restPrefix + '/eep/config',
        employeesRest: restPrefix + '/eep/employees',
        metaRest: restPrefix + '/eep/meta',
        loginPage: pagePrefix + '{! $Page.Login }',
        logoutPage: pagePrefix + '{! $Page.Logout }'
    }
    console.log('serverUrls=' + JSON.stringify(serverUrls));
    
    // This configures the Angular app (declared in app.js)
    eepApp.constant('ServerUrls', serverUrls);
})();
</script>
  
</body>
</html>
</apex:page>

With this setup, any service or controller that needs to reference one of the configuration values just declares a dependency on the ServerUrls object and references the values from that. The result is a clean separation of concerns.

Connecting DataTables to JSON generated by Apex

I had a quick go at coming up with an answer to this DataTables sAjaxSource question on Salesforce Stackexchange but didn’t get very far. Using DataTable’s sAjaxSource directly runs you into the problem that the Visualforce page containing the table and the Apex REST API that supplies the JSON use different host names and so the same origin security policy blocks the access. (The answer includes one way to work-around this.)

The approach I tried was to use Apex’s @RemoteAction mechanism, where a JavaScript function is generated in the page. So instead of referencing a URL, the JavaScript function is called and how it connects back to the server is its concern. The trick to getting it to work is to recognise that the JavaScript function that you add (via the fnServerData property) is intended to provide an interception point for the request to the URL you specify (via the sAjaxSource property). So both must be specified, even though in this case the URL is not used.

This an example of the output (using default styles):

dt

Here is the Visualforce page; most of the JavaScript is just cleaning up the JSON data returned from the controller to suit DataTables:

<apex:page controller="DataTableController">

<link
rel="stylesheet"
type="text/css"
href="https://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css"
/>

<apex:sectionHeader title="DataTables"/>

<table id="table" cellpadding="0" cellspacing="0" border="0">
    <thead>
        <th>Name</th>
        <th>Birthdate</th>
        <th>Phone</th>
        <th>Email</th>
        <th>Salary</th>
    </thead>
    <tbody>
    </tbody>
</table>

<script
type="text/javascript"
charset="utf8"
src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js"
>
</script>
<script
type="text/javascript"
charset="utf8"
src="https://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"
>
</script>
<script>

var j$ = jQuery.noConflict();

var fields = ['Name', 'Birthdate', 'Phone', 'Email', 'Salary__c'];

var aoColumns = [];
for (var i = 0; i < fields.length; i++) {
    aoColumns.push({'mData': fields[i]});
}

j$(document).ready(function() {
    j$('#table').dataTable({
        'aoColumns': aoColumns,
        'bProcessing': true,
        'bServerSide': true,
        'bFilter': false,
        'sAjaxSource': 'fakeUrl',
        'fnServerData': function(sSource, aoData, fnCallback) {
            console.log(JSON.stringify(aoData));
            // Call the @RemoteAction JavaScript function
            DataTableController.contacts(aoData, function(result, event) {
                if (event.type != 'exception') {
                    console.log(JSON.stringify(result));
                    for (var i = 0; i < result.aaData.length; i++) {
                        var r = result.aaData[i];
                        for (var j = 0; j < fields.length; j++) {
                            var field = fields[j];
                            if (r[field] == undefined) {
                                // DataTables pops a dialog for undefined values
                                r[field] = null;
                            } else if (field == 'Birthdate') {
                                // Dates transmitted as longs
                                var d = new Date(r[field]);
                                r[field] = ''
                                        + (d.getMonth() + 1)
                                        + '/'
                                        + d.getDate()
                                        + '/'
                                        + d.getFullYear()
                                        ;
                            }
                        }
                    }
                    // Call back into the DataTable function
                    fnCallback(result);
                } else {
                    alert(event.message);
                }
            });
        }
    });
});

</script>

</apex:page>

Most of the complexity in the Apex code is in interpreting the request parameters sent by DataTables including things like the multi-column sorting. Note that the conversion from JSON to Apex objects and Apex objects to JSON is left to the platform.

// See https://datatables.net/usage/server-side
global class DataTableController {

    // Defines shape of JSON response
    global class Response {
        public Integer sEcho;
        public Integer iTotalRecords;
        public Integer iTotalDisplayRecords;
        public SObject[] aaData;
        Response(Integer echo, Integer total, SObject[] sobs) {
            this.sEcho = echo;
            this.iTotalRecords = total;
            this.iTotalDisplayRecords = total;
            this.aaData = sobs;
        }
    }

    // DataTable passes JSON definition of what server should do
    private class Params {
    
        Map<String, Object> m = new Map<String, Object>();
        
        Integer echo;
        Integer start;
        Integer length;
        String[] columns;
        Integer[] sortColumns;
        String[] sortDirections;
        
        Params(List<Map<String, Object>> request) {
            for (Map<String, Object> r : request) {
                m.put((String) r.get('name'), r.get('value'));
            }
            echo = integer('sEcho');
            start = integer('iDisplayStart');
            length = integer('iDisplayLength');
            columns = stringArray('mDataProp');
            sortColumns = integerArray('iSortCol');
            sortDirections = stringArray('sSortDir');
        }
        
        String[] stringArray(String prefix) {
            String[] strings = new String[] {};
            for (Object o : array(prefix)) {
                strings.add(o != null ? esc(String.valueOf(o)) :null);
            }
            return strings;
        }
        
        Integer[] integerArray(String prefix) {
            Integer[] integers = new Integer[] {};
            for (Object o : array(prefix)) {
                integers.add(o != null ? Integer.valueOf(o) : null);
            }
            return integers;
        }

        Object[] array(String prefix) {
            Object[] objects = new Object[] {};
            for (Integer i = 0; true; i++) {
                Object o = m.get(prefix + '_' + i);
                if (o != null) {
                    objects.add(o);
                } else {
                    break;
                }
            }
            return objects;
        }
        
        Integer integer(String name) {
           Object o = m.get(name);
           if (o instanceof Decimal) {
               return ((Decimal) o).intValue();
           } else if (o instanceof Integer) {
               return (Integer) o;
           } else {
               return null;
           }
        }
        
        // Guard against SOQL injection
        String esc(String s) {
            return s != null ? String.escapeSingleQuotes(s) : null;
        }
    }
    
    @RemoteAction
    global static Response contacts(List<Map<String, Object>> request) {
    
        Params p = new Params(request);

        String soql = ''
                + ' select ' + String.join(p.columns, ', ')
                + ' from Contact'
                + ' order by ' + String.join(orderBys(p), ', ')
                + ' limit :length'
                + ' offset :start'
                ;
        System.debug('>>> soql=' + soql);

        Integer start = p.start;
        Integer length = p.length;
        return new Response(
                p.echo,
                [select Count() from Contact limit 40000],
                Database.query(soql)
                );
    }
    
    private static String[] orderBys(Params p) {
        Map<String, String> soqlDirections = new Map<String, String>{
                'asc' => 'asc nulls last',
                'desc' => 'desc nulls first'
                };
        String[] orderBys = new String[] {};
        Integer min = Math.min(p.sortColumns.size(), p.sortDirections.size());
        for (Integer i = 0; i < min; i++) {
            orderBys.add(''
                    + p.columns[p.sortColumns[i]]
                    + ' '
                    + soqlDirections.get(p.sortDirections[i])
                    );
        }
        return orderBys;
    }
}

Apex code formatting

I’ve been spending (wasting?) time on Salesforce Stack Exchange. Often code is posted and nearly always the formatting used gets in the way of understanding what the code is doing. I suggest this can muddle the thinking of the person writing the code, and certainly impacts those who have to pick the code up later.

So before contemplating gnarly logical problems in code, the first job is to remove the formatting noise. Having a Java background, my “go to” reference is Code Conventions for the Java Programming Language. But there are a couple of language mechanisms specific to Apex that are worth particular attention…

The first is the SOQL for loop that you should make your default pattern when querying and iterating over SObjects. (Some reasons why: where there are large numbers of SObjects, only a chunk of SObjects occupy heap space rather than all the SObjects; the scope of the variable is limited to the loop block; clear and elegant syntax.) But there can be a lot going on in the SOQL so give the SOQL the clarity it deserves and keep the line length reasonable by using line breaks:

public class MyClass {
    public void myMethod(Set<Id> accountIds) {
        for (Account a : [
                select Id, Name, BillingStreet, BillingCity, BillingState
                from Account
                where Id in :accountIds
                order by Name
                ]) {
            // Do something with each Account
        }
    }
}

The second is the support for named parameters in SObjects. Instead of creating an object with no values in its fields and then assigning values field by field, create the complete object, dedicating a separate line to each parameter:

@isTest
private class MyClassTest {
    @isTest
    static void myTestName() {
        Account a = new Account(
                Name = 'Acme',
                BillingStreet = '123 The Street',
                BillingCity = 'The City',
                BillingState = 'The State'
                );
        // Do something
    }
}

The above examples also use these formatting ideas:

  • Indent consistently and by 4 spaces (not tabs)
  • Indent continuations (where lines get too long and need to be wrapped) by 8 spaces more than their containing block to distinguish them from the start of a new block
  • Eliminate all blank lines that don’t have a purpose
  • Unfortunately Apex is case insensitive, but that is no reason to not use consistent capitalization to distinguish e.g. between types and variables

Finding Visualforce field ids

I was asked in a comment on Hack to find field ids – allows a default UI “New” page to be pre-populated to post the code so I have done so below. Remember this is a hack.

Before using this code, consider instead using the more recent Tooling API – see Andrew Fawcett’s Querying Custom Object and Field IDs via Tooling API.

public class LkidUtil {

    /**
     * This field id is needed to pass parameters to default UI. Unfortunately there is no API to obtain it, hence this hack.
     * Takes about 50-100ms. Results could be cached in a custom setting if that became problematic.
     */
    public static String findFieldId(SObjectType sobType, SObjectField field) {

        return findFieldIds(sobType, new SObjectField[] { field })[0];
    }
    
    public static String[] findFieldIds(SObjectType sobType, SObjectField[] fields) {
        
        // Crazy but necessary: parse default UI HTML for required id
        PageReference p = new PageReference('/' + sobType.getDescribe().getKeyPrefix() + '/e?nooverride=1');
        String html = p.getContent().toString();
        
        List ids = new List();
        for (SObjectField field : fields) {
        	
            DescribeFieldResult f = field.getDescribe();
            String label = f.getLabel();
            ids.add(matchFieldId(html, label));
        }
        return ids;
    }
    
    // Public for testing
    public static String matchFieldId(String html, String label) {
    	
    	// Non-greedy ? wasn't sufficient; now limit the matching characters to "word character", usually [A-Za-z0-9_], which is ok for the field id
    	Matcher m = Pattern.compile('(<span class="requiredMark">\\*</span>)?' + label + '').matcher(html);
    	
        // Use first match
        if (m.find()) {
            return m.group(1);
        } else {
            return null;
        }
    }
}

Note calls to this need guarding for tests to work:

String[] fieldIds;
if (!Test.isRunningTest()) {
    // Contains code that causes tests to (silently) abort
    fieldIds = LkidUtil.findFieldIds(Document__c.SObjectType, new SObjectField[] {
            Document__c.Claim__c,
            Document__c.BenefitClaimed__c,
            Document__c.Type__c
            });
} else {
    // Used in test
    fieldIds = new String[] {
            'FakeClaimFieldId',
            'FakeBenefitClaimedFieldId',
            'FakeTypeFieldId'
             };
}

And here is a unit test (limited by the fact that the page HTML cannot be generated in a test):

@isTest
private class LkidUtilTest {

    @isTest
    static void testRegexPatternRequiredField() {
    	
    	// Always worked
    	doTestRegexPattern('<td class="labelCol requiredInput"><label for="00NK0000000ap5l"><span class="requiredMark">*</span>Type</label></td>');
    	
    	// Was broken
    	doTestRegexPattern('<label for="Name"><span class="requiredMark">*</span>Document Name</label></td><td class="dataCol col02"><div class="requiredInput"><div class="requiredBlock"></div><input  id="Name" maxlength="80" name="Name" size="20" tabindex="1" type="text" /></div></td><td class="labelCol requiredInput"><label for="00NK0000000ap5l"><span class="requiredMark">*</span>Type</label>');
    }
    
    @isTest
    static void testRegexPatternNonRequiredField() {
    	
    	// Always worked
    	doTestRegexPattern('<td class="labelCol"><label for="00NK0000000ap5l">Type</label></td>');
    	
    	// Was broken
    	doTestRegexPattern('<label for="Name"><span class="requiredMark">*</span>Document Name</label></td><td class="dataCol col02"><div class="requiredInput"><div class="requiredBlock"></div><input  id="Name" maxlength="80" name="Name" size="20" tabindex="1" type="text" /></div></td><td class="labelCol"><label for="00NK0000000ap5l">Type</label>');
    }
    
    private static void doTestRegexPattern(String html) {
    	System.assertEquals('00NK0000000ap5l', LkidUtil.matchFieldId(html, 'Type'));
    }
}

Asynchronous processing in Force.com

This (2 year old) Salesforce document Asynchronous Processing in Force.com offers good insight into how asynchronous Apex is processed including concepts like “extended delay” and diagrams like this one:

PeekSet

It also offers best practice advice. Well worth a read if you are using @future or Batchable or the Bulk API.

JavaScript testing

Automated tests bring many benefits, but often jQuery JavaScript that is tightly coupled to its associated HTML doesn’t have such tests. As more logic moves into the client, this becomes less acceptable.

AngularJS is designed with testability high on the priority list, and I can confirm that its design and the testing frameworks available make writing JavaScript tests surprisingly easy and often quite enjoyable. I must admit to writing the tests after building the application, and so had to refactor the application to make it more testable. But as with other languages, this refactoring also improved the design of the application code.

I used Jasmine for my tests, both unit tests and end-to-end tests. Here is a unit test example that shows how clean the tests can look:

describe("The UtilService should", function() {

    var service;
    
    beforeEach(inject(function($injector) {
        service = $injector.get('UtilService');
    }));
    
    it('generate arrays', function() {
        expect(service.range(2, 6, 1)).toEqual([2, 3, 4, 5, 6]);
        expect(service.range(-5, 5, 3)).toEqual([-5, -2, 1, 4]);
    });
    
    it('format dates', function() {
        expect(service.formatDate(new Date(2013, 11, 25))).toEqual('2013-12-25');
        expect(service.formatDate(new Date(2014, 0, 3))).toEqual('2014-01-03');
    });
});

For unit tests, there is the rather amazing Karma test runner. This lets you run all your tests – hundreds if you have them, it is that quick – every time you save a JavaScript file. You can use locally installed browsers or run the tests on multiple browsers simultaneously via Selenium Grid locally or out in the cloud. Companies such as SauceLabs offer a huge number of browser/OS combinations so you can go as far as you need to with browser compatibility testing. And it is easy to hook into your Continuous Integration server such as Jenkins:

jenkins

For end-to-end tests there is the Protractor test framework. This also works with Selenium Grid and integrates with Jenkins. This took more work to get going, mainly because I had to add and call a test-only Apex @RestResource class to clear the application data at the start of each test run so the tests could assume no data as a starting point.

Thanks to Node.js packages, these test environments are really easy to setup too.

+1 for AngularJS

I’ve just spent a few weeks working on a (smallish) mobile-first portal application using AngularJS and Bootstrap at the client-side and mainly @RestResource Apex classes at the server-side. Despite the steep learning curve, I would encourage anyone looking to build a client-side MVC application to take a serious look at AngularJS. It is fun to work with, unobtrusive, and you end up with pretty clean (and testable) code and templates.

Here is one example of the elegance of AngularJS. If you are writing HTML/CSS pages, frameworks like JavaServer Pages and Visualforce require extra elements to be inserted for repetition or conditional rendering (at the server-side) that obscure the nesting of the HTML elements:

<ul>
    <apex:repeat var="href" value="{!hrefs}">
        <li>
            <a href="{!href}">Link</a>
        </li>
    </apex:repeat>
</ul>

AngularJS allows the use of much less intrusive attributes for this purpose:

<ul>
    <li>
        <a ng-repeat="href in hrefs" href="{{href}}">Link</a>
    </li>
</ul>

and is also nice enough to emit comments in the generated (at the client-side) HTML that help explain what happened.

I only discovered Batarang towards the end of my work. This extends Chrome’s Developer Tools with AngularJS-aware views such as the models contained in the various scopes.

I suggest buying a book to get a clearer understanding of the “why” as well as the “how”. The one I found in my local bookstore was AngularJS by Green & Seshadri. This has been a big help to me as have the egghead.io bite-sized videos.