Sample: Create dependent OptionSets (picklists)
Applies To: Dynamics CRM 2013
It is 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 achieve this requirement with a re-usable 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 files: SDK\SampleCode\JS\FormScripts
Goals for this Solution
This solution is intended to meet the following requirements:
It provides a generic, re-usable JScript library that can be used for any pair of option set fields.
It allows for a chain of dependent option set fields. Because each dependent option set field options are filtered based on the value of another field, additional option set fields 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 |
To Enable Filtering
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>
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) && (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); } };
Add the sample_SDK.DependentOptionSetSample.js Script web resource to the JScript 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 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.
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.
When you open the Ticket entity form you will find that the Sub Category and Type options are filtered depending on the values chosen for their respective option sets.
See Also
Use the Xrm.Page object model
Write code for Microsoft Dynamics CRM 2013 forms
Use JavaScript with Microsoft Dynamics CRM 2013
Customize entity forms
Xrm.Page.data.entity attribute (client-side reference)
Xrm.Page.ui control (client-side reference)