Building a Kendo UI Grid Application for Dynamics CRM 2015

In this post, I provide instructions on how to build a basic CRM entity grid that is based upon the Kendo UI Web Resource solution I created in a prior post;

Building a reusable Kendo UI Web Resource Solution for Dynamics CRM 2015.

The above is a sample of our CRM contact entity grid.  This grid includes sortable columns, paging controls and the display of a record counter (click on the grid for a larger version).  The grid uses the standard Office 365 style sheet provided in the Kendo UI solution.

Let’s get started

As a prerequisite you first need to build and deploy the required Kendo UI Web Resource Solution (mentioned earlier) in your CRM organization.

We are going to create the sample grid using data from the CRM 2015 Contact entity. Utilizing the REST and SOAP protocols available.

Using a developer platform such as Visual Studio, create a solution that supports HTML and JavaScript web development.

You can use your existing crmXtra_KenduUI solution if you want.

Visual Studio Solution

I will be using my existing crmXtra_KendoUI solution in this sample.  Whithin your solution, create a project folder named ‘KendoUI_Samples_CRM2015’ or something similar.

Next, create a child folder named ‘crmXtra_Samples’ followed by a child folder named ‘CRM2015_KendoUI_Grid_Sample’ and finally a child folder named ‘js’.

‘crmXtra_Samples’ will become the root web resource folder name once deployed to a CRM solution.

We will create two web resources:

  • CRM2015_KendoUI_Grid_Sample.html
  • CRM2015_KendoUI_Grid_Sample_ScriptLib.js

Creating the CRM2015_KendoUI_Grid_Sample.html file

Within the ‘CRM2015_KendoUI_GridSample’ folder add your HTML file named ‘CRM2015_KendoUI_Grid_Sample.html’.

Add the content shown below to your html file, further details will follow:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>A Kendo UI Grid sample for CRM 2015</title>
    <meta http-equiv="X-UA-Compatible" />
    <script type="text/javascript" src="../../../ClientGlobalContext.js.aspx"></script>
    <script type="text/javascript" src="../../../crmxtra_/KUI_V2017_1_223/js/jquery.min.js"></script>
    <script type="text/javascript" src="../../../crmxtra_/KUI_V2017_1_223/js/kendo.all.min.js"></script>
    <link href="../../../crmxtra_/KUI_V2017_1_223/css/kendo.common_office365.min.css" rel="stylesheet" type="text/css" />
    <link href="../../../crmxtra_/KUI_V2017_1_223/css/kendo.office365.min.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="js/CRM2015_KendoUI_Grid_Sample_ScriptLib.js"></script>
</head>
<body>
    <div id="crmXtra_ku_CrmContactGrid">
        <style scoped>
            body {margin: 3px; border: 0px; background-color: White; font-family: Arial, Helvetica, sans-serif; font-size: 75%;}
        </style>
    </div>
    <script type="text/javascript">
        $(document).ready(function ($) {
            try {
                crmXtra_InitFormLoad();
            }
            catch (err) {
                alert("CRM2015_KendoUI_Grid_Sample.html  Document Ready Error: " + err.message);
            }
        });
    </script>
</body>
</html>

The source code listed above contains the typical script and style sheet references used with a Kendo UI based web page.

  • On row 6 in the source code, there is a reference to ‘ClientGlobalContext.js.aspx’. This is typically required when building Web Resource pages in CRM. This provides the user access to data and other functionality within CRM.
  • On row 6 thru 10, the src= statements all begin with ‘../../../’. This is needed, since the web resource solution we are creating in CRM will have a folder structure that place our web page (HTML file) 3 levels deep from the CRM root.
  • Kendo UI require jQuery and so does our custom JavaScript that is used in this sample, so there is a reference to it on row 7. This is a reference to our existing Kendo UI Web Resource library that we deployed to CRM already.
  • On row 8, there is a reference to ‘kendo.all.min.js’. This provides us the ability to create any of the Kendo UI Widgets, not just the Grid used in this example.
  • On rows 9 and 10, are the references to the required files to use the Office365 style sheets.
  • On row 11 there is a reference to ‘js/CRM2015_KendoUI_Grid_Sample_ScriptLib.js’. This is the JavaScript that we will focus on next, that controls the design and behavior of our Grid.
  • On row 14 there is a DIV statement. This DIV statement represents the location of the Kendo UI Grid Widget. The layout and other details regarding the Grid will be setup within our JavaScript code.
  • On row 20, there is a ‘$(document).ready’ function. This function is initialized as soon as the base HTML page has rendered. Most web pages that use a Kendo UI Widget will have this function. Our JavaScript function ‘crmXtra_InitFormLoad’ is called here.

Creating the CRM2015_KendoUI_Grid_Sample_ScriptLib.js file

Within the ‘CRM2015_KendoUI_GridSample’, ‘js’ folder add your JavaScript file named ‘CRM2015_KendoUI_Grid_Sample_ScriptLib.js’.

Add the content shown below to your js file, further details will follow:

//CRM2015_KendoUI_Grid_Sample_ScriptLib.js
//Declare and intialize common used variables
var context = GetGlobalContext();
var serverUrl = context.getClientUrl();
var userid = context.getUserId();

var ODATA_ENDPOINT = "/XRMServices/2011/OrganizationData.svc";
var SOAP_ENDPOINT = "/XRMServices/2011/Organization.svc/web";

function crmXtra_InitFormLoad() {
    try {
        //Define FetchXML to get record count for the contact entity for use in grid display
        var CrmContactCountFetch = '<fetch aggregate="true" mapping="logical">' +
        '<entity name="contact">' +
        '<attribute name="contactid" alias="crmentitycount" aggregate="countcolumn" />' +
        '<filter>' +
        '<condition attribute="statecode" operator="eq" value="0" />' +
        '</filter></entity></fetch>';

        //Get the contact record count and create the sample grid when complete
        var crmcontactfetchrequest = crmXtra_CreateCrmFetchRequest(CrmContactCountFetch);
        crmXtra_ExecuteCrmSoapPostAction(crmcontactfetchrequest, serverUrl, SOAP_ENDPOINT, crmXtra_CreateContactGridSample);
    }
    catch (err) {
        alert("CRM2015_KendoUI_Grid_Sample_ScriptLib.js  Form Load Error: " + err.message);
    }
}

function crmXtra_CreateContactGridSample(XmlString) {
    //Create the grid using the record count xml (this is the callback function from 'crmXtra_ExecuteCrmSoapPostAction')
    try {
        //Set default recordcount value
        var CrmRecordCount = "0";
        //Check for FetchXml failure, if fail exit process.
        if (XmlString == "FAIL9999") { return false; }
        //Get the record count from the XML string (convert text xml to parsable jquery xml)
        var $xmldata = $($.parseXML(XmlString));
        var EntityNode = "";
        var CountNode = "0";
        crmlogicalentity = $xmldata.find("a\\:EntityLogicalName, EntityLogicalName").text();
        if (crmlogicalentity == "contact") {
            EntityNode = $xmldata.find("a\\:EntityLogicalName, EntityLogicalName").text(),
            CountNode = $xmldata.find("a\\:Value, Value").text();
            CrmRecordCount = CountNode;
        }
        else {
            //Non matching entity type, exit process
            return false;
        }
        //Define CRM DataSet Properties to create a CRM REST based datasource for the sample grid
        var CrmEntitySetName = "ContactSet";
        var CrmEntityAttributeSelection = "FirstName, LastName, Telephone1, EMailAddress1";
        var CrmEntityPrimaryKey = "ContactId";
        var CrmEntitySortAttribute = "LastName";
        var CrmEntitySortDirection = "asc";
        var CrmEntityFilter = "StateCode/Value eq 0";  //To return Active Records only

        var CrmEntityAttributeDef = [];
        CrmEntityAttributeDef.push({ field: "FirstName", type: "string" });
        CrmEntityAttributeDef.push({ field: "Lastname", type: "string" });
        CrmEntityAttributeDef.push({ field: "EMailAddress1", type: "string" });
        CrmEntityAttributeDef.push({ field: "Telephone1", type: "string" });

        var CrmEntityColumns = [];
        CrmEntityColumns.push({ field: "FirstName", title: "First Name", width: "200px" });
        CrmEntityColumns.push({ field: "LastName", title: "Last Name", width: "200px" });
        CrmEntityColumns.push({ field: "Telephone1", title: "Business Phone", width: "100px" });
        CrmEntityColumns.push({ field: "EMailAddress1", title: "E-mail", width: "200px" });

        var DataPageSize = 50;  //Cannot exceed 50 per CRM REST web service limit, unless reconfigured

        //Define Data Source/Layout for the contact grid
        var CrmContactDataSource = crmXtra_CrmEntityReadDataSource(serverUrl, ODATA_ENDPOINT, CrmEntitySetName, CrmEntityAttributeSelection, CrmEntityPrimaryKey, CrmEntitySortAttribute, CrmEntitySortDirection, CrmEntityAttributeDef, CrmEntityColumns, DataPageSize, CrmRecordCount, CrmEntityFilter);

        //Define Contact Entity Grid Widget
        var grid = $("#crmXtra_ku_CrmContactGrid").kendoGrid({
            dataSource: CrmContactDataSource,
            sortable: true,
            height: 400,
            columns: CrmEntityColumns,
            editable: false,
            filterable: false,
            pageable: true,
            groupable: false,
            navigatable: true,
            selectable: "row",
            sortable: {
                mode: 'single',
                allowUnsort: false
            },
            dataBound: function () {
                grid.table.find("tr").find("td:first input")
                    .change(function (e) {
                        if (!$(this).prop('checked')) {
                            grid.clearSelection();
                        }
                    });
            }
        }).data("kendoGrid");
    }
    catch (err) {
        alert("Script Failure: Function(crmXtra_CreateContactGridSample) Error Detail: " + err.message);
    }
}

function crmXtra_CreateCrmFetchRequest(CrmFetchXML) {
    //This function is used to construct a CRM FetchXML request
    //CrmFetchXML is a properly constructed FetchXML string
    try {
        var crmfetchrequest = '<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">';
        crmfetchrequest += '<s:Body>';
        crmfetchrequest += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' +
        '<request i:type="b:RetrieveMultipleRequest" ' +
        ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' +
        ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' +
        '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' +
        '<b:KeyValuePairOfstringanyType>' +
        '<c:key>Query</c:key>' +
        '<c:value i:type="b:FetchExpression">' +
        '<b:Query>';
        crmfetchrequest += CrmEncodeDecode.CrmXmlEncode(CrmFetchXML);  //This requires "ClientGlobalContext.js.aspx"
        crmfetchrequest += '</b:Query>' +
        '</c:value>' +
        '</b:KeyValuePairOfstringanyType>' +
        '</b:Parameters>' +
        '<b:RequestId i:nil="true"/>' +
        '<b:RequestName>RetrieveMultiple</b:RequestName>' +
        '</request>' +
        '</Execute>';
        crmfetchrequest += '</s:Body></s:Envelope>';
        return crmfetchrequest;
    }
    catch (err) {
        alert("Script Failure: Function(crmXtra_CreateCrmFetchRequest) Error Detail: " + err.message);
    }
}

function crmXtra_ExecuteCrmSoapPostAction(CrmFetchRequest, serverUrl, SOAP_ENDPOINT, async_callback) {
    //This function is used to execute a CrmFetchRequest and return the result in the format specified
    //CrmFetchRequest is a properly constructed CRM SOAP Fetch Request
    //serverUrl is the CRM web server URL
    //SOAP_ENDPOINT is the SOAP web service partial URL
    //async_callback is the function to call back when the async process is complete
    try {
        $.ajax({
            type: "POST",
            dataType: "xml",
            contentType: "text/xml; charset=utf-8",
            processData: false,
            url: serverUrl + SOAP_ENDPOINT,
            data: CrmFetchRequest,
            cache: false,
            async: true,
            beforeSend: function (xhr) {
                xhr.setRequestHeader(
                    "SOAPAction",
                    "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute"
                );
            }
        }).done(function (data) {
            //Convert jQuery XML result as text XML to pass to callback function
            xmlString = (new XMLSerializer()).serializeToString(data);
            //Return the value to the callback function
            async_callback(xmlString);
        }).fail(function (jqXHR, textStatus, errorThrown) {
            //Display Failure message
            alert("CRM SOAP Request failed: " + textStatus + "\n" + errorThrown);
            async_callback("FAIL9999"); //'FAIL9999' text used to indicate failure back to callback function
        });
    }
    catch (err) {
        alert("Script Failure: Function(crmXtra_ExecuteCrmSoapPostAction) Error Detail: " + err.message);
    }
}

function crmXtra_CrmEntityReadDataSource(serverUrl, ODATA_ENDPOINT, CrmEntitySetName, CrmEntityAttributeSelection, CrmEntityPrimaryKey, CrmEntitySortAttribute, CrmEntitySortDirection, CrmEntityAttributeDef, CrmEntityColumns, DataPageSize, CrmRecordCount, CrmEntityFilter) {
    //This function constructs a CRM Entity REST web service data source with read capability (no edit)
    //serverUrl is the CRM web server URL
    //ODATA_ENDPOINT is the REST web service partial URL
    //CrmEntitySetName is the name of the entity set e.g 'ContactSet'
    //CrmEntityAttributeSelection is a string containing the Crm Attributes to retrieve e.g. 'FirstName, LastName, Telephone1, EMailAddress1'
    //CrmEntityPrimaryKey is a string containing the name of the primary key attribute e.g. 'ContactId'
    //CrmEntitySortAttribute is a string containing the name of the attribute to sort the result set by e.g. 'LastName'
    //CrmEntitySortDirection is a string specifying the sort as Ascending or Descending e.g. 'asc' or 'desc'
    //CrmEntityAttributeDef is an array containing a list of the attributes and their respective data types for use by Kendo UI grid rendering
    //CrmEntityColumns is an array containing a list of the attribute column order, column header and column size for use by Kendo UI grid rendering
    //DataPageSize is the number of records displayed on a grid page, per CRM REST limits this is a maximum value of 50
    //CrmRecordCount is the maximum number of records that will be returned by the REST query and is used for paging logic. This value is obtain by a FetchXml query
    //CrmEntityFilter is the conditional filter value placed on the data values retrived e.g. 'StateCode/Value eq 0'  to retrieve active contact records only
    try {
        CrmDataSource = new kendo.data.DataSource({
            transport: {
                read: {
                    url: serverUrl + ODATA_ENDPOINT + "/" + CrmEntitySetName,
                    dataType: 'json'
                },
                parameterMap: function (options) {
                    var parameter = {
                        $top: options.take,
                        $skip: options.skip,
                        $select: CrmEntityAttributeSelection,
                        $orderby: options.sort[0].field + ' ' + options.sort[0].dir,
                        $filter: CrmEntityFilter
                    };
                    return parameter;
                }
            },
            schema: {
                model: {
                    id: CrmEntityPrimaryKey,
                    fields: function () { return CrmEntityAttributeDef; }
                },
                total: function (data) {
                    //Set the total record count
                    return CrmRecordCount;
                },
                parse: function (data) {
                    return data.d.results;
                },
                type: "json"
            },
            serverPaging: true,
            pageSize: DataPageSize,
            serverSorting: true,
            sort: { field: CrmEntitySortAttribute, dir: CrmEntitySortDirection }
        });
        return CrmDataSource;
    }
    catch (err) {
        alert("Script Failure: Function(crmXtra_CrmEntityReadDataSource) Error Detail: " + err.message);
    }
}

Save your JavaScript in your Visual Studio project, which by now should look something like:

A general explanation of the source code

The JavaScript consist of multiple functions used to format and populate the Kendo Grid widget. Some of the functions included are the basis for the CRM data source that supply contact records to the grid.

I’m assuming that you already are familiar with developing web resources for CRM, if not I suggest you get start with the Microsoft CRM SDK documentation, that will cover most of the code you see here. jQuery syntax and functionality is also used here, so for further details consult the documentation on the jQuery web site.

Besides laying out the basic columns and data definition for the grid, the part that can be tricky is building out a data source that will work with most CRM entities. In this case we are creating a data source for the CRM entity ‘Contact’. You should be able to change the data source to another CRM entity fairly simply by changing the entity set name and its attributes if needed.

CRM 2015 provide both ODATA and SOAP protocols. Most of the time using ODATA is simple and sufficient. However, since we are creating a grid with a standard Kendo pager and record counter we need to go beyond ODATA to SOAP for the record count. This is due to a limited implementation of the REST protocol in this version of CRM.

The following is a brief discussion of some of the JavaScript functions included;

  • function crmXtra_InitFormLoad() Is used to obtain the record count of the total contacts included in our grid. It constructs a Fetch request and executes a SOAP call in CRM.
  • function crmXtra_CreateContactGridSample(XmlString) Is used to create the Grid layout and attribute definition. A CRM read-only data source is referenced here. Within this function the DIV for the Kendo Grid on the HTML page is referenced and its structure and data source applied. For more details on the grid definition used, consult the Kendo UI Grid documentation.
  • function crmXtra_CreateCrmFetchRequest(CrmFetchXML) Is used to construct a SOAP Fetch XML request that is being used when obtaining the record count for our query in CRM.
  • function crmXtra_ExecuteCrmSoapPostAction(CrmFetchRequest, serverUrl, SOAP_ENDPOINT, async_callback) Is used to make an asynchronous SOAP call to CRM and return the data as an XML string to the callback function specified.
  • function crmXtra_CrmEntityReadDataSource(serverUrl, ODATA_ENDPOINT, CrmEntitySetName, CrmEntityAttributeSelection, CrmEntityPrimaryKey, CrmEntitySortAttribute, CrmEntitySortDirection, CrmEntityAttributeDef, CrmEntityColumns, DataPageSize, CrmRecordCount, CrmEntityFilter)
    Is used to construct a Kendo Read-Only data source using ODATA for the CRM entity specified.

Adding your Web Resource project to CRM

Within CRM 2015, create a new solution. Give it an appropriate solution name and select your publisher.

Below is a sample screenshot of the one I created;

Select Web Resources from the left navigation area and click ‘New’, to add the HTML web resource discussed earlier.
Name it, so that it has the necessary folder structure, for example ‘/crmXtra_Samples/CRM2015_KendoUI_Grid_Sample/CRM2015_KendoUI_Grid_Sample.html’.

Below is a sample screenshot of the one I created;

Upload your HTML source file, save and publish.

Click ‘New’ again, to add the Script web resource discussed earlier. Name it, so that it has the necessary folder structure, for example ‘/crmXtra_Samples/CRM2015_KendoUI_Grid_Sample/js/CRM2015_KendoUI_Grid_Sample_ScriptLib.js’.

Below is a sample screenshot of the one I created;

Upload your JavaScript source file, save and publish.

Testing and Previewing your Kendo UI Grid for CRM 2015

You can test and Preview your new Web Resource, by simply using the button on the HTML web resource you just created.

You should see something like;

For further details of the built in grid functionality, consult your Kendo UI Grid documentation.

Leave a Reply

Your email address will not be published. Required fields are marked *