Sample: Create dependent OptionSets (picklists)

 

Applies To: Dynamics CRM 2015

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 JScript library, form events, and an XML web resource.

To observe and verify the functionality of this sample you can install the DependentOptionSetsSample_1_0_0_2_managed.zip managed solution from the following location in the SDK download: SDK\SampleCode\JS\FormScripts

Download the Microsoft Dynamics CRM SDK package.

Goals for this Solution

This solution is intended to meet the following requirements:

  • It provides a generic, reusable JScript 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 in an XML web resource. This allows for changing the option mappings without changing the code. Editing an XML 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 Vista

Value:727000008 Label: Windows 7

Value:727000009 Label: Windows Server 2003

Value:727000010 Label: Windows Server 2008

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-XYZ 17 inch

Value:727000020 Label: LCD-XYZ 17 inch

Value:727000021 Label: LCD-XYZ 21 inch

Value:727000022 Label: LCD-XYZ 24 inch

Value:727000006 Label: Printer

Value:727000023 Label:Series 1000 Printer - Private

Value:727000024 Label: Series 2000 Color Printer - Private

Value:727000025 Label: Series 9000 Printer - Shared

Value:727000026 Label: Series 9000 Color Printer - Shared

Value:727000007 Label: Telephone

Value:727000027 Label: PSTN Phone

Value:727000028 Label: IP Phone

Value:727000029 Label: Mobile Phone

Enable filtering

  1. Convert the desired filtering of options into the following XML document and upload it as an XML web resource titled sample_TicketDependentOptionSetConfig.xml. The label values are included to make the document easier to edit but are not used in the script that filters the options.

    
    <DependentOptionSetConfig entity="sample_ticket" >
     <ParentField id="sample_category"
                  label="Category">
      <DependentField id="sample_subcategory"
                      label="Sub Category" />
      <Option value="727000000"
              label="Software">
       <ShowOption value="727000000"
                   label="Personal Productivity" />
       <ShowOption value="727000001"
                   label="Business Applications" />
       <ShowOption value="727000002"
                   label="Operating Systems" />
      </Option>
      <Option value="727000001"
              label="Hardware">
       <ShowOption value="727000003"
                   label="Desktop Computer" />
       <ShowOption value="727000004"
                   label="Laptop Computer" />
       <ShowOption value="727000005"
                   label="Monitor" />
       <ShowOption value="727000006"
                   label="Printer" />
       <ShowOption value="727000007"
                   label="Telephone" />
      </Option>
     </ParentField>
     <ParentField id="sample_subcategory"
                  label="Sub Category">
      <DependentField id="sample_type"
                      label="Type" />
      <Option value="727000000"
              label="Personal Productivity">
       <ShowOption value="727000000"
                   label="Word Processor" />
       <ShowOption value="727000001"
                   label="Spreadsheet" />
       <ShowOption value="727000002"
                   label="Internet Browser" />
       <ShowOption value="727000003"
                   label="E-mail" />
      </Option>
      <Option value="727000001"
              label="Business Applications">
       <ShowOption value="727000004"
                   label="Customer Relationship Management" />
       <ShowOption value="727000005"
                   label="Enterprise Resource Management" />
       <ShowOption value="727000006"
                   label="Human Resource Managment" />
      </Option>
      <Option value="727000002"
              label="Operating Systems">
       <ShowOption value="727000007"
                   label="Windows Vista" />
       <ShowOption value="727000008"
                   label="Windows 7" />
       <ShowOption value="727000009"
                   label="Windows Server 2003" />
       <ShowOption value="727000010"
                   label="Windows Server 2008" />
      </Option>
      <Option value="727000003"
              label="Desktop Computer">
       <ShowOption value="727000011"
                   label="Workstation x1000" />
       <ShowOption value="727000012"
                   label="Workstation x2000" />
       <ShowOption value="727000013"
                   label="Workstation x3000" />
       <ShowOption value="727000014"
                   label="Workstation x4000" />
      </Option>
      <Option value="727000004"
              label="Laptop Computer">
       <ShowOption value="727000015"
                   label="Laptop 1000 series" />
       <ShowOption value="727000016"
                   label="Laptop 2000 series" />
       <ShowOption value="727000017"
                   label="Laptop 3000 series" />
       <ShowOption value="727000018"
                   label="Laptop 4000 series" />
      </Option>
      <Option value="727000005"
              label="Monitor">
       <ShowOption value="727000019"
                   label="CRT-XYZ 17 inch" />
       <ShowOption value="727000020"
                   label="LCD-XYZ 17 inch" />
       <ShowOption value="727000021"
                   label="LCD-XYZ 21 inch" />
       <ShowOption value="727000022"
                   label="LCD-XYZ 24 inch" />
      </Option>
      <Option value="727000006"
              label="Printer">
       <ShowOption value="727000023"
                   label="Series 1000 Printer - Private" />
       <ShowOption value="727000024"
                   label="Series 2000 Color Printer - Private" />
       <ShowOption value="727000025"
                   label="Series 9000 Printer - Shared" />
       <ShowOption value="727000026"
                   label="Series 9000 Color Printer - Shared" />
      </Option>
      <Option value="727000007"
              label="Telephone">
       <ShowOption value="727000027"
                   label="PSTN Phone" />
       <ShowOption value="727000028"
                   label="IP Phone" />
       <ShowOption value="727000029"
                   label="Mobile Phone" />
      </Option>
     </ParentField>
    </DependentOptionSetConfig>
    
  2. Create a JScript 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.init = function (webResourceName) {
     //Retrieve the XML 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.setRequestHeader("Content-Type", "text/xml");
     xhr.onreadystatechange = function () { SDK.DependentOptionSet.completeInitialization(xhr); };
     xhr.send();
    };
    SDK.DependentOptionSet.completeInitialization = function (xhr) {
     if (xhr.readyState == 4 /* complete */) {
         if (xhr.status == 200) {
             xhr.onreadystatechange = null; //avoids memory leaks
       var JSConfig = [];
       var ParentFields = xhr.responseXML.documentElement.getElementsByTagName("ParentField");
       for (var i = 0; i < ParentFields.length; i++) {
        var ParentField = ParentFields[i];
        var mapping = {};
        mapping.parent = ParentField.getAttribute("id");
        mapping.dependent = SDK.Util.selectSingleNode(ParentField, "DependentField").getAttribute("id");
        mapping.options = [];
        var options = SDK.Util.selectNodes(ParentField, "Option");
        for (var a = 0; a < options.length; a++) {
         var option = {};
         option.value = options[a].getAttribute("value");
         option.showOptions = [];
         var optionsToShow = SDK.Util.selectNodes(options[a], "ShowOption");
         for (var b = 0; b < optionsToShow.length; b++) {
          var optionToShow = {};
          optionToShow.value = optionsToShow[b].getAttribute("value");
          optionToShow.text = optionsToShow[b].getAttribute("label");
          option.showOptions.push(optionToShow);
         }
         mapping.options.push(option);
        }
        JSConfig.push(mapping);
       }
       //Attach the configuration object to DependentOptionSet
       //so it will be available for the OnChange events 
       SDK.DependentOptionSet.config = JSConfig;
       //Fire the onchange event for the mapped optionset fields
       // so that the dependent fields are filtered for the current values.
       for (var depOptionSet in SDK.DependentOptionSet.config) {
        var parent = SDK.DependentOptionSet.config[depOptionSet].parent;
        Xrm.Page.data.entity.attributes.get(parent).fireOnChange();
       }
      }
     }
    };
     // This is the function set on the onchange event for 
     // parent fields
    SDK.DependentOptionSet.filterDependentField = function (parentField, childField) {
     for (var depOptionSet in SDK.DependentOptionSet.config) {
      var DependentOptionSet = SDK.DependentOptionSet.config[depOptionSet];
      /* Match the parameters to the correct dependent optionset mapping*/
      if ((DependentOptionSet.parent == parentField) &amp;&amp; (DependentOptionSet.dependent == childField)) {
       /* Get references to the related fields*/
       var ParentField = Xrm.Page.data.entity.attributes.get(parentField);
       var ChildField = Xrm.Page.data.entity.attributes.get(childField);
       /* Capture the current value of the child field*/
       var CurrentChildFieldValue = ChildField.getValue();
       /* If the parent field is null the Child field can be set to null */
       if (ParentField.getValue() == null) {
        ChildField.setValue(null);
        ChildField.setSubmitMode("always");
        ChildField.fireOnChange();
    
        // Any attribute may have any number of controls
        // So disable each instance
        var controls = ChildField.controls.get()
    
        for (var ctrl in controls) {
         controls[ctrl].setDisabled(true);
        }
        return;
       }
    
       for (var os in DependentOptionSet.options) {
        var Options = DependentOptionSet.options[os];
        var optionsToShow = Options.showOptions;
        /* Find the Options that corresponds to the value of the parent field. */
        if (ParentField.getValue() == Options.value) {
         var controls = ChildField.controls.get();
         /*Enable the field and set the options*/
         for (var ctrl in controls) {
          controls[ctrl].setDisabled(false);
          controls[ctrl].clearOptions();
    
          for (var option in optionsToShow) {
           controls[ctrl].addOption(optionsToShow[option]);
          }
    
         }
         /*Check whether the current value is valid*/
         var bCurrentValueIsValid = false;
         var ChildFieldOptions = optionsToShow;
    
         for (var validOptionIndex in ChildFieldOptions) {
          var OptionDataValue = ChildFieldOptions[validOptionIndex].value;
    
          if (CurrentChildFieldValue == OptionDataValue) {
           bCurrentValueIsValid = true;
           break;
          }
         }
         /*
         If the value is valid, set it.
         If not, set the child field to null
         */
         if (bCurrentValueIsValid) {
          ChildField.setValue(CurrentChildFieldValue);
         }
         else {
          ChildField.setValue(null);
         }
         ChildField.setSubmitMode("always");
         ChildField.fireOnChange();
         break;
        }
       }
      }
     }
    };
    
    SDK.Util = {};
    //Helper methods to merge differences between browsers for this sample
     SDK.Util.selectSingleNode = function (node, elementName) {
      if (typeof (node.selectSingleNode) != "undefined") {
       return node.selectSingleNode(elementName);
      }
      else {
       return node.getElementsByTagName(elementName)[0];
      }
     };
     SDK.Util.selectNodes = function (node, elementName) {
      if (typeof (node.selectNodes) != "undefined") {
       return node.selectNodes(elementName);
      }
      else {
       return node.getElementsByTagName(elementName);
      }
     };
    
  3. Add the sample_SDK.DependentOptionSetSample.js Script web resource to the JScript libraries available for the form.

  4. 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 XML web resource as a parameter. Use the field on the Handler Properties dialog box to enter: "sample_TicketDependentOptionSetConfig.xml" into the field Comma separated list of parameters that will be passed to the function.

  5. 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".

  6. 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".

  7. Save and publish all customizations.

See Also

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

© 2016 Microsoft. All rights reserved. Copyright