Cancelling save event based on the result of async operation

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.

Async Save block does not work

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

Console Log Save Blocked

I have tested this only in Chrome on Dynamics 365 Online v9. Hope this is useful.

4 comments

Leave a comment