Hack to find field ids – allows a default UI “New” page to be pre-populated

PS There is now an API – albeit one that requires a REST call – that allows the field ids to be obtained. See Querying Custom Object and Field IDs via Tooling API.

The default (layout-based) page presented when a “New Abc” button is clicked for a custom object “Abc” supports the passing of default values for the input fields via the URL. But the field id/name values required vary from org to org and there is no platform API to obtain the values. (This is the same problem described in another post on this blog nearly two years ago Lkid hack value is fragile so API support for this is unlikely to appear anytime soon.)

So where you have business logic that can default some values for the user, it is hard to create a portable solution. Here is a hack around the problem.

The approach accepts that the only place the required id values can be found is in the HTML itself and so obtains the HTML for the default edit page and uses a regular expression to pull out pairs of field labels and id values. The describe APIs can then be used to relate the labels to the underlying fields. (The method getDefaultValue is where the business logic to default the values goes.) A little formatting is needed to create the right parameter value strings:

private Map<String, String> createDefaultValues() {
    // Obtain the magic ids
    PageReference p = new PageReference('/' + Abc__c.SObjectType.getDescribe().getKeyPrefix() + '/e?nooverride=1');
    String html = p.getContent().toString();
    Map<String, String> labelToId = new Map<String, String>();
    Matcher m = Pattern.compile('<label for="(.*?)">(<span class="requiredMark">\\*</span>)?(.*?)</label>').matcher(html);
    while (m.find()) {
        String label = m.group(3);
        String id = m.group(1);
        labelToId.put(label, id);
    }
    // Relate the magic ids to the SObject fields
    Map<String, String> params = new Map<String, String>();
    for (SObjectField field : Abc__c.SObjectType.getDescribe().fields.getMap().values()) {
        Object value = getDefaultValue(field);
        if (value != null) {
            DescribeFieldResult f = field.getDescribe();
            String label = f.getLabel();
            if (labelToId.containsKey(label)) {
                // Format
                String valueAsString;
                if (f.getType() == DisplayType.Date) {
                    valueAsString = ((Date) value).format();
                } else if (f.getType() == DisplayType.Datetime) {
                    valueAsString = ((Datetime) value).format();
                } else if (f.getType() == DisplayType.Boolean) {
                    valueAsString = ((Boolean) value) ? '1' : '0';
                } else {
                    valueAsString = String.valueOf(value);
                }
                params.put(labelToId.get(label), valueAsString);
            }
        }
    }
    return params;
}
private Object getDefaultValue(SObjectField field) {
    // Defaulting business logic goes here
}
public PageReference url() {
    PageReference p = new PageReference('/' + Abc__c.SObjectType.getDescribe().getKeyPrefix() + '/e');
    Map<String, String> m = p.getParameters();
    m.putAll(createDefaultValues());
    m.put('nooverride', '1');
    m.put('retURL', ApexPages.currentPage().getParameters().get('retURL'));
    return p;
}

Then this Visualforce page is set to override the “New” “Standard Button or Link” for the “Abc” object. The page executes the above logic (but does not output any HTML) and then that logic returns the URL of the default “New” page but complete with default values for any fields that getDefaultValue returned a non-null value for:

<apex:page standardController="Abc__c" extensions="AbcNewController" action="{! url }"/>

Some words of caution:

  • It is not possible to unit test the part of this that obtains the magic id values because the testing framework does not support the getContent call. Instead the testing framework logs this message “Methods defined as TestMethod do not support getContent call, test skipped”.
  • This logic will break if salesforce change the label HTML that is emitted to something that the regular expression does not match.
  • Long (say greater than 2k) GET URLs may not work in all browsers and through all proxy servers. So consider the URL length when using large numbers of fields or fields with large values in them.

PS I hit one more gotcha related to case sensitivity in the ids; see the code changes in Outputting a URL that has keys that only differ by case that address this problem.

PPS Recently used this in a page where the logic failed. The problem is in the regular expression. While the term “(.*?)” is nominally non-greedy it was matching much more than just the field id. Changing this term to “(\\w+?)” fixes the problem because this only matches the characters A-Z, a-z, 0-9 (and underscore) which is what the field id is composed of so that the match stops immediately at the quotes surrounding the id.

Advertisements

14 thoughts on “Hack to find field ids – allows a default UI “New” page to be pre-populated

  1. WTH (What the hack), this is awesome !! I wish this post came across me few months back, we created custom settings to keep track of all these bad LKIDs 😦 Great post and solution !!

    Salesforce should ideally provide the field ID as part of describe information, do we have an idea posted for the same ?

  2. Terrific. I’m trying to apply it to prefilling a Description on a Task, from the prior Task, with a “Clone Me” button.

    It didn’t like:

    PageReference p = new PageReference(‘/’ + Task.SObjectType.getDescribe().getKeyPrefix() + ‘/e’);

    (Save error: INvalid field sobjecttype for SObject Task is the Error Message).

    So I tried:

    PageReference p = new PageReference(‘/’ + Activity.SObjectType.getDescribe().getKeyPrefix() + ‘/e’);

    (Save error: Method does not exist or incorrect signature: Activity.SObjectType.getDescribe() is the Error Message.).

    Hoping I’m close, but knowing it may not be possible. Any clues?

    Jim DeLong

  3. Well entering “/00T/e” manually on the command-line displays a new task page and that is what I would expect your first code example to produce. The error message suggests you have a variable with the same name as the type in scope; the language gives such variables priority (and ignores upper/lower case differences) over type names.

  4. Could someone give an example of their “Defaulting Business Logic”? I’m having a hard time finding any documentation that explains how to set the default values.

    • Here is a piece of a controller class called from an “Add Document” button in a custom “Claim” Visualforce page. This results in the Document editing UI opening with the Claim and Type fields pre-populated so that when the Document is saved it is correctly attached to the Claim. (Both Claim and Document are custom objects in this example.)

      // claim, typeValue and currentPageUrl are fields of the controller
      public PageReference addDocument() {
          
          PageReference pr = new PageReference('/' + Document__c.getDescribe().getKeyPrefix() + '/e');
          Map<String, String> params = pr.getParameters();
          
          String[] fieldIds = LkidUtil.findFieldIds(Document__c.SObjectType, new SObjectField[] {
                  Document__c.Claim__c,
                  Document__c.Type__c
                  });
          
          params.put(fieldIds[0], (String) claim.Name);
          params.put(fieldIds[0] + '_lkid', claim.Id);
          params.put(fieldIds[1], typeValue);
          
          params.put('saveURL',currentPageUrl);
          params.put('retURL', currentPageUrl);
          
          return pr;
      }
      

      Here is part of the resulting URL:

      /a10/e?00N50000002m122=Legal&CF00N500000029BvQ=C-2012-055720&CF00N500000029BvQ_lkid=a0850000005rpqyAAA
      
  5. Hi Wes!

    Thanks for this post. I re-used a bit of your code and whipped up a script that addresses this entire hard-coded ID issue a lot of people are having when creating their custom ‘New’ button. Hopefully you will find this useful and thanks for the code sample!

    http://threeheadsonapike.wordpress.com/2013/02/11/salesforce-url-hacking-prepopulating-fields/

    Example VF buttons that can be created using this:

    1. Pre-populate a text field having API label: ‘Text__c’ on a custom object called: ‘Custom__c’:

    /apex/RedirectWithVariables?object=Custom__c&Text__c=Text Value

    2. Pre-populate a picklist having API label: ‘Picklist__c’ on a custom object called ‘Custom__c’:

    /apex/RedirectWithVariables?object=Custom__c&Picklist__c=Picklist Value

    3. Create a new record of a given record type (use the label of the record type instead of the name):

    /apex/RedirectWithVariables?object=Custom__c&RecordType=Custom Record Type

    4. Pre-populate an example lookup field having API label ‘Lookup__c’ on a custom object called: ‘Custom__c. Important to note that a lookup field requires two parameters, the ID of the record that it references and the text value of the field as seen by the user. To set the text value use the API name of the lookup and to set the ID value of the lookup append ‘ID_’ to the API name of the lookup as seen in the following example:

    /apex/RedirectWithVariables?object=Custom__c&Lookup__c=Text Value&ID_Lookup__v=ID_Value

  6. Hi,This is a good approach but I am stuck up when i want to get the id of ‘Created By ID’ fields because this is not exist at the time of editing.Is there any way to get the id of system generated fields.

    • Puja, From a quick look in the source of a page, fields like “Created By” have a fixed ID of e.g. “CreatedBy_ileinner” so it isn’t necessary to parse the edit page to find these, you can hard code them (remembering all of this is fragile and will break if Salesforce change their implementation).

  7. I have managed to obtain the field Ids via the Tooling API, which I have also worked up a small library to call it via Apex. Should help easy the risk to implementing this long standing UI customisation requirement at least. http://andyinthecloud.com/2014/01/05/querying-custom-object-and-field-ids-via-tooling-api/

    // Constructs the Tooling API wrapper (default constructor uses user session Id)
    ToolingAPI toolingAPI = new ToolingAPI();

    // Query CustomObject object by DeveloperName (note no __c suffix required)
    List customObjects =
    toolingAPI.queryCustomObject(‘DeveloperName = \’Test\”).records;

    // Query CustomField object by TableEnumOrId (use CustomObject Id not name for Custom Objects)
    ToolingAPI.CustomObject customObject = customObjects[0];
    Id customObjectId = customObject.Id;
    List customFields =
    toolingAPI.queryCustomField(‘TableEnumOrId = \” + customObjectId + ‘\”).records;

    // Dump field names (reapply the __c suffix) and their Id’s
    System.debug(customObject.DeveloperName + ‘__c : ‘ + customObject.Id);
    for(ToolingAPI.CustomField customField : customFields)
    System.debug(
    customObject.DeveloperName + ‘__c.’ +
    customField.DeveloperName + ‘__c : ‘ +
    customField.Id);

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s