Displaying FetchXML results with XSLT on the client side in a Dynamics CRM 2011 web resource

A few weeks back, I wrote a post that showed how to retrieve and display FetchXML using jQuery in a Dynamics CRM web resource. In that example, I used jQuery's each() method to iterate through each result and append them to an HTML element on the page. Using each() is a good approach if you need to actually do something with each row, but if you just want to display data, XSLT is a much easier way to do it. In this post, I will show how to use client-side XSLT to format a FetchXML response for display.

Fetching the data

To display data, obviously we need to retrieve some data first. Using the approach from my earlier post, we will use the following FetchXML query to return a list of contacts:

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">  
<entity name="contact">  
<attribute name="fullname" />  
<attribute name="contactid" />  
<attribute name="ownerid" />  
<attribute name="gendercode" />  
<attribute name="address1_stateorprovince" />  
<attribute name="createdon" />  
</entity>  
</fetch>  

When I execute this query, I get the output in the attached contacts.xml file: contacts_raw.xml (50.68 kb).

Designing the display format

It we are going to use XSLT to format XML data, we will need an XSL style sheet. A detailed discussion of XSL is beyond the scope of this post, but I will point out a couple of things about my style sheet:

First, I use a template to match the root node, and then I loop through each entity with this for-each element:

The "//a:Entities" tells it to match an a:Entities element with any ancestors so you don't have to match all the way to the top-level s:Envelope ancestor.

Second, FetchXML represents an attribute (or formattedvalue) as an element with one "key" child and one "value" child. The "key" child is the attribute name, and the "value" is, obviously, the value. Thus, from each entity, I select the attribute and formattedvalue data to display using elements like this:

What that means is "select the 'b:value' element that has an 'a:KeyValuePairOfstringanyType' that has a 'b:key' element equal to 'fullname.'" Alternatively, you can think abou it as "select the 'b:value' element that has a sibling 'b:key' element equal to 'fullname.'"

Here is the complete XSL style sheet: contacts.xsl (1.35 kb).

Once you have your XSL style sheet ready, upload it to your CRM instance as a web resource of type "Style Sheet (XSL)." Make sure you remember the relative path of the new resource. In my case it is "new_contacts.xsl."

Applying the transformation

Now that we have some FetchXML results and an XSL style sheet, all that's left to do is to transform the returned FetchXML and display it on the page. To do that, I have made a few modifications to my earlier FetchXML retrieval logic. Because jQuery retrieves things by default asynchronously, I start with retrieving the XSL document, then I use a callback function to retrieve the FetchXML results and finally I use a callback function to do the transformation. In the interest of not cluttering my code with global variables, I pass everything I need through the chain of callbacks. For example, my XSL retrieval method takes the FetchXML request as a parameter just so it can pass it to its callback. Then that callback takes the XSL document so that its callback has the XSL.

The code to do the apply and display the transformation is below:

function processData(xml, xsl) {  
/// <summary>  
/// transforms xml with a supplied xsl style sheet and appends the output to a div on the page  
/// </summary>  

//instantiate a variable to hold the transformed xml  
var resultDocument = "";  
resultDocument = TransformToHtmlText(xml, xsl);  
alert("Output started");  
$("#outputdiv").append(resultDocument);  

alert("Output complete");  
}  

function TransformToHtmlText(xmlDoc, xsltDoc) {  
/// <summary>  
/// Transforms a XML document to a HTML string by using a XSLT document  
/// 1. Use type XSLTProcessor, if browser (FF, Safari, Chrome etc) supports it  
/// 2. Use function [transformNode] on the XmlDocument, if browser (IE6, IE7, IE8) supports it  
/// 3. Use function transform on the XsltProcessor used for IE9 (which doesn't support [transformNode] any more)   
/// 4. Throws an error, when both types are not supported  
/// taken from "How to fix: DOMParser.TransformNode not supported in IE9"  
/// http://www.roelvanlisdonk.nl/?p=2113  
/// </summary>  
// 1.  
if (typeof (XSLTProcessor) != "undefined") {  
var xsltProcessor = new XSLTProcessor();  
xsltProcessor.importStylesheet(xsltDoc);  
var xmlFragment = xsltProcessor.transformToFragment(xmlDoc, document);  
return xmlFragment;  
}  
// 2.  
if (typeof (xmlDoc.transformNode) != "undefined") {  
return xmlDoc.transformNode(xsltDoc);  
}  
else {  

try {  
// 3  
if (window.ActiveXObject) {  
var xslt = new ActiveXObject("Msxml2.XSLTemplate");  
var xslDoc = new ActiveXObject("Msxml2.FreeThreadedDOMDocument");  
xslDoc.loadXML(xsltDoc.xml);  
xslt.style sheet = xslDoc;  
var xslProc = xslt.createProcessor();  
xslProc.input = xmlDoc;  
xslProc.transform();  

return xslProc.output;  
}  
}  
catch (e) {  
// 4  
alert("The type [XSLTProcessor] and the function [XmlDocument.transformNode] are not supported by this browser, can't transform XML document to HTML string!");  
return null;  
}  

}  
}

Two points to note:

  1. The processData method doesn't apply the transformation itself. It passes the XML and XSL to another method called TransformToHtmlText.
  2. The TransformToHtmlText method allows us to easily handle differences in the way that different browsers (or even different versions of the same browser) handle XML. For more information take a look at http://www.roelvanlisdonk.nl/?p=2113 and http://stackoverflow.com/questions/12149410/object-doesnt-support-property-or-method-transformnode-in-internet-explorer-1.

Trying it out

Here is a complete web page with the code for you to download and try out in your own CRM instance: fetchxml_xslt.htm (7.02 kb). Just upload it as you would any other web resource, publish and open. At that point you will be presented with a screen that looks like this:

The FetchXML / jQuery / XSLT example page

Either leave the pre-populated FetchXML in the textarea or supply your own. Once you click the "fetch and process" button, you will start to see some output and a couple of status alert windows before the output finally displays. The end result should look something like this:

The FetchXML / jQuery / XSLT example page showing output

comments powered by Disqus