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;
}