DEV Community

Cover image for Automatically Refresh Forms After Status Changes in Model Driven Apps
Riccardo Gregori
Riccardo Gregori

Posted on

Automatically Refresh Forms After Status Changes in Model Driven Apps

When designing complex state models in Model Driven Apps, sometimes it can happen you need to "freeze" the possibility for the user to update a record data while some operation runs in background, and then make it editable again when the operation completes.

A few practical examples:

  • Document Generation and E-signature: A quote record triggers automatic PDF generation and sends it to DocuSign or Adobe Sign. While the document is being generated, sent, and awaiting signature, the contract terms should be locked. Once the signature is complete (or rejected), the form becomes editable again for next steps.
  • Credit Check Integration: When a customer service rep creates a new customer account, an external credit check service is called in the background. The form needs to be frozen while waiting for the credit score, payment terms recommendations, and risk assessment to return. You don't want users changing customer details while the credit bureau is still processing the original information.
  • Inventory Reservation in Order Processing: When an order is placed, a background process checks inventory across multiple warehouses and reserves stock. During this reservation process (which might involve calling external warehouse management systems), the order quantities should be frozen to prevent overselling or conflicts.
  • Compliance and Regulatory Checks: In healthcare or financial services, when a case is submitted, it might trigger automated compliance screenings (sanctions lists, fraud detection, regulatory reporting). The case details must remain locked during these checks to maintain audit trail integrity.
  • Data Enrichment from Third-party APIs: A lead record triggers enrichment processes pulling company data from LinkedIn, Clearbit, or Dun & Bradstreet. While these APIs are called and data is being validated and merged, you want to prevent users from manually entering conflicting information.

Typically, you can leverage the out of the box statecode and statuscode fields of the interested table to model the frozen statuses. If we take for instance the first example above, a possible state model for the account table can be the following:

State model representation

Logical Status statecode statuscode Description
New Active New The quote is being created and filled with data
Submitted Inactive Submitted The quote has been submitted for the digital signature, the background operation is running
Signed Active Signed The quote is open for post-signature steps (approvals, project generation, etc)
Cancelled Inactive Cancelled The quote has been logically removed from the system
Sent Inactive Sent Quote sent to the client
Won Inactive Won The client accepted the quote
Lost Inactive Lost The client has rejected the quote

Where the meaning of the statecode=Inactive is "not updatable by the user". I often prefer to change the label of that state to "Read only", as I find this wording less misleading and more intuitive.

If the trigger that changes the status to the inactive one comes from the form, we have a small UX problem: the form will be disabled (as required), the operation will run in the background (as required), but when the operation ends (after a couple of seconds, potentially), the user doesn't have any clue about the completeness. He needs to manually trigger the form refresh to see the changes to the current record.

Of course you can leverage MDA Push Notification system in the async workflow to notify the user but... he still needs to reopen the record to see the changes.

Fixing this UX issue is quite easy, you can just add a few lines of JavaScript in the form that:

  • runs only when the status changes to the inactive one you want to monitor
  • checks iteratively via WebApi for the status to change
  • when the status changes, refreshes the form data.

just like the following one:

class Form {
    formType = {
        Create: 1,
        Update: 2,
        ReadOnly: 3,
        Disabled: 4,
        BulkEdit: 6
    };

    statuscode = {
        New: 1,
        Submitted: 2
    }

    // Polling interval for status monitoring
    pollingInterval = null;

    onLoad(executionContext) {
        const formContext = executionContext.getFormContext();
        const formType = formContext.ui.getFormType();

        // Start auto-refresh monitoring if status is Initializing
        this.startStatusMonitoring(formContext);
        formContext.data.addOnLoad(() => {
            this.startStatusMonitoring(formContext);
        });
    }

    /**
     * Starts monitoring the record status if it's in Initializing state
     * @param {object} formContext - The form context
     */
    startStatusMonitoring(formContext) {
        const currentStatus = formContext.getAttribute("statuscode").getValue();

        // Only start monitoring if status is Submitted
        if (currentStatus !== this.statuscode.Submitted) {
            return;
        }

        console.log("Starting status monitoring for Initializing record");

        // Clear any existing interval
        if (this.pollingInterval) {
            clearInterval(this.pollingInterval);
        }

        // Start polling every 2 seconds
        this.pollingInterval = setInterval(async () => {
            try {
                await this.checkStatusChange(formContext);
            } catch (error) {
                console.error("Error checking status change:", error);
                // Continue polling even if there's an error
            }
        }, 2000);
    }

    /**
     * Checks if the record status has changed and refreshes the page if it has
     * @param {object} formContext - The form context
     */
    async checkStatusChange(formContext) {
        const recordId = formContext.data.entity.getId().replace(/[{}]/g, "");
        const entityName = formContext.data.entity.getEntityName();

        try {
            // Retrieve the current statuscode from the server
            const result = await Xrm.WebApi.retrieveRecord(entityName, recordId, "?$select=statuscode");
            const serverStatus = result.statuscode;
            const currentFormStatus = formContext.getAttribute("statuscode").getValue();

            console.log(`Current form status: ${currentFormStatus}, Server status: ${serverStatus}`);

            // If status has changed from Submitted, refresh the page
            if (currentFormStatus === this.statuscode.Submitted && serverStatus !== this.statuscode.Submitted) {
                console.log("Status changed, refreshing page...");

                // Clear the polling interval
                if (this.pollingInterval) {
                    clearInterval(this.pollingInterval);
                    this.pollingInterval = null;
                }

                // Refresh the current window
                formContext.data.refresh();
            }
        } catch (error) {
            console.error("Failed to retrieve record status:", error);
            // Don't stop polling on API errors, as they might be temporary
        }
    }

    /**
     * Stops the status monitoring (useful for cleanup)
     */
    stopStatusMonitoring() {
        if (this.pollingInterval) {
            clearInterval(this.pollingInterval);
            this.pollingInterval = null;
            console.log("Status monitoring stopped");
        }
    }
}

Greg = window.Greg || {};
Greg.greg_quote = Greg.greg_quote || {};
Greg.greg_quote.Form = new Form();
Enter fullscreen mode Exit fullscreen mode

Hope this helps you too!

Top comments (0)