Swimburger

Delaying JavaScript Execution Until HTML Elements are Present in Power Apps and Dynamics CRM

Niels Swimberghe

Niels Swimberghe - - Dynamics

Follow me on Twitter, buy me a coffee

Delaying JavaScript Execution Until HTML Elements are Present in Power Apps and Dynamics CRM

This blog post was written for MetroStar Systems and originally published at MetroStar's blog here.

You can customize forms using JavaScript in Model-Driven Power Apps forms and Dynamics CRM forms, but your form will run your JavaScript way before the form is ready to be customized.

Even when the form triggers the "loaded" event, that doesn't guarantee that the HTML elements you want to customize are available. If you're working with tabs, then the content on other tabs is only loaded as you switch to that tab. There's an event to respond when a tab switch occurs, but not when the content of the tab is "loaded." This makes it hard to know when to run JavaScript to customize the HTML generated by the form.

The reason this may be difficult is likely because the software wants to discourage you from interacting with the HTML directly. But sometimes the built-in APIs are not sufficient for your needs, and you will want to directly modify the DOM (Document Object Model). 

Let's look at an example:

Building a Password Field Toggle #

Picture this: A stakeholder asks for a new feature where the Social Security Number (SSN) is automatically masked by asterisks, but they want to be able to click on a button to unmask the SSN when needed. 

Trying to Query the Input #

You can add the SSN text field to a form and find out the HTML ID for the input by digging through the DOM in your browser's developer tools; however, when you try to get the element by ID using JavaScript, you will receive "null" instead (example below). 

document.getElementById('your_input_id') // => null

This JavaScript may be running inside of an iframe for security reasons, so you should also try the top frame like this:

window.top.document.getElementById('your_input_id') // => null

Unfortunately, it will still be "null" because the control hasn't been rendered by the time of execution. What if you run the code when the form is loaded (seen below)? 

Xrm.Page.data.addOnLoad(() => window.top.document.getElementById('your_input_id')) // => still null

You can already start to interact with the form through the Xrm APIs, but the element still hasn't been rendered "onLoad." As you can see, it is quite hard to develop this feature. Nevertheless, with some extra JavaScript, you can work around this roadblock.

Polling for Node Presence #

Since there isn't an event you can rely on to ensure the availability of your input, you have to periodically check if it is available. Here's a script that takes an array of ID's that will trigger the callback when all nodes for the ID's can be found using "document.getElementById":

function waitForElementsToExist(elementIds, callback, options) {
    options = Object.assign({
        checkFrequency: 500, // check for elements every 500 ms
        timeout: null, // after checking for X amount of ms, stop checking
    }, options);

    // poll every X amount of ms for all DOM nodes
    let intervalHandle = setInterval(() => {
        let doElementsExist = true;
        for (let elementId of elementIds) {
            let element = window.top.document.getElementById(elementId);
            if (!element) {
                // if element does not exist, set doElementsExist to false and stop the loop
                doElementsExist = false;
                break;
            }
        }

        // if all elements exist, stop polling and invoke the callback function 
        if (doElementsExist) {
            clearInterval(intervalHandle);
            if (callback) {
                callback();
            }
        }
    }, options.checkFrequency);

    if (options.timeout != null) {
        setTimeout(() => clearInterval(intervalHandle), options.timeout);
    }
}

Here's how you can use the function:

waitForElementsToExist(['your_input_id'], () => doStuffWithInput);
function doStuffWithInput() { }

By default, the function polls every 500ms, but you can change that by passing in an option. You can also set a timeout in case you don't want to keep polling forever. In most cases, it only makes sense to poll for a couple of seconds.

Here's how you can change the options to let the function poll every one second and timeout after five seconds:

waitForElementsToExist(['your_input_id'], () => doStuffWithInput, {checkFrequency: 1000,  timeout: 5000});

You can pass in multiple ID's in which case all nodes need to be found before the callback is invoked. You could simplify this by only accepting a single ID instead of an array but that depends on your needs.

You may now see that your SSN field is on a separate tab. In this case, you'd want to wrap this into a tabStateChange callback like this:

let yourTab = Xrm.Page.ui.tabs.getByName('your_tab_name');
yourTab.addTabStateChange(function () {
    if (yourTab.getDisplayState() === 'expanded') {
        waitForElementsToExist(['your_input_id'], () => doStuffWithInput, { checkFrequency: 500, timeout: 3000 });
    }
});

This snippet does the following:

  • Gets a tab with the name "your_tab_name"
  • Registers a tab state change event handler which will be triggered when the tab is shown or hidden
  • Inside the handler, it will run the polling code only when the tab's display state is "expanded"

Inside the callback, you can be certain that your input is rendered and you can start building your password toggle feature.

Summary #

When your JavaScript runs inside of Dynamics CRM forms, or Model-Driven Power Apps forms, certain HTML elements will not have been rendered yet. Even when querying for these elements "onLoad," you may not find them. By checking if these elements exist on an interval, you can quickly detect when certain DOM nodes have rendered.

Related Posts

Related Posts

Power Apps logo

How to Create Dataverse Activities using Power Automate

- Dynamics
In this Power Platform tutorial, you will set up an HTTP webhook using the "When an HTTP request is received" action. With the HTTP request JSON, you will calculate the duration between two timestamps and fetch a contact and user by finding matching phone numbers. Then, you will create a phone call and update the status of the phone call.
Azure Pipelines and Power Apps logo

How to deploy Power Apps Portals using Azure Pipelines

- Dynamics
Learn how to automatically deploy Power Apps portals using the Microsoft Power Platform CLI and Azure DevOps.
Azure Pipelines and Power Apps logo

How to deploy Power Apps Solutions using Azure Pipelines

- Dynamics
Learn how to automatically deploy Power Apps solutions using the Power Platform Build Tools and Azure DevOps.
CRM and PowerShell logo

Use PowerShell to communicate with Dynamics CRM using the .NET XRM SDK

- Dynamics
If you've developed client applications or plugins for Dynamics CRM before, you are familiar with the CRM/XRM DLL's. You may have gotten those DLL's from the CRM installation, the SDK zip, or the NuGet package. Another way to interact with the CRM DLL's is through PowerShell. PowerShell is built upon .NET meaning you can call exactly the same CRM libraries from PowerShell as from .NET applications.
CRM and PowerShell logo

PowerShell snippet: Get optionset value/labels from Dynamics CRM Entity/Attribute

- Dynamics
Instead of having to use the CRM interface to copy all the labels and values manually, you can save yourself a lot of time using this PowerShell script. Using the following script file named "GetOptionSet.ps1", you can list all the value/label pairs for a given Entity + OptionSet-Attribute: