CRM 2013: JavaScript – Retrieve record using OData/REST endpoint without jQuery

The CRM 2013 SDK states that jQuery in form scripts and ribbon commands is not recommended. So how do I RETRIEVE (or create, update, delete) attribute values for a related record using JavaScript? It turns out that using Ajax and the OData endpoint to manipulate the XMLHttpRequest object is the key.

In addition, I shall make use of several new XRM methods, including “Save”,  “alertDialog” and “getClientUrl” method. So, here we go!

CRM 2013 Xrm methods

In a previous post I discussed control and form notifications as well as how to prevent the CRM 2013 “auto-save” behaviour via JavaScript. In this post I shall use several Xrm methods from the CRM 2013 SDK (version 6.0.2 at time of writing) below:

  • Xrm.Page.data.entity.save(null | “saveandclose” | “saveandnew” )
    Saves the record synchronously with the options to close the form or open a new form after the save is completed.
  • Xrm.Page.data.save().then(successCallback, errorCallback)
    Saves the record asynchronously with the option to set callback functions to be executed after the save operation is completed.
  • Xrm.Utility.alertDialog(message, onCloseCallback)
    Displays a (non-blocking) dialog box containing an application-defined message. This method is only available for Updated Entities and will work for CRM for tablets (which does not allow the use of JavaScript functions that block the flow of code). The “onCloseCallback” function is executed when a user clicks on the “OK” button of the dialog.
  • Xrm.Page.context.getClientUrl()
    Returns the base URL that was used to access the application. This method is used to replace the deprecated “getServerUrl” method.

Let’s look at an example where the above methods might be of use.

Example

Suppose you have a requirement like this:

  • A user opens a contact form in CRM 2013.
  • If the user sets an existing account as the “Parent Customer” on the contact form, ask the user if the contact’s address should be copied to the account’s address.
  • If yes, then copy the information from the account to the contact and save the contact record. Display a message afterwards to inform the user whether the address information has been copied successfully from the account to the contact.
  • If the user deletes an existing account from the “Parent Customer” field on the contact form, ask the user if the contact’s address should be deleted as well. If yes, then delete the contact address fields and save the contact record.

In reality, the requirements should probably be a bit more involved. For instance, if the parent customer’s address field is empty, and the user replies that it should be copied into the current contact, then this will delete any existent address. However, to simplify the presentation here, we stick to the requirements above.

For example, the two fields outlined by the above are the “Parent Customer” and “Address” (composite) fields:

ODataRetrieve-01

How would one go about tackling this requirement? There are many ways you can do this. E.g., consider setting up a dialog to ask the user if he wants to copy the address information. If yes, kick off a child workflow to do this, or, use a combination of JavaScript and backend plugin code.

For the purpose of this post however, I shall use Ajax to work with the XmlHttpRequest object in JavaScript:

The “OnChange” event of the contact’s “Parent Customer” field triggers a JavaScript method, which does the following:

  1. Check if the “Parent Customer” field is set to blank or set to an existing account record.
  2. If “Parent Customer” is set to an account record, ask the user if he wants the account’s address to be copied to the new contact record.
  3. If “Parent Customer” is set to blank, ask the user if he wants the contact’s address to be deleted.
  4. If yes to (2), retrieve the address information and copy it to the address field of the contact record.
  5. If yes to (3), delete the address information from the contact record.

You can in fact do Step (4) in at least two different ways using Ajax: using an OData endpoint or using a SOAP endpoint (see the wonderful new detailed section “Use web service data in web resources – OData and Modern app SOAP endpoint” in the CRM 2013 SDK (version 6.0.2 at time of writing) if you don’t know what I’m talking about.

As I’m using OData in this post (hence the title, folks!), the first thing I test is the OrganizationData web service in CRM 2013 to retrieve a specific account record via its GUID. Again, the CRM 2013 SDK is your friend if you don’t know how to query using the OData endpoints.

Happily in the world of CRM 2013, the same OData URI as in CRM 2011, works like a charm and returns the correct Account information when I tested the following OData URL in IE10 (to see the unformatted view of the result, you might need to turn off feed reading view in IE 10 via Internet Options > Content > Feeds and Web Slices Settings > un-tick “Turn on feed reading view”).

http://<CRM name>/<CRM Organization Name>/XrmServices/2011/OrganizationData.svc/AccountSet?(guid’XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX’)

ODataRetrieve-02

CRM 2013 SDK Says Avoid jQuery Library with Form Scripts and Ribbon Commands!

Now that we have the OData URL, in the good old days of CRM 2011 you can either:

  1. work with native XmlHttpRequest object by including the OData URL and sending the request to the server directly, or
  2. load the jQuery library and use $.ajax method which is a wrapper for the XmlHttpRequest object.

To see the difference in syntax, go to the CRM 2013 SDK and look at the code examples given in “Use the OData endpoint with Ajax and Jscript web resources”.

However!!

The CRM 2013 SDK (version 6.0.2 at time of writing) has a big bold statement that says

We do not recommend using jQuery in form scripts and ribbon commands. Most of the benefit provided by jQuery is that it allows for easy cross-browser manipulation of the DOM. This is explicitly unsupported within form scripts and ribbon commands.
One place that the CRM 2013 SDK recommends the use of jQuery is when you create your own HTML web resources to “provide user interfaces because it is an excellent cross-browser library [..] and there is no restriction against manipulating the DOM.

Unfortunately different versions of jQuery library loaded interferes with each other in CRM 2013 and while its usage is no longer recommended in CRM 2013, you can mitigate this by checking for “conflict” (see this excellent post for more detail).

Thus if you want to avoid jQuery library altogether but you still want to retrieve (or create, update or delete) attributes of an entity record, you will need to work with the native “XMLHttpRequest” object provided by the browser.

Fortunately the JavaScript for XMLHttpRequest object manipulation has not changed. One key difference to watch out for is that when you build the OData URI, “context.getServerUrl()” is deprecated as of CRM 2011 and it definitely does not work in CRM 2013. Use “Xrm.Page.context.getClientUrl()” instead. The “XMLHttpResponse” where the returned object is in JSON format (as set by the request header) looks like this:

ODataRetrieve-03

Convert the text of the JSON object into JavaScript script by using “JSON.parse(…)” in order to make the result easier to manipulate.

JSON.parse(this.responseText).d

ODataRetrieve-04

If you need more information on dealing with “XMLHttpRequest/Response” objects and what the “readystate” and status codes mean, try either the MSDN or the CRM 2013 SDK.

OData and JavaScript In Action …

Let’s take a look at the actual JavaScript functions. Only relevant functions containing the Xrm methods mentioned above, are posted below. If you want to see the code working for your CRM 2013 environment, you will need to create your own JavaScript library with an unique namespace, and copy the functions below.

Check if “Parent Customer” field is set to blank or set to an existing account record.

If “Parent Customer” is set to an account record, asks the user if he wants the account’s address to be copied to the new contact record.

If “Parent Customer” is set to blank, asks the user if he wants the contact’s address to be deleted.


//Hook this method to the "OnChange" event of "parentcustomerid" lookup
OnAttributeChange_ParentCustomer: function(){

    var parentCustomerLookup = Xrm.Page.getAttribute("parentcustomerid").getValue();

    if (parentCustomerLookup == null) {
        var deleteAddress = confirm("You have deleted the Parent Customer from this contact record. Do you want to delete the contact's address as well?");
        if (deleteAddress == true){
            this.UserConfirmsToDeleteAddress();
        }
    }

    if (parentCustomerLookup != null && parentCustomerLookup[0].entityType == "account") {
        var accountId = parentCustomerLookup[0].id;
        var accountName = parentCustomerLookup[0].name;
        var copyAddress = confirm("If parent account \"" + accountName + "\" has address information, do you want to overwrite this contact address with account address?");
        if (copyAddress == true) {
            this.UserConfirmsToCopyAddress(accountId);
        }
    }

},

Note that as this function exists inside a namespace which I have not shown here, to hook this method up to the “OnChange” event of “Parent Customer” field on the contact record, you will need to specify the full name of the method E.g. “<Your namespace>.OnAttributeChange_ParentCustomer”.

The messages asking the user if they wish to copy/delete the address information are displayed via the “windows.confirm” method. Because this is a blocking method, this same JavaScript function will NOT work for forms viewed on CRM for Tablet (see the CRM SDK for more detail).

If “Parent Customer” is blank and the user chooses to delete the address information from the contact record, the function calls a separate method named “UserConfirmsToDeleteAddress()”.

If “Parent Customer” contains an account record, the method “UserConfirmsToCopyAddress( accountId )” is called, with the account Id Guid as the parameter of this method.

If yes to delete address, delete address information from contact record.

The JavaScript method for “UserConfirmsToDeleteAddress” looks like this:

UserConfirmsToDeleteAddress: function(){

    //Delete contact address information
    Xrm.Page.getAttribute("address1_city").setValue(null);
    Xrm.Page.getAttribute("address1_country").setValue(null);
    Xrm.Page.getAttribute("address1_line1").setValue(null);
    Xrm.Page.getAttribute("address1_line2").setValue(null);
    Xrm.Page.getAttribute("address1_line3").setValue(null);
    Xrm.Page.getAttribute("address1_stateorprovince").setValue(null);
    Xrm.Page.getAttribute("address1_postalcode").setValue(null);

    //Saves the record synchronously
    Xrm.Page.data.entity.save();
},

If yes to copy address, retrieve the address information and copy to address field of contact record.

The “UserConfirmsToCopyAddress( accountId )” method looks like this:

UserConfirmsToCopyAddress: function (id) {
    this.GetAccount(
        id,
        this.CopyAccountAddressToContactAddress,
        this.GetAccountErrorCallback
    );
},

Note that this method calls another method named “GetAccount( id, successCallback, errorCallback)” (displayed below) which contains the code to retrieve the account information via the OData endpoint. If an error is returned via the OData retrieval, the errorCallback function, given by “GetAccountErrorCallback”, will be called. If the OData retrieval is successful, the object “account” in the method is populated and is passed as a parameter into the successCallback function. In this example, the successCallback is given by the “CopyAccountAddressToContactAddress” function, which copies the account’s address to the contact record.

The “GetAccount” method contains the XMLHttpRequest request manipulation via the OData endpoint:

GetAccount: function (accountId, successCallback, errorCallback) {

    //OData URI to get address information from parent account record
    var oDataURI = Xrm.Page.context.getClientUrl()
        + "/XRMServices/2011/OrganizationData.svc/"
        + "AccountSet(guid'" + accountId + "')"
        + "?$select="
        + "Address1_City,"
        + "Address1_Country,"
        + "Address1_Line1,"
        + "Address1_Line2,"
        + "Address1_Line3,"
        + "Address1_StateOrProvince,"
        + "Address1_PostalCode";

    //Asynchronous XMLHttpRequest to retrieve account record
    var req = new XMLHttpRequest();
    req.open("GET", encodeURI(oDataURI), true);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.onreadystatechange = function () {
        //debugger;
        if (this.readyState == 4 /* complete */) {
            req.onreadystatechange = null; //avoids memory leaks
            if (this.status == 200) {
                //parse the response string as a JSON object into the successCallback method.
                successCallback(JSON.parse(this.responseText).d);
            }
            else {
                errorCallback(accountId);
            }
        }
    };
    req.send();
},

If the XMLHttpResponse object is successfully retrieved, it gets parsed as a JSON object and passed to the successCallback function, i.e. “CopyAccountAddressToContactAddress”. The contact record is saved via the Xrm.Page.data.save().then() method, which takes 2 anonymous functions, the first a succussCallback and the second an errorCallback function. Both methods display non-blocking alert dialogs to the user.

CopyAccountAddressToContactAddress: function (account) {

    //Set contact address information
    Xrm.Page.getAttribute("address1_city").setValue(account.Address1_City);
    Xrm.Page.getAttribute("address1_country").setValue(account.Address1_Country);
    Xrm.Page.getAttribute("address1_line1").setValue(account.Address1_Line1);
    Xrm.Page.getAttribute("address1_line2").setValue(account.Address1_Line2);
    Xrm.Page.getAttribute("address1_line3").setValue(account.Address1_Line3);
    Xrm.Page.getAttribute("address1_stateorprovince").setValue(account.Address1_StateOrProvince);
    Xrm.Page.getAttribute("address1_postalcode").setValue(account.Address1_PostalCode);

    //Saves the contact record asynchronously with callback methods.
    Xrm.Page.data.save().then(
        function () {
            var msg2 = "Successfully copied and saved contact record.";
            //display a non-blocking alert dialog
            Xrm.Utility.alertDialog(msg2, function () { });
        },
        function () {
            var msg3 = "PT.Contact_Main.CopyAccountAddressToContactAddress Error: Cannot save contact form with new parent account informatin. Please check contact and account records.";
            //display a non-blocking alert dialog
            Xrm.Utility.alertDialog(msg3, function () { });
        }
    );
},

If the account record is not retrieved successfully in “GetAccount” method, the following errorCallback will be called:

GetAccountErrorCallback: function(id){

    var msg1 = "PT.Contact_Main.GetAccount() Error: unable to retrieve account record with accountId = " + id + ".";
    //display a non-blocking alert dialog
    Xrm.Utility.alertDialog(msg1, function(){});

},

Once the JavaScript method “<namespace>.OnAttributeChange_ParentCustomer” is hooked up to the OnChange event of the “Parent Customer” lookup on the contact record, when you set the “Parent Customer” field to an existing account record, you’ll first see an alert asking the user if the account’s address should be copied to the current contact record.

ODataRetrieve-05

Selecting “OK” will copy the “Sweet Fruit Ltd” account address to Thomas Johnson’s contact record.

ODataRetrieve-06

If I were to go and delete the “Parent Customer” field on the contact record, I now see the following message:

ODataRetrieve-07

Clicking on “OK” in the message will delete the address information from Thomas Johnson’s contact record.

ODataRetrieve-08

And we are done!

Advertisements

13 thoughts on “CRM 2013: JavaScript – Retrieve record using OData/REST endpoint without jQuery

    1. Fantastic beat ! I wish to apprentice while you amend your website, how can i subscribe for a blog web site? The account aided me a acceptable deal. I had been a little bit acquainted of this your broadcast offered bright clear concept kbeaekkebeed

  1. This is amazing! Thank you very much for sharing. This saved me a bunch of time. I have one piece I question. If the ajax is not supported on the form, and the XMLHttpRequest is not support across browsers, does MS CRM xrm library do something that makes this still cross browser supported? I know I will test across browsers myself. However, when building my first run as a web resource I had to use ajax because XMLHttpRequest kept giving errors in Firefox and Chrome. Any insight appreciated. Great work!

  2. I can’t seem to find the cause of the error below. I get it when triggering copy of your JavaScript. I’m sure it is some dumb error. Any suggestions? THANKS

    ReferenceError: ‘OnAttributeChange_ParentCustomer’ is undefined
    at eval code (eval code:1:1)
    at RunHandlerInternal (https://d

  3. This question is not related to this topic.
    I have one client, want the connection between the WordPress and Microsoft Dynamics CRM means two way integration.

    How to achieve this or is there any proper documentation for this?

    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s