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

Viewed 1700

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.

Thank you for your time and help.

1 Answers

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://docs.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.

Related