Saturday 28 January 2012

Record Type Specific Picklist Values

This post is my solution to an issue that seems to have been around for a while - how to get at the picklist values for a particular record type programmatically.  I've been working on a JavaScript chart (using my framework of choice for charting - Dojo) that will show how far through the sales process an opportunity is.

Initially I was using the describe capability for the StageName field, which worked fine for a single record type. However, as soon as I introduced multiple opportunity record types with different sales processes, I hit problems - regardless of the sales process, the full set of picklist values was returned each time, even if I navigated to the field via an opportunity with the appropriate record type.  Some googling and searching on the Developerforce discussion boards seemed to confirm that there isn't a simple way to achieve this.

Clearly the information about which stages are applicable to a record type is available to the Salesforce UI, as the Stage picklist contains the values specific to the record type.  Even better, this is respected when using a Visualforce input field.  Therefore one way of getting at this information for use in a Visualforce controller is to have the page supply it.  Utilizing a technique I've written about before in DML During Initialisation, I created a page that would send the picklist values back to the controller the first time it was loaded, and then display the details thereafter.

The first time that the page is loaded the following outputpanel is rendered:

   <apex:outputPanel rendered="{!loadonce}">
      <apex:form id="frm">
         <apex:actionFunction name="reloadWithStages" action="{!reload}"/>
         <div id="test1" style="width: 100%; height: 150px;"></div> 
         <apex:outputPanel layout="block" id="vals" style="display:none">
            <apex:inputField value="{!Opportunity.StageName}" required="false" id="stages"/>
            <apex:inputText value="{!valsText}" required="false" id="back"/>
         </apex:outputPanel>
      </apex:form>
      <script>
         function reload()
         {
            var ele=document.getElementById('{!$Component.frm.stages}');
            var idx=0;
            var valText='';
            for (idx=0; idx<ele.length; idx++)
            {
               valText+=ele.options[idx].text + ':';
            }
   
            var backele=document.getElementById('{!$Component.frm.back}');
            backele.value=valText;
 
            reloadWithStages();
         }

         window.onload=reload(); 
      </script>
   </apex:outputPanel>
   

This contains a hidden form with an inputfield for the opportunity stage and an input text element that will be used to submit the stage options to the controller. The reload() function is installed as an onload handler, and this extracts the option text, concatenates it into a single string value, populates the input text with this string and then submits the form.
When the page is reloaded, the following outputpanel is rendered - this simply creates a pageblocktable to iterate through the available values:

   <apex:outputPanel rendered="{!NOT(loadonce)}">
      <apex:pageBlock title="Status Values for record type {!Opportunity.RecordType.Name}">
         <apex:pageBlockTable value="{!pickListVals}" var="plVal">
            <apex:column headerValue="Stage">
               <apex:outputText value="{!plVal}"/>
            </apex:column>
         </apex:pageBlockTable>
   </apex:pageBlock>
   </apex:outputPanel>

The controller is shown below - the heavy lifting is done by the reload() method - this parses the string containing the picklist values and stores them in a list property. it also sets the loadonce property to false, to ensure that the page is reloaded once and once only:

public with sharing class RecordTypePickListController 
{
 public List<String> pickListVals {get; set;}
 public String valsText {get; set;}
 public Boolean loadOnce {get; set;}
 private Opportunity opp;
 
 public RecordTypePickListController(ApexPages.StandardController std)
 {
  opp=(Opportunity) std.getRecord();
  loadOnce=true;
 }
 
 public PageReference reload()
 {
  pickListVals=new List<String>();
  Boolean skip=true;
  for (String val : valsText.split(':'))
  {
   if (skip)
   {
    skip=false;
   }
   else
   {
    pickListVals.add(val);
   }
  }

  loadOnce=false;
  
  return null;
 }
}


Here are a couple of screenshots of the page doing its thing for opportunities with two different record types.  The first with just a few of the stages:


and the second with most of them:



For the sake of completeness, the entire page is shown below:

<apex:page standardController="Opportunity" extensions="RecordTypePickListController">

   <apex:outputPanel rendered="{!loadonce}">
      <apex:form id="frm">
         <apex:actionFunction name="reloadWithStages" action="{!reload}"/>
         <div id="test1" style="width: 100%; height: 150px;"></div> 
         <apex:outputPanel layout="block" id="vals" style="display:none">
            <apex:inputField value="{!Opportunity.StageName}" required="false" id="stages"/>
            <apex:inputText value="{!valsText}" required="false" id="back"/>
         </apex:outputPanel>
      </apex:form>
      <script>
         function reload()
         {
            var ele=document.getElementById('{!$Component.frm.stages}');
            var idx=0;
            var valText='';
            for (idx=0; idx<ele.length; idx++)
            {
               valText+=ele.options[idx].text + ':';
            }
   
            var backele=document.getElementById('{!$Component.frm.back}');
            backele.value=valText;
 
            reloadWithStages();
         }

         window.onload=reload(); 
      </script>
   </apex:outputPanel>
   
   <apex:outputPanel rendered="{!NOT(loadonce)}">
      <apex:pageBlock title="Status Values for record type {!Opportunity.RecordType.Name}">
         <apex:pageBlockTable value="{!pickListVals}" var="plVal">
            <apex:column headerValue="Stage">
               <apex:outputText value="{!plVal}"/>
            </apex:column>
         </apex:pageBlockTable>
   </apex:pageBlock>
   </apex:outputPanel>
</apex:page>

And a special thanks to the blogger software error that meant I had to write this post twice! Always save at regular intervals.

11 comments:

  1. Bob, this causes two entries in the browser history, so the back button does not go back to the previous page, you have to jump back two pages (in FF9.0.1)

    ReplyDelete
    Replies
    1. Yeah, the action function isn't carrying out a rerender, it's just refreshing the page. An exercise for the avid student!

      Delete
  2. Thanks. That worked great.

    ReplyDelete
  3. Bob,
    Can you tell me how to get the dependent picklist values in apex based on the Controlling picklist value?
    We are using AngularJs and Bootstrap so we dont want to use standard salesforce tag for dependent picklist.

    Thanks in advance

    ReplyDelete
    Replies
    1. @Karthik,


      Did u get solution for your issue, I m having exactly same issue.

      Thanks

      Delete
    2. @Karthik,


      Did u get solution for your issue, I m having exactly same issue.

      Thanks

      Delete
    3. Hope this link will help!!

      http://titancronus.com/blog/2014/05/01/salesforce-acquiring-dependent-picklists-in-apex/

      Delete
  4. Hi Bob,

    I have an alternative solution, your thoughts please..

    Add the picklist values as numeric codes like 0 to 9 associate them with Record Type1 and 10 to 19 associate them with Record Type2.

    In our code, loop in based on these numeric codes only to show 0-9 or 10-19 based on the record Type name.

    Use Translations in salesforce to load the exact labels for these numeric codes which the end users would like to see as list of values.

    Does that sound good?

    Regards,
    Rupesh Bhatia

    ReplyDelete
    Replies
    1. The problem I have with solutions of this nature is that it puts the onus on administrators to remember to create the picklist values according to the rules, and it means the code relies on the rules having been followed. It will work, but I prefer to remove the human element from the process.

      Delete
    2. Also these numbers will display in reports and users who are not using your VF page will see them too. (standard Salesforce pages). Not user friendly.

      Delete
  5. Hi Bob,
    Is there any work around for getting default Picklist values for different record types.
    Like I have 5 opportunity record types and I have different default picklist value of picklists with different record types.

    can I fetch default picklist values with record type combinations?

    Thanks,
    Sanjeev

    ReplyDelete