EDIT (20/06/2021): OnSave can natively handle async https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/events/form-onsave#asynchronous-event-handler-support. So, this post is now obsolete.
When you want to cancel a save event in CRM Dynamics 365 Customer Engagement, you use “preventDefault()” to block the save operation. This works when you block the operation based on the information that is currently on the form/page, but it does not work, if you want to block the save based on the result of an async operation.
In this contrived example, I would like to block the save of the current form, if there exists an user with “homephone” field set to 12345. The async operation is performed by “retrieveMultipleRecords” which returns a Promise.
The code below does not work
Xrm.Page.data.entity.addOnSave((e)=>{ Xrm.WebApi.retrieveMultipleRecords('systemuser','$select=fullname,jobtitle,homephone').then(x=>{ console.log(`DataXml OnSave: ${Xrm.Page.data.entity.getDataXml()}`); if(x.entities.some(x=>x.homephone == '12345')){ e.getEventArgs().preventDefault(); console.log('User with homephone 12345 exists. Save blocked.'); } }); });Result
Notice the the save event completed and form’s load event fired even though preventDefault ran. The “jobtitle” field that I modified also succeeded, when I expected it to not succeed.
In order to block the save, you’ll have to restructure the code little differently, like the one below. Block save before async operation and explicitly call save, when your criteria for save is met and use closure variable to keep track of whether to save or not.
Working 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); } } })());Result
I have tested this only in Chrome on Dynamics 365 Online v9. Hope this is useful.
Wow. This was really interesting. FYI I used this for an answer in SO here: https://stackoverflow.com/questions/64420560/crm-365-blocking-the-onsave-operation-when-validations-require-fetchxmlqueri
[…] In 2017, Natraj Yegnaraman shared a clever method to cancel the save of a form. His approach was to cancel the save before the asynchronous operation and then retrigger the save if needed after the asynchronous operation is resolved. You can find all the details on the following link. […]
[…] In 2017, Natraj Yegnaraman shared a clever method to cancel the save of a form. His approach was to cancel the save before the asynchronous operation and then retrigger the save if needed after the asynchronous operation is resolved. You can find all the details on the following link. […]
[…] In 2017, Natraj Yegnaraman shared a clever method to cancel the save of a form. His approach was to cancel the save before the asynchronous operation and then retrigger the save if needed after the asynchronous operation is resolved. You can find all the details on the following link. […]