Skip navigation

Monthly Archives: January 2012

There’s nothing like a fresh Salesforce release to reassure SF consultants who, gainfully distracted by quotidian customer requests, have had their impetus to innovate temporarily quenched. Translation — I love working on a platform that innovates faster than I can.

Before Richard Vanhook jumps out of his skin (check out his excellent idea posted over a year ago), let me assure you that Salesforce has NOT added a much needed Apex API for Custom Labels / Translations. However, with Spring 12, it HAS finally enabled us to use Visualforce to dynamically render and use Custom Labels — and associated Translations — when the Label name is NOT known beforehand.

How does this make Custom Labels / Translation Workbench more useful? There are several thoughts that come to mind, but I’ll just describe one use case:

Rebranding the UI — At Skoodat, our trademark is design-oriented products, so we’ve done some heavy-duty overhauling of the standard SFDC UI, and have built a custom markup language to dynamically drive the content of our pages. Having the ability to dynamically insert text that automatically gets translated into other languages is a huge win, and the triumvirate of Salesforce’s Translation Workbench, built-in multi-language architecture, and Custom Labels, really makes the implementation of this fairly painless.

HOWEVER, for an ISV Partner interested in rendering pages dynamically based on custom metadata, this whole architecture is inaccessible, because there’s no way to access the value of a Label or Translation through Apex by name, i.e. there is no equivalent of loosely-typed System.Label.get('MyLabelName') … only the strongly-typed System.Label.MyLabelName .

So, how is this magic achieved? Well, with Spring 12, there will actually be 2 ways to achieve it:

  1. Dynamic Visualforce Bindings (which have now been extended to Global Variables)
  2. Dynamic Visualforce Components (which are finally Generally Available [i.e. not Pilot, not Beta] — and thus Packageable!!!

For this example, I first enabled Translation Workbench, and added one language: Spanish. I then created 2 Custom Labels: ‘Release’ and ‘WelcomeMessage’

Custom Labels

and then created an Apex controller called CustomLabelsController:


public class CustomLabelsController {

    public Component.Apex.OutputText output { public get; private set; }
    public String labelName { public get; private set; }

    public CustomLabelsController() {

        // Get the name of the Custom Label to display
        // from a Query String parameter
        Map params = ApexPages.currentPage().getParameters();
        labelName = params.get('label');

        // Instantiate a new Dynamic Visualforce Component,
        // and set its value attribute to a dynamic expression
        output = new Component.Apex.OutputText();
        output.expressions.value = '{!$Label.' + labelName + '}';
    }
}

This controller reads in a query string parameter called ‘label’ and assigns it to a page property (which we will retrieve from Visualforce using a Dynamic Binding), and then instantiates a new Dynamic OutputText component, setting the value property to the VF expression needed to retrieve a Custom Label.

I then created the following Page (called ‘CustomLabels’) which uses this controller:


<apex:page controller="CustomLabelsController">

    <apex:pageBlock title="Using Dynamic VF BINDINGS">
        <apex:outputText value="{!$Label[labelName]}"/>
    </apex:pageBlock>

    <apex:pageBlock title="Using Dynamic VF COMPONENTS">
        <apex:dynamicComponent componentValue="{!output}"/>
    </apex:pageBlock>

</apex:page>

In this page, I first use Dynamic VF Bindings to access the $Label Global Variable, which, as of Spring 12, can now be used with Dynamic Bindings to retrieve an arbitrary label name. I then display the Dynamic Component.

When I navigate to ‘/apex/CustomLabels?label=WelcomeMessage’, I am greeted in our language:

Where this becomes incredibly useful is when we start translating our Labels. I translated my labels into Spanish:

Spanish translation of Welcome Message

and then changed my user language to Spanish. Returning to my page, I was greeted by the following:

For  those of you who joined the Dynamic VF Components Pilot, this functionality technically has existed since Summer 11. In my mind, however, this is really a feature that would most appeal to ISV’s, so the inability to use it in Managed Packages sidelined its appeal until Spring 12.

Advertisements

Anonymous Apex can be extremely useful when trying to perform complex, cross-object, bulk data manipulation tasks that might only need to be done once, and never used again. For such situations, writing a piece of code to achieve your functionality, writing test code, and deploying it to production — when you might never use the code again — doesn’t make much sense. Enter Anonymous Apex!

However, using Anonymous Apex can potentially be VERY dangerous. You could royally mess up a lot of data, and never even know until it was too late. Unless…

You take some precautions.

What if you could verify that your long, convoluted Anonymous Apex routine was actually doing what it was supposed to do as it proceeded, similar to the way Apex Unit Tests work with System.asserts?

Well, you can! Anonymous Apex supports all of the System.assert statements, AND it supports Database.rollbacks. When the two are used in tandem, you get a powerful disaster prevention mechanism that makes Anonymous Apex a lot less risky and hackish.

Example 1: Find all unresolved, non-high Priority Cases over a year old related to major Accounts (i.e. those with really big Closed Opportunities, and make them High or Critical priority, and Escalate them to the top Service representative


List bigDeals = 
	[select AccountId 
	from Opportunity
	where Amount > 400000 
	and StageName = 'Closed Won']; 
	
// Sanity check --- make sure we found some deals
System.assert(bigDeals.size() > 0,
	'We did not find any Big Deals'); 

Set accountIds = new Set(); 
for (Opportunity opp : bigDeals) {
	if (!accountIds.contains(opp.AccountId)) {
		accountIds.add(opp.AccountId);
	}
}
// Sanity check
System.assert(accountIds.size() > 0, 
	'We did not find any Account Ids'); 

List unresolvedCases = 
	[SELECT Id, Priority, Status, Reason 
	FROM Case 
	WHERE Status != 'Closed' 
	AND AccountId in :accountIds 
	AND Priority not in ('High','Critical')
	AND CreatedDate = TODAY]; 

// Sanity check 
System.assert(unresolvedCases.size() > 0,
	'We did not find any unresolved Cases tied to our Big Deals'); 

// Find our best Support Rep,
// to whom we will reassign our highest priority cases
User awesomeRep = 
	[select id,LastName 
	from User 
	where Email = 'awesome_support_rep@company.com' 
	limit 1];
	
// Make sure we found the User we think we did
System.assertNotEquals(null, awesomeRep, 'awesomeRep'); 
System.assertEquals(
	'Rep Last Name',
	awesomeRep.LastName,
	'awesomeRep.LastName'
);

Integer numDefects = 0;
Integer numOtherCases = 0; 

for (Case c : unresolvedCases) { // Escalate the case
	c.Status = 'Escalated'; 
	// If the Case Reason is 'Defect', 
	// the Priority should be 'Critical' 
	if (c.Reason == 'Defect') { 
		numDefects++; 
		c.Priority = 'Critical';
	}
	// Otherwise, Priority should be 'High' 
	else { 
		numOtherCases++; 
		c.Priority = 'High'; 
	} 
	// Set the Owner to 
	if (c.OwnerId != awesomeRep.Id) c.OwnerId = awesomeRep.Id; 
}
// Sanity check
System.assert(numDefects > 0 || numOtherCases > 0, 
	'we only want to proceed '
	+ 'if we updated at least some of our cases'); 

// Sanity check --- force code to stop here
// to let us examine how things went 
// before we actually do the update
System.assert(false,'Sanity Check!'); 

Savepoint sp = Database.setSavepoint();
try { 
	update unresolvedCases; 
} catch (Exception ex) { 
	// If we catch any errors, 
	// rollback the database to how it was 
	// prior to the DML call
	Database.rollback(sp);
	// Then display the exception
	System.assert(false,ex.getMessage()); 
}

I often find that code that starts out as Anonymous Apex routines eventually makes its way into actual classes / controllers / triggers —- and Unit Tests.

If you haven’t used Anonymous Apex before, you can use it from all sorts of locations — from the Fore.com System Log (old or new — the new version has excellent code/syntax highlighting), from the Force.com IDE, or from SoqlXplorer (on the Mac)).