locked
Updating CRM entity using Kendo UI grid RRS feed

  • Question

  • Hello,

    i found example of integrating kendo UI grid with CRM 2011 http://code.msdn.microsoft.com/Kendo-UI-DataSource-Kendo-33a3dc77#content.

    This example shows how to fetch data from CRM 2011 and show this data in Kendo UI grid, but there is no example of how to update entity. Example above is for CRM 2011, but works also in CRM 2013 (which I use).

    I am trying to make this work in CRM, but there are some differences between the format of HTTP request of Kendo UI grid (when row is updated) and the format which CRM OData endpoint expects.

    I found example in CRM SDK for updating entity through JavaScript (http://msdn.microsoft.com/en-us/library/gg309549.aspx).

    1.question:

    Debugging through IE, i see that URL of the updated entity (Account) needs to be in this format:

    /Test13/XRMServices/2011/OrganizationData.svc/AccountSet(guid'e612bae1-5803-e411-80c9-00155d0a0aac')

    I need to fetch account GUID of updated row and somehow put it in the url.

    How can I make my URL look like in the example above?

    2.question:

    I need to have a body of HTTP request in the following format:

    {"Name":"Updated Account Name","Address1_AddressTypeCode":{"Value":3},"Address1_City":"Sammamish","Address1_Line1":"123 Maple St.","Address1_PostalCode":"98074","Address1_StateOrProvince":"WA","EMailAddress1":"someone@microsoft.com"}

    I tried to get this by modifying parameterMap in Kendo grid datasource definition, so that it uses JSON data of selected row, but i get too much data. I get data in the following format:

    {"__metadata":{"uri":"https://crm13.dev.teched.hr/Test13/XRMServices/2011/OrganizationData.svc/AccountSet(guid'bdaf6deb-f9e7-e311-80c3-00155d0a0aac')","type":"Microsoft.Crm.Sdk.Data.Services.Account"},"TerritoryCode":{"__metadata":{"type":"Microsoft.Crm.Sdk.Data.Services.OptionSetValue"},"Value":1},"LastUsedInCampaign":null,"Address1_Name":null,"TimeZoneRuleVersionNumber":null,"OverriddenCreatedOn":null,"EntityImageId":null,"OwnershipCode":{"__metadata":{"type":"Microsoft.Crm.Sdk.Data.Services.OptionSetValue"},"Value":null},"CustomerSizeCode":{"__metadata":{"type":"Microsoft.Crm.Sdk.Data.Services.OptionSetValue"},"Value":1},"PrimaryContactId":{"__metadata":{"type":"Microsoft.Crm.Sdk.Data.Services.EntityReference"},"Id":"23b06deb-f9e7-e311-80c3-00155d0a0aac","LogicalName":"contact","Name":"Yvonne McKay (sample)"},"CreditLimit":{"__metadata":{"type":"Microsoft.Crm.Sdk.Data.Services.Money"},"Value":null},"EMailAddress2":null,"VersionNumber":null,"Address1_County":null,"EntityImage_Timestamp":null,"Telephone3":null,"DoNotBulkPostalMail":false,"Address1_FreightTermsCode":{"__metadata":{"type":"Microsoft.Crm.Sdk.Data.Services.OptionSetValue"},"Value":null},"Address2_Line1":null,"Aging90":{"__metadata":{"type":"Microsoft.Crm.Sdk.Data.Services.Money"},"Value":null},"DoNotPostalMail":false,"Telephone2":null,"OwningBusinessUnit":

    etc.

    How can I get this in correct format?

    3.question

    I see that CRM OData endpoint expects X-HTTP-METHOD = MERGE when the entity is updated.

    How can I do this with Kendo grid and when row is updating.

    I will continue to make this work, but if someone has experience with this, i would be grateful.

    My complete HTML with Javascript :

    <!DOCTYPE html>
    
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Kendo UI Demo</title>
        <!-- CDN-based stylesheet references for Kendo UI Web -->
        <link rel="stylesheet" href="http://cdn.kendostatic.com/2012.2.913/styles/kendo.common.min.css" />
        <link rel="stylesheet" href="http://cdn.kendostatic.com/2012.2.913/styles/kendo.default.min.css" />
    </head>
    <body style="border-width: 0px; padding-left: 0px; padding-top: 0px; margin-left: 0px; margin-top: 0px; margin-bottom: 0px; margin-right: 0px">
       <div id="grid"></div> 
        <!-- CDN-based script reference for jQuery --> 
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> 
        <!-- CDN-based script reference for Kendo UI DataViz --> 
        <script type="text/javascript" src="http://cdn.kendostatic.com/2012.2.913/js/kendo.web.min.js"></script> 
        <script type="text/javascript" src="ClientGlobalContext.js.aspx"></script> 
        <script type="text/javascript"> 
            $(document).ready(function ($) {
    
                var context = GetGlobalContext();
    
                var serverUrl = window.parent.document.location.protocol + '//' + window.parent.document.location.host + '/' + context.getOrgUniqueName();
    
                dataSource = new kendo.data.DataSource({
                    transport: {
                        read: {
                            url: serverUrl + "/XRMServices/2011/OrganizationData.svc/AccountSet",
                            dataType: 'json'
                        },
                        update: {
                            url: serverUrl + "/XRMServices/2011/OrganizationData.svc/AccountSet",
                            dataType: "jsonp",
                            type: "POST"
                        },
                        destroy: {
                            url: serverUrl + "/XRMServices/2011/OrganizationData.svc/AccountSet",
                            dataType: "jsonp"
                        },
                        create: {
                            url: serverUrl + "/XRMServices/2011/OrganizationData.svc/AccountSet",
                            dataType: "jsonp"
                        },
    
    
                        //parameterMap: function (options) {
                        //    var parameter = {
                        //        $select: 'Name,Telephone1,Address1_City'
                        //    };
    
                        //    return parameter;
                        //}
    
                        parameterMap: function (options, operation) {
                            if (operation !== "read") {
    
                                var tr = grid.table.find("tr").find("td:first input").attr("checked", "true").closest("tr");
    
                                var selectedRowJSON = $("#grid").data("kendoGrid").dataItem(tr).toJSON();
    
    
                                var postData = kendo.stringify(selectedRowJSON);
    
                                return postData;
                            }
                        }
                    },
                    batch: false,
                    schema: {
                        model: {
                            id: "AccountId",
                            fields: {
                                Name: { type: "string" },
                                Telephone1: { type: "string" },
                                Address1_City: { type: "string" }
                            }
                        },
                        total: function (data) {
    
                            var fetchXml = '<fetch mapping="logical" aggregate="true">' +
                                               '<entity name="account">' +
                                                    "<attribute name='accountid' alias='count' aggregate='count'/>" +
                                               '</entity>' +
                                           '</fetch>';
    
                            var _oService = new FetchUtil(context.getOrgUniqueName(), serverUrl);
                            var res = _oService.Fetch(fetchXml);
    
                            var count = res[0].attributes.count.formattedValue;
    
                            return count;
                        },
                        parse: function (data) {
                            return data.d.results;
                        },
                        type: "json"
                    },
                    serverPaging: true,
                    pageSize: 10,
                    serverSorting: true,
                    sort: { field: "Name", dir: "asc" }
                });
    
                var grid = $("#grid").kendoGrid({
                    dataSource: dataSource,
                    height: 450,
                    columns: [
    
                    {
                        template: '<input type="checkbox" />',
                        sortable: false,
                        width: 45
                    },
                    {
                        title: 'Account Name',
                        field: "Name",
                        width: 180
                    },
                    {
                        title: 'Main Phone',
                        field: "Telephone1",
                        width: 300
                    },
                    {
                        title: 'Address 1: City',
                        field: "Address1_City",
                        filterable: false,
                        width: 300,
                        sortable: false
                    },
    				{
    				    command: ["edit", "destroy"],
    				    title: "&nbsp;",
    				    width: "200px"
    				}
                    ],
                    toolbar: ["create"],
                    editable: "inline",
                    pageable: true,
                    selectable: "multiple, 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();
                                }
                            });
                    },
                    change: function (e) {
                        window.setTimeout(function () {
                            var checkbox = $(e.sender.select()[0]).find("td:first input").prop("checked", true);
                            grid.table.find("tr").find("td:first input:checked").not(checkbox).prop("checked", false);
                        }, 0);
                    }
    
                }).data("kendoGrid");
    
                grid.thead.find("th:first")
                    .append($('<input class="selectAll" type="checkbox"/>'))
                    .delegate(".selectAll", "click", function () {
                        var checkbox = $(this);
    
                        grid.table.find("tr")
                            .find("td:first input")
                            .attr("checked", checkbox.is(":checked"))
                            .trigger("change");
                    });
            }).ajaxSend(function (e, jqxhr, settings) {
                if (settings.url.toLowerCase().indexOf("XRMServices/2011/OrganizationData.svc".toLowerCase()) >= 0) {
                    jqxhr.setRequestHeader("Accept", "application/json");
                }
            });
        </script>
        <script id="FetchUtil" type="text/javascript">
            var XMLHTTPSUCCESS = 200;
            var XMLHTTPREADY = 4;
    
            function FetchUtil(sOrg, sServer) {
                this.org = sOrg;
                this.server = sServer;
    
                if (sOrg == null) {
                    if (typeof (ORG_UNIQUE_NAME) != "undefined") {
                        this.org = ORG_UNIQUE_NAME;
                    }
                }
    
                if (sServer == null) {
                    this.server = window.location.protocol + "//" + window.location.host;
                }
            }
    
            FetchUtil.prototype._ExecuteRequest = function (sXml, sMessage, fInternalCallback, fUserCallback) {
                var xmlhttp = new XMLHttpRequest();
                xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
                xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
                xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
                xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
    
                if (fUserCallback != null) {
                    //asynchronous: register callback function, then send the request. 
                    var crmServiceObject = this;
                    xmlhttp.onreadystatechange = function () {
                        fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
                    };
                    xmlhttp.send(sXml);
                } else {
                    //synchronous: send request, then call the callback function directly 
                    xmlhttp.send(sXml);
                    return fInternalCallback.call(this, xmlhttp, null);
                }
            }
    
            FetchUtil.prototype._HandleErrors = function (xmlhttp) {
                /// <summary>(private) Handles xmlhttp errors</summary> 
                if (xmlhttp.status != XMLHTTPSUCCESS) {
                    var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
                    alert(sError);
                    return true;
                } else {
                    return false;
                }
            }
    
            FetchUtil.prototype.Fetch = function (sFetchXml, fCallback) {
                /// <summary>Execute a FetchXml request. (result is the response XML)</summary> 
                /// <param name=”sFetchXml”>fetchxml string</param> 
                /// <param name=”fCallback” optional=”true” type=”function”>(Optional) Async callback function if specified. If left null, function is synchronous </param> 
    
                var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
                request += "<s:Body>";
    
                request += '<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>';
    
                request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);
    
                request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';
    
                request += '</s:Body></s:Envelope>';
    
                return this._ExecuteRequest(request, "Fetch", this._FetchCallback, fCallback);
            }
    
            FetchUtil.prototype._FetchCallback = function (xmlhttp, callback) {
                ///<summary>(private) Fetch message callback.</summary> 
                //xmlhttp must be completed 
                if (xmlhttp.readyState != XMLHTTPREADY) {
                    return;
                }
    
                //check for server errors 
                if (this._HandleErrors(xmlhttp)) {
                    return;
                }
    
                var sFetchResult = xmlhttp.responseXML.selectSingleNode("//a:Entities").xml;
    
                var resultDoc = new ActiveXObject("Microsoft.XMLDOM");
                resultDoc.async = false;
                resultDoc.loadXML(sFetchResult);
    
                //parse result xml into array of jsDynamicEntity objects 
                var results = new Array(resultDoc.firstChild.childNodes.length);
    
                for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
                    var oResultNode = resultDoc.firstChild.childNodes[i];
                    var jDE = new jsDynamicEntity();
                    var obj = new Object();
    
                    for (var j = 0; j < oResultNode.childNodes.length; j++) {
                        switch (oResultNode.childNodes[j].baseName) {
                            case "Attributes":
                                var attr = oResultNode.childNodes[j];
    
                                for (var k = 0; k < attr.childNodes.length; k++) {
    
                                    // Establish the Key for the Attribute 
                                    var sKey = attr.childNodes[k].firstChild.text;
                                    var sType = '';
    
                                    // Determine the Type of Attribute value we should expect 
                                    for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
                                        if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
                                            sType = attr.childNodes[k].childNodes[1].attributes[l].text;
                                        }
                                    }
    
                                    switch (sType) {
                                        case "a:OptionSetValue":
                                            var entOSV = new jsOptionSetValue();
                                            entOSV.type = sType;
                                            entOSV.value = attr.childNodes[k].childNodes[1].text;
                                            obj[sKey] = entOSV;
                                            break;
    
                                        case "a:EntityReference":
                                            var entRef = new jsEntityReference();
                                            entRef.type = sType;
                                            entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
                                            entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
                                            entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
                                            obj[sKey] = entRef;
                                            break;
    
                                        default:
                                            var entCV = new jsCrmValue();
                                            entCV.type = sType;
                                            entCV.value = attr.childNodes[k].childNodes[1].text;
                                            obj[sKey] = entCV;
    
                                            break;
                                    }
                                }
    
                                jDE.attributes = obj;
                                break;
    
                            case "Id":
                                jDE.guid = oResultNode.childNodes[j].text;
                                break;
    
                            case "LogicalName":
                                jDE.logicalName = oResultNode.childNodes[j].text;
                                break;
    
                            case "FormattedValues":
                                var foVal = oResultNode.childNodes[j];
    
                                for (var k = 0; k < foVal.childNodes.length; k++) {
                                    // Establish the Key, we are going to fill in the formatted value of the already found attribute 
                                    var sKey = foVal.childNodes[k].firstChild.text;
    
                                    jDE.attributes[sKey].formattedValue = foVal.childNodes[k].childNodes[1].text;
                                }
                                break;
                        }
                    }
                    results[i] = jDE;
                }
    
                //return entities 
                if (callback != null) callback(results);
                else return results;
            }
    
            function jsDynamicEntity(gID, sLogicalName) {
                this.guid = gID;
                this.logicalName = sLogicalName;
                this.attributes = new Object();
            }
    
            function jsCrmValue(sType, sValue) {
                this.type = sType;
                this.value = sValue;
            }
    
            function jsEntityReference(gID, sLogicalName, sName) {
                this.guid = gID;
                this.logicalName = sLogicalName;
                this.name = sName;
                this.type = 'EntityReference';
            }
    
            function jsOptionSetValue(iValue, sFormattedValue) {
                this.value = iValue;
                this.formattedValue = sFormattedValue;
                this.type = 'OptionSetValue';
            }
        </script>
    </body>
    </html>
    

    Friday, July 4, 2014 9:31 AM