Dynamics 365 System Fields Challenge: Disabling the Owner Field with JavaScript

When working with Dynamics 365, most custom fields behave exactly as you’d expect. For instance, if you want to disable a custom field on a form, a simple call like this works seamlessly:

formContext.getControl("custom_fieldname").setDisabled(true);

However, when it comes to system fields like the Owner field (ownerid) you might run into unexpected behavior. In this post, we’ll explore why this happens and how to work around it.


The Issue with System Fields

Dynamics 365 uses system fields such as ownerid to manage essential data (in this case, the record owner). Unlike most custom fields, system fields like ownerid often appear in multiple places on a form. For example, the Owner field can be displayed both in the main form body and in the header. This duplication means that the standard approach of retrieving a control with:

formContext.getControl("ownerid").setDisabled(false);

…can fail or only affect one instance of the control. In our case, you might see an error such as:

TypeError: Cannot read properties of null (reading 'setDisabled')

This error occurs because Dynamics 365 isn’t returning a single control reference for ownerid—or it’s returning null for one of the instances—making it necessary to handle these controls differently.


The Workaround: Iterating Over All Controls

The solution is to retrieve the attribute rather than a single control, then iterate over all associated controls. Each instance of the control can be accessed via the attribute’s controls collection. Here’s how you can do it:

formContext.getAttribute("ownerid").controls.forEach(function (ctrl) {
    ctrl.setDisabled(false);
});

Breaking It Down

  1. Retrieve the Attribute:

    • formContext.getAttribute("ownerid") fetches the attribute object, which represents the data for the Owner field across all form instances.
  2. Access All Controls:

    • Every control bound to the ownerid attribute is stored in the controls collection. This collection might include the header control, the body control, or any other instance present on the form.
  3. Iterate and Disable:

    • Using the forEach loop, we iterate through each control in the collection and call setDisabled(false) on it. This ensures that no matter where the Owner field appears on the form, it is enabled or disabled as intended.

Conclusion

Dynamics 365 provides robust capabilities for customizing forms, but system fields like Owner come with their own set of quirks. While custom fields handle setDisabled without issue, system fields might require you to iterate over multiple controls due to their multi-instance rendering on a form.

By retrieving the attribute and iterating over its controls, you can ensure that all instances of a system field like ownerid are properly updated, leading to a more consistent and error-free user experience. Understanding these nuances not only helps in solving current issues but also empowers you to better manage other system fields in Dynamics 365.

Dynamics 365 WebAPI Asynchronous Calls

Column Editing Text Editor Editing Dynamics 365 development is a constantly evolving field, with the JavaScript API being no exception. Exciting advancements have been made in this space, such as the introduction of WebApi’s method, which allows for basic CRUD operations (Link).

Gone are the days of worrying about including JSON files or calling jQuery libraries just to make simple API calls. The platform now provides straightforward methods for performing basic CRUD operations.

To illustrate this, here’s a simple example. I utilized the fantastic REST Builder tool to generate the code. This code searches for an email address and retrieves the full name of the corresponding contact record.

function FindContactRecord(email) {
    Xrm.WebApi.online.retrieveMultipleRecords("contact", "?$select=fullname&$filter=(emailaddress1 eq '" + email + "')&$top=1").then(
        function success(results) {
            var result = results.entities[0];
            var fullname = result["fullname"];
            ProcessData(fullname);
        },
        function (error) {
            console.log(error.message);
        }
    );
}

The above code works great for a single record, since the ProcessData function gets invoked in the success function of the WebAPI call. When we start to do bulk operations, the code starts executing asynchronously. Here is an example of calling the function multiple times. This code is only for illustrations purposes. If we were to query multiple contact records, our best practice is to batch multiple email addresses into a single WebAPI call.

function FindContactRecords(EmailArray) {
    var contactFullnames = new Array();
    for (var i = 0; i < EmailArray.length; i++) {
        contactFullnames.push(FindContactRecord(email[i].trim()));
    }
    return contactFullnames;
}

function FindContactRecord(email) {
    Xrm.WebApi.online.retrieveMultipleRecords("contact", "?$select=fullname&$filter=(emailaddress1 eq '" + email + "')&$top=1").then(
        function success(results) {
            var result = results.entities[0];
            var fullname = result["fullname"];
            return (fullname);
        },
        function (error) {
            console.log(error.message);
        }
    );
}

//Main function 
var contactFullnames = FindContactRecords(EmailArray);
ProcessData(contactFullnames);

The main issue we face with the above code is that the contactFullname array will not return elements since JavaScript engine is executing the WebAPI calls asynchronously for performance reasons.

So how do we fix this problem? By using async, await and then statements, JavaScript will return a promise which will not execute subsequent code until the async code completes processing. The changes in blue illustrates the required changes.

async function FindContactRecords(EmailArray){
    var contactFullnames = new Array();
    for (var i = 0; i < EmailArray.length; i++) {
       await contactFullnames.push(FindContactRecord(email[i].trim()));
    }
    return contactFullnames;
}

async function FindContactRecord(email) {
    await Xrm.WebApi.online.retrieveMultipleRecords("contact", "?$select=fullname&$filter=(emailaddress1 eq '" + email + "')&$top=1").then(
        function success(results) {
            var result = results.entities[0];
            var fullname = result["fullname"];
            return (fullname);
        },
        function (error) {
            console.log(error.message);
        }
    );
}

//Main function 
FindContactRecords(EmailArray).then(
    contactFullnames => ProcessData(contactFullnames)
);

There are many articles that discusses async/await, but how to applies to Dynamics 365 is not something well covered. I hope this  article helps.