Blog, Development

Cancelling save event based on the result of async operation

This post is similar to previous one related to showing/hiding of the button based on the result of async operation but in this scenario script allows/disallows form to be saved. In my case I check if there is any account with the same “Account Number” available already and if there is script blocks the save. Yes, I know that it’s safer to allow platform to that task for me (for example using Alternate Key feature or so) but it’s just an example that demonstrates the approach could be used in similar use cases.

So here is the code with detailed comments what is happening:

var AccountForm = (function () {

    var SaveMode = {
        Save: 1,
        SaveAndClose: 2,
        SaveAndNew: 59,
        Autosave: 70
    };

    //this is variable that shows if validation was successfully passed or not
    var isValidationNeeded = true;
    
    function OnSave(executionContext) {
        //so if there are several save handlers and one of previous already called preventDefault
        //there is no need to do any validations anymore
        if (executionContext.getEventArgs().isDefaultPrevented()) {
            return;
        }

        //getting save mode from event
        var saveMode = executionContext.getEventArgs().getSaveMode();

        //if savemode is not one of listed - just quit the execution and let the record to be saved
        if (saveMode !== SaveMode.Save &&
            saveMode !== SaveMode.SaveAndClose &&
            saveMode !== SaveMode.SaveAndNew &&
            saveMode !== SaveMode.Autosave) {
            return;
        }

        //so if validation was successfully passed - flag is reset
        //and code just leaves the form alone and allows changes to be saved
        if (!isValidationNeeded) {
            isValidationNeeded = true;
            return;
        }

        //getting of the form context from execution context object
        var formContext = executionContext.getFormContext();

        //getting of "Account Number" value from field
        var accountNumber = formContext.getAttribute("accountnumber").getValue();

        //if field is blank there is no need to do any checks - code just leaves form
        if (accountNumber == null) {
            return;
        }

        //preventing of the save operation before async operation is started
        executionContext.getEventArgs().preventDefault();

        //initial composing of query - account number is equal to value from form
        var query = "$select=accountid&$filter=accountnumber eq '" + accountNumber + "'";

        //if form is "Update"
        if (formContext.ui.getFormType() === 2) {
            //then current record should be ignored
            query += " and  accountid ne '" + formContext.data.entity.getId() + "'";
        }

        Xrm.WebApi.retrieveMultipleRecords("account", query).then(
            function success(results) {
                //so if there are other records with the same account number
                if (results.entities.length !== 0) {
                    //this message is shown to user only when user caused save, autosave is just blocked
                    if (saveMode !== SaveMode.Autosave) {
                        Xrm.Navigation.openAlertDialog({
                            text: "Account with this Account Number already exists"
                        });
                    }
                } else {
                    //othervice validation flag is set to "Passed"
                    isValidationNeeded = false;
                    //and save event is called again
                    if (saveMode === SaveMode.Save ||
                        saveMode === SaveMode.Autosave) {
                        formContext.data.entity.save();
                    } else if (saveMode === SaveMode.SaveAndClose) {
                        formContext.data.entity.save("saveandclose");
                    } else {
                        formContext.data.entity.save("saveandnew");
                    }
                }
            },
            function (error) {
                //if something went wrong - error message is shown to user
                Xrm.Navigation.openAlertDialog({ text: error.message });
            }
        );
    }
    
    return {
        OnSave: OnSave
    };
})();

Don’t forget to set “Pass execution context as first parameter” checkbox during the registration of handler:

If you attach onsave handlers on-the-fly inside onload handler using addOnSave method executionContext is passed inside automatically as a first parameter.

PS I appreciate Chris Groh for code review and suggestions related to:

  • different save modes
  • sequential execution of several save handlers

13 Comments

  1. Great article Andrew! And the previous one also.
    In order to do same thing, I used Promise. With your code, it seems easier.

    Thanks for sharing

  2. Hi Andrew,
    Thanks for your article.
    We’ve used the same approach for some time.
    But if you need ot save something on the form calling
    ctx.getFormContext().data.save().then(() => {
    then -> will always get a canceled callback.
    of course, there’s a workaroud but it should be kept in mind.

    1. Sergii,
      You are welcome. There always will be a scenario that is not covered by the code provided. That’s why we are paid – to close those gaps.
      Thanks,
      Andrew

  3. Neha,
    I’m not sure why it doesn’t work for you. I did my development in online environment and it worked fine for me.
    Andrew

  4. Hi, should this script be attached to the OnSave event of the form? or is it better to attach it to the OnLoad event? Thanks

  5. Thank you Andrew. I was close to the same solution but after reading your code I figured out a few critical aspects.
    Excellent article!

  6. Microsoft wrote that functions formContext.data.entity.save() will be deprecated . I applied new one as suggested
    formContext.data.save();
    formContext.data.save({ saveMode: 2}).
    And it is working until latest Microsft update Dec 17 2021 . After this Microsoft Christmas gift(update) when the user press button Save and Close, then the data on my form could be saved, but the form won’t close, Any help or comments for this case will be useful

    1. Hello Mikhail,
      I’m not sure why it doesn’t work when it should according to the documentation.
      I would recommend getting in touch with Microsoft and ask what’s wrong and as a workaround in a callback of the promise that is returned calling formContext.data.save – call https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/formcontext-ui/close in order to close the form after it was saved. I understand that it’s a workaround but it should fix the flow for your users.
      Andrew

      1. Hi Andrew,
        We contacted with Microsoft support regarding this issue , and they confirmed that some “enhancements” were done , and MS product team will be notified about this. I hope, the documentation will be updated as well. So far we applied to fix as
        formContext.data.save({ saveMode: SaveMode.SaveAndClose })
        .then(successCallback, errorCallback);
        and
        function successCallback() {
        formContext.ui.close();
        };
        It is working fine.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.