Force 201 – Force.com Development

February 18, 2012

Work-around for missing “Lookup Relationship” data type custom fields on Task (Activity)

Filed under: Apex,Visualforce — Force 201 @ 1:06 pm

Unlike other standard objects such as Contact and Account, and custom objects, Task (Activity) custom fields cannot be of data type “Lookup Relationship”. (This type allows a relationship to be created with some other object.) So while you can relate a Task to say a Contact through the WhoId field and some other object through the WhatId field, you are stuck if you want to relate the Task to a third object.

Here is a work-around. It uses three custom fields added to Task (Activity):

  • ThirdPartyId – 18 character text field not shown in layout
  • ThirdPartyName – 255 character text field not shown in layout
  • ThirdParty – text formula field shown in layout

The formula produces a basic hyperlink in the Task page:

IF (ThirdPartyId__c != null && ThirdPartyName__c != null, HYPERLINK('/' + ThirdPartyId__c, ThirdPartyName__c, "_self"), null)

(see Hyperlink formula fields for a bit more background) that looks like this:

But there then needs to be a way to choose the related object and all that can be done to allow this is to add a custom button or link to the page. I used a custom button that opens a Visualforce page that in turn allows the related object to be looked up. In my case I wanted to allow either a Contact or an Account to be selected and so needed to use fields of the right lookup type; I re-used an existing managed package custom object to supply those fields but could have created a specific custom object instead. Note that the object instance is never persisted: it just supplies the right meta data for the <apex:inputField>s and the selected id value and name are set back on the Task.

Here is the page:

produced by this Visualforce:

<apex:page standardController="Task" extensions="ChooseThirdPartyController">
    <apex:sectionHeader title="Choose Third Party"/>
    <apex:form >
        <apex:pageBlock mode="edit">
            <apex:pageMessages />
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{!save}" />
                <apex:commandButton value="Cancel" action="{!cancel}" immediate="true" />
            </apex:pageBlockButtons>
            <apex:pageBlockSection title="Task" columns="1">
                <apex:outputField value="{!Task.Subject}"/>
            </apex:pageBlockSection>
            <apex:pageBlockSection title="Third Party" columns="1">
                <apex:inputField value="{!cr.cve__Contact__c}"/>
                <apex:inputField value="{!cr.cve__Account__c}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

with the logic implemented in this controller extension:

public with sharing class ChooseThirdPartyController {

    private ApexPages.StandardController standardController;

    public ChooseThirdPartyController(ApexPages.StandardController standardController) {
        this.standardController = standardController;
        if (!Test.isRunningTest()) {
            this.standardController.addFields(new String[] {String.valueOf(Task.ThirdPartyId__c)});
        }
    }
    
    private Task t {
        get {
            return (Task) standardController.getRecord();
        }
    }

    // Could use any object here that has the right type of lookup fields on it   
    public cve__ClaimRelationship__c cr {
        get {
            if (cr == null) {
                cr = new cve__ClaimRelationship__c();
                // Default the initial value
                if (t.ThirdPartyId__c != null) {
                    if (t.ThirdPartyId__c.startsWith(Contact.SObjectType.getDescribe().getKeyPrefix())) {
                        cr.cve__Contact__c = t.ThirdPartyId__c;
                    } else if (t.ThirdPartyId__c.startsWith(Account.SObjectType.getDescribe().getKeyPrefix())) {
                        cr.cve__Account__c = t.ThirdPartyId__c;
                    }
                }
            }
            return cr;
        }
        private set;
    }
    
    public PageReference save() {
        if (cr.cve__Contact__c != null && cr.cve__Account__c != null) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Pick a Contact or an Account but not both'));
            return null;
        } else {
            if (cr.cve__Contact__c != null) {
                Contact c = [select Id, Name from Contact where Id = :cr.cve__Contact__c];
                t.ThirdPartyId__c = c.Id;
                t.ThirdPartyName__c = c.Name;
            } else if (cr.cve__Account__c != null) {
                Account a = [select Id, Name from Account where Id = :cr.cve__Account__c];
                t.ThirdPartyId__c = a.Id;
                t.ThirdPartyName__c = a.Name;
            } else {
                t.ThirdPartyId__c = null;
                t.ThirdPartyName__c = null;
            }
            return standardController.save();
        }
    }
}

February 15, 2012

Page headers and footers in renderAs=”PDF” Visualforce pages

Filed under: CSS,Visualforce — Force 201 @ 5:49 pm

This page Creating Professional PDF Documents with CSS and Visualforce describes how @page and @bottom-right CSS can be used to include headers and footers in paginated PDF output. But the default styling for the footer text is a largish serifed font. So this:

@page {
    @bottom-right {
        content: "{!documentName} {!claimantInsuredNumbers}/{!claim.Name} - Page " counter(page) " of " counter(pages);
    }
}

results in this in the output bottom right of each PDF page:

which if CSS is being used to style the page content can leave the footer presented in a mis-matching style.

Googling for @bottom-right results in a hit on this page Prince Page Headers and Footers that includes examples of setting styles. Borrowing from that on the assumption that the technique is generic, this change:

@page {
    @bottom-right {
        content: "{!documentName} {!claimantInsuredNumbers}/{!claim.Name} - Page " counter(page) " of " counter(pages);
        font-family: sans-serif;
        font-size: 80%;
    }
}

results in the desired styling:

February 1, 2012

Add “Version Settings” to the check-list…

Filed under: Apex,Packaging — Force 201 @ 6:24 pm

I have some global code in a managed package that is used by some non-packaged (source) code in a customer’s org. An upgrade of the managed package broke the non-packaged code in a rather confusing way. The runtime error reported was:

System.TypeException: Invalid conversion from runtime type DefaultContactHistorySource to ContactHistorySource

where ContactHistorySource was a recently introduced managed package global interface and DefaultContactHistorySource was a recently introduced non-global implementation of that interface. Neither of these was used directly by the non-packaged code.

The work-around (discovered after many hours of failure) was to simply change the version number of the managed package that the non-packaged code depended on via the “Version Settings” tab. It appears that although ContactHistorySource and DefaultContactHistorySource were not being used directly, some version filtering was nevertheless being applied – perhaps because ContactHistorySource was global. Unfortunately the error that was reported didn’t offer much help in identifying the problem. This is also a rather nasty case of a managed package upgrade breaking existing code.

Other things to watch out for with managed packages are Add “Deployment Status” to the check-list… and Always double check that managed packages are deployed.

January 29, 2012

My laptop is 10 times faster

Filed under: Apex — Force 201 @ 3:46 pm

This Salesforce blog post Apex Runtime Update for Developer Edition Orgs describes a significant change in how Developer Edition orgs execute Apex code, and this 3rd party blog post by Alex Berg Salesforce’s Apex Runtime Design – Old vs New provides more explanation. However I couldn’t find any statement about when this change will be rolled out into production orgs.

Before this change the performance of Apex when executing CPU intensive logic in an application was surprisingly poor – see My laptop is 300 times faster. The good news is that running the same test today in a na3 Developer Edition org multiple times (to allow bytecode caching) resulted in an average time of 208ms. Hence the new conclusion that my laptop is only 10 times faster.

This is a dramatic improvement, making Force.com a more practical platform for executing CPU intensive logic as long as the logic remains within the “total number of executed code statements” governor limit.

January 23, 2012

Outputting a URL that has keys that only differ by case

Filed under: Apex,Visualforce — Force 201 @ 1:19 pm

There is a problem with the code in Hack to find field ids – allows a default UI “New” page to be pre-populated related to case sensitivity. The field ids are case sensitive so it is possible (and if your object has many fields likely) that field ids may only differ by case. Such fields are not correctly defaulted in the “New” page.

While specifications like RFC 3986 suggest that query string keys can be assumed to be case sensitive, the PageReference class in Force.com works hard to ensure that query string keys are not case sensitive. These tests illustrate the problem:

@isTest
static void testMap() {
    Map<String, String> m = new Map<String, String>();
    m.put('00N50000002ldUw', 'abc');
    m.put('00N50000002ldUW', '123');
    // Assertions are as expected
    System.assertEquals('abc', m.get('00N50000002ldUw'));
    System.assertEquals('123', m.get('00N50000002ldUW'));
}
@isTest
static void testParameters() {
    PageReference p = new PageReference('/');
    Map<String, String> m = p.getParameters();
    m.put('00N50000002ldUw', 'abc');
    m.put('00N50000002ldUW', '123');
    // Assertions illustrate that keys that differ by case are not distinguished
    System.assertEquals('123', m.get('00N50000002ldUw'));
    System.assertEquals('123', m.get('00N50000002ldUW'));
}

There does not appear to be a work-around using PageReference; building the entire URL string manually and then passing it into the constructor doesn’t help, nor does replacing the alphabetic characters with their URL encoded form. The other standard practice of replacing a (case sensitive) 15 digit id with the (case insensitive) 18 character version doesn’t work in this case because the “New” page appears to be doing literal string matching of the ids and so is expecting the 15 character version.

A way of avoiding the PageReference class is to build the URL as a string and then use JavaScript to redirect to the URL. These changes to the code in Hack to find field ids – allows a default UI “New” page to be pre-populated fix the case problem and result in all fields being defaulted. The controller returns a URL string:

private String createUrl() {
    String url = '/' + DescribeSObjectResultCache.get(Abc__c.SObjectType).getKeyPrefix()
            + '/e'
            + '?retURL=' + ApexPages.currentPage().getParameters().get('retURL')
            + '&nooverride=1'
            ;
    Map m = createDefaultValues();
    for (String key : m.keySet()) {
        url += '&' + key + '=' + EncodingUtil.urlEncode(m.get(key), 'UTF-8');
    }
    return url;
}
public String url {
    get {
        if (url == null) {
            url = createUrl();
        }
        return url;
    }
    private set;
}

and the page is rendered but immediately redirects to the URL supplied by the controller:

<apex:page standardController="Abc__c" extensions="AbcNewController" showHeader="false" sidebar="false" standardStylesheets="false">
<script type="text/javascript">
window.onload = function() {
    window.location = '{! url }';
}
</script>
</apex:page>

January 22, 2012

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

Filed under: Apex,Visualforce — Force 201 @ 9:13 pm

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.

January 3, 2012

When runAllTests=”false” actually means runAllTests=”true”

Filed under: Ant,Packaging — Force 201 @ 6:15 pm

After several deployments into test environments, we deployed into a customer’s production environment yesterday. One of the deployment steps after installing some managed packages is to push several profiles into the target org using the Ant deploy task with a package.xml that just includes the profiles. It was an unwelcome surprise that all the unit tests in the production org ran. These are tests written by a third party that could have had dangerous side effects; in previous deployments this had not happened and runAllTests=”false” being present in the Ant script suggested it should not.

The explanation is in the Ant deploy runAllTests documentation:

This parameter is ignored when deploying to a Salesforce production organization. Every unit test in your organization namespace is executed.

Whatever the motivation for this behavior, I suggest that returning an error message (containing this text) when runAllTests=”false” is specified for a production org would be a better approach to handling the situation than just ignoring the attribute and running the tests.

December 20, 2011

Available: the ability to create an instance of an Apex class from the class name

Filed under: Apex — Force 201 @ 8:32 am

I have some extremely ugly work-around code based on triggers to approximate the ability to ability to create an instance of an Apex class from the class name. But thank-you David Glick and others for posting these comments that show that there is now the necessary mechanism in the platform, albiet a little hidden inside the recently introduced JSONParser.

November 16, 2011

Winter ’12 Developer Console

Filed under: Apex,Force.com,Packaging — Force 201 @ 10:56 am

I didn’t take much notice of the Winter ’12 Force.com Platform Release Developer Console as there was no mention of that important word “breakpoint”. But if you are in debug hell then the features that are provided to allow debugging without having to add System.debug calls – including the ability to capture and view the heap at points marked in your source code – are worth investigating.

This (hour long) presentation demonstrates how to use the features:

It also suggests that the Eclipse tooling (and 3rd party additions to that tooling) will eventually be able to use the same API. I find the current browser-based console fragile (using Chrome) and a bit awkward to use as it packs a lot of tabs and frames into a small space; hopefully the richer UI facilities in Eclipse will help.

Note that while this tooling can be used as you develop your managed package, when that managed package is installed in a customer’s org all the code-related debug information is unavailable (by default but can be turned back on via a support request) so the ability to debug is also lost.

November 2, 2011

Referencing static resources where the name is not known at compile time

Filed under: Apex,Visualforce — Force 201 @ 7:08 pm

PS Note that the Dynamic References to Global Variables feature in Spring ’12 addresses this problem.

If you have a static resource such as an image that you want to display in a page there is a well documented method of referencing that static resource – see e.g. Delivering Static Resources with Visualforce. If the static resource is named “MyResource”, that resource is referenced using “$Resource.MyResource”. But this relies on the name being known at Visualforce page compilation time and the compilation also verifies that the static resource exists. If you want to provide the static resource name dynamically from a controller property there appears to be no way to do it. (In my case I wanted the name of a customer-specific ZIP file containing a help system to be configurable via a custom setting.)

By inspection (and as described in the above link), “$Resource.MyResource” results in a reference in the HTML of the form “/resource/1319816508000/MyResource”. With a bit of rooting around, that observation leads to this code:

public String myResource {
    get {
        String namespace = null;
        String name = 'MyResource';
        StaticResource sr = [
                select LastModifiedDate
                from StaticResource
                where NamespacePrefix = :namespace
                and Name = :name
                limit 1
                ];
        return '/resource/' + sr.LastModifiedDate.getTime() + '/' + (namespace != null ? namespace + '__' : '') + name;
    }
}

which so far has worked for me.

Next Page »

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 33 other followers