Start tracking your progress
Trailhead Home
Trailhead Home

Build UI for Dependent Picklists

Learning Objectives

After completing this unit, you’ll be able to:
  • Make a request to User Interface API to get all picklist values for a record type.
  • Name the property that contains a map of an object’s dependent fields.
  • Name the property that contains a list of a field’s controlling fields.

What’s Special About Dependent Picklists?

It’s tricky to build a user interface for dependent picklist fields, but User Interface API makes it easier. The values of a dependent picklist field are filtered based on a selection in another picklist or checkbox (called the controlling field). Look at this screenshot of the dependent picklist editor in the Record Viewer app. A Country picklist controls a State/Province picklist (which depends on the Country picklist). When a user selects a country, the State/Province values are filtered to match. When a user selects a state or a province, that selection filters the City values. Dependent picklist editor showing USA, California, Los Angeles.

You get the idea—dependent picklists make it faster to fill out a form because the values are filtered so you spend less time scrolling. And a dependent picklist editor makes it easier to edit related fields, especially on a mobile device. Imagine how difficult it could be to scroll from field to field as dependent values change. It’s much easier to change all the related picklist values in one dialog box.

But it’s these relationships between the controlling fields and the dependent fields that make it difficult to build a user interface. A picklist (or a checkbox) can control multiple dependent picklists. And a dependent picklist is controlled but it can also control. In fact, a dependent picklist can control several other dependent picklists. The dependent picklist field hierarchy is a tree. And an object can have a forest of trees.

User Interface API exists to solve problems for UI developers, so we exposed properties that describe these field relationships.
  • objectInfo.dependentFields contains a map of an object's dependent fields.
  • objectInfo.fields[fieldName].controllingFields contains a list of a field's controlling fields.
We also exposed a resource that gets all the picklist values for a specified record type.
GET /ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}

The Record Viewer app uses these properties and resource to build its dependent picklist editor.

Use the Dependent Picklist Editor

To see the dependent picklist editor in the Record Viewer app, create a dependent relationship between at least two fields on an object.

In your Trailhead Playground, create a field dependency for the Account object. Then we’ll edit an account in the Record Viewer app to see the dependent picklist editor in action.

  1. From Setup, enter object and select Object Manager.
  2. Select Account | Fields & Relationships | Field Dependencies.
  3. Click New.
  4. Let’s create a relationship between two existing fields. For the controlling field, select Rating. For the dependent field, select Customer Priority. Click Continue.
  5. Edit the field dependencies so that Hot includes High, Warm includes Low and Medium, and Cold includes Low. Click Save.Edit field dependency matrix in Salesforce.
  6. Open the Record Viewer app and select an account from the Recent Items List.
  7. Click Edit.
  8. Next to the Account Rating or Customer Priority fields, click the pencil icon. Pencil icon beside the Account Rating field.

    The dependent picklist editor dialog box opens. Use it to select a value for both fields. When you select a value for one field, the values for the other field are filtered based on your selections in Setup.Dependent picklist editor with Account Rating and Customer Priority fields.

How Dependent Picklists Are Exposed in User Interface API

Dependent picklist fields are part of a dependency tree, which is a hierarchy of controlling and dependent fields. Each node in a tree can have any number of child nodes. A node without any children is called a leaf.

Our sample dependency tree is simple because it has one root field, Rating, which controls one field CustomerPriority__c. If Rating controlled another field, our dependency hierarchy would start to look like a tree. But with only two fields, our dependency tree doesn’t have any branches, it’s just a trunk. Rating controls CustomerPriority.

An object can have any number of these dependency trees. To build UI that lets users edit dependent picklists, you need to know the complete hierarchy of each tree.

For example, the Account object could have two dependency trees, one whose root is Country, and one whose root is Rating. These dependency trees from our examples are simple, because each controlling field controls only one dependent field. You can imagine, though, that the Country field, in addition to controlling the State/Province field, could also control a Language field and a Time Zone field.

As you’ve learned, User Interface API has two response properties that provide the field dependency structure. The properties are both included in the objectInfo payload, which is part of the response from /ui-api/record-ui/{recordIds}. To see the JSON response, open an account object in the Record Viewer app, and click Show JSON. JSON in the RecordView app.

objectInfo.dependentFields
This property provides the dependent field trees for all the fields on an object. This sample JSON has one root field, Rating, but an object could have multiple root fields.
"objectInfos" : {
    "Account" : {
      ...
      "dependentFields" : {
        "Rating" : {
          "CustomerPriority__c" : { }
        }
      },
      ...
When an object has an extended hierarchy of dependent fields, like our Country, State/Province, City example, the dependentFields property nests them until it reaches a leaf node, like City.
"objectInfos" : {
    "Account" : {
      ...
      "dependentFields": {
        "Country__c": {
          "StateProvince__c": {
            "City__c": {}
          }
        }
      },
      ...
objectInfo.fields[fieldName].controllingFields
This property provides the list of fields that control this particular field, starting from the field that immediately controls this field. In our simple example, the CustomerPriority__c field has one controlling field, Rating.
"objectInfos" : {
    "Account" : {
      ...
      "fields" : {
       ...
        "CustomerPriority__c" : {
            "apiName" : "CustomerPriority__c",
            ...
            "controllingFields" : [ "Rating" ],
            ...
When an object has an extended hierarchy of controlling fields, like our Country, State/Province, City example, the controllingFields property lists them in order starting with the immediate controlling field.
"objectInfos" : {
    "Account" : {
      ...
      "fields" : {
       ...
        "City__c" : {
            "apiName" : "City__c",
            ...
            "controllingFields" : [ "StateProvince__c", "Country__c"],
            ...
In addition to knowing the relationship between the dependent fields, we also need to know the values for every picklist field on an object in relation to its immediate controlling field, because that’s the field that filters the values. User Interface API gives us a handy endpoint to get all these values for a record type in a single call.
GET /ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}
This excerpt from the JSON response shows the values for the CustomerPriority__c field.
"CustomerPriority__c" : {
      "controllerValues" : {
        "Hot" : 0,
        "Warm" : 1,
        "Cold" : 2
      },
      "defaultValue" : null,
      "url" : "/services/data/v45.0/ui-api/object-info/Account/picklist-values/012000000000000AAA/CustomerPriority__c",
      "values" : [ {
        "attributes" : null,
        "label" : "High",
        "validFor" : [ 0 ],
        "value" : "High"
      }, {
        "attributes" : null,
        "label" : "Low",
        "validFor" : [ 1, 2 ],
        "value" : "Low"
      }, {
        "attributes" : null,
        "label" : "Medium",
        "validFor" : [ 1 ],
        "value" : "Medium"
      } ]
    },

The controllerValues property provides the immediate controlling field’s picklist values and indexes. So in our example, the field is Customer Priority, and the immediate controlling field is Rating. The Rating values are Hot, Warm, and Cold, and the indexes are 0, 1, and 2.

For each Customer Priority value (High, Low, and Medium), the validFor property contains the Rating values that, when selected in a picklist, include the Customer Priority value, instead of filtering it out.

For example, when Rating is Hot (0), the Customer Priority picklist is filtered to include only High. When the Rating is Warm (1), the Customer Priority picklist is filtered to include Low and Medium. When Rating is Cold (2), the Customer Priority picklist is filtered to include only Low.

Now that we understand what information we need and how to get it from User Interface API, let’s look at how the Record Viewer app builds its dependent picklist editor.

Get the Picklist Values

When a user clicks to Create, Clone, or Edit a record, the Record Viewer app prefetches the picklist values for all the fields on the object for the specified record type.

You can see the code in RecordViewerWrapper.js. Look for a call to dispatch(actions.fetchPicklists(creds, apiName, recordType)) for each scenario: onNewRecordClick, onCloneClick, and onEditClick.

To see the code that constructs the UI API URL that fetches the picklist values, look at the picklistFetcher.js saga.
/* /sagas/picklistFetcher.js */

let url = action.creds.instanceUrl + '/services/data/v45.0/ui-api/object-info/' 
          + action.apiName + '/picklist-values/' + action.recordType + '/';
The picklists.js reducer updates the Redux store with the picklist values.
/* /reducers/picklists.js */

const picklists = (state = {values: undefined}, action) => {
  switch (action.type) {
    case 'RECEIVE_PICKLISTS':
      return {
        fieldValues : action.result.picklistFieldValues
      }
    case 'FETCH_PICKLISTS': // Clear values when new collection is requested.
      return {
        fieldValues: undefined
      }
    default:
      return state
  }
}

export default picklists

Display the Dependent Picklist Editor

When a record is in edit mode, the app displays a pencil icon beside dependent fields to indicate that the user can open the dependent picklist editor. Pencil icon beside dependent picklist fields.

When the user clicks the pencil icon, the app opens the dependent picklist editor as a modal dialog box. In code, the dependent picklist editor is called the DepGraphEditor component.Dependent picklist editor

The code that does this work is the RecordRow.js component. It checks whether the field exists in a dependency tree or not, and identifies the root field of the dependency tree. It renders the pencil icon beside the fields and opens the modal dialog box when the user clicks the pencil icon.
/* /components/RecordRow.js */

  if (objectInfo 
    && ((component.field && component.field in objectInfo.dependentFields)
       || (component.fieldInfo && component.fieldInfo.controllingFields.length > 0))) {
    // Last field in controlling fields is the root field
    let lastControllingIndex =  component.fieldInfo.controllingFields.length - 1
    let rootField = null
    if (component.field in objectInfo.dependentFields){
      rootField = component.field
    } else {
      rootField = component.fieldInfo.controllingFields[lastControllingIndex]
    }
    
    // Retrieve the picklist fields that need to show up.
    const subfieldTree = objectInfo.dependentFields[rootField]
    const modalFields = getFlattenedTree(objectInfo, subfieldTree, rootField)

    // Open a modal on click of the input field.
    let fieldTree = {}
    fieldTree[rootField] = subfieldTree

    return (
      <div>
        <label
          key={'componentInput' + itemLabel + ',' + i}
          onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())}
          value={currPicklistValue}>
          {editValues[component.field].current}
        </label>
        <button className="fa fa-pencil"
        key={'componentInput' + itemLabel + 'button,' + i}
        onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())}>
        </button>
      </div>
    );
  }

To open the modal dialog box, the onClick() method invokes the method onEditDepGraph(), which dispatches the action EDIT_DEP_GRAPH, which changes the mode to EditDepGraph. The modal dialog box opens when the mode changes.

To see the mode change, look at the record.js reducer code.
/* /reducers/record.js */

case 'EDIT_DEP_GRAPH':
  return {
    ...state,
    prevMode: action.prevMode, // save previous mode to return to on close
    mode: 'EditDepGraph'
  }
To see how the modal dialog box is opened on mode change, look at Record component code, which returns the DepGraphEditor component for EditDepGraph mode.
/* /components/Record.js */

<div>
    {uiMode === 'EditDepGraph' &&
      <DepGraphEditor 
          depGraph={depGraph}
          picklists={picklists}
          editValues={recordView.editValues}
          onFieldValueUpdate={onDepGraphFieldValueUpdate}
          onClose={() => onDepGraphClose(prevMode)}/> }

Update the Picklist Values

When a user selects a new value for a picklist field, the app must update the values of its child picklist fields. And if those children have children, the app must update them as well, all the way to leaf picklist fields, which don’t have children. To make these updates, it’s important to know the dependency subtree beneath the field whose value changed.

Also, if the preselected value for a field stays valid, the app must preserve that value.

To see the code, check out the saga depGraphValueUpdater.js. The code gets the dependency tree structure beneath the field with a new value by using the property controllingFields and the fieldTree property that we extracted in the RecordRow component.
/* /sagas/depGraphValueUpdater.js */

// Traverse down to the target field in the tree.

const field = modalFieldsMap[action.field];
var treeNode = action.fieldTree;
for (var i = field.controllingFields.length -1; i>=0; i--) {
  var controllingField = field.controllingFields[i]
  treeNode = treeNode[controllingField];
}

treeNode = treeNode[action.field];

// Now treeNode is the subtree rooted at the target field.

Using this extracted subtree structure and the picklist values obtained when the user clicked to Create, Clone, or Edit a record, we construct the legal picklist values for each picklist field in the subtree.

To see how the app gets the legal picklist values for a particular field, see getLegalValues() in /helpers/depGraphHelper.js. To see how the code traverses the subtree and recursively populates the legal values for every child picklist field using breadth first search, look at the depGraphValueUpdater.js saga.

Congratulations, that’s all there is to it! While it’s true that we couldn’t make building UI for dependent picklists easier than slicing bread, hopefully it’s easier than baking a chocolate soufflé, and just as delicious.

To recap—to construct UI for a dependent picklist editor, User Interface API gives you all the information you need in three places, the objectInfo.dependentFields property, the objectInfo.fields[fieldName].controllingFields property, and the response from the /ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId} endpoint.

User Interface API gives you the power and flexibility to build custom Salesforce user interfaces that bring your customers joy. Hopefully, the Record Viewer app gets you off to a good start. Feel free to fork the GitHub repo and submit pull requests. We look forward to hearing about your apps!

retargeting