If you were to imagine a custom form in Microsoft Dynamics CRM where you could select any entity and any field on that entity you would realize a few things about option sets.

  1. To enter and support every entity and field would be a daunting and impossible task.
  2. Even if you were to spend the time tackling the previous hurdle, any customizations you made to those entities would have to be tracked and re-applied in the option sets, thereby duplicating your workload.
  3. Option sets do not inherently support dynamic lists.

But wait, you might say, there’s an example of how to dynamically modify the option set with JavaScript that ships with the SDK. This example still relies on the idea that you will have pre-filled the option set with all the possible options and then will remove the ones you no longer want. This really wasn’t a viable solution for what we were trying to do either.

The solution we came up with was to utilize single line of text fields hidden on the form to store the values for our option sets. We’ll demonstrate this with one mapping field. So let’s start by setting up our form.

We create a new entity with the following fields:

  • Mapping Field (blank option set)
  • Selected Mapping Field (single line of text)

We’ll add the fields to our form and leave the “Selected” fields visible for now:

dynamic option sets: mapping field

Now we can dive into the JavaScript for populating our Mapping Entity field. It should be noted that the code has been written for readability. If you do plan on implementing this there are places where refactoring should be performed and efficiency improved.

function getAttributes(entityName) {

    var lookupFields = Xrm.Page.ui.controls.get("po_mappingfield");

    try {
        var xml = "" +
        "<!--?xml version="1.0" encoding="utf-8"?-->" +
        "" +
        "" +
        "" +
        "" +
        "" +
        "" +
        "EntityFilters" +
        "Attributes" +
        "" +
        "" +
        "MetadataId" +
        "00000000-0000-0000-0000-000000000000" +
        "" +
        "" +
        "RetrieveAsIfPublished" +
        "true" +
        "" +
        "" +
        "LogicalName" +
        "" + entityName + "" +
        "" +
        "" +
        "" +
        "RetrieveEntity" +
        "" +
        "" +
        "" +
        "" + "";

        var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");

        xmlHttpRequest.Open("POST", "/XRMServices/2011/Organization.svc/web", false);
        xmlHttpRequest.setRequestHeader("Accept", "application/xml, text/xml, */*");
        xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
        xmlHttpRequest.send(xml);

        var doc = xmlHttpRequest.responseXML;
        var nodeList = doc.selectNodes("//c:AttributeMetadata");
        for (i = 0; i < nodeList.length; i++) {
            xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.loadXML(nodeList[i].xml);
            if (xmlDoc.selectSingleNode("//c:DisplayName/a:UserLocalizedLabel/a:Label") != null) {
                var display = xmlDoc.selectSingleNode("//c:DisplayName/a:UserLocalizedLabel/a:Label").text;
                var value = xmlDoc.selectSingleNode("//c:LogicalName").text;
                var option = document.createElement("OPTION");
                option.value = option.innerText = value;
                option.text = option.innerText = display;
                lookupFields.addOption(option);

            }
        }
    }
    catch (err) {
        alert("Script encountered error: " + err.name + "nMessage: " + err.message);
    }
}

The XML request in this function retrieves all available attributes of the entity that is passed into the function. After we’ve received the XML response back from the request, we loop through each attribute node in the document and use XPath statements to grab both the display and schema name.

Finally, we create option elements, fill out their value and text attributes and add it to our option set.

Now that we have a helper method, which will populate our control, we need to call it from an event and pass it a value. For this example, we’ll utilize the onLoad functionality of our form and hardcode the value lead to be passed to our helper method.

 function OnLoadGetAttributes()
{
    getAttributes("lead");

    $("#po_mappingfield").html($("option", "#po_mappingfield").sort(function (a, b) {
        return a.text == b.text ? 0 : a.text < b.text ? -1 : 1
    }));

    $("#po_mappingfield option:eq(0)").attr("selected", "selected");

}

In addition to calling our helper method, we utilize jQuery to sort our returned results for us. Then we ensure the dropdown field is set to the default blank option.

Now we’ll add our JavaScript and the jQuery library to our solution as a web resource, add the libraries to our source and bind our form onLoad event to the OnLoadGetAttributes method.

Once published, you should be able to create a new mapping entity from CRM with a dropdown that is populated with all available attribute or field names:

Now that we’ve got the values in a dropdown, we need a way to store them. Since CRM does not have an option set tied to the back of this, in order for those values to persist, we’ll need to affix another method to the onChange event of the po_mappingfield dropdown:

function onLookupFieldChange() {
    var fieldLookup = document.getElementById("po_mappingfield");
    var field = fieldLookup.options[fieldLookup.selectedIndex].value;

    var selectedMappingField = Xrm.Page.data.entity.attributes.get("po_selectedmappingfield");
    selectedMappingField.setValue(field);
}

You might notice here that we’re utilizing generic JavaScript to access the Mapping Field dropdown rather than the CRM provided getValue function. Since this isn’t a real option set as far as CRM is concerned, it won’t pass back any value.

After adding our new function to our JavaScript web resource, we’ll make sure to call the function on the onChange event of the Mapping Field.

If you create a new Mapping Entity and select an option from the dropdown, you should now see the Selected Mapping Entity field populated with the schema name of the attribute:

This new value will persist when saving the entity. We can now hide this field if we wished too. It is now just a friendly storage site on your form.

There’s one piece of the puzzle left and that’s how to handle when the page loads. If we were to load the page as it exists, our hidden field would contain the schema name of our attribute, but the dropdown would only have the default value selected.

On our current onLoad function, we can add a check to determine if a Selected Mapping Field exists:

function OnLoadGetAttributes()
{
    getAttributes("lead");

    $("#po_mappingfield").html($("option", "#po_mappingfield").sort(function (a, b) {
        return a.text == b.text ? 0 : a.text < b.text ? -1 : 1
    }));

    var selectedMapping = Xrm.Page.data.entity.attributes.get("po_selectedlookupfield");

    if (selectedMapping.getValue() != null) {
        $('#po_mappingfield option[value="' + selectedMapping.getValue() + '"]').prop('selected', true);
    }
    else {
        $("#po_mappingfield option:eq(0)").attr("selected", "selected");
    }
}

Now when our page loads, not only will the dropdown be populated, but it will have selected the proper attribute if we have one stored in our Selected Mapping Field.

Bam!!! There you have it. Hope this helps you as it has the CRM Experts at PowerObjects.

Happy CRM’ing!

  • CRMGuy

    This is not working for me, parentNode is undefined error: CRM 2013 (On-Premise)
    Can you provide an updated code for CRM 2013
    thanks

Return to Top ▲Return to Top ▲