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 |
Value:727000000 |
Value:727000000 |
Value:727000001 |
||
Value:727000002 |
||
Value:727000003 |
||
Value:727000001 |
Value:727000004 |
|
Value:727000005 |
||
Value:727000006 |
||
Value:727000002 |
Value:727000007 |
|
Value:727000008 |
||
Value:727000009 |
||
Value:727000010 |
||
Value:727000001 |
Value:727000003 |
Value:727000011 |
Value:727000012 |
||
Value:727000013 |
||
Value:727000014 |
||
Value:727000004 |
Value:727000015 |
|
Value:727000016 |
||
Value:727000017 |
||
Value:727000018 |
||
Value:727000005 |
Value:727000019 |
|
Value:727000020 |
||
Value:727000021 |
||
Value:727000022 |
||
Value:727000006 |
Value:727000023 |
|
Value:727000024 |
||
Value:727000025 |
||
Value:727000026 |
||
Value:727000007 |
Value:727000027 |
|
Value:727000028 |
||
Value:727000029 |
Enable filtering
- 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"
]
}
}
]
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 } }
Add the sample_SDK.DependentOptionSetSample.js Script web resource to the JavaScript libraries available for the form.
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.
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".
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".
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