Template for Global Configuration Variables in Apex

Summary: This article discusses a pattern or template for enabling and customizing features in an application developed upon the Force.com platform.

Force.com and “configurability”: new expecations for developers

A great thing about developing on the Force.com platform is how configurable the product is out of the box. Need a new field on a screen? Easy, find the right page layout and modify it. Want a field to be required? Go find the field and make it required. It’s crazy easy. So easy in fact, that more and more folks can contribute to the solution of a system. Even folks without an IT background that 5-10 years ago would never make these types of configuration changes are doing it. Can you imagine Joe the BA adding an Oracle constraint to make a field required? Of course not.

The downside to this amazing “configurability” of the Force.com platform however, is it creates a disconnect between developers who are not used to including this level of configurability, and salesforce users who have grown to love this type of configuration and now expect it in anything associated with salesforce. Below I discuss a “global configuration variable” pattern that makes adding configurability to your solution simple, clean and reliable.

What’s a global configuration variable?

A global variable (as opposed to a global configuration variable) is a variable which is accessible from any scope. It’s widely accepted that using global variables is a bad practice because those variables can easily be modified from anywhere within your codebase and as a result, using them becomes chaotic and difficult to debug. While similar in that its accessible from any scope, a global configuration variable should also be:

  1. read only (because it would be a bad practice if it weren’t)
  2. type cast (client code shouldn’t be burdened with type-casting a string to an integer)
  3. cached (each variable shouldn’t be loaded each time it’s accessed)
  4. sanity checked upon load (for example, a timeout shouldn’t be negative so default to 0)
  5. easily changeable in production (Joe the BA should be able to do it)

Usage

Best explained in a diagram:

What components are included?

  • GlobalConfigurationVariable__c.object: Defines the data store for the global configuration variables (I think I just sounded like George W Bush saying “the google”). If you’re unfamiliar with custom settings, essentially they are specialized custom objects whose records are cached in the Force.com application tier and accessing those records doesn’t burn any governor limits.
  • GlobalConfigurationVariables.cls: The main class which you’ll need to modify with your configuration variables. The example template is show below.
  • GlobalConfigurationVariablesTest.cls: Test class which you’ll need to modify as well.
  • GlobalConfigurationVariableTestUtils.cls: No need to modify. Unit test helper that makes achieving 100% code coverage much easier.
  • GlobalConfigurationVariableTestUtilsTest.cls: No need to modify. Unit test helper’s test class (yeah, I know the name is confusing but you shouldn’t need to touch this class or the unit test helper class).

GlobalConfigurationVariables.cls

/**
 * Please consider this class a template.  You will need to customize
 * this class according to the global variables you might require in
 * your application.  Also, you might consider creating multiple
 * classes similar to this broken down by global configuration variables that make
 * sense to be packaged together.
 *
 * First (1), define your global configuration variables as member variables of this class.
 * For example, if you're defining a twitter username as a global variable, then
 * the variable might be:
 *
 * public String twitterUsername {get; private set;}
 *
 * Next (2), define the key for your global variable.  This is the value which
 * must be entered as the name of the "Global Variable" custom settings record
 * defined at:
 *
 * Setup | App Setup | Develop | Custom Settings | Global Variable | Manage
 * (NOTE: if you haven't deployed that Custom Settings object yet, then obviously
 * the above won't be available.)
 *
 * The key can be the same value as member variable but that isn't required.
 * Using same example as above, the variable key might be:
 *
 * public static final String KEY_TWITTER_USERNAME = 'twitter.username';
 *
 * Third (3), define how the variable will be loaded.  Included are two
 * methods which read Strings and Integers.  If you have a different data type,
 * definie a different "retrieve" method.
 *
 * And last (4), consider putting in some sanity checking.  For example, if
 * you have a timeout variable defined as integer, perhaps it's a good idea
 * to make sure it's > 0 but less than 60000 milliseconds.
 *
 * Once your configuration variables are defined, you should access the configuration variables in your
 * code via the following:
 *
 * GlobalConfigurationVariables.getInstance().twitterUsername
 *
 * And I suggest you always call getInstance and you never store a reference of
 * the GlobalConfigurationVariables object.  That way, you can be assured that all transactions
 * will get the latest copy of the configuration variables AND only do it once.
 *
 * @author Richard Vanhook
 */
global class GlobalConfigurationVariables {

    //==================================================
    // (1) Define your configuration variables
    //==================================================
    global String exampleString {get; private set;}
    global Integer exampleInteger {get; private set;}

    //==================================================
    // (2) Define variable keys
    //==================================================
    global static final String KEY_STRING_EXAMPLE = 'ExampleString';
    global static final String KEY_INTEGER_EXAMPLE = 'ExampleInteger';

    private GlobalConfigurationVariables(){
        final Map<String,GlobalConfigurationVariable__c> all = GlobalConfigurationVariable__c.getAll();
        //==================================================
        // (3) Load configuration variables
        //==================================================
        exampleString = retrieveString(KEY_STRING_EXAMPLE, all);
        exampleInteger = retrieveInteger(KEY_INTEGER_EXAMPLE, all);

        //==================================================
        // (4) Do some sanity checking
        //==================================================
        if(exampleInteger == null || exampleInteger < 0 || exampleInteger > 60000){
            exampleInteger = 1000;
        }
    }

    //==================================================
    // HELPER METHODS
    //==================================================
    global static GlobalConfigurationVariables instance;

    global static GlobalConfigurationVariables getInstance(){
        if(instance == null){
            instance = new GlobalConfigurationVariables();
        }
        return instance;
    }

    private static Integer retrieveInteger(String key, Map<String,GlobalConfigurationVariable__c> all){
        Integer returnValue = null;
        if(all != null && !isBlank(key) && all.get(key) != null){
            try{
                if(all.get(key).value__c != null){
                    returnValue = Integer.valueOf(all.get(key).value__c);
                }
            }catch(System.TypeException e){}
        }
        return returnValue;
    }

    private static String retrieveString(String key, Map<String,GlobalConfigurationVariable__c> all){
        String returnValue = null;
        if(all != null && !isBlank(key) && all.get(key) != null){
            returnValue = all.get(key).value__c;
        }
        return returnValue;
    }

    private static boolean isBlank(String str) {
        return str == null || str.trim() == null || str.trim().length() == 0;
    }

}

Component install instructions:

  1. Unzip GlobalConfigurationVariables.zip to a local folder
  2. Open Eclipse and set up a Force.com project (steps for doing this are outside the scope of this article)
  3. Include object metadata components in your project (via right-click project -> Force.com -> Add/Remove Metadata Components)
  4. Drag and drop file GlobalConfigurationVariable__c.object into your objects folder
  5. Drag and drop class files into your classes folder

VisualForce HTML5 Template

Below is the simplest VisualForce page I’ve been able to construct that renders valid html5 markup (per http://html5.validator.nu). Controlling the exact output rendered by a VF page is tricky business; especially so for generating doctypes. The trick here is getting the page attributes right, using apex:outputText unescaped, and returning the doctype via a binding. I don’t think it matters from a validation perspective but a space unfortunately precedes the doctype declaration. If anyone know of an even simpler way to do this, please share!

<apex:page standardStylesheets="false"
    sidebar="false"
    showHeader="false"
    contentType="text/html" cache="true"><apex:outputText escape="false" value="{!"<!DOCTYPE html>"}"/>
<html>
    <head>
        <title>test</title>
    </head>
    <body>test</body>
</html>
</apex:page>

And here’s the report saying its valid:

VisualForce Internationalization Lessons Learned

As it relates to internationalization with VisualForce, I sometimes feel like a spelunker charting the depths of some mysterious cave.  The equivalent of the flashlight going out is realizing you’ve rolled something to production you thought was correctly internationalized but in fact, was not.  That’s happened to me one to many times in the last year or so and as a result, I’m going to do my best to pass along some of the lessons I’ve learned the hard way.

Beyond the basics of translating your static VisualForce content, I’ve observed two main hot-spots:

  1. Correctly setting the apex:page language attribute
  2. Referencing static content in Apex Controllers (as opposed to the VisualForce page)

For #1, I’ve developed a set of utility methods to make this much easier and placed them in a class called LanguageUtils.cls in apex-lang.  Want your VF page to be translated according to user’s browser settings?  No problem, simply use the getLangCodeByBrowser() method.  Or perhaps you want to allow an http parameter to be passed but in case that’s null, use the currently running user’s language?  Use method getLangCodeByHttpParamOrIfNullThenUser().  Trust me, there are hidden gems in these methods like defaulting zh to zh_CN.

For #2, this has been especially painful.  You might be tempted to say “dude, just don’t reference static content in Apex Controllers”; however, many times it’s not that simple.  You might have a design which is more efficient by referencing static content in an Apex Controller.  Or, like I’ve had, a product feature requirement which absolutely necessitated the need for it.  Regardless, developers need to be able to reference static content in Apex Controllers.  A good example where you can, but absolutely shouldn’t, is Custom Labels:  references to Custom Labels in an Apex Controller can ONLY be translated according to the language of currently running user.  No matter what value you throw in the apex:page language attribute, a label will only be translated according to the language of the currently running user.  I’ve submitted this as a defect to salesforce and I certainly hope they fix this soon.  The obvious workaround is to not reference them in an Apex Controller.  Unfortunately in this case, that’s the only option.

Another type of static content which is problematic is picklist values.  Picklist values can be retrieved in an Apex Controller via a describe.  The tricky part is, in order to control the translation via the apex:page language attribute, the describe cannot be done in the constructor for the page’s controller.  Instead, the describe must be done in an “onload” action method.  Here’s a demo of this issue via the following two links.  Both pages will be rendered in Simplified Chinese.

Link 1, picklist describe performed in constructor;  picklist will not be translated

Link 2, picklist describe performed in page onload action method;  picklist will be translated

VisualForce Pagination with apex-lang

Pagination in VisualForce is a frequently occurring requirement for force.com developers. While the platform includes a native api for pagination in the StandardSetController class, it’s fairly inflexible; especially for complex pagination. And just as important, the native api supports pagination with sObjects only, so if you want to show pages of “plain old apex objects”, you’re out of luck. (And in case you didn’t know, sObjects are NOT Objects in apex. This is very different from Java in that all classes inherit from Object in Java.)

UPDATE:  Starting in Spring ‘10, an sObject can be referred to as an Object.

In this blog entry, I’ll introduce you to pagination in apex-lang via a concept called “paginators”. I’ll describe the design and then go through several examples of varying levels of difficulty which demonstrate how to effectively utilize a paginator.

There are two types of paginators in apex-lang, one for sObjects (SObjectPaginator) and one for “plain old apex objects (ObjectPaginator). The classes are essentially mirrors of each other; however, the redundancy is necessary (unfortunately) due to the fact that sObjects are not Objects in apex.

Once a paginator is constructed, your code will need to call the all-important setRecords() method in order to populate the paginator with data. Now comes the key part. The setRecords() method will invoke a handleNewPage() method on any “listeners” of the paginator. This means you’ll need to implement a handleNewPage() method in your code (and implement corresponding interface ObjectPaginatorListener or SObjectPaginatorListener) in order to receive page changes. The beauty of this design is the handleNewPage() method will also be called for user triggered events. These include the user clicking on standard pagination actions such as next, previous, last, etc. Paginators have these methods built-in and those methods can be bound directly on your page. Below is a sequence diagram which illustrates the interaction between the VisualForce page, controller, and paginator.

PaginatorInteraction

Note: It might seem you should be able to bind directly to the paginator’s page variable and implementing the handlePageChange method should be correspondingly optional as well. However, there’s another unfortunate Apex/VisualForce feature which prevents a VisualForce page from binding directly to the page variable on a paginator. To illustrate the issue, the following page & controller fails to execute.

1.  public class MyController{
2.      public List records {get;set;}
3.      public MyController(){
4.          records = [select id,name,industry from Account limit 100];
5.      }
6.  }
7.
8.  <apex:page>
9.      <apex:repeat value="{!records}" var="record">
10.         <apex:outputText value="{!record.industry}"><br/>
11.     </apex:repeat>
12. </apex:page>

The failure occurs at run-time on line 10 since record is of type sObject. sObjects do not contain a property called industry so the page fails. This means that to bind directly to named properties, your record must be of a concrete type. This necessitates the need for having code which uses a paginator to implement the handlePageChange() method. In this method, your code will need to perform a down-cast; the casting stinks but there’s really no other generic way of handling this.

On to the code examples!

First, let’s start with just about the simplest example possible. The below controller runs a SOQL query on Account and divides the results into pages of 15 records each via SObjectPaginator. The handlePageChange() method is called by the paginator as part of the setRecords() invocation as well as when the user clicks on the Next and Previous links.

Demo: http://rvdemo-developer-edition.na6.force.com/AccountPaginationDemo

<!-- ======================================================= -->
<!-- Controller -->
global class AccountPaginationDemo implements SObjectPaginatorListener {
	global List<Account> accounts {get;private set;}
	global SObjectPaginator paginator {get;private set;}
	global AccountPaginationDemo(){
		accounts = new List<Account>();
		//15 is pageSize, this refers to this class which acts as listener to paginator
		paginator = new SObjectPaginator(5,this);
		paginator.setRecords([select id,name from Account limit 100]);
	}
	global void handlePageChange(List<SObject> newPage){
		accounts.clear();
		if(newPage != null){
			for(SObject acct : newPage){
				accounts.add((Account)acct);
			}
		}
	}
}
<!-- Page: page -->
<apex:page showHeader="false" sidebar="false" standardStylesheets="true" controller="AccountPaginationDemo">
	<apex:composition template="DemoTemplate">
		<apex:define name="body">
			<apex:form >
			    <apex:pageBlock title="Accounts" id="accounts">
			        <apex:pageBlockTable value="{!accounts}" var="account">
						<apex:column >
							<apex:facet name="header">Name</apex:facet>
							<apex:outputPanel >{!account.name}</apex:outputPanel>
						</apex:column>
						<apex:facet name="footer">
							<apex:outputPanel >
								<apex:outputText value="Page {!paginator.pageNumberDisplayFriendly} of {!paginator.pageCount} in {!paginator.recordCount} Results"/>
								<apex:outputPanel >    </apex:outputPanel>
								<apex:commandLink value="Previous" action="{!paginator.previous}"
									rendered="{!IF(paginator.hasPrevious,'true','false')}"/>
								<apex:outputText value="Previous" rendered="{!IF(NOT(paginator.hasPrevious),'true','false')}"/>
								<apex:outputPanel > | </apex:outputPanel>
								<apex:commandLink value="Next" action="{!paginator.next}"
									rendered="{!IF(paginator.hasNext,'true','false')}"/>
								<apex:outputText value="Next" rendered="{!IF(NOT(paginator.hasNext),'true','false')}"/>
							</apex:outputPanel>
						</apex:facet>
			        </apex:pageBlockTable>
			    </apex:pageBlock>
			</apex:form>
		</apex:define>
	</apex:composition>
</apex:page>

The next example shows how a “run-time” property can be added to an sObject and the same basic paginator interface can be utilized via the Object paginator version as opposed to the sObject version. In the below controller, a “plain old apex object” called SelectableAccount is used to store both an Account and a Boolean flag representing whether or not the Account is selected (i.e. the “run-time” property). Instead of creating an SObjectPaginator, this example converts the retrieved Accounts into SelectableAccounts and creates an ObjectPaginator instead.

Demo: http://rvdemo-developer-edition.na6.force.com/SelectableAccountPaginationDemo

<!-- ======================================================= -->
<!-- Controller -->
global class SelectableAccountPaginationDemo implements ObjectPaginatorListener {
	global List<SelectableAccount> accounts {get;private set;}
	global ObjectPaginator paginator {get;private set;}
	global SelectableAccountPaginationDemo(){
		this.accounts = new List<SelectableAccount>();
		List<SelectableAccount> all = new List<SelectableAccount>();
		List<Account> records = [select id,name from Account limit 100];
		if(records != null){
			for(Account acct : records){
				all.add(new SelectableAccount(acct));
			}
		}
		//15 is pageSize, this refers to this class which acts as listener to paginator
		paginator = new ObjectPaginator(5,this);
		paginator.setRecords(all);
	}
	global void handlePageChange(List<Object> newPage){
		accounts.clear();
		if(newPage != null){
			for(Object acct : newPage){
				accounts.add((SelectableAccount)acct);
			}
		}
	}
	global class SelectableAccount{
		global Boolean selected{get;set;}
		global Account obj{get;set;}
		global SelectableAccount(Account obj){
			this.obj = obj;
		}
	}
}
<!-- Page: page -->
<apex:page showHeader="false" sidebar="false" standardStylesheets="true" controller="SelectableAccountPaginationDemo">
	<apex:composition template="DemoTemplate">
		<apex:define name="body">
			<apex:form >
			    <apex:pageBlock title="Accounts" id="accounts">
			        <apex:pageBlockTable value="{!accounts}" var="account">
						<apex:column >
							<apex:inputCheckbox value="{!account.selected}"/>
						</apex:column>
						<apex:column >
							<apex:facet name="header">Name</apex:facet>
							<apex:outputPanel >{!account.obj.name}</apex:outputPanel>
						</apex:column>
						<apex:facet name="footer">
							<apex:outputPanel >
								<apex:outputText value="Page {!paginator.pageNumberDisplayFriendly} of {!paginator.pageCount} in {!paginator.recordCount} Results"/>
								<apex:outputPanel >    </apex:outputPanel>
								<apex:commandLink value="Previous" action="{!paginator.previous}"
									rendered="{!IF(paginator.hasPrevious,'true','false')}"/>
								<apex:outputText value="Previous" rendered="{!IF(NOT(paginator.hasPrevious),'true','false')}"/>
								<apex:outputPanel > | </apex:outputPanel>
								<apex:commandLink value="Next" action="{!paginator.next}"
									rendered="{!IF(paginator.hasNext,'true','false')}"/>
								<apex:outputText value="Next" rendered="{!IF(NOT(paginator.hasNext),'true','false')}"/>
							</apex:outputPanel>
						</apex:facet>
			        </apex:pageBlockTable>
			    </apex:pageBlock>
			</apex:form>
		</apex:define>
	</apex:composition>
</apex:page>

The next and final example is quite a bit more complicated. The example allows browsing of all accounts and contacts. There are two result sets: an Account result set and a Contact result set. The currently selected Account and Contact is in bold and the corresponding record’s details are shown below. In essence, this is a “quick” view screen.

Demo: http://rvdemo-developer-edition.na6.force.com/PaginatorDemo

<!-- ======================================================= -->
global class PaginatorDemo {
	public List<AccountWrapper> accounts {get;set;}
	public SObjectPaginator acctPaginator{get;set;}
	public Account selectedAccount{get;set;}
	public List<ContactWrapper> contacts {get;set;}
	public SObjectPaginator contPaginator{get;set;}
	public Contact selectedContact{get;set;}

	public PaginatorDemo(){
		this.accounts = new List<AccountWrapper>();
		this.contacts = new List<ContactWrapper>();
		this.acctPaginator = new SObjectPaginator(5,new AccountListListener(this));
		this.contPaginator = new SObjectPaginator(5,new ContactListListener(this));
		this.acctPaginator.setRecords([select id,name from account]);
	}

	global void handleAccountListPageChange(List<SObject> newPage){
		this.accounts.clear();
		if(newPage != null && newPage.size() > 0){
			for(Integer i = 0; i < newpage.size(); i++){
				AccountWrapper acct = new AccountWrapper((Account)newPage.get(i));
				accounts.add(acct);
				acct.serialNumber = i + acctPaginator.pageStartPosition;
			}
			PageUtils.param('accountId',accounts.get(0).obj.id);
			handleAccountSelected();
		}
	}

	public PageReference handleAccountSelected(){
		String accountId = PageUtils.param('accountId');
		for(AccountWrapper acct : accounts){
			acct.selected = acct.obj.id == accountId;
			if(acct.selected){
				selectedAccount = acct.obj;
				this.contPaginator.setRecords([select id,name from contact where accountid = :acct.obj.id]);
			}
		}
		return null;
	}

	global void handleContactListPageChange(List<SObject> newPage){
		System.debug('new contact list page: ' + ArrayUtils.toString(newPage));
		this.contacts.clear();
		if(newPage != null && newPage.size() > 0){
			for(Integer i = 0; i < newpage.size(); i++){
				ContactWrapper cntct = new ContactWrapper((Contact)newPage.get(i));
				contacts.add(cntct);
				cntct.serialNumber = i + (contPaginator == null ? 0 : contPaginator.pageStartPosition);
			}
			Contact wrapper = (Contact)this.contacts.get(0).obj;
			if(wrapper != null){
				PageUtils.param('contactId',wrapper.id);
				handleContactSelected();
			}
		}
	}

	public PageReference handleContactSelected(){
		String contactId = PageUtils.param('contactId');
		for(ContactWrapper contact : contacts){
			contact.selected = contact.obj.id == contactId;
			if(contact.selected){
				selectedContact = contact.obj;
			}
		}
		return null;
	}

	global class AccountListListener implements SObjectPaginatorListener {
		private PaginatorDemo controller;
		global AccountListListener(PaginatorDemo controller){
			this.controller = controller;
		}
		global void handlePageChange(List<SObject> newPage){
			controller.handleAccountListPageChange(newPage);
		}
	}

	global class ContactListListener implements SObjectPaginatorListener {
		private PaginatorDemo controller;
		global ContactListListener(PaginatorDemo controller){
			this.controller = controller;
		}
		global void handlePageChange(List<SObject> newPage){
			controller.handleContactListPageChange(newPage);
		}
	}

	public class AccountWrapper{
		public Account obj{get;set;}
		public Integer serialNumber{get;set;}
		public Boolean selected{get;set;}
		public AccountWrapper(Account obj){
			this.obj = obj;
		}
	}

	public class ContactWrapper{
		public Contact obj{get;set;}
		public Integer serialNumber{get;set;}
		public Boolean selected{get;set;}
		public ContactWrapper(Contact obj){
			this.obj = obj;
		}
	}
}
<!-- ======================================================= -->
<apex:page showHeader="false" sidebar="false" standardStylesheets="true" controller="PaginatorDemo">
	<apex:form >
		<p>
			<apex:actionStatus startStyle="color: #F00; font-weight: bold;" startText="Updating...." stopText="" id="updateStatus"/>
		</p>
		<table width="100%" border="0" cellpadding="3" cellspacing="0">
			<tr>
				<td valign="top" width="50%">
				    <apex:pageBlock title="Accounts {!acctPaginator.pageStartPositionDisplayFriendly} - {!acctPaginator.pageEndPositionDisplayFriendly} of {!acctPaginator.recordCount}" id="accounts">
				        <apex:pageBlockTable value="{!accounts}" var="account">
							<apex:column >
								<apex:facet name="header">#</apex:facet>
								<apex:outputText value="{!account.serialNumber+1}"/>
							</apex:column>
							<apex:column >
								<apex:facet name="header">Name</apex:facet>
								<apex:outputPanel rendered="{!account.selected}"><b>{!account.obj.name}</b></apex:outputPanel>
								<apex:commandLink rendered="{!NOT(account.selected)}" action="{!handleAccountSelected}" value="{!account.obj.name}"
									 reRender="accounts,contacts,accountDetail,contactDetail" status="updateStatus">
									<apex:param name="accountId" value="{!account.obj.id}"/>
								</apex:commandLink>
							</apex:column>
							<apex:facet name="footer">
								<apex:outputPanel >
									<apex:commandLink action="{!acctPaginator.first}" value="<< First"
										reRender="accounts,contacts,accountDetail,contactDetail" status="updateStatus"/>

									<apex:outputPanel > | </apex:outputPanel>
									<apex:commandLink action="{!acctPaginator.previous}" rendered="{!acctPaginator.hasPrevious}"
										value="< Previous" reRender="accounts,contacts,accountDetail,contactDetail" status="updateStatus"/>
									<apex:outputText rendered="{!NOT(acctPaginator.hasPrevious)}"
										value="< Previous"/>

									<apex:outputPanel > | </apex:outputPanel>

									<apex:commandLink action="{!acctPaginator.next}" rendered="{!acctPaginator.hasNext}"
										value="Next >" reRender="accounts,contacts,accountDetail,contactDetail" status="updateStatus"/>
									<apex:outputText rendered="{!NOT(acctPaginator.hasNext)}"
										value="Next >" />

									<apex:outputPanel > | </apex:outputPanel>

									<apex:commandLink action="{!acctPaginator.last}" value="Last >>" reRender="accounts,contacts,accountDetail,contactDetail"
										status="updateStatus"/>
								</apex:outputPanel>
							</apex:facet>
				        </apex:pageBlockTable>
				    </apex:pageBlock>
					<apex:outputPanel id="accountDetail">
						<apex:detail subject="{!selectedAccount.id}" relatedList="false" title="false"/>
					</apex:outputPanel>
				</td>
				<td valign="top" width="50%">
				    <apex:pageBlock title="Contacts {!contPaginator.pageStartPositionDisplayFriendly} - {!contPaginator.pageEndPositionDisplayFriendly} of {!contPaginator.recordCount}" id="contacts">
				        <apex:pageBlockTable value="{!contacts}" var="contact">
							<apex:column >
								<apex:facet name="header">#</apex:facet>
								<apex:outputText value="{!contact.serialNumber+1}"/>
							</apex:column>
							<apex:column >
								<apex:facet name="header">Name</apex:facet>
								<apex:outputPanel rendered="{!contact.selected}"><b>{!contact.obj.name}</b></apex:outputPanel>
								<apex:commandLink rendered="{!NOT(contact.selected)}" action="{!handleContactSelected}" value="{!contact.obj.name}"
									 reRender="contacts,contactDetail" status="updateStatus">
									<apex:param name="contactId" value="{!contact.obj.id}"/>
								</apex:commandLink>
							</apex:column>
							<apex:facet name="footer">
								<apex:outputPanel id="contactsLinks">
									<apex:commandLink action="{!contPaginator.first}" value="<< First"
										reRender="contacts,contactDetail" status="updateStatus"/>

									<apex:outputPanel > | </apex:outputPanel>
									<apex:commandLink action="{!contPaginator.previous}" rendered="{!contPaginator.hasPrevious}"
										value="< Previous" reRender="contacts,contactDetail" status="updateStatus"/>
									<apex:outputText rendered="{!NOT(contPaginator.hasPrevious)}"
										value="< Previous"/>

									<apex:outputPanel > | </apex:outputPanel>

									<apex:commandLink action="{!contPaginator.next}" rendered="{!contPaginator.hasNext}"
										value="Next >" reRender="contacts,contactDetail" status="updateStatus"/>
									<apex:outputText rendered="{!NOT(contPaginator.hasNext)}"
										value="Next >" />

									<apex:outputPanel > | </apex:outputPanel>

									<apex:commandLink action="{!contPaginator.last}" value="Last >>" reRender="contacts,contactDetail"
										status="updateStatus"/>
								</apex:outputPanel>
							</apex:facet>
				        </apex:pageBlockTable>
				    </apex:pageBlock>
					<apex:outputPanel id="contactDetail">
						<apex:detail subject="{!selectedContact.id}" relatedList="false" title="false"/>
					</apex:outputPanel>
				</td>
			</tr>
		</table>

	</apex:form>
</apex:page>

Dependent picklists in VisualForce

I’ve created the following javascript/prototype “DependentPicklistHelper” class to make it simpler to set up dependent picklists in VisualForce.  Here it is:

/**
 * Helper class which manages dependent picklists in visual force pages.
 * Dependent upon the prototype js library as well as the Salesforce AJAX toolkit.
 *
 * USAGE (Example Visualforce Page):
 * =================================================================================
 * <apex:page id="page" standardController="AnObject__c" >
 *
 *    <apex:includeScript value="{!$Resource.prototype}"/>
 *  <apex:includeScript value="/soap/ajax/15.0/connection.js"/>
 *    <apex:includeScript value="{!$Resource.DependentPicklistHelper_js}"/>
 *
 *    <apex:form id="form">
 *        <apex:inputField onclick="helper.handleControllerChange()" id="controllingField"
 *            value="{!AnObject__c.ControllingField__c}"/>
 *         <apex:selectList id="dependentField" value="{!AnObject__c.Dependent_Field__c}">
 *             <apex:selectOption itemValue="None" itemLabel="--None--"/>
 *         </apex:selectList><p/>
 *    </apex:form>
 *
 *    <script type="text/javascript">
 *        var helper;
 *
 *        document.observe('dom:loaded', function(){
 *            helper = new DependentPicklistHelper(
 *                '{!$Api.Session_ID}','AnObject__c',
 *                'ControllingField__c','Dependent_Field__c',
 *                'page:form:controllingField','page:form:dependentField'
 *            );
 *        });
 *    </script>
 *
 * </apex:page>
 * =================================================================================
 */
var DependentPicklistHelper = Class.create({
    /**
     * Constructor
     *
     * @param sSessionId the salesforce session id (i.e. {!$Api.Session_ID})
     * @param sObjectName the salesforce object name (i.e. Opportunity or Account)
     * @param sControllerField the field name on the salesforce object which represents
     * the controlling field
     * @param sDependentField the field name on the salesforce object which represents
     * the dependent picklist
     * @param controllerPicklistDomID the HTML DOM element ID of the contolling picklist
     * @param dependentPicklistDomID the HTML DOM element ID of the dependent picklist
     */
    initialize: function(sSessionId,sObjectName,sControllerField,
        sDependentField,controllerPicklistDomID,dependentPicklistDomID){
        this.controllerPicklistDomID = controllerPicklistDomID;
        this.dependentPicklistDomID = dependentPicklistDomID;
        this.indexToEnabledFields = new Hash();

        //get metadata via ajax toolkit
        sforce.connection.sessionId = sSessionId;
        var objectDesc = sforce.connection.describeSObject(sObjectName);

        //find the controller and dependent field metadata
        var controllerFieldMetadata = objectDesc.fields.find(function(field){
                return field.name == sControllerField;});
        var dependentFieldMetadata = objectDesc.fields.find(function(field){
                return field.name == sDependentField;});

        //build the controller / dependent field valid values matrix in var indexToEnabledFields
        //iterate through each dependent picklist value
        dependentFieldMetadata.picklistValues.each(function(value){
            //convert the validFor base64 encoded field to list of enabled indices
            var indices = getEnabledBitsIn(decodeBase64(value.validFor));
            //for each index, push the current dependent picklist value to matrix
            indices.each(function(index){
                if(Object.isUndefined(this.indexToEnabledFields.get(index))){
                    this.indexToEnabledFields.set(index,new Array());
                }
                this.indexToEnabledFields.get(index).push(value.value);
            }.bind(this));
        }.bind(this));

        //substitute index with controlling field value
        for(var index=0, l=controllerFieldMetadata.picklistValues.length; index < l; ++index){
            var enabled = this.indexToEnabledFields.get(index);
            enabled = Object.isUndefined(enabled) ? new Array() : enabled;
            this.indexToEnabledFields.unset(index);
            this.indexToEnabledFields.set(controllerFieldMetadata.picklistValues[index].value,enabled);
        }

        this.handleControllerChange();

    },
    /**
     * Handler method for whenever the controlling picklist changes
     */
    handleControllerChange: function(){
        var controllerFieldValue = $F(this.controllerPicklistDomID);
        var enabled = this.indexToEnabledFields.get(controllerFieldValue);
        enabled = Object.isUndefined(enabled) ? new Array() : enabled;
        var dependentPicklistElmt = $(this.dependentPicklistDomID);
        dependentPicklistElmt.childElements().each(function(elmt){
            elmt.remove();
        });
        var optionsHtml = '';
        optionsHtml += '<option value="None" selected="true">--None--</option>';
        if(enabled.size()<=0){
            dependentPicklistElmt.disable();
        } else {
            dependentPicklistElmt.enable();
            enabled.each(function(value){
                optionsHtml += '<option value="'+value+'">'+value+'</option>';
            });
        }
        dependentPicklistElmt.insert(optionsHtml);
    }
});

/**
 * Helper method to convert a string into a set of enabled bit flags.
 *
 * Examples:
 * getEnabledBitsIn('abc') => [1, 2, 7, 9, 10, 14, 17, 18, 22, 23]
 * getEnabledBitsIn('123') => [2, 3, 7, 10, 11, 14, 18, 19, 22, 23]
 *
 * @param str the string
 */
function getEnabledBitsIn(str){
    str = (Object.isUndefined(str) || str == null) ? '' : str;
    var returnArray = new Array();
    for(var index=0, l=str.length*8; index < l; ++index){
        if((str.charCodeAt(index >> 3) & (0x80 >> index % 8)) != 0){
            returnArray.push(index);
        }
    }
    return returnArray;
}

/**
 * Helper method for decoding base 64 string.
 *
 * Examples
 * decodeBase64('SGVsbG8gd29ybGQ=') => 'Hello world'
 *
 * @param str the string
 */
function decodeBase64(str){
    var indexBase64 = new Array(
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
        52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
        -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
        15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
        -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
        41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
    );

    var out = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    // trim invalid characters in the beginning and in the end of the string
    str = str.replace(/^[^a-zA-Z0-9\+\/\=]+|[^a-zA-Z0-9\+\/\=]+$/g,"")
    var len = str.length;
    do{
        enc1 = indexBase64[str.charCodeAt(i++)];
        enc2 = indexBase64[str.charCodeAt(i++)];
        enc3 = indexBase64[str.charCodeAt(i++)];
        enc4 = indexBase64[str.charCodeAt(i++)];

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        out += String.fromCharCode(chr1);

        if (enc3 != -1){
            out += String.fromCharCode(chr2);
        }
        if (enc4 != -1){
            out += String.fromCharCode(chr3);
        }
    }
    while (i < len);

    if (i != len){
        return "";
    }
    return out;
}

Posted in VisualForce. Tags: . 29 Comments »