Finding Visualforce field ids

PS This hack is broken in Summer ’15; the class name “requiredMark” looks like it has changed to “assistiveText”.

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.