A Picklist Component For Your Visualforce Pages

The last couple of months, numerous times in order to view picklist values in a form either for filter the views or creating custom data entry forms, I had to come up with ways to get around the limitations and somehow hook the inputField with the picklist to show what I needed.

Now, with the Salesforce Winter 09 improvements now it is much easier to tackle this issue.

In this article we will design an Apex component that gets the picklist name as attribute and renders the picklist values in form of a dropdown list.

This component will be really useful for non-multi-select picklists. I will post another article on multiselect picklists in near future.

Let's see how the Component will be added to the Page:

<c:picklist id="industryPicklist" value="{!accountIndustry}" picklistField="Industry" systemEntity="Account" usedefaultvalue="true"/>

What you need to define for the component is what object, which picklist field of that Object and a property of your page's Controller allowing you to read what user's choice was.

Now let's dive into the code and see how this component is developed. Like most other Visualforce page developments our component has two parts. The User Interface elements and the controller behind it.


<apex:component controller="picklistController">
<apex:attribute name="SystemEntity" description="" type="String" required="true" default="Account" assignTo="{!systemObject}"></apex:attribute>
<apex:attribute name="picklistField" description="" type="String" required="true" default="Type" assignTo="{!picklist_Field}"></apex:attribute>
<apex:attribute name="value" description="" type="String" required="true"></apex:attribute>
<apex:attribute name="defaultLabel" description="default value to be shown to user" type="String" assignTo="{!listDefaultLabel}" default="--None--"></apex:attribute>
<apex:attribute name="defaultValue" description="default value to be shown to user" type="String" assignTo="{!listDefaultValue}" default=""></apex:attribute>
<apex:attribute name="useDefaultValue" description="whether a default value to be shown or not" type="Boolean" assignTo="{!haveDefaultValue}" default="false"></apex:attribute>

<apex:selectList value="{!value}" size="1">
<apex:selectOptions value="{!items}"></apex:selectOptions>
</apex:selectList>
</apex:component>


The Component has only a selectList and a few attributes. Below are a list of attributes used for this component:

  • systemEntity: API name of the Object which has a picklist field
  • picklistField: API name of the Object's picklist field
  • value: This field should be bound to a property in parent Page's Controller
  • defaultLabel: Label of the default value shown to user, only visible when useDefaultValue is set to true.
  • defaultValue: Value of the default item shown to user, only visible when useDefaultValue is set to true.
  • useDefaultValue: A Boolean value tells the component whether to show a default value or not.
Now let's see how the Controller is look like:


public class picklistController {

//System object name, such as Account

private String systemObject;

public String getSystemObject() {

return systemObject;

}

public void setSystemObject(String value)

{

systemObject = value;

}

//picklist name such as "Type" of account object

private String picklist_Field;

public String getPicklist_Field() {

return picklist_Field;

}

public void setPicklist_Field(String value) {

picklist_Field= value;

}

//default item's value

public String listDefaultValue

{

get;

set;

}

//default item's label

public String listDefaultLabel

{

get;

set;

}

// whether the default item should be shown

public Boolean haveDefaultValue

{

get;

set;

}

public List getItems()

{

List entries_local;

if (systemObject != null)

{

//use GlobalDecribe to get a list of all available Objects

Map gd = Schema.getGlobalDescribe();

Set objectKeys = gd.keySet();

for(String objectKey: objectKeys)

{

//Iterate through all objects to locate selected Object

if (objectKey == systemObject.toLowerCase())

{

Schema.SObjectType systemObjectType = gd.get(objectKey);

Schema.DescribeSObjectResult r = systemObjectType.getDescribe();

Map M = r.fields.getMap();

Set fieldNames = M.keySet();

if (picklist_Field == null)

{

break;

}

//iterate through all fields of the object to locate the picklist field

for(String fieldName: fieldNames)

{

if (fieldName == picklist_Field.toLowerCase())

{

Schema.SObjectField field = M.get(fieldName);

Schema.DescribeFieldResult fieldDesc = field.getDescribe();

//extract the picklist values

entries_local = fieldDesc.getPicklistValues();

break;

}

}

}

}//end for

}

//Loading the picklist values and default item for our selectList

List options = new List();

//take care of Default value

if (haveDefaultValue == true)

{

if (listDefaultValue == null)

listDefaultValue = '';

if (listDefaultLabel== null)

listDefaultLabel = '--None--';

options.add(new SelectOption(listDefaultValue,listDefaultLabel));

}

//take care of picklist values

if (entries_local != null)

{

for(Schema.PicklistEntry picklistItem : entries_local)

{

options.add(new SelectOption(picklistItem.getValue(),picklistItem.getLabel()));

}

}

return options;

}

}



15 comments:

  1. Hi, nice post. I was wondering how you are getting around the limit on the getpicklistvalues() method. This has been a problem for me especially w/ multiselects. I'm hitting the limit of 10 per class almost right away.

    Thanks!
    Kyle.

    ReplyDelete
  2. I am doing some custom work for a executive recruiting firm who profiles candidates by 16 different skills and experience categories with each category having about 10 or more values. I have created custom multi-select picklist fields on the contact record. For each contact/candidate, they select the values that apply. I want to create a relevance search query tool that allows the user to select the picklist values and responds with a list of condidates that best match the query. Any thoughts on an approach in salesforce

    ReplyDelete
  3. Hi,
    I am trying to implement the picklist controller as you have mentioned above. I am getting an error in the function definition line of getItems()
    When I change the def to:
    public List getItems(), i get error in the other lines too that have Map and set kind of values. Since I am a novice in Java and Apex as a whole. Could you help me figure this out.
    Thanks,

    ReplyDelete
  4. After some struggle, i have been able to figure out the collections to be used.
    This is how the controller looks like now(w/o errors):
    public class PickListController {
    //System object name, such as Account

    private String systemObject;
    public String getSystemObject()
    {
    return systemObject;
    }

    public void setSystemObject(String value)
    {
    systemObject = value;
    }

    //picklist name such as "Type" of account object
    private String picklist_Field;
    public String getPicklist_Field()
    {
    return picklist_Field;
    }
    public void setPicklist_Field(String value)
    {
    picklist_Field= value;
    }

    //default item's value
    public String listDefaultValue{get; set;}

    //default item's label
    public String listDefaultLabel{get;set;}

    // whether the default item should be shown
    public Boolean haveDefaultValue{get;set;}
    public List getItems()
    {
    List entries_local;
    if (systemObject != null)
    {
    //use GlobalDecribe to get a list of all available Objects
    Map gd = Schema.getGlobalDescribe();
    Set objectKeys = gd.keySet();
    for(String objectKey: objectKeys)
    {
    //Iterate through all objects to locate selected Object
    if (objectKey == systemObject.toLowerCase())
    {
    Schema.SObjectType systemObjectType = gd.get(objectKey);
    Schema.DescribeSObjectResult r = systemObjectType.getDescribe();
    Map M = r.fields.getMap();
    Set fieldNames = M.keySet();
    if (picklist_Field == null)
    {
    break;
    }
    //iterate through all fields of the object to locate the picklist field
    for(String fieldName: fieldNames)
    {
    if (fieldName == picklist_Field.toLowerCase())
    {
    Schema.SObjectField field = M.get(fieldName);
    Schema.DescribeFieldResult fieldDesc = field.getDescribe();
    //extract the picklist values
    entries_local = fieldDesc.getPicklistValues();
    break;
    }
    }
    }
    }//end for
    }

    //Loading the picklist values and default item for our selectList
    List options = new List();

    //take care of Default value
    if (haveDefaultValue == true)
    {
    if (listDefaultValue == null)
    listDefaultValue = '';
    if (listDefaultLabel== null)
    listDefaultLabel = '--None--';
    options.add(new SelectOption(listDefaultValue,listDefaultLabel));
    }
    //take care of picklist values
    if (entries_local != null)
    {
    for(Schema.PicklistEntry picklistItem : entries_local)
    {
    options.add(new SelectOption(picklistItem.getValue(),picklistItem.getLabel()));
    }
    }
    return options;
    }
    }

    Do let me know if you see any issues.
    Thanks

    ReplyDelete
  5. Am getting error on public list getItems()

    plz help me...am new to apex coding

    ReplyDelete
  6. This seems to be publishing error, the signs < > that are html reserved characters are omitted.

    the method should be like:
    public List<SelectOption> getItems()

    I will update the post as soon as possible.

    ReplyDelete
  7. There are several "mistakes" in the controller code for the component as it is published - the signature definition for the getItems() method as you have mentioned, but also Map variables, etc.

    The primary question I have is : How do you get the selected picklist value to use in the controller of the "parent" page? (aka the page in which the custom component is used)

    The need to access a value from a custom component in the component's "parent" page is very important, especially if your component contains a picklist, etc.

    Sam - can you PLEASE post the code for the page in which you used this component so we can all see exactly how the two can work together?

    Thanks in advance!

    ReplyDelete
  8. There are several "mistakes" in the controller code for the component as it is published - the signature definition for the getItems() method as you have mentioned, but also Map variables, etc.

    The primary question I have is : How do you get the selected picklist value to use in the controller of the "parent" page? (aka the page in which the custom component is used)

    The need to access a value from a custom component in the component's "parent" page is very important, especially if your component contains a picklist, etc.

    Sam - can you PLEASE post the code for the page in which you used this component so we can all see exactly how the two can work together?

    Thanks in advance!

    ReplyDelete
  9. Bind a parent page property to the component's value attribute and you will be able see get what user selects on the parent page.

    ReplyDelete
  10. public class picklistController {

    //System object name, such as Account

    private String systemObject;

    public String getSystemObject() {

    return systemObject;

    }

    public void setSystemObject(String value)

    {

    systemObject = value;

    }

    //picklist name such as "Type" of account object

    private String picklist_Field;

    public String getPicklist_Field() {

    return picklist_Field;

    }

    public void setPicklist_Field(String value) {

    picklist_Field= value;

    }

    //default item's value

    public String listDefaultValue

    {

    get;

    set;

    }

    //default item's label

    public String listDefaultLabel

    {

    get;

    set;

    }

    // whether the default item should be shown

    public Boolean haveDefaultValue{get;set;}
    public List getItems()
    {

    List entries_local=new list();

    if (systemObject != null)

    {

    //use GlobalDecribe to get a list of all available Objects

    Map gd = Schema.getGlobalDescribe();

    Set objectKeys = gd.keySet();

    for(String objectKey: objectKeys)

    {

    //Iterate through all objects to locate selected Object

    if (objectKey == systemObject.toLowerCase())

    {

    Schema.SObjectType systemObjectType = gd.get(objectKey);

    Schema.DescribeSObjectResult r = systemObjectType.getDescribe();

    Map M = r.fields.getMap();

    Set fieldNames = M.keySet();

    if (picklist_Field == null)

    {

    break;

    }

    //iterate through all fields of the object to locate the picklist field

    for(String fieldName: fieldNames)

    {

    if (fieldName == picklist_Field.toLowerCase())

    {

    Schema.SObjectField field = M.get(fieldName);

    Schema.DescribeFieldResult fieldDesc = field.getDescribe();

    //extract the picklist values

    entries_local = fieldDesc.getPicklistValues();

    break;

    }

    }

    }

    }//end for

    }

    //Loading the picklist values and default item for our selectList

    List options = new List();

    //take care of Default value

    if (haveDefaultValue == true)

    {

    if (listDefaultValue == null)

    listDefaultValue = '';

    if (listDefaultLabel== null)

    listDefaultLabel = '--None--';

    options.add(new SelectOption(listDefaultValue,listDefaultLabel));

    }

    //take care of picklist values

    if (entries_local != null)

    {

    for(Schema.PicklistEntry picklistItem : entries_local)

    {

    options.add(new SelectOption(picklistItem.getValue(),picklistItem.getLabel()));

    }

    }

    return options;
    }

    }
    This will not throw error...when you save

    ReplyDelete
  11. hi sam,
    i have a doubt in the line below. what this line says?? bound to a parent page's controller, where this component is used??? U mean this in the below line.. please reply, it will be very useful for me if u answer this...

    value: This field should be bound to a property in parent Page's Controller...

    thanks,
    venkat

    ReplyDelete
  12. Can anybody tell me how to retrieve values of an object from custom controller class into it's corresponding visualforce page??please

    ReplyDelete
  13. as long as you add the object as a property to the controller class, you can access it via merge fields on the visualforce page: {!propertyname.fieldname}

    ReplyDelete