Search code examples
javascriptrestvalidationdynamics-crm-365

CRM 365 - Blocking the "onSave" operation when validations require FetchXMLQueries be executed


The CRM365 forms provide the OnSave event to be able to perform business validations. If one of the validations fails, we can use the executionContext.getEventArgs().preventDefault() to stop the record creation. It is all good vor validating the fields of the current form, however, there are situations when the validations must also require executing a query against the entity records. For example, creating a reservation of a room must check if there is any other pre-existing reservation and stop if they overlap. The problem is that the REST API call is an asynch and needs time to execute and return results. By the time the information becomes available in the Response, the OnSave function is way ended and the record saved basically without validation.

My questions are as follows:

  1. Is any reverse of the executionContext.getEventArgs().preventDefault()? We can stop the save operation but is any "allow save" sort of speak? I have tried to the formContext.data.entity.save(); but since I am in the OnSave event it created an infinite loop. It is almost unthinkable to me that this flag can be set but not reset.

  2. Is any effective to stop the JavaScript or make it "sleep" until the REST API data becomes available? everything revolves around SetTimeout function but that is a non-blocking function and my JavaScript just runs through it, of course.

I am sure I am not the only running into this situation, it must be pattern to solve these REST API based validations.

I should be probably add that I am looking for a client side based solution; all these could be relatively easy to implement in a plugin or custom workflow.


Solution

  • I found this link and verified it in a new Dynamics Trial (2020 release wave 2) and the XRM components seem there:

    Reference Link: https://community.dynamics.com/crm/b/dynamicscrmdevdownunder/posts/cancelling-save-event-based-on-the-result-of-async-operation

    MSDN Reference:https://learn.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/formcontext-data-entity/addonsave

    Additional comments for Save and Close:https://dreamingincrm.com/2017/10/12/cancelling-save-event-based-on-the-result-of-async-operation/


    Edit so that this is not a "link-only" answer

    • Uses Clone to create copies of Xrm.Page.ui and Xrm.Page.data.entity - this is so that if the user presses Save and Close these objects are still available
    • Creates saveHandler method that uses RetrieveMultiple to simulate an async validation process

    Code:

    Xrm.Page.data.entity.addOnSave((()=>{
        let isSave = false;
        var uiClone = parent.jQuery.extend(true, {}, Xrm.Page.ui);
        var entityClone = parent.jQuery.extend(true, {}, Xrm.Page.data.entity);
     
        var closeHandler = ()=>{
            console.log('local. close blocked.');
        };
     
        var saveHandler = (ev)=>{
                console.log('local. save blocked.');
                Xrm.WebApi.retrieveMultipleRecords('systemuser','$select=fullname,jobtitle,homephone').then(x=>{
                    isSave = !x.entities.some(x=>x.homephone == '12345');
                    if(isSave){
                        Xrm.Page.data.entity.save = entityClone.save;
                        Xrm.Page.ui.close = uiClone.close;
                        if((typeof ev === 'string' && ev === 'saveandclose') ||
                            (ev.getEventArgs && ev.getEventArgs() && ev.getEventArgs().getSaveMode() === 2)){
                            console.log('saveandclose');
                            entityClone.save('saveandclose');
                        }
                        else{
                            console.log('save');
                            entityClone.save();
                        }
                    }
                    else{
                        console.log('User with homephone 12345 exists. Save blocked.');
                    }
                });
        };
     
        return (e)=>{
            var eventArgs = e.getEventArgs();
            console.log(`DataXml OnSave: ${Xrm.Page.data.entity.getDataXml()}`);
            console.log(`Save Mode: ${eventArgs.getSaveMode()}`);
            if(isSave) {
                console.log('proceed to save');
                Xrm.Page.data.entity.save = entityClone.save;
                Xrm.Page.ui.close = uiClone.close;
                return;
            }
            else{
                Xrm.Page.data.entity.save = saveHandler;
                Xrm.Page.ui.close = closeHandler;
                if(eventArgs.getSaveMode() !== 2){
                    eventArgs.preventDefault();
                }
                saveHandler(e);
            }
        }
    })());
    

    All credit for this code sample goes to the original author.