July 20, 2013

Generic Input with Dynamic Visualforce Binding

For part of our AppExchange app ChattoMate, I wanted to present the user with a standard Salesforce lookup UI when choosing an ID value for any given lookup field, and then store that resulting ID in a text field; the tricky part is that not only is the field they'll choose unknown, but so is the object; and so we must embark upon a brief adventure into the realm of dynamic Visualforce binding.

Creating The Input Object

Creating an instance of any given object. As mentioned in the introduction, the user is able to choose which object they want to work with at runtime, so we need to be able to create an object of any given type with which we can then use to gather input. This is actually pretty easy, we can just a Schema.SObjectType object to create a new object, and keep a reference to the new object by using the base SObject type:

public SObject inputObj {get; set;} 
String objectName = 'Account'; // would come from the user 
Schema.SObjectTypetargetType = 
  Schema.GetGlobalDescribe().Get(objectName); 

if(targetType != null) 
{ 
   inputObj = targetType.NewSObject(); 
}

Gathering Input

In the corresponding page we can now get input into any field on this object through the use of dynamic binding whereby we specify the field name using a string like so:

<apex:inputField value="{!inputObj['OwnerId']}"/>

So far, so good, but one of the requirements is that the field is also dynamic, i.e. it can be chosen by the user. We can account for that by just specifying a public string variable in the controller and using that as the field name:

<apex:inputField value="{!inputObj[fieldName]}"/>

Read Only Fields

This works well for most fields, but there are exceptions: CreatedById and IsDeleted are two fields which the user can not directly write to, and so the the UI will reflect that by showing the equivalent output display rather than an input element. This can be avoided if needed by providing special cases for these fields and when chosen, switch the input to another field on the object of the same type, for example with CreatedById you can simply use OwnerId to get the same lookup functionality. With look-up fields there's an additional criteria of course; the replacement field must reference the same object type as well. Swapping CreatedById for ParentId (on Account) would not have the desired result.