The trouble with Picklists, Visualforce, and Record Types

I recently came across a problem where I had to render the values in a picklist on a custom visualforce page. As usual, I googled it up, and went to a few forums etc., and found that there was a solution to this using the “describe()” method. Sure enough, I implemented it, and like everything else in life, **it wasn’t that simple**. Problem was, I also had a record type on the object, so the users should see the values for the appropriate record type based on their profile.

So let’s go step-by-step :- first, here’s the scene – I have a defined a custom object called “Appraisal”, used for employee appraisal. On this custom object, I have two record types -
(1) For the regular employees profile
(2) For the senior management.

The “Appraisal” object also has a “Appraisal Summary Type” picklist, that allows the user to pick a single line summary for the appraisal. Since “regular employees” don’t have much choice, picklist values for the Regular Employee profile are :- [“Excellent”, “Good”, “Average”, “Bad”].

Values for Senior Management profile are :- [“Let’s give him a raise”, “Let’s take care of him next year”, “Just an average Joe”, “Dude, this guy’s a complete loser”]. Here’s how the object has been setup :-

image

Here’s how the picklist values look like, after splitting them up for different record types :-

image image

 

Getting picklist values on a custom Visualforce page (with a StandardController)

So let’s start by getting the values for this picklist on a custom VF page. This page uses a standardcontroller to create a new Appraisal object. Here’s the code for that :-

Code Snippet
  1. <apex:page standardController="Appraisal__c">
  2. <apex:form>
  3.     <apex:pageBlock>
  4.         <!-- Just creating a simple form with two fields - Name & Appraisal Summary Type.
  5.         Goal is that user should be able to enter a name, select a type, and boom !! save the object.
  6.         Bigger goal here is to make sure that the right values appear in the drop down for Appraisal Summary
  7.         Type, based on user profile -->
  8.         <apex:outputLabel>Enter Name : </apex:outputLabel>
  9.         &nbsp; &nbsp;
  10.         <apex:inputField id="ipName" value="{!Appraisal__c.Name}" />
  11.         <apex:outputLabel>Select Appraisal Summary Type : </apex:outputLabel>
  12.         &nbsp; &nbsp;
  13.         <apex:inputField id="ddlASType" value="{!Appraisal__c.Appraisal_Summary_Type__c}" />
  14.         <apex:commandButton action="{!Save}" value="Save" />
  15.     </apex:pageBlock>   
  16.     </apex:form>
  17. </apex:page>

As you can see in the screenshot below, the picklist resolved itself to show the correct list of values based on my profile. How did it do that ? Because we are tying it to a field in the StandardController, and the controller logic behind the scenes takes care of identifying the correct values based on your record type.

image

If I change my profile to the “Senior Management” profile, I will see the senior management picklist values on the same page (screenshot below for the non-believer in you).

image

Getting picklist values on a custom Visualforce page (with a CustomController)

So fas so good, the standard controller works pretty well. However, all Visualforce programmers encounter, at some point, a custom page that does not use a standard controller. Nor can it use an extension. Instead, it uses a custom controller. In this scenario, you lose the ability to automatically filter the picklist based on recordtype. Instead, you need to manually query and populate the values in the picklist. Here’s an example :-

First, the VF page (make sure to read the embedded comments for a better understanding) :-

Code Snippet
  1. <apex:page controller="PicklistPicker_Class">
  2. <apex:form >
  3.     <apex:pageBlock >
  4.         <apex:outputLabel >Enter Name : </apex:outputLabel>
  5.         &nbsp; &nbsp;
  6.         <!-- Notice how each input value is now tied to a specific property in
  7.         the controller class -->
  8.         <apex:inputText id="ipName" value="{!SelectedName}" />
  9.         <apex:outputLabel >Select Appraisal Summary Type : </apex:outputLabel>
  10.         &nbsp; &nbsp;
  11.         <!-- Instead of an inputField, we need to now use a selectList, and populate it manually
  12.         using the "SelectedAppraisalType" method. Notice how this is completely different, and difficult
  13.         compared to the apex:inputField we used earlier. -->
  14.         <apex:selectList id="ddlASType" value="{!SelectedAppraisalType}" size="1">
  15.             <apex:selectOptions value="{!AppraisalTypeList}" />
  16.         </apex:selectList>
  17.         <apex:commandButton value="Save" />
  18.     </apex:pageBlock>   
  19.     </apex:form>
  20. </apex:page>

And now the Apex class :-

Code Snippet
  1. public with sharing class PicklistPicker_Class {
  2.     public String AppraisalName
  3.     {
  4.         get
  5.         {
  6.             if(AppraisalName == null)
  7.                 AppraisalName = '';
  8.             return AppraisalName;
  9.         }
  10.         set;
  11.     }
  12.     // The below is standard procedure to get the values in a picklist.
  13.     // You can take a look at the original Salesforce article @
  14.     // http://blogs.developerforce.com/developer-relations/2008/12/using-the-metadata-api-to-retrieve-picklist-values.html
  15.     public List<SelectOption> AppraisalTypeList
  16.     {
  17.         get
  18.         {
  19.             List<SelectOption> options = new List<SelectOption>();
  20.             Schema.DescribeFieldResult fieldResult = Appraisal__c.Appraisal_Summary_Type__c.getDescribe();
  21.                List<Schema.PicklistEntry> ple = fieldResult.getPicklistValues();
  22.        
  23.            for( Schema.PicklistEntry f : ple)
  24.            {
  25.               options.add(new SelectOption(f.getLabel(), f.getValue()));
  26.            }      
  27.            return options;
  28.         }
  29.         set;
  30.     }
  31.     public String SelectedAppraisalType
  32.     {get;set;}
  33.     public String SelectedName
  34.     {get;set;}
  35.     // I have deliberately omitted the "Save" method to keep our description simple.
  36.     // In a real world scenario, this will have to be implemented as well.
  37.     public void Save()
  38.     {}
  39. }

Here is how it looks when I preview the page :-

image

Checkout the values in our picklist – there is no filtering based on record type. You would have expected salesforce to have the same consistent behavior even when you explicitly query the picklist values using the API – not happening here. When you use the API to query a list of values in a picklist, it does not use your profile to apply the record type on those values.

Getting record type-based picklist values

So we hack the system a little bit to get our job done. First, we declare a “dummy” field on the custom controller that can be supplied to an <apex:inputField>. Then, we use a hidden variable to figure out what value the user has selected. Confused ? Go through the code below.

Code Snippet
  1. <apex:page controller="PicklistPicker_Class">
  2. <apex:form id="myForm">
  3.     <apex:pageBlock id="myPageBlock">
  4.         <script>
  5.             function updateHiddenField() {
  6.                 var hdnField = document.getElementById('{!$Component.myForm:myPageBlock:hdnAppraisalST}');
  7.                 // We set the value of the hidden variable here
  8.                 hdnField.value = document.getElementById('{!$Component.myForm:myPageBlock:ipAppraisalST}').value;
  9.             }
  10.         </script>
  11.         <apex:outputLabel >Enter Name : </apex:outputLabel>
  12.         &nbsp; &nbsp;
  13.         <apex:inputText id="ipName" value="{!SelectedName}" />
  14.         <br />
  15.         <apex:outputLabel >Select Appraisal Summary Type : </apex:outputLabel>
  16.         &nbsp; &nbsp;
  17.         <apex:inputField id="ipAppraisalST" value="{!dummyAppraisal.Appraisal_Summary_Type__c}"
  18.             onChange="updateHiddenField();" />
  19.         <apex:inputHidden id="hdnAppraisalST" value="{!SelectedAppraisalSummaryType}" />
  20.         <apex:commandButton value="Save" action="{!Save}" />
  21.         <br />
  22.         <br />
  23.         <apex:outputText value="{!opMessage}" />
  24.     </apex:pageBlock>
  25.     </apex:form>
  26. </apex:page>

Here’s the controller :

Code Snippet
  1. public with sharing class PicklistPicker_Class {
  2.     public String SelectedAppraisalSummaryType
  3.     {get;set;}
  4.     public String SelectedName
  5.     {get;set;}
  6.     public Appraisal__c dummyAppraisal
  7.     {
  8.         get
  9.         {
  10.             if(dummyAppraisal==null)
  11.                 dummyAppraisal = new Appraisal__c();
  12.             return dummyAppraisal;
  13.         }
  14.         set;
  15.     }
  16.     public void Save()
  17.     {
  18.         opMessage = 'You added name = ' + SelectedName;
  19.         opMessage = opMessage + '.... And you selected picklist value = ' + SelectedAppraisalSummaryType;
  20.     }
  21.     public String opMessage
  22.     {get;set;}
  23. }

The secret here is the dummyAppraisal object. We know that an <apex:inputField> can filter the picklist values based on record types. So we create a dummy object, and use that to build our picklist (ipAppraisalST in the VF page).

The other neat trick is to hook up the picklist with a hidden variable. The problem  here is that even though you are now able to display a picklist with the correct values, we cannot get the value that the user selects back in the controller class. This is where the hidden variable and the javascript function comes in. When the picklist value changes, our javascript function populates the selected value in the hidden variable as well. Since the hidden variable is linked to the “SelectedAppraisalSummaryType” property in the controller, the selected value is now available in this property in the controller. Here’s what the screen looks like, for my Senior Management profile record type :

image

5 comments:

  1. Very helpful post - I implemented for my custom object and it works !!

    ReplyDelete
  2. i implemented using standard controller, but i'm unable to see different picklist values for record types .I have selected different picklist values for each record type.The record type which is set as default record type, tht record type picklist values are being displyes in both the record types...please advice me

    ReplyDelete
  3. Hey, thanks so much. I did as you described, and it works great!

    ReplyDelete
  4. Visualforce supports records types. Its as easy as assigning the record type to the object in the constructor.



    Then use the inputfield with the value of customObject.

    ReplyDelete
  5. I was using the same "Dummy object" thing..but it came out that if USER doesn't have CREATE permission on Object (here object - Appraisal__c) then USER can't t even see the picklist field ( I thnk becuase user doesn't have permission to create new instance)

    ReplyDelete