Sample: Create dependent OptionSets (picklists)

 

Applies To: Dynamics 365 (online), Dynamics 365 (on-premises), Dynamics CRM 2016, Dynamics CRM Online

It’s a common requirement that the values in one option set field need to be filtered by a value chosen in another option set field. This topic describes one approach to do this with a reusable JavaScript library, form events, and JSON data retrieved using a web resource.

To observe and verify the functionality of this sample you can install the DependentOptionSetsSample_2_0_0_0_target_CRM_8.0_managed.zip managed solution included with the Visual Studio solution available for download from code.msdn.microsoft.com - Create Dependent OptionSets (Picklists) for Microsoft Dynamics CRM forms.

Goals for this Solution

This solution is intended to meet the following requirements:

  • It provides a generic, reusable JavaScript library that can be used for any pair of option set fields.

  • It allows for a chain of dependent option set fields. Because the options of each dependent option set field are filtered based on the value of another field, additional option set field options can be filtered by the option chosen in the first dependent option set field. This allows for the possibility of a set of hierarchically dependent option set fields.

  • The filtering of dependent options is set as JSON in a JavaScript web resource. This allows for changing the option mappings without changing the code. Editing JSON data in a JavaScript web resource is easier for a non-developer to configure options with less opportunity to break the code.

  • The solution supports multiple languages. The filtering is based solely on the data value of the options rather than any text in the options.

  • Filtering works for any number of instances of an attribute control on the form.

Example

This section describes one application of this approach and the procedure to apply the sample library.

The Ticket (sample_ticket) entity form has three option set fields and options that allow for categorization of products. The following table shows the desired filtering of options set options.

Category

(sample_category)

Sub Category

(sample_subcategory)

Type

(sample_type)

Value:727000000
Label: Software

Value:727000000
Label: Personal Productivity

Value:727000000
Label: Word Processor

Value:727000001
Label: Spreadsheet

Value:727000002
Label: Internet Browser

Value:727000003
Label: E-mail

Value:727000001
Label: Business Applications

Value:727000004
Label: Customer Relationship Management

Value:727000005
Label: Enterprise Resource Management

Value:727000006
Label: Human Resource Management

Value:727000002
Label: Operating Systems

Value:727000007
Label: Windows 10

Value:727000008
Label: Windows 8

Value:727000009
Label: Windows Server 2016

Value:727000010
Label: Windows Server 2012

Value:727000001
Label: Hardware

Value:727000003
Label: Desktop Computer

Value:727000011
Label: Workstation x1000

Value:727000012
Label: Workstation x2000

Value:727000013
Label: Workstation x3000

Value:727000014
Label: Workstation x4000

Value:727000004
Label: Laptop Computer

Value:727000015
Label: Laptop 1000 series

Value:727000016
Label: Laptop 2000 series

Value:727000017
Label: Laptop 3000 series

Value:727000018
Label: Laptop 4000 series

Value:727000005
Label: Monitor

Value:727000019
Label: CRT

Value:727000020
Label: LCD

Value:727000021
Label: LED

Value:727000022
Label: Projector

Value:727000006
Label: Printer

Value:727000023
Label:Inkjet

Value:727000024
Label: Laser

Value:727000025
Label:LED

Value:727000026
Label: Solid-ink

Value:727000007
Label: Telephone

Value:727000027
Label: Landline

Value:727000028
Label: Mobile

Value:727000029
Label: Smart Phone

Enable filtering

  1. Convert the desired filtering of options into the following JSON document and upload it as an JavaScript web resource titled sample_TicketDependentOptionSetConfig.js.
		[
          {
              "parent": "sample_category",
              "child": "sample_subcategory",
              "options": {
                  "727000000": [
                      "727000000",
                      "727000001",
                      "727000002"
                  ],
                  "727000001": [
                      "727000003",
                      "727000004",
                      "727000005",
                      "727000006",
                      "727000007"
                  ]
              }
        
          },
          {
              "parent": "sample_subcategory",
              "child": "sample_type",
              "options": {
                  "727000000": [
                      "727000000",
                      "727000002",
                      "727000003"
                  ],
                  "727000001": [
                      "727000004",
                      "727000005",
                      "727000006"
                  ],
                  "727000002": [
                      "727000007",
                      "727000008",
                      "727000009",
                      "727000010"
                  ],
                  "727000003": [
                      "727000011",
                      "727000012",
                      "727000013",
                      "727000014"
                  ],
                  "727000004": [
                      "727000015",
                      "727000016",
                      "727000017",
                      "727000018"
                  ],
                  "727000005": [
                      "727000019",
                      "727000020",
                      "727000021",
                      "727000022"
                  ],
                  "727000006": [
                      "727000023",
                      "727000024",
                      "727000025",
                      "727000026"
                  ],
                  "727000007": [
                      "727000027",
                      "727000028",
                      "727000029"
                  ]
              }
          }
        ]
  1. Create a JavaScript web resource named sample_SDK.DependentOptionSetSample.js using the following code.

    //If the SDK namespace object is not defined, create it.
    if (typeof (SDK) == "undefined")
    { SDK = {}; }
    // Create Namespace container for functions in this library;
    SDK.DependentOptionSet = {};
    SDK.DependentOptionSet.config = null;
    
    /**
     * @function SDK.DependentOptionSet.init
     * @param {string} webResourceName the name of the JavaScript web resource containing the JSON definition 
     * of option dependencies
     */
    SDK.DependentOptionSet.init = function (webResourceName) {
        if (SDK.DependentOptionSet.config == null) {
            //Retrieve the JavaScript Web Resource specified by the parameter passed
            var clientURL = Xrm.Page.context.getClientUrl();
    
            var pathToWR = clientURL + "/WebResources/" + webResourceName;
            var xhr = new XMLHttpRequest();
            xhr.open("GET", pathToWR, true);
            xhr.onreadystatechange = function () {
                if (this.readyState == 4 /* complete */) {
                    this.onreadystatechange = null;
                    if (this.status == 200) {
                        SDK.DependentOptionSet.config = JSON.parse(this.response);
                        SDK.DependentOptionSet.completeInitialization();
                    }
                    else {
                        throw new Error("Failed to load configuration data for dependent option sets.");
                    }
                }
            };
            xhr.send();
        }
        else {
            SDK.DependentOptionSet.completeInitialization();
        }
    };
    
    /**
     * @function SDK.DependentOptionSet.completeInitialization
     * Initializes the dependent option set options when the form loads
     */
    SDK.DependentOptionSet.completeInitialization = function () {
        //If the parent field is null, make sure the child field is null and disabled
        // Otherwise, call fireOnChange to filter the child options
        for (var i = 0; i < SDK.DependentOptionSet.config.length; i++) {
            var parentAttribute = Xrm.Page.getAttribute(SDK.DependentOptionSet.config[i].parent);
            var parentFieldValue = parentAttribute.getValue();
            if (parentFieldValue == null || parentFieldValue == -1) {
                var childAttribute = Xrm.Page.getAttribute(SDK.DependentOptionSet.config[i].child);
                childAttribute.setValue(null);
                childAttribute.controls.forEach(function (c) { c.setDisabled(true); });
            }
            else {
                parentAttribute.fireOnChange();
            }
        }
    }
    
    /**
     * @function SDK.DependentOptionSet.filterDependentField
     * Locates the correct set of configurations
     * @param {string} parentFieldParam The name of the parent field
     * @param {string} childFieldParam The name of the dependent field
     */
    SDK.DependentOptionSet.filterDependentField = function (parentFieldParam, childFieldParam) {
        //Looping through the array of all the possible dependency configurations
        for (var i = 0; i < SDK.DependentOptionSet.config.length; i++) {
    
            var dependentOptionSet = SDK.DependentOptionSet.config[i];
    
            /* Match the parameters to the correct dependent optionset mapping*/
            if ((dependentOptionSet.parent == parentFieldParam) &&
                (dependentOptionSet.child == childFieldParam)) {
    
    /*
    * Using setTimeout to allow a little time between calling this potentially recursive function.
    * Without including some time between calls, the value at the end of the chain of dependencies 
    * was being set to null on form load.
    */
                setTimeout(SDK.DependentOptionSet.filterOptions,
                    100, parentFieldParam,
                    childFieldParam,
                    dependentOptionSet);    
            }
        }
    };
    
    /**
     * @function SDK.DependentOptionSet.filterOptions
     * Filters options available in dependent fields when the parent field changes
     * @param {string} parentFieldParam The name of the parent field
     * @param {string} childFieldParam The name of the dependent field
     * @param {object} dependentOptionSet The configuration data for the dependent options
     */
    SDK.DependentOptionSet.filterOptions = function (parentFieldParam, childFieldParam, dependentOptionSet)
    {
        /* Get references to the related fields*/
        var parentField = Xrm.Page.getAttribute(parentFieldParam);
        var parentFieldValue = parentField.getValue();
        var childField = Xrm.Page.getAttribute(childFieldParam);
        /* Capture the current value of the child field*/
        var currentChildFieldValue = childField.getValue();
        /* If the parent field is null, set the Child field to null */
        //Interactive Service Hub, CRM for Tablets & CRM for phones can return -1 when no option selected
        if (parentFieldValue == null || parentFieldValue == -1) {
            childField.setValue(null);
            childField.fireOnChange(); //filter any dependent optionsets
            // Any attribute may have any number of controls
            // So disable each instance
            childField.controls.forEach(function (c) {
                c.setDisabled(true);
            });
            //Nothing more to do when parent attribute is null
            return;
        }
    
        //The valid child options defined by the configuration
        var validOptionValues = dependentOptionSet.options[parentFieldValue.toString()];
    
        //When the parent field has a value
        //Any attribute may have more than one control in the form,
        // So iterate over each one
        childField.controls.forEach(function (c) {
            c.setDisabled(false);
            c.clearOptions();
            //The attribute contains the valid options
            var childFieldAttribute = c.getAttribute();
    
            //The attribute options for the Interactive Service Hub, CRM for Tablets & 
            // CRM for phones clients do not include a definition for an unselected option.
            // This will add it
            if (Xrm.Page.context.client.getClient() == "Mobile") {
                c.addOption({ text: "", value: -1 });
            }
    
            //For each option value, get the definition from the attribute and add it to the control.
                validOptionValues.forEach(function (optionValue) {
                    //Get the option defnition from the attribute
                    var option = childFieldAttribute.getOption(parseInt(optionValue));
                    //Add the option to the control
                    c.addOption(option);
                })
        });
        //Set the value back to the current value if it is a valid value.                
        if (currentChildFieldValue != null &&
            validOptionValues.indexOf(currentChildFieldValue.toString()) > -1) {
            childField.setValue(currentChildFieldValue);
        }
        else {
            //Otherwise set it to null
            childField.setValue(null);
            childField.fireOnChange(); //filter any other dependent optionsets
        }
    }
    
  2. Add the sample_SDK.DependentOptionSetSample.js Script web resource to the JavaScript libraries available for the form.

  3. In the Onload event for the form, configure the event handler to call the SDK.DependentOptionSet.init function and pass in the name of the JavaScript web resource containing the JSON configuration data as a parameter. Use the field on the Handler Properties dialog box to enter: "sample_TicketDependentOptionSetConfig.js" into the field Comma separated list of parameters that will be passed to the function.

  4. In the OnChange event for the Category field, set the Function to SDK.DependentOptionSet.filterDependentField.

    In the Comma separated list of parameters that will be passed to the function text box enter: "sample_category", "sample_subcategory".

  5. In the OnChange event for the Sub Category field, set the Function to SDK.DependentOptionSet.filterDependentField.

    In the Comma separated list of parameters that will be passed to the function text box enter: "sample_subcategory ", "sample_type".

  6. Save and publish all customizations.

See Also

Use the Xrm.Page object model
Write code for Microsoft Dynamics 365 forms
Use JavaScript with Microsoft Dynamics 365
Customize entity forms
Xrm.Page.data.entity attribute (client-side reference)
Xrm.Page.ui control (client-side reference)

Microsoft Dynamics 365

© 2016 Microsoft. All rights reserved. Copyright