Thursday 15 April 2010

MS Dynamics CRM 4.0 Record Counter (revisited) ...

Few months ago, I talked about some of the work that we had done in adding some functionalities to CRM 4.0.In this post I would like to furnish some details about our way of implementing a "CRM Record Counter".

Google for "CRM Record Counter" and you will get "n" results and the common thing about the solutions offered in these sites is that they ask you to download/write and register a custom plugin. The way the plug-in works is that it gets invoked each time a search is fired (due to changing the view or by performing a quick search or an advanced find).The plugin has to be attached to the "Execute" message in CRM. After registering the plugin successfully, you would see that all the views that present a list of records will now start presenting the totals as a first row in the list ! Awesome, isn't it?

Technically, the plugin intercepts the search request and response and just adds the required detail to the response appropriately.

The best aspect of this solution is that it works for quick search, Advanced find, look ups as well as associated views too. No other solution can beat this, for sure. The downside of this solution is that it flushes this data in exports too ! I mean, if the user does an export to excel, the total row is also exported as the first row in excel. Well, this is not a major thing that prevents one from accepting this solution.Users can always delete the first row and deal with the rest of the data.

Having explained a bit of detail about a solution that is available on the web, I would like to share my knowledge on how we can do all this (with some limitations, though) without having to register a custom plugin, (yes, you heard it right ;-)).

Let me talk a bit about how the grid in CRM is rendered. Here is a screenshot of a view from CRM:








Now, if you want to manipulate the data that is in the table, you should be changing a file by name "grid.htc". This file is located @ \Microsoft Dynamics CRM\CRMWeb\_static\_grid on the server where CRM is installed.

Open this file and you will observe that it looks like a "hi-fi" javascript code.In this file, define a function as given below:


function PrintFetchXMLForActiveView() {


var fetchXML = '';

if (top.stage != null && top.stage.crmGrid != null) {

var sviewID = top.stage.crmGrid.GetParameter('viewid');


//alert(sviewID);


var xml = "<?xml version='1.0' encoding='utf-8'?>" +

"<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'" +

" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +

" xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" +

GenerateAuthenticationHeader() +

"<soap:Body>" +

"<Retrieve xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" +

"<entityName>savedquery</entityName>" +

"<id>" + sviewID + "</id>" +

"<columnSet xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:ColumnSet'>" +

"<q1:Attributes>" +

"<q1:Attribute>fetchxml</q1:Attribute>" +

"</q1:Attributes>" +

"</columnSet>" +

"</Retrieve>" +

"</soap:Body>" +

"</soap:Envelope>";

// Prepare the xmlHttpObject and send the request.

var xHReq = new ActiveXObject("Msxml2.XMLHTTP");

xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);

xHReq.setRequestHeader("SOAPAction", http://schemas.microsoft.com/crm/2007/WebServices/Retrieve);

xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");

xHReq.setRequestHeader("Content-Length", xml.length);

xHReq.send(xml);

// Capture the result.

var resultXml = xHReq.responseXML;

// // DEBUG: alert(resultXml.selectSingleNode("//q1:modifiedon").nodeTypedValue);

// // DEBUG: alert(crmForm.all.modifiedon.DataValue);


if (resultXml.selectSingleNode("//q1:fetchxml") != null) {

fetchXML = resultXml.selectSingleNode("//q1:fetchxml").nodeTypedValue;

}

}

else {

if (window.top != null && window.top.resultRender != null && window.top.resultRender.FetchXml != null) {

fetchXML = window.top.resultRender.FetchXml.value;

}

}

if (fetchXML.length > 0) {

fetchXML = fetchXML.replace(/\"/g, "'").replace(/</g, "&lt;").replace(/>/g, "&gt;");

// Fire FetchXML and get the totals

var queryForTotals = "<?xml version='1.0' encoding='utf-8'?>" +

"<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'" +

" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +

" xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" +

GenerateAuthenticationHeader() +

"<soap:Body>" +

"<Fetch xmlns='http://schemas.microsoft.com/crm/2007/WebServices'><fetchXml>" +

fetchXML +

"</fetchXml></Fetch>" +

"</soap:Body>" +

"</soap:Envelope>";


xHReq = new ActiveXObject("Msxml2.XMLHTTP");

xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);

xHReq.setRequestHeader("SOAPAction", http://schemas.microsoft.com/crm/2007/WebServices/Fetch);

xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");

xHReq.setRequestHeader("Content-Length", queryForTotals.length);

xHReq.send(queryForTotals);


// Capture the result.

var resultXmlForFetch = xHReq.responseXML;

// Capture the result and UnEncode it.

var resultSet = new String();

resultSet = resultXmlForFetch.text;

resultSet.replace('&lt;', '<');

resultSet.replace('&gt;', '>');


// Create an XML document that you can parse.

var oXmlDoc = new ActiveXObject("Microsoft.XMLDOM");

oXmlDoc.async = false;

// Load the XML document that has the UnEncoded results.

oXmlDoc.loadXML(resultSet);

// Display the results.

var results = oXmlDoc.getElementsByTagName('result');


TotalRecordsMessage = '--> Total Records = ' + results.length;

//alert(TotalRecordsMessage);


}

}


The purpose of the script is to track the id of the current view, fire an XMLHTTP request (call the CRM web service through javascript), parse the results and store the details in local variables. For example, if the current view is "Active Contacts", then the script determines the ID of the view, fires an XML HTTP request to query for the total number of records related to the current view. After we have the total number of records stored in a local variable, we then have to update the status bar of the table to present this detail.

Advantages :
The biggest advantage of this solution is that it doesn't require you to register a plug-in. All that one needs to know is to place the grid.htc file @ the appropriate location and ask the users of CRM to do a Ctrl+F5 (in order to download the latest scripts and CSS from the server). As you can see, the procedure to install this solution is its highlight.

Limitations:
  1. The solution doesn't work for quick find and look ups. It only works for saved views. Since the solution works in "Advanced Find" too, a workaround for the limitation is to use "Advanced Find" if you require totals.

  2. Since the solution is a change to a globally referenced script, all the org. units will get this detail (whether you like it or not :()

  3. Since we are modifying a script that comes along with CRM installation, I am not sure if it works seamlessly when you move to a higher version of CRM !
I have presented two possible solutions for "CRM Record counter" and I leave this to your discretion to pick a solution that better addresses your requirements :)

Download the script from here and please feel free to comment.

3 comments:

Jim said...

Kiran, Thanks for sharing. Where's the best place in grid.htc to call the PrintFetchXMLForActiveView() function from? How do I display the TotalRecordsMessage in the statusbar? Many thanks.

Kiran banda said...

Hi Jim,

The script is available for download in my post.
Please download the script and look for "Kiran" or "KB" to understand where the changes are made :)

Jim said...

Hi Kiran, Fabulous. Thanks very much.