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.

3 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