Using jQuery in CRM 2011

With the introduction of Web Resources to CRM 2011, the model for client-side scripting changes completely – in the past we were restricted to embedding chunks of Javascript into the CRM Database to enable certain client side actions, and beyond invoking the CRM Document Object Model for certain CRM-related Functions and Properties, these chunks of Javascript were completely stand-alone lacking any easy ability to include other JS libraries or common functions.

The ability to include Web Resources in a CRM 2011 Solution change this – we are now able to embed Script Libraries into the CRM Database to be used across different events in the solution or potentially by other Web Resources – such that the following is invoke/include structure is possible:

Web Resource 1
function displayLookupName (lookupName, lookup) {
  var displayValueId = “Blank”;
  var displayValueName = “Blank”;
  if ( lookup != null ) {
    displayValueId = lookup[0].id;
    displayValueName = lookup[0].name;
  }
  alert("Display " + lookupName + " Lookup Name Dialogue: " + displayValueName + " : " + displayValueId + "");
}
Web Resource 2
function getLookupProperty( lookupAttributeName ) {
  var lookup;
  lookup = Xrm.Page.getAttribute(lookupAttributeName).getValue();
  displayLookupName(lookupAttributeName , lookup);
}
Event in CRM
getLookupProperty ("parentcustomerid");
CRM2011 Scripting Flow Diagram

Simple diagram showing the execution flow from the User to the Dynamics CRM Platform calling out to the 2 Web Resources in turn

This models more traditional software development where we often have code calling out to other classes or references.  Other than the possible fear of Spaghetti Coding this gives us a far greater level of flexible in how we add scripting into Dynamics CRM – and helps us maintain coding standards to avoid duplicating pieces of common scripting which was often common in CRM 3 and 4 projects.

Incorporating jQuery as a Web Resource

The major benefit of calling out to other classes or scripting is the ability to reuse 'black-box' solutions of well tested functionality where we do not need to worry about how the class or script is working, only that it does work and we can use this to achieve the results we are looking for.  This concept of working at a higher level of abstraction is common across software development when we are almost always working at higher levels of abstraction to avoid the lower-levels of complexity we are not interested in.

The concept of using Web Resources as shared Libraries across a CRM Solution allows us to consider including such common 'black-box' shared Web Libraries into CRM for use in our custom scripting.  jQuery is one of the more common libraries that has been refined over a period of time and is used in many Web Applications (and indeed is now included by default in NET 4 Web Projects) to provide a common set of functions for making service calls to minimise the about of scripting required.

jQuery Solution in CRM 2011

Adding a Solution to a CRM 2011 Deployment that contains the jQuery and JSON Scripting files as Web Resources

The functions within the jQuery library can then be used to easily invoke the CRM 2011 WCF Services via AJAX calls to the REST End-Points without having to develop lengthy scripting to generate the XML Messages manually.

CRM2011 Scripting Flow Diagram using jQuery

Example Execution path for a Custom Web Resource that uses jQuery to retrieve data from the CRM Organisation Data Service to present back to the CRM User

The link below can be used to download a Managed CRM 2011 Solution containing these jScript and JSON2 Websources:

http://cid-0f98a78f1c3c4457.office.live.com/embedicon.aspx/.Public/jQueryCRM2011_1_6_0_0_managed.zip

Or we can download the scripting files and include them manually:

To compare this approach of retrieving data back from CRM Service using this method of invoking the REST Endpoint via JSON against SOAP XML Messages, the two script examples below illustrate how to retrieve the Account Number for a selected Account in CRM -  the first generating the XML Messages manually and the second using jQuery functions to generate the calls.

SOAP Method: SOAP Call to retrieve the Account Number

constructRetrieveRequestXML = function (EntityId, EntityType, returnfieldName) {
    var xml = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
    xml += "<s:Body>";
    xml += "<Retrieve xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\"";
    xml += " xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
    xml += "<entityName>" + EntityType + "</entityName>";
    xml += "<id>" + EntityId + "</id>";

    xml += "<columnSet xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
    xml += "<a:AllColumns>false</a:AllColumns>";
    xml += "<a:Columns xmlns:b=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">";
    xml += "<b:string>" + returnfieldName + "</b:string>";
    xml += "</a:Columns>";
    xml += "</columnSet>";

    xml += "</Retrieve>";
    xml += "</s:Body>";
    xml += "</s:Envelope>";

    return xml;
}

retrieveRequest = function (xml, serverUrl, successCallback) {
    var xmlHttpRequest = new XMLHttpRequest();
    xmlHttpRequest.open("POST", serverUrl, true)
    xmlHttpRequest.setRequestHeader("Accept", "application/xml, text/xml, */*");
    xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Retrieve");
    xmlHttpRequest.onreadystatechange = function () { retrieveResponse(xmlHttpRequest, successCallback); };
    xmlHttpRequest.send(xml);
}

getError = function (faultXml) {
    ///<summary>
    /// Parses the WCF fault returned in the event of an error.
    ///</summary>
    ///<param name="faultXml" Type="XML">
    /// The responseXML property of the XMLHttpRequest response.
    ///</param>
    var errorMessage = "Unknown Error (Unable to parse the fault)";
    if (typeof faultXml == "object") {
        try {
            var bodyNode = faultXml.firstChild.firstChild;
            //Retrieve the fault node
            for (var i = 0; i < bodyNode.childNodes.length; i++) {
                var node = bodyNode.childNodes[i];

                //NOTE: This comparison does not handle the case where the XML namespace changes
                if ("s:Fault" == node.nodeName) {
                    for (var j = 0; j < node.childNodes.length; j++) {
                        var faultStringNode = node.childNodes[j];
                        if ("faultstring" == faultStringNode.nodeName) {
                            errorMessage = faultStringNode.text;
                            break;
                        }
                    }
                    break;
                }
            }
        }
        catch (e) { };
    }
    return new Error(errorMessage);
}

getServerUrl = function () {
    ///<summary>
    /// Returns the URL for the SOAP endpoint using the context information available in the form
    /// or HTML Web resource.
    ///</summary
    var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
    var serverUrl = "";
    if (typeof GetGlobalContext == "function") {
        var context = GetGlobalContext();
        serverUrl = context.getServerUrl();
    }
    else {
        if (typeof Xrm.Page.context == "object") {
            serverUrl = Xrm.Page.context.getServerUrl();
        }
        else
        { throw new Error("Unable to access the server URL"); }
    }
    if (serverUrl.match(/\/$/)) {
        serverUrl = serverUrl.substring(0, serverUrl.length - 1);
    }
    return serverUrl + OrgServicePath;
}

retrieveResponse = function (resp, successCallback) {
    if (resp.readyState == 4) { // complete
        if (resp.status == 200) {
            //Success
            successCallback(resp.responseXML);
        }
        else {
            //Failure
            alert("error! - " + getError(resp.responseXML).description);
        }
    }
}

function parentAccountChangedSOAP() {
    var parentcustomerid = Xrm.Page.getAttribute("parentcustomerid").getValue();

    if (parentcustomerid != null) {
        if (parentcustomerid[0].type == 1) // check that the Customer Relationship is to an Account
        {
            var accountId = parentcustomerid[0].id;

            var xml = constructRetrieveRequestXML(accountId, "account", "accountnumber");

            var processAccountNumber = function (responseXml) {
                try {
                    var accno = null;
                    var pairNames = responseXml.selectNodes("//b:key");
                    var pairValues = responseXml.selectNodes("//b:value");

                    if (pairNames != null) {
                        for (var i = 0; i != pairNames.length; i++) {
                            if (pairNames[i].text == "accountnumber") {
                                accno = pairValues[i].text;
                            }
                        }
                    }

                    if (accno != null) {
                        alert("Account Number: " + accno);
                    }
                    else {
                        alert("No Account Number could be determined");
                    }
                }
                catch (ex) {
                    alert("Process Account Number: " + ex.description);
                }
            };

            retrieveRequest(xml, getServerUrl(), processAccountNumber);
        }
    }
}

Using the traditional SOAP XML is similar to how we could use Javascript to send XML Messages to the CRM 4 Webservice, however these XML Messages are now been passed to the CRM 2011 WCF Service so the syntax differs slightly. (indeed it is possible to use legacy versions of the CRM 4 Webservices schema within CRM 2011 via http://crm2011/mscrmservices/2007/CrmService.asmx for scripting backwards compatibility between the versions.)

We could then attach this Web Resource to the Contact Form in CRM and invoke the parentAccountChangedSOAP method on the onChange event of the Parent Customer field:

SOAP Method

Attaching the SOAP script method via a Web Resource to the onChange event of the Parent Customer field within the Contact Form

jQuery AJAX Call to retrieve the Account Number

function parentAccountChanged() {
    var parentcustomerid = Xrm.Page.getAttribute("parentcustomerid").getValue();

    if (parentcustomerid != null) {
        if (parentcustomerid[0].type == 1) // check that the Customer Relationship is to an Account
        {
            var accountId = parentcustomerid[0].id;
            var accountNumber = getAccountNumber(accountId);
        }
    }
}

function getAccountNumber(accountId) {
    var entity = "Account";
    var select = "?$select=AccountId,AccountNumber"
    var oDataSelect;

    // build query string
    oDataSelect = "/XRMServices/2011/OrganizationData.svc/" + entity + "Set(guid'" + accountId + "')" + select + "";

    $.ajax({
        type: "GET",
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: oDataSelect,
        beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); },
        success: function (data, textStatus, XmlHttpRequest) {
            // Use for a single selected entity
            ProcessReturnMsg(data.d);
        },
        error: function (xmlHttpRequest, textStatus, errorThrown) {
            alert("Status: " + textStatus + "; ErrorThrown: " + errorThrown);
        }
    });
}

function ProcessReturnMsg(accountEntity) {
    // simply show the Account Number on-screen
    alert("Account Number: " + eval(accountEntity.AccountNumber));
}

In a similar fashion we can then associate this Web Resource to the Contact Form in CRM and invoke the 'parentAccountChanged' method to a particular event - in this case the Parent Customer onChange event.  The only difference here is the added requirement to associate the jQuery and JSON2 Web Resources as well:

jQuery JSON Method

Attaching the jScript method and relevant Web Resources to the onChange event of the Parent Customer field on the Contact Form

This second version of the scripting is much shorter and therefore can produce more readable script – whilst the scripts are essentially performing the same function (although against different service end-points in CRM 2011), the inclusion of the jQuery library as a Web Resource allows us to re-use the functions to vastly shorter our script for invoking the service, which allows us to focus on the business logic of the script as opposed to parsing XML files.

The following page in the CRM 2011 SDK has further details on using jQuery and JSON within CRM, and sample Web Resources and scripting.

NOTE: However certain aspects of invoking the WCF OrganizationData Service have their own ‘gotchas’ – see the list at the end of this article..

Using jQuery on the Opportunity Product Form

To use this concept of jQuery calls in CRM 2011 for a practical example, we can develop a simple script to automatically populate the ‘Unit of Measure’ field when selecting a Product for an Opportunity Product or Order Product – a simple customisation that I have found useful for improving the usability of CRM.

We can add the following script to a Web Resource that invokes the jQuery library to call the CRM 2011 REST Endpoint and retrieve fields from the selected Product record.

function productChanged() {
    var productid = Xrm.Page.getAttribute("productid").getValue();

    if (productid != null) {
        retrieveSelectedProduct(productid[0].id);
    }
}

function retrieveSelectedProduct(productid) {
    var entity = "Product";
    var select = "?$select=ProductId,DefaultUoMId,ProductNumber"
    var oDataSelect;

    // build query string
    oDataSelect = "/XRMServices/2011/OrganizationData.svc/" + entity + "Set(guid'" + productid + "')" + select + "";

    $.ajax({
        type: "GET",
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: oDataSelect,
        beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); },
        success: function (data, textStatus, XmlHttpRequest) {
            // Use for a single selected entity
            ProcessReturnMsg(data.d);
        },
        error: function (xmlHttpRequest, textStatus, errorThrown) {
            alert("Status: " + textStatus + "; ErrorThrown: " + errorThrown);
        }
    });
}

function ProcessReturnMsg(productEntity) {
    if (productEntity.DefaultUoMId != null) {
        var uomId = productEntity.DefaultUoMId.Id;
        var uomName = productEntity.DefaultUoMId.Name;

        // insert the default unit of measure
        var defaultuomid = new Array();
        defaultuomid[0] = new Object();
        defaultuomid[0].id = uomId;
        defaultuomid[0].name = uomName;
        defaultuomid[0].entityType = "uom";

        Xrm.Page.getAttribute("uomid").setValue(defaultuomid);
    }
}

We can then invoke this script from the Order Product form for the Product field's onChange event.

Adding the Script to the Order Product Form

Referencing the relevant Web Resources (including jQuery and JSON that the Default Unit of Measure depends upon) to the Order Product Form in CRM 2011

This is essentially a CRM 2011 + jQuery version of this script which performed the same function in CRM 4.  Scripts that were used in CRM 4 for retrieving information back from the CRM Webservices can be updated in the same fashion.

Conclusion

This concept of adding various Web Resources to work at a higher level of abstraction will likely become wide-spread in CRM 2011 Development Projects - and as jQuery allows us to make simpler script calls to Read or Update CRM Calls in a supported fashion that is well documented in the SDK, it is likely to be a corner stone of CRM Development in future and so is well worth any aspiring CRM Developer being familiar with the technique, and CRM Consultants aware of how this affects Requirements and Design of solutions using 2011.

Gotcha’s and jQuery Reference

The CRM 2011 Organization Data Service is case sensitive for working with jQuery, so when working with scripts we always need to supply the correct Schema Name for fields that CRM expects.

To look up what these schema are, we can examine the Fields area for the entity:

image

Or download the Organization Data Service WSDL file to find how the field has been capitalised:

image

So the following script will not function correctly:

    var entity = "new_discountitem";
    var select = "?$select=new_discountitemid,new_name"
    var oDataSelect = "/XRMServices/2011/OrganizationData.svc/" + entity + "Set(guid'" + productid + "')" + select + "";

Whilst the following will:

    var entity = "new_discountitem";
    var select = "?$select=new_discountitemId,new_name"
    var oDataSelect = "/XRMServices/2011/OrganizationData.svc/" + entity + "Set(guid'" + productid + "')" + select + "";

This can be a bit of gotcha for developers who have been working with previous versions of CRM as previously Schema Names could always be thought of as all lower-case, however this no longer the case in 2011.

About these ads
This entry was posted in CRM 2011, Development, JavaScript and tagged , , , . Bookmark the permalink.

13 Responses to Using jQuery in CRM 2011

  1. sclarebout says:

    Hi, very interesting, but I have a question about something I’m working on…
    I just created a custom group and button which would allow the user of CRM to send an opportunity to a new quote in NAV. I already have an url from a sample quote from NAV, so this url has to be triggered when the button has been clicked.

    So, the function has to get information from the server & the url and then has to do a callback from data defined in the code…
    How can this be done? Do you have experience with this? Thanks!

    • So when a button is clicked on the Opportunity form of CRM – the code (Jscript?) for the button calls a URL for a Quote in Navision.

      Am not too hot on NAV – but you could include certain CRM fields in the request string of the URL, which NAV would take interpret. So the URL invoked has something like:

      http://navserver/navpage.aspx?name=%5BCRMOpportunity.Name%5D&customer=%5BCRMOpportunity.CustomerIdName%5D&currency=%5BCRMOpportunity.TransactionCurrencyIdName%5D

      The code behind ‘navpage.aspx’ would then do something with these fields in relation to the Quote in NAV. This may form a simple integration between CRM and NAV.

      If you are looking for more detailed integration (such as raising a Quote in NAV based on an Opportunity in CRM) then it might be useful to look into CRM Plugins which would communicate information to NAV upon certain events in CRM, such as Opportunity changing status; or using a data integration tool such as Scribe to regularly integrate data between the two apps.

      Microsoft have also released a CRM to NAV Connector, which whilst having some limitations, can be useful for connecting the two systems: http://msdn.microsoft.com/en-us/library/gg502460.aspx

      Kind Regards, Paul.

      • sclarebout says:

        Thanks for the reply, I’ve already installed the connector…

        Now, I’m working on it, I’ve created a new entity in my solution with custom fields to write the code to trigger the url ;)

        Regards!

  2. Pingback: Javascript, JQuery, CRM 2011 -Kevin's Mocha

  3. Ian says:

    Hi,

    I’m using a jquery and trying to get the data for an OwnerId field.
    Do you know what the syntax is that enables me to get the ID and Name data?

    Cheers for any thoughts

    • Hi there,

      Might need to check this over but I think the syntax is much the same (the Owner field essentially being a EntityReference field like any other Lookup)

      So, assuming that you want to get the Owner of a record (denoted below as [yourEntity]) that is related to the current Form record, and taking the above example script and changing certan lines:

      var entity=”[yourEntity]“;
      var select = “?$select=[yourEntity]Id, OwnerId”;

      Then further down:

      function ProcessReturnMsg(yourEntityRecord) {
      if ( yourEntityRecord.OwnerId != null )
      {
      var userId = yourEntityRecord.OwnerId.Id;
      var username = yourEntityRecord.OwnerId.Name;
      alert(“The owner of the [yourEntity] record related to this record is ” + username );
      }
      }

      Not too sure that is exactly right (writing code directly into a comment is always a bit prone to error) but think it is about right.

  4. Casper Schau says:

    About your section: Using jQuery on the Opportunity Product Form
    Have you ever succesfully tried this on a OnPremise solution? I can make this work on a Online CRM but not on a OnPremise.

    • Aye – have used jQuery for On-Premise as well as Online, what kind of problem do you encounter when trying to use via On-Premise and might be able to help out?

    • Karthik says:

      Check the ServerURL …. At first i wasn’t able to retrieve record due to serverURL mismatch and after i had hardcoded my serverurl i was… Not the best way but useful to check

  5. Simon says:

    Good article, thanks! Shouldn’t the entity for your oData path be ProductSet or have I missed something in my understanding? Any idea on how I can stretch it to retrieve the text value from picklist (optionset)? For example, I’d like an onchange event on my custom entity to get the Access Mode (optionset that’s on the User form) of the current logged in CRM user and save it to a text field on my custom entity. My oData path is quering the SystemUserSet entity selecting the AccessMode field and filtering on the current userID which I’ve already retrieved using UserID = Xrm.Page.context.getUserId(); but after that I’m stuck. Not sure how to get it into my text field?

  6. Karthik says:

    Hi,,

    I have been able to retrieve the details from Account Entity using your first method….
    However i have doubt how to use queryexpression in your first method such as using filter operator, in between,,, Etc.

  7. xaccor says:

    What;s wrong with this request?
    oDataSelect has this val /XRMServices/2011/OrganizationData.svc/AccountSet(guid’A513FA46-DAEC-E111-880E-080027C902B8′)?$select=AccountId,AccountNumber
    the Alert shows:
    Status: error; ErrorThrown: Bad Request

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