Customer Engagement & Dynamics CRM Forum

Expand all | Collapse all

Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

Jump to Best Answer
  • 1.  Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    GOLD CONTRIBUTOR
    Posted 9 days ago
    When triggering a record save via JS, how does one ensure it completes before later code executes?

    Example 1:  An auto form selector switches forms based on a type field, if that field is correct.  If the user changes the type, the function is called, which checks to see if there are any unsaved changes, and if so, saves them before navigating to the correct form.
            if (formContext.data.entity.getIsDirty()) {
                formContext.data.entity.save();
            }
            //Switch to the correct form. (This assumes the form is available in the formSelector items)
            //formContext.ui.formSelector.items.get(formDict[orderType]).navigate();
            setTimeout(formContext.ui.formSelector.items.get(formDict[orderType]).navigate(),0); //Timeout set to push navigate call onto event loop / ensure it runs after save() completes. 


    According to MSFT's documentation (https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/formcontext-data-entity/save), the formContext.data.entity.save() function supposedly runs synchronously.  Yet, the formContext.data.save() function runs async. (Source: https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/formcontext-data/save).  This code does navigate to the new form, BUT only after the browser pops up a dialog box informing the user there are unsaved changes.  You'll note, I've tried adding a setTimeOut to push the navigate method onto the event loop so that it would only run after the callstack was cleared, but it still pops up un unsaved changes message to the user. How do I ensure the save completes first and the popup doesn't occur?

    Example 2: A custom button added to our appointment entity performs some validation and prepends "Cancelled: " to the subject line before marking an appointment as cancelled. 

                if (!currentSubject.startsWith("Cancelled:")) {
                    Xrm.Page.getAttribute("subject").setValue("Cancelled: " + currentSubject);
                    console.log("menuCancelOnClick.updatedSubjectSubject:" + Xrm.Page.getAttribute("subject").getValue());
                }
                console.log("menuCancelOnClick:CallingSaveThenCancel");
                //Deactivate the record. MUST save first to ensure there is not a race condition between form contents and result of the deactivation.
                //Xrm.Page.data.save().then(deactivateRecordThroughWebapi());//, alert("Unable to Save Record on Cancel. Please report to IS Team."));
                //Xrm.Page.data.save().then(alert("Record Saved"), alert("Record Not Saved."));
                //alert("Xrm.Page.Data.Save Functions Done. Click OK to Save using Xrm.Page.data.entity.save, which runs synchronously.");
                //Xrm.Page.data.entity.save(); 
                //alert("Record should be saved now.");
                //Confirmed that the save.then promise DOES NOT WORK CORRECTLY. It calls both the success and error callbacks without waiting for the save to complete. 
                //However, the Xrm.Page.data.entity.save() function runs synchronously and does work for this purpose. Added internally to the deactivate function. 
                //Xrm.Page.data.save();
                Xrm.Page.data.entity.save();
                deactivateRecordThroughWebapi();


    As before, you'll see where I've attempted use the Xrm.Page.data..save().then(deactivateRecordThrougghWebapi()) promise to ensure the record deactivates AFTER the change to the subject is applied... (Nevermind the deprecated Xrm.Page model). Even using the "synchronous" save function, per MSFTs documentation, it is clear that the save() function doesn't fully complete before the deactivate record api call occurs. The end result appears to be a race condition, with the active cached version of the record apparently finishing the save() operation AFTER the deactivate function, causing the apt to immediately reactivate. How do I ensure the save() and refresh complete before the record deactivation?

    Any pointers?






    ------------------------------
    Ryan Perry
    Business Systems Analyst
    Auric Solar
    ------------------------------


  • 2.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    GOLD CONTRIBUTOR
    Posted 9 days ago
    Edited by Daryl LaBar 9 days ago
    Why doesn't the save().then() promise method work for you?

    If you want to run some logic after a save another simple option is to add an OnChange event for the ModifiedOn attribute.  This gets updated and triggered each time the save call has returned from the server.

    On a side note, I don't trust the synchronous save call.  What if a JS action cancels the save?  Does your code run afterwords regardless?

    ------------------------------
    Daryl LaBar
    President, MVP
    Gap Integrity
    Fishers IN
    ------------------------------



  • 3.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    GOLD CONTRIBUTOR
    Posted 8 days ago

    Hi Daryl,

    That is a great question.  When I use the promise, the unsaved changes dialog box still appears and BOTH the success and error callbacks are called. Example based on the form selector code: 

    formContext.data.save().then(formContext.ui.formSelector.items.get(formDict[orderType]).navigate(),console.log("Error Saving Record."));
    
    When the triggering field is changed, the result is:
    1. an Unsaved Changes dialog box is displayed to the user. I'd like to supress this, in favor of just saving and switching forms immediately.
    2. Before clicking OK on the unsaved changes dialog box, the failure callback is executed and "Error Saving" is logged to console.
    3. After clicking OK, the form navigates still to the correct form. If the user clicks Cancel, it displays an additional error message, code 0x83215603.
    I first saw this same behavior (executing both callbacks) on the Apt Cancellation example.    In that case, I thought I had found a workaround by using the formContext.data.entity.save() method, which MSFT indicates is synchronous, followed by the deactivate method, only to have a user later report that they still had a record reopen again.

    Other than a bug, what would cause both the success and failure callbacks to fire?

    ------------------------------
    Ryan Perry
    Business Systems Analyst
    Auric Solar
    ------------------------------



  • 4.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    GOLD CONTRIBUTOR
    Posted 8 days ago
    The only reason I can think of that would result in both things getting called, is if you're calling the save method multiple times.  I'd put some debug logging in to see how many times you are calling the save.

    You also shouldn't be getting an Unsaved changes dialog popup on save.  (the only time I've ever seen this is on the e-mail form, where the server is returning some html text, and the client re-formats it, and declares it as dirty.)  I'd find the fields that are being marked as dirty to figure out why.

    If you want to abandon all changes and immediately navigate you can update every field to not required and disabled and you won't get the "Are you sure?" dialog.

    ------------------------------
    Daryl LaBar
    President, MVP
    Gap Integrity
    Fishers IN
    ------------------------------



  • 5.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    TOP CONTRIBUTOR
    Posted 8 days ago

    Ryan

    Have you tried addOnSave?

    The function to be executed when the record is saved. The function will be added to the bottom of the event handler pipeline. The execution context is automatically passed as the first parameter to the function. See Execution context for more information.


    https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/formContext-data-entity/addOnSave

    Cheers!



    ------------------------------
    Rex Kenley Tan, MCSA: Web Applications
    Tallmadge OH
    https://www.youracclaim.com/users/rex-kenley-tan

    *Always be CURRENT with JavaScript & C#, NEVER be obsolete.

    DISCLAIMER: All views expressed on this site are my own and DO NOT represent the opinions of ANY entity whatsoever with which I have been, am now, or will be affiliated.
    ------------------------------



  • 6.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    GOLD CONTRIBUTOR
    Posted 8 days ago
    Thanks @Daryl LaBar and @Rex Kenley Tan,

    I spotted my novice mistake. If I have my terminology correct, in the save function callback arguments, I am passing function expressions, rather than a function declarations. They are being evaluated, rather than passed in as complete function objects.

    This works:
    formContext.data.save().then(function () { formContext.ui.formSelector.items.get(formDict[orderType]).navigate(); });
    ​​
    From a coding standpoint, is there a shorter way to write it? given that "navigate" is already a function, I initially assumed I could just drop the evaluation / call operator symbol () and it should work like this, but it doesn't appear to do so.
    formContext.data.save().then(formContext.ui.formSelector.items.get(formDict[orderType]).navigate);


    Thanks Much,



    ------------------------------
    Ryan Perry
    Business Systems Analyst
    Auric Solar
    ------------------------------



  • 7.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.
    Best Answer

    GOLD CONTRIBUTOR
    Posted 8 days ago
    Oh I should have caught that the first time.  You're second example should work.  Just remember that the call to get the form occurs before the actual save event in this case.  I also think functions are way better for readability here:

    formContext.data.save().then(navigateToForm);
    
    function navigateToForm(){
        formContext.ui.formSelector.items.get(formDict[orderType]).navigate(); 
    }


    ------------------------------
    Daryl LaBar
    President, MVP
    Gap Integrity
    Fishers IN
    ------------------------------



  • 8.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    TOP CONTRIBUTOR
    Posted 8 days ago
    Edited by Rex Kenley Tan 8 days ago

    Use destructuring and arrow functions

    let {data, ui} = formContext;
    data.save().then(() => ui.formSelector.items.get(formDict[orderType]).navigate());

    *One thing most js programmers don't realize is that by calling formSelector, they are making an unnecessary "hop" back to the top of the object when they can just "stay" where they are.


    ------------------------------
    Rex Kenley Tan, MCSA: Web Applications
    Tallmadge OH
    https://www.youracclaim.com/users/rex-kenley-tan

    *Always be CURRENT with JavaScript & C#, NEVER be obsolete.

    DISCLAIMER: All views expressed on this site are my own and DO NOT represent the opinions of ANY entity whatsoever with which I have been, am now, or will be affiliated.
    ------------------------------



  • 9.  RE: Troubleshooting Javascript Save() function: Promises, Async Calls, and Popups.

    GOLD CONTRIBUTOR
    Posted 8 days ago
    Thank you both.  Both scripts are now functioning as intended.

    ------------------------------
    Ryan Perry
    Business Systems Analyst
    Auric Solar
    ------------------------------



If you've found this thread useful, dive deeper into User Group community content by role