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'));
    }
}

Leave a comment