Featured Post

Web API Requests Series

Web API Series:  Create and Retrieve , Update and Delete , Retrieve Multiple , Associate/Disassociate , Impersonation , Run Workflows

07 April 2016

Activities Timeline

I have recently been playing with a very nice HTML Timeline control and have integrated within the Account form in CRM using the new Web API, of course! The Timeline is used to display activities related to the current Account record, it could be used on any record type though.

Here is a screenshot of the beta version:

Some of the functionalities so far:
  • Add new activities (phone calls, emails, tasks, appointments)
  • Drag to reschedule activities
  • Select activities to display
  • Zoom in and out to adjust the period displayed
  • Refresh view
  • Go to Today's activities

04 March 2016

Web API Requests Series

Web API Series: Create and Retrieve, Update and Delete, Retrieve Multiple, Associate/Disassociate, Impersonation, Run Workflows

CRM 2016 - WEB API Operations - Associate/Disassociate for Many to Many

Associate and Disassociate for Many to Many (i.e. Collection-Valued navigation property) relationships using the new CRM 2016 Web API in Javascript.
In this example I am going to show you how to Associate/Disassociate two out of the box records: an opportunity and an account.
You can find more information about Single and Collection-Valued properties here and here

this.associateRecords = function (parentId, parentType, relationshipName, childId, childType, successCallback, errorCallback) {
        var req = new XMLHttpRequest();
        req.open("POST", encodeURI(getWebAPIPath() + parentType + "(" + parentId + ")/" + relationshipName + "/$ref"), true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 204 || this.status == 1223) {
                    successCallback();
                }
                else {
                    errorCallback(bd_Utilities.WebAPI.errorHandler(this));
                }
            }
        };
        var childEntityReference = { "@odata.id": getWebAPIPath() + "/" + childType + "(" + childId + ")" };

        req.send(JSON.stringify(childEntityReference));  
    };

//Account: E48456EE-49B0-E611-810F-3863BB357C39
//Opportunity: F45D2DF7-6DBF-E511-8109-3863CB343C91
associate: function () {
        bd_Utilities.WebAPI.associateRecords("E48333EE-49B9-E511-810F-3863BB357C38", "accounts", "opportunity_customer_accounts", "F25D2DF7-6CBF-E511-8109-3863BB343C90", "opportunities",
            function () {

            },
            function () {

            });
    }

And the Disassociate

this.disassociateRecords = function (parentId, parentType, relationshipName, childId, childType, successCallback, errorCallback) {
        var req = new XMLHttpRequest();
     
        var webApiPath = getWebAPIPath();
        var parentUri = webApiPath + "/" + parentType + "(" + parentId + ")/";
        var childUri = webApiPath + "/" + childType + "(" + childId + ")";

        req.open("DELETE", encodeURI(parentUri + relationshipName + "/$ref?$id=" + childUri), true);

        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 204 || this.status == 1223) {
                    successCallback();
                }
                else {
                    errorCallback(bd_Utilities.WebAPI.errorHandler(this));
                }
            }
        };
        req.send(JSON.stringify());
    };

function getWebAPIPath() {
        return getClientUrl() + "/api/data/v8.0/";
}

function getClientUrl() {
        //Get the organization URL
        if (typeof GetGlobalContext == "function" &&
            typeof GetGlobalContext().getClientUrl == "function") {
            return GetGlobalContext().getClientUrl();
        }
        else {
            //If GetGlobalContext is not defined check for Xrm.Page.context;
            if (typeof Xrm != "undefined" &&
                typeof Xrm.Page != "undefined" &&
                typeof Xrm.Page.context != "undefined" &&
                typeof Xrm.Page.context.getClientUrl == "function") {
                try {
                    return Xrm.Page.context.getClientUrl();
                } catch (e) {
                    throw new Error("Xrm.Page.context.getClientUrl is not available.");
                }
            }
            else { throw new Error("Context is not available."); }
        }
    }

CRM 2016 - WEB API CRUD Operations - Impersonate another User

Web API series Part 1, Part 2, Part 3
Impersonating another user is very simple, you just need to set the XMLHttpRequest header as shown below.
Here are a couple of basic examples on how to impersonate a User in CRUD operations with the new CRM 2016 Web API in Javascript, here I am just going to show you impersonation in Create and Update. Impersonating another user should also be possible in Delete and Retrieve operations. I haven't tried it yet.

updateRecordOnBehalfOf = function (id, object, entitySetName, successCallback, errorCallback, impersonatedUserId) {

        var req = new XMLHttpRequest();
        req.open("PATCH", encodeURI(getWebAPIPath() + entitySetName + "(" + id + ")"), true);
        req.setRequestHeader("MSCRMCallerID", impersonatedUserId);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 204 || this.status == 1223) {
                    successCallback();
                }
                else {
                    errorCallback(bd_Utilities.WebAPI.errorHandler(this));
                }
            }
        };
        req.send(JSON.stringify(object));
    };

createOnBehalfOf = function (entitySetName, entity, successCallback, errorCallback, impersonatedUserId) {
        var req = new XMLHttpRequest();
        req.open("POST", encodeURI(getWebAPIPath() + entitySetName), true);      
        req.setRequestHeader("MSCRMCallerID", impersonatedUserId);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 204) {
                    if (successCallback)
                        successCallback(this.getResponseHeader("OData-EntityId"));
                }
                else {
                    if (errorCallback)
                        errorCallback(bd_Utilities.WebAPI.errorHandler(this.response));
                }
            }
        };
        req.send(JSON.stringify(entity));
    };

I hope this help you getting started with the new CRM 2016 Web API.

02 March 2016

CRM 2016 - WEB API CRUD Operations (Part 3)

Following Part 1 and Part 2 here is how to "Update" and "Delete" using the new CRM WebAPI.

    this.updateRecord = function (id, object, entitySetName, successCallback, errorCallback) {

        var req = new XMLHttpRequest();
        req.open("PATCH", encodeURI(getWebAPIPath() + type + "(" + id + ")"), true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 204 || this.status == 1223) {
                    successCallback();
                }
                else {
                    errorCallback(bd_Utilities.WebAPI.errorHandler(this));
                }
            }
        };
        req.send(JSON.stringify(object));
    };

    this.deleteRecord = function (id, entitySetName, successCallback, errorCallback) {

        var req = new XMLHttpRequest();
        req.open("DELETE", encodeURI(getWebAPIPath() + type + "(" + id + ")", true));
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {

            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 204) {
                    successCallback();
                }
                else {
                    errorCallback(bd_Utilities.WebAPI.errorHandler(this));
                }
            }
        };
        req.send();
    };

CRM 2016 - WEB API CRUD Operations (Part 2)


Retrieving multiple records using paging with the new CRM 2016 Web API in Javascript

On my previous post I talked about how to "Retrieve" a single record by GUID and how to create a record.
In this post I will show you how to perform a simple "RETRIEVEMULTIPLE" using the new CRM Web API.

Straight to the code, which you can add to the other functions from my previous post.
The highlighted code is necessary to achieve paging in cases where we want to, for whatever reason, or when there are more than 5000 records in the result set.
(Note that is different from the previous implementation of the OData service)
If you do not want to do paging and you have more than 5000 records remove this line:
"req.setRequestHeader("Prefer", "odata.maxpagesize=500");" and every page will contain 5000 records not 50 as in the previous OData service.


this.retrieveMultipleRecords = function (entitySetName, select, successCallback, errorCallback, onComplete) {

        var req = new XMLHttpRequest();

//This part works, but I am not sure if it is the best way
        if (select.indexOf("skiptoken") != -1) {
            req.open("GET", select, true);
        }
        else {
            req.open("GET", encodeURI(getWebAPIPath() + entitySetName + select), true);
        }

        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.setRequestHeader("Prefer", "odata.maxpagesize=500");

        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;                
                if (this.status == 200) {
                    var returned = JSON.parse(this.responseText, bd_Utilities.WebAPI.dateReviver);
                    if (successCallback) {
                        successCallback(returned);
                    }

                    if (returned["@odata.nextLink"] != null) {
                        var queryOptions = returned["@odata.nextLink"];

                        bd_Utilities.WebAPI.retrieveMultipleRecords(entitySetName, queryOptions, successCallback, errorCallback, onComplete);
                    }
                    else {
                        onComplete();
                    }
                }
                else {
                    if (errorCallback) {
                        errorCallback(bd_Utilities.WebAPI.errorHandler(this.response));
                    }
                }
            }
        };
        req.send();
    };

And here is the function call:

retrieveMultiple: function () {
        "use strict";
        bd_Utilities.WebAPI.retrieveMultipleRecords("new_events", "?$select=new_name",

            function (results) {
                events = events.concat(results.value);
            },
            function (error) { console.log(error.message); },

            function () {
                console.log("Retrieve multiple complete!");
            });
    },

01 March 2016

CRM 2016 - WEB API CRUD Operations (Part 1)

How to CRUD with the new CRM 2016 Web API in Javascript

In this post I wanted to talk of the new Web API available in CRM 2016. I am not sure about you but I could not find any example, other than the basic Create operation provided by Microsoft, of how to use the new Web API for CRUD operations.
So here it is an example of a RETRIEVE (and the CREATE from the Microsoft example found here) query using the Web API, I will be adding more examples of "Retrieve Multiple, "Update" and "Delete" as I discover how to implement them.

"use strict";
var bd_Utilities = window.bd_Utilities || {};
bd_Utilities.WebAPI = bd_Utilities.WebAPI || {};

(function () {
    this.create = function (entitySetName, entity, successCallback, errorCallback) {
        var req = new XMLHttpRequest();
        req.open("POST", encodeURI(getWebAPIPath() + entitySetName), true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 204) {
                    if (successCallback)
                        successCallback(this.getResponseHeader("OData-EntityId"));
                }
                else {
                    if (errorCallback)
                        errorCallback(bd_Utilities.WebAPI.errorHandler(this.response));
                }
            }
        };
        req.send(JSON.stringify(entity));
    };

this.retrieve = function (id, entitySetName, select, expand, successCallback, errorCallback) {
        var req = new XMLHttpRequest();
        req.open("GET", encodeURI(getWebAPIPath() + entitySetName + "(" + id + ")" + select), true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 200) {
                    if (successCallback)
                        successCallback(JSON.parse(this.responseText, bd_Utilities.WebAPI.dateReviver));
                }
                else {
                    if (errorCallback)
                        errorCallback(bd_Utilities.WebAPI.errorHandler(this.response));
                }
            }
        };
        req.send();
    };
 
//This is the calling function:

search: function () {
        bd_Utilities.WebAPI.retrieve("00000000-0000-0000-0000-000000000090", "contacts", "?$select=firstname,lastname", "",
       
        function (result) {
            var contact = result;
            console.log("Contact retrieved")
        },
        function (error) { console.log(error.message); });
    }

//And these are some helper functions from the CRM SDK

    //Internal supporting functions
    function getClientUrl() {
        //Get the organization URL
        if (typeof GetGlobalContext == "function" &&
            typeof GetGlobalContext().getClientUrl == "function") {
            return GetGlobalContext().getClientUrl();
        }
        else {
            //If GetGlobalContext is not defined check for Xrm.Page.context;
            if (typeof Xrm != "undefined" &&
                typeof Xrm.Page != "undefined" &&
                typeof Xrm.Page.context != "undefined" &&
                typeof Xrm.Page.context.getClientUrl == "function") {
                try {
                    return Xrm.Page.context.getClientUrl();
                } catch (e) {
                    throw new Error("Xrm.Page.context.getClientUrl is not available.");
                }
            }
            else { throw new Error("Context is not available."); }
        }
    }

    function getWebAPIPath() {
        return getClientUrl() + "/api/data/v8.0/";
    }

    //Internal validation functions
    function isString(obj) {
        if (typeof obj === "string") {
            return true;
        }
        return false;

    }

    function isNull(obj) {
        if (obj === null)
        { return true; }
        return false;
    }
    function isUndefined(obj) {
        if (typeof obj === "undefined") {
            return true;
        }
        return false;
    }
    function isFunction(obj) {
        if (typeof obj === "function") {
            return true;
        }
        return false;
    }
    function isNullOrUndefined(obj) {
        if (isNull(obj) || isUndefined(obj)) {
            return true;
        }
        return false;
    }
    function isFunctionOrNull(obj) {
        if (isNull(obj))
        { return true; }
        if (isFunction(obj))
        { return true; }
        return false;
    }

    // This function is called when an error callback parses the JSON response
    // It is a public function because the error callback occurs within the onreadystatechange
    // event handler and an internal function would not be in scope.
    this.errorHandler = function (resp) {
        try {
            return JSON.parse(resp).error;
        } catch (e) {
            return new Error("Unexpected Error")
        }
    }

    this.dateReviver = function (key, value) {
        ///<summary>
        /// Private function to convert matching string values to Date objects.
        ///</summary>
        ///<param name="key" type="String">
        /// The key used to identify the object property
        ///</param>
        ///<param name="value" type="String">
        /// The string value representing a date
        ///</param>
        var a;
        if (typeof value === 'string') {
            a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
            if (a) {
                return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
            }
        }
        return value;
    }

}).call(bd_Utilities.WebAPI);