Oct 14, 2022

Refresh Sub Grid once Async operation is completed

This is a recent challenge came on my way. I had to refresh a sub grid of a form after an operation (say a field value change or Save). Challenge is Sub Grid values are being modified asynchronously through a server side logic. In summery, I have to delay the refresh till that happens.

If I elaborate this further, I have a field (i.e. Amount) in the form of Service entity. Once it is updated the server side logic will trigger and synchronously populate a field (i.e. Item Amount) of the child Service Item entity, which is the child record type of the grid.

While we need to run this Java Script on Save. Below is the code in my on Save script and I will explain each of the pieces  come with the functions being called.

var formContext = executionContext.getFormContext();
var dirtyAttributes = CheckDirtyFieldsOnForm(formContext);
if (dirtyAttributes != null && dirtyAttributes.length > 0 
   && dirtyAttributes.includes("new_amount")
   && formContext.getAttribute("new_amount") != null 
   && formContext.getAttribute("new_amount").getValue() > 0) {
	var childId = RetrieveChildRecordToBeUpdate(formContext);
	   if (childId != null) {
		IsServiceItemAmountSet(formContext, childId, 1);
	   }
      }

This is the first function, which identifies all the fields going to be updated. This returns and array where we need to check if Amount field is among them.

function CheckDirtyFieldsOnForm(formContext) {
  var attributes = formContext.data.entity.attributes.get();
  var dirtyAttributes = [];
  if (attributes != null) {
	for (var i in attributes) {
	   if (attributes[i].getIsDirty()) {
		dirtyAttributes.push(attributes[i].getName());
	      }
	   }
	}
  return dirtyAttributes;
}

If it returns Amount field, we know we need to refresh the grid. Lets retrieve the Service Item Id which we are interested in. (Note: even if there are more than one line items, selecting one is enough since we are going to use this item to check if async operation is completed)

function RetrieveChildRecordToBeUpdate(formContext) {
  var gridContext = formContext.getControl("grid_serviceitems");
    if (gridContext == null || gridContext == "undefined")
       return;

	var complGrid = gridContext.getGrid();
	var complRows = complGrid.getRows();
	var rowCount = complRows.getLength();
	if (rowCount == 0) {
			return;
	}
	for (var i = 0; i < rowCount; i++) {
	  var rowEntity = complRows.get(i).getData().getEntity();
        // Can add some logic if you have filter criteria for 
        // select line item
	  return rowEntity._entityId.guid;
	}
	return;
 }

Since we have Service Item Id, now we have to keep on checking by retrieving Service Item details through server calls (i.e. WebApi) if its updated. In our scenario what I need to check is if Item Amount is populated. Once it happen, we can refresh the grid since we know Sync operation is completed and refreshed grid will show the new values.

This is the recursive code that keep on checking if update has taken place.

function IsServiceItemAmountSet(formContext, Id, index) {
   Xrm.WebApi.online.retrieveRecord("new_serviceitem", Id, "?$select=new_itemamount").then(
      function success(result) {
	   if (result.new_lineamount != null && result.new_lineamount > 0) {
		formContext.getControl("grid_serviceitems").refresh();
		return true;
	   }
	   else {
		 return false;
	   }
	 },
	 function (error) { }
   ).then(function (isSuccess) {
	 if (!isSuccess && index < 5) {
	    index++;
	    setTimeout(function () {
		IsChildClaimAmountSet(formContext, Id, index);
	    }, 2000);
       }
   });
}

This function do recursive WebApi calls in 2 second intervals. Anyway, I am using an index to count these calls, since I need to prevent infinite loops in case of failure in service side logic. Hence maximum no of attempts is limited to 5. I assume within 10 seconds Async operation would have been completed.