D365 OptionSet filtering

In Dynamics365 (aka Microsoft CRM, aka PowerApps) most lookup fields (lists) are lookups to Entities, and there is plenty of code and great examples which show how to filter a lookup (dropdown) list, based on another form field (onChange).

However, filtering an OptionSet is a whole different matter. An OptionSet isn’t stored data (list), it is a discrete set (think: address types, Counties/States/Provinces). OptionSets aren’t even listed in the same tree as Entities. Which also makes them a pain in the wazoo to find and maintain.

If you need to do something extra with an OptionSet lookup, Microsoft does have some decent documentation, which begins to scratch the surface, but the demo code is lacking, and goog/bing/stackover didn’t help either. It took me a day or two to figure out this stuff on my own. So I thought I would share some demo code, and hopefully save someone else a bunch of time.

First, in your form’s OnLoad event, use an event handler (calls a function) to filter an OptionSet.
Make sure you only bind the event once. Otherwise it will run (as many times as you added bindings) and waste a bunch of performance.

function onload(executionContext) {
   var formContext = executionContext.getFormContext();
   formContext.getAttribute("demo_address").addOnChange(applyAddressTypeFilter);
}

If you only want to remove items, just use .removeOption(). However, once you’ve removed an option, you can’t magically get it back without refreshing the form and losing any changes. Instead, you need to lookup the entire OptionSet and iterate though it

function applyAddressTypeFilter(executionContext) {
    var formContext = executionContext.getFormContext();
    var addressType = formContext.getControl("demo_addresstype");
    //Note: oddly, you cannot lookup an OptionSet directly. 
    //    You need to get it from an entity which uses that OptionSet
    const entityName = "demo_address";
    const optionSetName = "demo_addresstypes";

    //get the full OptionSet name/value pairs from the server
    var query = Xrm.Utility.getGlobalContext().getClientUrl() + "/api/data/v9.2" +
        "/EntityDefinitions(LogicalName='" + entityName + "')" +
        "/Attributes(LogicalName='" + optionSetName + "')" +
        "/Microsoft.Dynamics.CRM.PicklistAttributeMetadata" +
        "?$select=LogicalName&$expand=GlobalOptionSet($select=Options)";
    fetch(query, {
       method: "GET",
       headers: {
        "OData-MaxVersion": "4.0",
        "OData-Version": "4.0",
        "Content-Type": "application/json; charset=utf-8",
        "Accept": "application/json",
        "Prefer": "odata.include-annotations=OData.Community.Display.V1.FormattedValue"
       }
     }).then(function success(response) {
           return response.json().then((json) => 
           { if (response.ok) { return [response, json]; } 
             else { throw json.error; } 
           });
     }).then(function (responseObjects) {
        var responseBody = responseObjects[1];
        var options = responseBody.GlobalOptionSet.Options;

        addressType.clearOptions();
        for (var i = 0; i < options.length; i++) {
            //I'm loading all of these, but you could filter
            //if (options[i].Value == some value like 100000005) ...

            //This next line is the magic sauce. 
            //MS left this out of their demo code. Argh!
            addressType.addOption( {
               value: options[i].Value, 
               text: options[i].Label.LocalizedLabels[0].Label 
            }); //the above line, what-daheck?! OptionSet text is buried deep
         }
     }).catch(function (error) {
         console.log(error.message);
     });
}

The tricky part of this one is the .addOption() function. You can’t just add a name/value pair and there is no CreateOption or OptionSet type. You just have to know that a new OptionSet is just an ordinary JS object that you make on-the-fly.

About Tim Golisch

I'm a geek. I do geeky things.
This entry was posted in PowerApps, Programming and tagged , , , , . Bookmark the permalink.

Leave a comment