Restrict/Cancel Save operation conditionally in JavaScript as per Async operations result in Dynamics 365
Sometimes you get requirements where you are doing some validation check in JavaScript by calling XRM.WebAPI or some Async operation and if validation failed, you want to restrict Save operation. If the call code is synchronous then there is no issues. But if the code is asynchronous, you need to take special care otherwise it will not work.
Here I have taken a business scenario “If a student already registered for a course on a date, then users cannot create another course registration for the same student for the same course on the same course start date. Also in the alert system should display a link to old record so that user can click the link to go to existing record for editing without create new record.”
Follow below steps:
Step 1 : Create the JS Web Resource
Create a Solution and add a JS web resource. This JS web resource will be called on Main Form Save event and Quick Create Form save event. The code checks if any record exist using Async XRM.WebAPI.RetrieveMultiple method with same course, student and course start date. If exist shows a Web Resource prompt and cancels Save operation.
use the below code in JS Web Resource. This web resource pass data parameter to HTML web resource.
var CourseForm = (function () {
var SaveMode = {
Save: 1,
SaveAndClose: 2,
SaveAndNew: 59,
Autosave: 70
};
//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();
var student = formContext.getAttribute("soft_student").getValue()[0].id;
var course = formContext.getAttribute("soft_course").getValue()[0].id;
var coursestartdate = formContext.getAttribute("soft_coursestartdate").getValue();
if (student == "" || course == "" || coursestartdate == null) {
return;
}
//preventing of the save operation before async operation is started
executionContext.getEventArgs().preventDefault();
var formatteddate = coursestartdate.getFullYear().toString() + "-" + (coursestartdate.getMonth() + 1).toString() + "-" + coursestartdate.getDate().toString();
var fetchXML = "?fetchXml=<fetch mapping='logical'><entity name='soft_studentregisteredcourses'><attribute name='soft_name' /><order attribute='soft_name' descending='false' /><filter type='and'><condition attribute='soft_coursestartdate' operator='on' value='" + formatteddate + "' /><condition attribute='soft_course' operator='eq' uitype='soft_course' value='" + course + "' /><condition attribute='soft_student' operator='eq' uitype='soft_student' value='" + student + "' /><condition attribute='statecode' operator='eq' value='0' /></filter></entity></fetch>";
Xrm.WebApi.retrieveMultipleRecords("soft_studentregisteredcourses", fetchXML).then(
function success(results) {
//so if there are other records with the same student and course with course start date
if (results.entities.length !== 0) {
//this message is shown to user only when user caused save, autosave is just blocked
if (saveMode !== SaveMode.Autosave) {
var recordURL = GetEntityRecordUrl(executionContext, false, true, results.entities[0].soft_studentregisteredcoursesid);
var entityName = executionContext.getFormContext().data.entity.getEntityName();
var entityId = results.entities[0].soft_studentregisteredcourseid;
//var data = {
//EntityName: entityName,
//EntityId: entityId
//};
//custom parameter that you need in the modal dialog
var dialogParameters = {
pageType: "webresource",//required
webresourceName: "soft_rewardvalidatehtml.html",//Html Webresource that will be shown
data:recordURL //optional
};
var navigationOptions = {
target: 2,//use 1 if you want to open page inline or 2 to open it as dialog
width: 400,
height: 300,
position: 1,//1 to locate dialog in center and 2 to locate it on the side,
title: "Validation Error"
};
Xrm.Navigation.navigateTo(dialogParameters, navigationOptions).then(
function (returnValue) {
//returnValue is blank when "Cancel" button is clicked
if (!returnValue) {
return;
}
console.log(returnValue);
//Add your processing logic here
},
function (e) {
//put your error handler here
});
}
}
else {
//otherwise 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 });
}
);
}
function GetEntityRecordUrl(executionContext, forceClassic = false, newWindow = true, recordId) {
var strUIType = 'forceUCI=1';
if (forceClassic == true || forceClassic == 1)
strUIType = 'forceClassic=1';
if (newWindow == 0)
newWindow = false;
var entityName = executionContext.getFormContext().data.entity.getEntityName();
var entityId = recordId;
var entityRecordUrl = executionContext.getContext().getClientUrl() + '/main.aspx?' + strUIType;
entityRecordUrl += '&newWindow=' + newWindow;
entityRecordUrl += '&pagetype=entityrecord&etn=' + entityName + '&id=' + entityId;
return entityRecordUrl;
}
return {
OnSave: OnSave
};
})();
Save and Publish the JS Web Resource.
Step 2 : Create HTML Web resource and read parameter
The JS Web resource pass record id parameter to HTML web resource and in the HTML Web resource we read the record id and format the URL with record ID. Create an HTML web resource with correct name used in JS web resource. Use the code below. In the HTML web resource there is a OK button which on clicking closes the pop-up.
<html><head>
<meta charset="utf-8">
<title>Error</title>
<script type="text/javascript" src="../ClientGlobalContext.js.aspx"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<style type="text/css">
.footer {
position: fixed;
bottom: 0;
right: 0;
padding-bottom: 10px;
padding-right: 10px;
}
.footerButton {
width: 150px;
}
</style>
<script type="text/javascript">
function onPageLoad() {
debugger;
var urlParams = getUrlParameters();
document.getElementById("errorMessage").innerHTML="You cannot add same course for the student as already the student registered for the course for same date.</br> Check the Existing Record : <a target='_blank' href="+urlParams+">Record Link</a>";
}
function getUrlParameters() {
var queryString = location.search.substring(1);
var paramsvalue = encodeURI(queryString).split("=")[1];
return decodeURIComponent(decodeURIComponent(paramsvalue));
}
</script>
<meta></head>
<body onload="onPageLoad();" onfocusout="parent.setEmailRange();" style="overflow-wrap: break-word;">
<div id="errorMessage" style="font-family: Arial;padding:20px" class="alert alert-danger">
</div>
<div style="width:95%;text-align:right">
<button class="btn btn-primary" onclick="javascript:window.close('','_parent','');">OK</button>
</div>
</body></html>
Step 3 – Call JS method from Main Form Save Event and Quick Create Save Event
Now you can open Main form of Student Registered course table and add JS library and call the function on save.
Do the same thing for the Quick create form.
Publish all components or solution.
TEST NOW.
If you open Student Registered Courses table and try to create a record with same student, course and course start date you will see below pop-up.
The Record Link will open the existing record. Like that if you use quick create option also it will shows message and cancel save operation.
Hope this helps.
Follow my blog for more trending topics on Dynamics 365, Azure, C#, Power Portals and Power Platform. For training, Courses and consulting, call to us at +91 832 886 5778 I am working more for community to share skills in Dynamics 365 and Power Platform. Please support me by subscribing my YouTube Channel. My YouTube Channel link is this : https://www.youtube.com/user/sppmaestro