Showing posts with label MSCRM Web Service Toolkit. Show all posts
Showing posts with label MSCRM Web Service Toolkit. Show all posts

Wednesday, March 28, 2012

Release: XrmSvcToolkit v0.1 (a JavaScript Library for Microsoft Dynamics CRM 2011)

I am happy to announce the availability of XrmSvcToolkit v0.1, an upgraded version of CRM Web Service Toolkit, which now works with Microsoft Dynamics CRM 2011 by using the latest SOAP and REST endpoints.

The following functionalities are currently supported by the toolkit for SOAP and REST endpoints.
  • createRecord (REST)
  • updateRecord (REST)
  • deleteRecord  (REST)
  • retrieve  (REST)
  • retrieveMultiple  (REST)
  • associate  (REST)
  • disassociate  (REST)
  • setState (SOAP)
  • fetch (SOAP)
  • execute (SOAP)
There is a dependency of the toolkit, which is the JSON library that has been included in the downloadable solutions on codeplex website. The signature of all functions have been changed from previous version, so it may take a little effort to get used to. But if you have ever worked with jQuery, the interface may sound familiar to you. Hope you like the change.

To get started with the toolkit, you can import the solution files from codeplex website, or upload the provided JS files as web resources to CRM. Then you can add the library to your form so that you can consume it in your form script. Include both json2 and xrmservictoolkit in your form library, and make sure to have json2 before the toolkit. 

Where is the documentation?

As a matter of fact, I haven't invested much time writing the documentation of the toolkit. My fellow CRM MVP Mitch Milam is working on writing documentation for the toolkit in his secret upcoming project. I am counting on him.

In the meantime, you can check sample code in the accompanied unit test file (XrmSvcToolkitTest.aspx), which illustrate the typical scenarios how the toolkit can be used. 

Some Final Notes

I would like to apologize for the wait of the community for a new version of the toolkit, the primary reason is that I was late to the CRM 2011 party, and the new service interface was quite a challenge for me to have a level of confidence to get into the implementation of an upgrade.

One thing that I should point out is, after I have finished all the coding of the toolkit the other day (Feb 22, I believe), I was preparing for the release, so I started to create a new project on codeplex. I first picked a name of xrmservicetoolkit, but immediately realized that the project name had already been taken. What I noticed was that another community member (jaimieji) had already published an open source project using this name, which was doing something very similar. I had a look of the source code, it was a pretty good quality implementation. You may want to have a look of his implementation, as he has provided more functionalities than mine.

Also, Daniel René Thul has his implementation as well. His implementation is interesting and is based on jQuery, which you may want to check out as well.
I have been holding the release of the toolkit for about a month, until Codeplex sent me a final threatening email today that my project will be deleted from their system if I don't publish the project today.

Also, it should be noted, my implementation is not feature-rich enough to satisfy all your development needs. But I hope it can be used as a starting point or reference for your project engagement, and it should not be hard to add new features to the toolkit.

Since CRM R8 is going to be a cross-browser version according to Microsoft public announcement recently, there is some work to be done in order to support different browsers. Hopefully I am not occupied at the time so that I can provide an upgrade swiftly. 

Thursday, January 27, 2011

CRM Web Service Toolkit Works for CRM 2011

This is a quick update with regard to CRM Web Service Toolkit. I had a chance to play around with the toolkit on CRM 2011 today, it turns out to be a surprise that the toolkit just works fine on CRM 2011 without requiring any modification. I tried to run all the unit tests that I developed in CrmServiceToolkitTest.aspx page, all tests have passed without any problems.

CRM Web Service Toolkit on CRM 2011

It's not me who has made this magic, but because CRM 2011 provided full backward compatibility with its web service interfaces. However, it's not my recommendation to use the CRM4 style web service interfaces, neither is this the best practice. But since CRM2011 SOAP Endpoint documentation is still under construction (shown below), I think it's not a terribly bad idea to keep using the CRM4 style web service interfaces for the time being, until the SOAP Endpoint details have been fully revealed.

CRM2011 SOAP EndPoint Doc 

Some may argue that we should all use CRM2011's REST endpoint instead since it's a new approach which may be more efficient, but be advised that the REST endpoint is not a full implementation of all CRM services. You are limited to only Create, Retrieve, Update, and Delete actions when you use REST endpoint.

I only tested on CRM2011 Beta, but I am relatively positive that the toolkit will work just fine for RC version, most likely the RTM version as well.

Sunday, July 04, 2010

Release: MSCRM4 Web Service Toolkit for JavaScript v2.1

Today I am pleased to announce the release of CRM Web Service Toolkit for JavaScript v2.1. The new release includes the following enhancements:
  • All the major functions now support asynchronous calls through an optional callback function parameter. When the callback function is provided, the toolkit will perform an asynchronous service call, otherwise it would be a synchronous call. 
  • A new function setState has been added to the toolkit, which allows you to update a CRM record's status.
  • Two functions (associate, disassociate) have been added to the toolkit, which you can use to associate or disassociate two CRM records that have an N:N relationship. 
  • The signature of queryByAttribute function has been changed, so it takes one parameters now, instead of a bunch of optional parameters in previous version.
In order to make use of the toolkit, you can refer to my previous release page for sample code of all major functions.

Here are a few additional samples that can help you get up to speed with the new release.
  1. Use the optional asynchronous callback function. As just mentioned, all major functions now support asynchronous calls. The asynchronous callback function should take single parameter which is whatever you are expecting to get by using the synchronous call. For instance, if you make an asynchronous call to CrmServiceToolkit.Fetch() method, your callback function should be dealing with the fetch result which is an array of BusinessEntity as the single parameter. Here is a quick sample.
    // callback function
    function fetchCallback(fetchedContacts) {
        alert(fetchedContacts.length);
        alert(fetchedContacts[0].getValue('lastname'));        
        alert(fetchedContacts[0].getValue('creditlimit'));
        alert(fetchedContacts[0].getValue('creditlimit', 'formattedvalue'));
        alert(fetchedContacts[0].getValue('birthdate'));
    };
    
    // Fetch all contact records whose first name is John using FetchXML query
    var firstname = 'John';
    var fetchXml = [
    "<fetch mapping='logical'>",
       "<entity name='contact'>",
          "<attribute name='contactid' />",
          "<attribute name='firstname' />",
          "<attribute name='lastname' />",
          "<attribute name='creditlimit' />",
          "<attribute name='birthdate' />",
          "<filter>",
             "<condition attribute='firstname' operator='eq' value='", firstname, "' />",
          "</filter>",
       "</entity>",
    "</fetch>"
    ].join("");
    
    // Use CrmServiceToolkit.Fetch() to make an asynchronous call.
    var fetchedContacts = CrmServiceToolkit.Fetch(fetchXml, fetchCallback);
    
  2. Use CrmServiceToolkit.setState() to update a CRM record's status.
    // Use CrmServiceToolkit.setState() to update a CRM record's status. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var response = CrmServiceToolkit.setState('contact', contactId, 'Inactive', 2);
    alert(response);
  3. Use CrmServiceToolkit.associate() to associate two CRM records that have an N:N relationship.
    // Use CrmServiceToolkit.associate() to associate two CRM records that have an N:N relationship. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var orderId = '3210F2AF-1630-EB11-8AB1-0003AAA0126A';
    var response = CrmServiceToolkit.associate('contactorders_association', 'contact', contactId, 'salesorder', orderId);
    alert(response);
  4. Use CrmServiceToolkit.disassociate() to disassociate two CRM records that have an N:N relationship.
    // Use CrmServiceToolkit.disassociate() to disassociate two CRM records that have an N:N relationship. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var orderId = '3210F2AF-1630-EB11-8AB1-0003AAA0126A';
    var response = CrmServiceToolkit.disassociate('contactorders_association', 'contact', contactId, 'salesorder', orderId);
    alert(response);
  5. Use CrmServiceToolkit.queryByAttribute() to retrieve all CRM records that match the query criteria.
    // Use CrmServiceToolkit.queryByAttribute() to retrieve all CRM records that match the query criteria. 
    var queryOptions = {
        entityName : "contact",
        attributes : ["firstname", "lastname"], // Search by firstname and lastname
        values : ["John", "Smith"], // Find all contacts whose firstname is John, lastname is Smith
        columnSet : ["familystatuscode", "creditlimit", "birthdate"],
        orderby : ["creditlimit", "birthdate"]
    };
    
    var fetchedContacts = CrmServiceToolkit.queryByAttribute(queryOptions);

For all the rest functions, you should be able to find related sample code from my previous release page.

[CREDITS]
  • The idea behind CrmServiceToolkit.BusinessEntity was inspired by Ascentium CrmService JavaScript Library, after I have finished most of version 1.0 coding. Hats off to Ascentium CRM practice team.
  • Thanks to Daniel René Thul for his contribution on the implementation of asynchronous support in this release.
P.S. Please excuse me that I have to break my word in the previous release page, guess this would be the last release before CRM5. :-)

[Update - Oct 16, 2010] A bug fix has been included in the release so that the BusinessEntity can work properly with null value now.

Tuesday, May 25, 2010

Use CRM Web Service Toolkit to Implement Associate/Disassociate/SetState Functions

There was a comment in my new CRM Web Service Toolkit release page, complaining the following functions were missing from the toolkit when comparing to Ascentium library.

- Associate
- Disassociate
- SetState

I want to make it very clear upfront. It was never my intention to beat anyone or anything by writing the toolkit. I wrote it simply because I had too much pain to write ad-hoc JavaScript functions to make CRM Web Service calls. It was very inefficient, and also error-prone.

If any of you ever care about how and why it happened, here is a bit story behind the toolkit. I started with a few reusable JavaScript functions at the very beginning, without knowing Ascentium library exists (If I knew in the first place, I would probably never started), and gradually made it into a helper utility. To be honest, it took me quite some effort to get there, as I was an amateur JavaScript developer. I liked most of the implementation, but it was too simple, can only do a couple of things. So I decided to make it better, that's how CRM Service Toolkit 1.0 was born, which killed me almost another whole weekend time plus some evening time during that week, it was the time that I came cross Ascentium library, from which I incorporated the self-contained BusinessEntity concept. The toolkit looked a lot better, but since it's my spare time hobby project, I didn't actually bring the toolkit to my work project until almost 3 months later due to time constraint and distraction of other engagements. As soon as I started to use it in my work project, I immediately realized some problems, most significantly, adding toBoolean() prototype function to JavaScript Number object was simply a wrong decision. That's the biggest motivation for me to write a 2.0 version, as I feel obliged that I have to address this bad design. In the meantime, I wanted to incorporate some security features to the toolkit as they are very often used in CRM projects. That's where v2.0 came from. But since I can only do it on my personal time, it took me roughly a month to find some spare time to really focus on the v2.0 enhancements.

Way off-topic, I just want to make it clear about my intention of writing the toolkit.

Back to the topic of those missing functions, I actually thought about introducing them to the toolkit library, but I decided not to do so, in order to keep the toolkit as nimble as possible, I didn't seem to see they are so often used.

However if you ever need those functions, here are the implementations:

Associate and Disassociate Functions

/**
 * Associate two CRM records that have a N:N relationship. 
 * @param {String} relationshipName Name of the many-to-many relationship.
 * @param {String} entity1Name Entitiy name of the first record to be associated.
 * @param {String} entity1Id CRM Record ID (GUID) of the first record to be associated.
 * @param {String} entity2Name Entitiy name of the second record to be associated.
 * @param {String} entity2Id CRM Record ID (GUID) of the second record to be associated.
 * @return {object} The XML representation of the result.
 */
associate = function(relationshipName, entity1Name, entity1Id, entity2Name, entity2Id)
{
    var request = [
"<Request xsi:type='AssociateEntitiesRequest'>",
    "<Moniker1>",
        "<Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity1Id, "</Id>",
        "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity1Name, "</Name>",
    "</Moniker1>",
    "<Moniker2>",
        "<Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity2Id, "</Id>",
        "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity2Name, "</Name>",
    "</Moniker2>",
    "<RelationshipName>", relationshipName, "</RelationshipName>",
"</Request>"
].join("");

    return CrmServiceToolkit.Execute(request);
};

/**
 * Disassociate two CRM records that have a N:N relationship. 
 * @param {String} relationshipName Name of the many-to-many relationship.
 * @param {String} entity1Name Entitiy name of the first record to be disassociated.
 * @param {String} entity1Id CRM Record ID (GUID) of the first record to be disassociated.
 * @param {String} entity2Name Entitiy name of the second record to be disassociated.
 * @param {String} entity2Id CRM Record ID (GUID) of the second record to be disassociated.
 * @return {object} The XML representation of the result.
 */
disassociate = function(relationshipName, entity1Name, entity1Id, entity2Name, entity2Id) {
    var request = [
"<Request xsi:type='DisassociateEntitiesRequest'>",
    "<Moniker1>",
        "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity1Name, "</Name>",
        "<Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity1Id, "</Id>",
    "</Moniker1>",
    "<Moniker2>",
        "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity2Name, "</Name>",
        "<Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entity2Id, "</Id>",
    "</Moniker2>",
    "<RelationshipName>", relationshipName, "</RelationshipName>",
"</Request>"
].join("");

    return CrmServiceToolkit.Execute(request);
};

SetState Function

[Update - May 26, 2010] vlad007 left a comment pointing out the request XML was not in right sequence, so I have just updated the script. Thanks vlad007!
/**
 * Set a CRM record's state by its statecode and statuscode. 
 * @param {String} entityName Entitiy name of the CRM record to be updated.
 * @param {String} id CRM Record ID (GUID) to be updated.
 * @param {String} statecode New statecode in string, eg, "Active", "Inactive".
 * @param {Integer} statuscode New statuscode in integer, use -1 for default status.
 * @return {object} The XML representation of the result.
 */
setState = function(entityName, id, stateCode, statusCode) {
    var request = [
"<Request xsi:type='SetStateDynamicEntityRequest'>",
    "<Entity>",
        "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", entityName, "</Name>",
        "<Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>", id, "</Id>",
    "</Entity>",
    "<State>", stateCode, "</State>",
    "<Status>", statusCode, "</Status>",
"</Request>"
].join("");

    return CrmServiceToolkit.Execute(request);
};

Please be advised, those functions were not actually unit tested. Please let me know if you have problems using them.

The above code should also be ablet to give you some basic ideas about how to add support of any other CRM messages that you may need to the toolkit.

Cheers!

Sunday, May 23, 2010

Release: MSCRM4 Web Service Toolkit for JavaScript v2.0

[UPDATE - July 4, 2010] A new version has been released at http://danielcai.blogspot.com/2010/07/crm-web-service-toolkit-for-javascript.html, please ensure to check out.

Here is another update of CRM Web Service Toolkit for JavaScript that I released to codeplex site today, most likely this is going to be the last release before CRM5. This new release is based on previous version (v1.0), and it comes with a few more enhancements:
  1. A new method called queryByAttribute() has been added, which allows to retrieve a specific entity's record by using one or more than one pair of attribute and value
  2. Three new methods have been added to help facilitate user and user security role related queries, including getCurrentUserId(), getCurrentUserRoles(), isCurrentUserInRole()
  3. The toBoolean() prototype function that I added to JavaScript Number type in the previous version is now obsolete, instead I have added a new prototype function to the toolkit's BusinessEntity object. So if you want to retrieve a CRM Boolean type field's value, you should use something like this: businessEntity.getValueAsBoolean('new_mybooleanfield')
  4. A new prototype function called getValueAsLookup has been added to the toolkit's BusinessEntity object, which allows you to parse the values of a CRM lookup field that you retrieved through the toolkit and convert it to a CRM lookup control's DataValue. For instance, you could do something like this: crmForm.all.new_mylookup.DataValue = businessEntity.getValueAsLookup("new_mylookup", "new_mylookupentity")
Again, here are a few samples that might help you get started with the toolkit.
  1. Use CrmServiceToolkit.Create() to create a CRM record.
    // Use CrmServiceToolkit. Create() to create a CRM contact record.
    var contact = new CrmServiceToolkit.BusinessEntity("contact");
    contact.attributes["firstname"] = "Diane";
    contact.attributes["lastname"] = "Morgan";
    contact.attributes["gendercode"] = 2;
    contact.attributes["familystatuscode"] = 1; // Picklist : Single - 1
    contact.attributes["creditlimit"] = 3000;
    
    var createResponse = CrmServiceToolkit.Create(contact);
  2. Use CrmServiceToolkit.Update() to update a CRM record.
    //Use CrmServiceToolkit.Update() to update a CRM contact record. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var contact = new CrmServiceToolkit.BusinessEntity("contact");
    contact.attributes["contactid"] = contactId;
    contact.attributes["firstname"] = "Diane";
    contact.attributes["lastname"] = "Lopez";
    contact.attributes["familystatuscode"] = 2; // Married
    
    var updateResponse = CrmServiceToolkit.Update(contact);   
  3. Use CrmServiceToolkit.Retrieve() to retrieve a CRM record.
    // Use CrmServiceToolkit.Retrieve() to retrieve a CRM contact record.
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C'; 
    var cols = ["firstname", "lastname", "familystatuscode", "creditlimit", "birthdate", "donotemail"];
    var retrievedContact = CrmServiceToolkit.Retrieve("contact", contactId, cols);
    
    alert(retrievedContact.getValue('lastname'));
    alert(retrievedContact.getValue('firstname'));
    alert(retrievedContact.getValue('familystatuscode')); // Picklist's value (integer)
    alert(retrievedContact.getValue('familystatuscode', 'name')); // Picklist's selected text
    alert(retrievedContact.getValue('creditlimit')); // Currency field's value
    alert(retrievedContact.getValue('creditlimit', 'formattedvalue')); // Currency field's formatted value (string)
    alert(retrievedContact.getValue('birthdate')); // Datetime field's date/time value
    alert(retrievedContact.getValue('birthdate', 'date')); // Datetime field's date string
    alert(retrievedContact.getValue('birthdate', 'time')); // Datetime field's time string
    alert(retrievedContact.getValueAsBoolean('donotemail')); // Bit field's value
  4. Use CrmServiceToolkit.RetrieveMultiple() to retrieve a collection of CRM records.
    // Retrieve all contacts whose first name is John. 
    var firstname = 'John'; 
    var query = [
    "<q1:EntityName>contact</q1:EntityName>",
    "<q1:ColumnSet xsi:type='q1:ColumnSet'>",
       "<q1:Attributes>",
          "<q1:Attribute>firstname</q1:Attribute>",
          "<q1:Attribute>lastname</q1:Attribute>",
          "<q1:Attribute>familystatuscode</q1:Attribute>",
          "<q1:Attribute>ownerid</q1:Attribute>",
          "<q1:Attribute>creditlimit</q1:Attribute>",
          "<q1:Attribute>birthdate</q1:Attribute>",
          "<q1:Attribute>donotemail</q1:Attribute>",
       "</q1:Attributes>",
    "</q1:ColumnSet>",
    "<q1:Distinct>false</q1:Distinct>",
    "<q1:Criteria>",
       "<q1:FilterOperator>And</q1:FilterOperator>",
       "<q1:Conditions>",
          "<q1:Condition>",
             "<q1:AttributeName>firstname</q1:AttributeName>",
             "<q1:Operator>Equal</q1:Operator>",
             "<q1:Values>",
                "<q1:Value xsi:type='xsd:string'>", firstname, "</q1:Value>",
             "</q1:Values>",
          "</q1:Condition>",
       "</q1:Conditions>",
    "</q1:Criteria>"
    ].join("");
    
    var retrievedContacts = CrmServiceToolkit.RetrieveMultiple(query);
    
    alert(retrievedContacts.length);
    alert(retrievedContacts[0].getValue('lastname'));
    alert(retrievedContacts[0].getValue('firstname'));
    alert(retrievedContacts[0].getValue('familystatuscode');
    alert(retrievedContacts[0].getValue('familystatuscode', 'name'));
    alert(retrievedContacts[0].getValue('creditlimit'));
    alert(retrievedContacts[0].getValue('creditlimit', 'formattedvalue'));
    alert(retrievedContacts[0].getValue('birthdate'));
    alert(retrievedContacts[0].getValue('birthdate', 'date'));
    alert(retrievedContacts[0].getValue('birthdate', 'time'));
    alert(retrievedContacts[0].getValueAsBoolean('donotemail'));
  5. Use CrmServiceToolkit.Fetch() to retrieve a collection of CRM records using FetchXML query.
    // Fetch all contact records whose first name is John using FetchXML query
    var firstname = 'John';
    var fetchXml = [
    "<fetch mapping='logical'>",
       "<entity name='contact'>",
          "<attribute name='contactid' />",
          "<attribute name='firstname' />",
          "<attribute name='lastname' />",
          "<attribute name='familystatuscode' />",
          "<attribute name='ownerid' />",
          "<attribute name='creditlimit' />",
          "<attribute name='birthdate' />",
          "<attribute name='accountrolecode' />",
          "<attribute name='donotemail' />",
          "<filter>",
             "<condition attribute='firstname' operator='eq' value='", firstname, "' />",
          "</filter>",
       "</entity>",
    "</fetch>"
    ].join("");
    
    var fetchedContacts = CrmServiceToolkit.Fetch(fetchXml);
    
    alert(fetchedContacts.length);
    alert(fetchedContacts[0].getValue('lastname'));
    alert(fetchedContacts[0].getValue('firstname'));
    alert(fetchedContacts[0].getValue('familystatuscode');
    alert(fetchedContacts[0].getValue('familystatuscode', 'name'));
    alert(fetchedContacts[0].getValue('creditlimit'));
    alert(fetchedContacts[0].getValue('creditlimit', 'formattedvalue'));
    alert(fetchedContacts[0].getValue('birthdate'));
    alert(fetchedContacts[0].getValue('birthdate', 'date'));
    alert(fetchedContacts[0].getValue('birthdate', 'time'));
    alert(fetchedContacts[0].getValueAsBoolean('donotemail'));
  6. Use CrmServiceToolkit.Delete() to delete a CRM record.
    // Use CrmServiceToolkit.Delete() to delete a CRM contact record. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var deleteResponse = CrmServiceToolkit.Delete("contact", contactId);
    alert(deleteResponse);
  7. Use CrmServiceToolkit.Execute() to execute a message.
    // Use CrmServiceToolkit.Execute() to execute a message. 
    var whoAmI = CrmServiceToolkit.Execute("<Request xsi:type='WhoAmIRequest' />");
    currentUserId = whoAmI.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
    alert("Current user's ID is " + currentUserId);
  8. Use CrmServiceToolkit.queryByAttribute() to retrieve a CRM record using one criterion.
    // Use CrmServiceToolkit.queryByAttribute() to retrieve a set of CRM records.
    var retrievedContacts = CrmServiceToolkit.queryByAttribute("contact", "firstname", "John"); // Retrieve all contacts whose first name is John.
    
    alert(retrievedContacts[0].getValue('lastname'));
    alert(retrievedContacts[0].getValue('firstname'));
    NOTE: In this example, I didn't specify columnSet parameter, so it will return all available fields of the contact entity, which is a really BAD practice. You should always specify what you want to get, if that's possible.

    NOTE: The signature of this method has been changed in v2.1, please refer to the latest release page if you are using v2.1.

  9. Use CrmServiceToolkit.queryByAttribute() to retrieve a CRM record using more than one criterion, with specified column set and sorting order.
    // Use CrmServiceToolkit.queryByAttribute() to retrieve a set of CRM records using more than one criterion with specified column set or sorting order
    var attributes = ["firstname", "lastname"];
    var values = ["John", "Wayne"];
    var cols = ["familystatuscode", "ownerid", "creditlimit", "birthdate", "donotemail", "donotphone"];
    var orderby = ["jobtitle"]; // Sort by Job Title
    var retrievedContacts = CrmServiceToolkit.queryByAttribute("contact", attributes, values, cols, orderby);
    
    alert(retrievedContacts[0].getValue('middlename'));
    NOTE: Again, the signature of this method has been changed in v2.1, please refer to the latest release page if you are using v2.1.

  10. Use CrmServiceToolkit.getCurrentUserId() to get the current user's ID.
    // Use CrmServiceToolkit.getCurrentUserId() to get the current user's ID.
    var currentUserId = CrmServiceToolkit.getCurrentUserId();
    
    alert(currentUserId);
  11. Use CrmServiceToolkit.getCurrentUserRoles() to get all the system roles that the current user has been assigned to.
    // Use CrmServiceToolkit.getCurrentUserRoles() to get all the system roles that the current user has been assigned to.
    var roles = CrmServiceToolkit.getCurrentUserRoles();
    
    alert(roles[0]); // Prompt the user's first role. 
  12. Use CrmServiceToolkit.isCurrentUserInRole() to check if the current user has a particular role.
    // Use CrmServiceToolkit.isCurrentUserInRole() to check if the current user has a particular role.
    var isSystemAdministrator = CrmServiceToolkit.isCurrentUserInRole("System Administrator");
    
    alert("I " + (isSystemAdministrator ? "AM" : "AM NOT") + " a System Administrator. "); 
As usual, here are a few notes about using the toolkit.
  1. The following CRM JavaScript functions have been used in order to keep the file size minimal (Aside from this reason, I am not a big fan of reinventing the wheel).
    • GenerateAuthenticationHeader() function
    • _HtmlEncode() function
    • CrmEncodeDecode.CrmXmlDecode() function
    • CrmEncodeDecode.CrmXmlEecode() function

    If you ever need to run the toolkit out of the context of a CRM form, you'll need to make the above functions available to the toolkit script.

  2. When you retrieve records from CRM using the toolkit's Fetch, Retrieve, RetrieveMultiple, or the new queryByAttribute methods, what you get will be the instance(s) of CrmServiceToolkit.BusinessEntity, which contains all CRM attributes (fields) that have been returned from CRM. However, you should not try to access those attributes directly, instead you use the instance function - getValue() or getValueAsBoolean() to get the value of the CRM field. The reason behind this is, CRM doesn't return anything if a field's value is null, in which case your JS code will blow up if you try to access the field (attribute) directly. With that being said, you should also be informed that a CRM filed's value could be null, be sure to handle it properly in your JS code.
  3. As mentioned previously, when dealing with the value of CRM bit data type that you have retrieved from CRM (Only Fetch, Retrieve, RetrieveMultiple, queryByAttribute methods are really concerned), you should use getValueAsBoolean() method to get the value. This seems to be the only field type that the toolkit cannot detect correctly. For all other type of CRM fields, you can pretty much use getValue() instance method to do the job.
  4. The toolkit will throw error if the CRM service calls failed with any exceptions, it's always a good idea to use try/catch block to manage the potential errors. An example would be:
    // It's always a good idea to contain any errors that could be thrown be the toolkit.
    try
    {
        var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C'; 
        var cols = ["firstname", "lastname", "familystatuscode", "creditlimit", "birthdate", "donotemail"];
        var retrievedContact = CrmServiceToolkit.Retrieve("contact", contactId, cols);
    
        // Do the rest of work
    }
    catch(err) {
        var errMsg = "There was an error when retrieving the contact information...\n\n";
        errMsg += "Error: " + err.description + "\n";
        alert(errMsg);
    }
  5. CRM's Execute message is a versatile message. Anything that you cannot easily achieve through the other 6 messages, you should resort to the toolkit’s Execute() method. Again, please refer to MSCRM 4.0 SDK for more CRM messages.
  6. The toolkit release has a test page included (CrmServiceToolkitTest.aspx), which utilizes QUnit as the test engine. In order to run the test script, you should deploy it along with all other files to ISV/CrmServiceToolkit folder (Please create this folder first), then you can launch http://crmserver:port/MyOrgName/ISV/CrmServiceToolkit/CrmServiceToolkitTest.aspx to run it. If you are in good luck, you should see a screen like this:
    CrmWebServiceToolkit2Test
    NOTE: The unit tests will actually write a contact record to your CRM database, and it will be deleted as part of the unit tests. 
I hope that I have covered everything.

[CREDITS] The idea behind CrmServiceToolkit.BusinessEntity was inspired by Ascentium CrmService JavaScript Library, after I have finished most of version 1.0 coding. Hats off to Ascentium CRM practice team.

P.S. You should probably have noticed that I have repeated most of the content in my previous toolkit release page, the reason is that I want to provide a single updated page for you to have all the information, so you don't have to go back and forth between the old release page and this release page.

Have fun with the toolkit, hope the toolkit can help you become a more productive CRM developper.

[UPDATE - July 4, 2010] A new version has been released at http://danielcai.blogspot.com/2010/07/crm-web-service-toolkit-for-javascript.html, please ensure to check out.

Thursday, January 21, 2010

MSCRM 4.0 Web Service Toolkit (JavaScript)

[Update - May 23, 2010] There has been a new version released (v2.0), which you might want to check out.

Today, I managed to get an updated CRM Web Service Toolkit (Formerly CRM Web Service Helper) released to codeplex site. This is a major update from the previous helper utility, with the following enhancements:

  1. The toolkit now supports all important CRM Web Service messages, including Create, Update, Delete, Fetch, Retrieve, RetrieveMultiple, Execute.
  2. The toolkit tries to automatically determine the data type returned from CRM Web Service, when retrieving messages (Fetch, Retrieve, RetrieveMultiple) are used. The only exception is CRM bit type, which I cannot differentiate from a numeric type returned by CRM Web Service. For CRM bit type, you will need to convert to boolean using the Number's toBoolean() prototype function (aka instance function), which is provided in the toolkit.
  3. All request string / response strings are now properly encoded or decoded.

[CREDITS] The idea behind CrmServiceToolkit.BusinessEntity was inspired by Ascentium CrmService JavaScript Library, after I have finished most of the code. Hats off to Ascentium CRM practice team.

In order to help you get started with the toolkit, the following are a few samples that might give you some hints about how to use the toolkit in your form script.

  1. Use CrmServiceToolkit.Create() to create a CRM record.
    // Use CrmServiceToolkit. Create() to create a CRM contact record.
    var contact = new CrmServiceToolkit.BusinessEntity("contact");
    contact.attributes["firstname"] = "Diane";
    contact.attributes["lastname"] = "Morgan";
    contact.attributes["gendercode"] = 2;
    contact.attributes["familystatuscode"] = 1; // Picklist : Single - 1
    contact.attributes["creditlimit"] = 3000;
    
    var createResponse = CrmServiceToolkit.Create(contact);
  2. Use CrmServiceToolkit.Update() to update a CRM record.
    //Use CrmServiceToolkit.Update() to update a CRM contact record. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var contact = new CrmServiceToolkit.BusinessEntity("contact");
    contact.attributes["contactid"] = contactId;
    contact.attributes["firstname"] = "Diane";
    contact.attributes["lastname"] = "Lopez";
    contact.attributes["familystatuscode"] = 2; // Married
    
    var updateResponse = CrmServiceToolkit.Update(contact);   
  3. Use CrmServiceToolkit.Retrieve() to retrieve a CRM record.
    // Use CrmServiceToolkit.Retrieve() to retrieve a CRM contact record.
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C'; 
    var cols = ["firstname", "lastname", "familystatuscode", "creditlimit", "birthdate", "donotemail"];
    var retrievedContact = CrmServiceToolkit.Retrieve("contact", contactId, cols);
    
    alert(retrievedContact.getValue('lastname'));
    alert(retrievedContact.getValue('firstname'));
    alert(retrievedContact.getValue('familystatuscode')); // Picklist's value (integer)
    alert(retrievedContact.getValue('familystatuscode', 'name')); // Picklist's selected text
    alert(retrievedContact.getValue('creditlimit')); // Currency field's value
    alert(retrievedContact.getValue('creditlimit', 'formattedvalue')); // Currency field's formatted value (string)
    alert(retrievedContact.getValue('birthdate')); // Datetime field's date/time value
    alert(retrievedContact.getValue('birthdate', 'date')); // Datetime field's date string
    alert(retrievedContact.getValue('birthdate', 'time')); // Datetime field's time string
    alert(retrievedContact.getValue('donotemail').toBoolean()); // Bit field's value
  4. Use CrmServiceToolkit.RetrieveMultiple() to retrieve a collection of CRM records.
    // Retrieve all contacts whose first name is John. 
    var firstname = 'John'; 
    var query =
    "<q1:EntityName>contact</q1:EntityName>" +
    "<q1:ColumnSet xsi:type='q1:ColumnSet'>" +
       "<q1:Attributes>" +
          "<q1:Attribute>firstname</q1:Attribute>" +
          "<q1:Attribute>lastname</q1:Attribute>" +
          "<q1:Attribute>familystatuscode</q1:Attribute>" +
          "<q1:Attribute>ownerid</q1:Attribute>" +
          "<q1:Attribute>creditlimit</q1:Attribute>" +
          "<q1:Attribute>birthdate</q1:Attribute>" +
          "<q1:Attribute>donotemail</q1:Attribute>" +
       "</q1:Attributes>" +
    "</q1:ColumnSet>" +
    "<q1:Distinct>false</q1:Distinct>" +
    "<q1:Criteria>" +
       "<q1:FilterOperator>And</q1:FilterOperator>" +
       "<q1:Conditions>" +
          "<q1:Condition>" +
             "<q1:AttributeName>firstname</q1:AttributeName>" +
             "<q1:Operator>Equal</q1:Operator>" +
             "<q1:Values>" +
                "<q1:Value xsi:type='xsd:string'>" + firstname + "</q1:Value>" +
             "</q1:Values>" +
          "</q1:Condition>" +
       "</q1:Conditions>" +
    "</q1:Criteria>";
    
    var retrievedContacts = CrmServiceToolkit.RetrieveMultiple(query);
    
    alert(retrievedContacts.length);
    alert(retrievedContacts[0].getValue('lastname'));
    alert(retrievedContacts[0].getValue('firstname'));
    alert(retrievedContacts[0].getValue('familystatuscode');
    alert(retrievedContacts[0].getValue('familystatuscode', 'name'));
    alert(retrievedContacts[0].getValue('creditlimit'));
    alert(retrievedContacts[0].getValue('creditlimit', 'formattedvalue'));
    alert(retrievedContacts[0].getValue('birthdate'));
    alert(retrievedContacts[0].getValue('birthdate', 'date'));
    alert(retrievedContacts[0].getValue('birthdate', 'time'));
    alert(retrievedContacts[0].getValue('donotemail').toBoolean());
  5. Use CrmServiceToolkit.Fetch() to retrieve a collection of CRM records using FetchXML query.
    // Fetch all contact records whose first name is John using FetchXML query
    var firstname = 'John';
    var fetchXml =
    "<fetch mapping='logical'>" +
       "<entity name='contact'>" +
          "<attribute name='contactid' />" +
          "<attribute name='firstname' />" +
          "<attribute name='lastname' />" +
          "<attribute name='familystatuscode' />" +
          "<attribute name='ownerid' />" +
          "<attribute name='creditlimit' />" +
          "<attribute name='birthdate' />" +
          "<attribute name='accountrolecode' />" +
          "<attribute name='donotemail' />" +
          "<filter>" +
             "<condition attribute='firstname' operator='eq' value='" + firstname + "' />" +
          "</filter>" +
       "</entity>" +
    "</fetch>";
    
    var fetchedContacts = CrmServiceToolkit.Fetch(fetchXml);
    
    alert(fetchedContacts.length);
    alert(fetchedContacts[0].getValue('lastname'));
    alert(fetchedContacts[0].getValue('firstname'));
    alert(fetchedContacts[0].getValue('familystatuscode');
    alert(fetchedContacts[0].getValue('familystatuscode', 'name'));
    alert(fetchedContacts[0].getValue('creditlimit'));
    alert(fetchedContacts[0].getValue('creditlimit', 'formattedvalue'));
    alert(fetchedContacts[0].getValue('birthdate'));
    alert(fetchedContacts[0].getValue('birthdate', 'date'));
    alert(fetchedContacts[0].getValue('birthdate', 'time'));
    alert(fetchedContacts[0].getValue('donotemail').toBoolean());
  6. Use CrmServiceToolkit.Delete() to delete a CRM record.
    // Use CrmServiceToolkit.Delete() to delete a CRM contact record. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var deleteResponse = CrmServiceToolkit.Delete("contact", contactId);
    alert(deleteResponse);
  7. Use CrmServiceToolkit.Execute() to execute a message.
    // Use CrmServiceToolkit.Execute() to execute a message. 
    var whoAmI = CrmServiceToolkit.Execute("<Request xsi:type='WhoAmIRequest' />");
    currentUserId = whoAmI.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
    alert("Current user's ID is ' + currentUserId);

    I have to point out, CrmServiceToolkit.Execute() method can do a lot more than just WhoAmIRequest. Please refer to MSCRM 4.0 SDK for more details.

A few more notes about the toolkit.
  1. The following CRM JavaScript functions have been used in order to keep the file size minimal (Aside from this reason, I am not a big fan of reinventing the wheel).
    • GenerateAuthenticationHeader() function
    • _HtmlEncode() function
    • CrmEncodeDecode.CrmXmlDecode() function
    • CrmEncodeDecode.CrmXmlEecode() function

    If you ever need to run the toolkit out of the CRM form, you will need to make the above functions available to the toolkit script.

  2. When you retrieve records from CRM using the toolkit's Fetch, Retrieve, RetrieveMultiple methods, what you get will be the instance(s) of CrmServiceToolkit.BusinessEntity, which contains all CRM attributes (fields) that have been returned from CRM. However, you should not try to access those attributes directly, instead you use the instance function - getValue() to get the value of the CRM field. The reason behind this is, CRM doesn't return anything if a field's value is null, in which case your JS code will blow up if you try to access the field (attribute) directly. With that being said, you should also be informed that a CRM filed's value could be null, be sure to handle it properly in your JS code.
  3. As mentioned previously, be careful when dealing with CRM bit data type if you are retrieving CRM records from CRM (Only Fetch, Retrieve, RetrieveMultiple methods are really concerned). The value you get from entity’s getValue() function is actually a number, which in most case will satisfy your needs. In case you need to assign the retrieved value to another CRM bit field, you should use toBoolean() function to convert to actual JavaScript bool type, as I have shown in the above sample.
  4. CRM's Execute message is a versatile message. Anything that you cannot easily achieve through the other 6 messages, you should resort to the toolkit’s Execute() method. Again, please refer to MSCRM 4.0 SDK for more CRM messages.
  5. The toolkit release has a test page included (CrmServiceToolkitTest.aspx), which utilizes QUnit as the test engine. In order to run the test script, you should deploy it along with all other files to ISV/CrmServiceToolkit folder (Please create this folder first), then you can launch http://crmserver:port/YourOrgName/CrmServiceToolkit/CrmServiceToolkitTest.aspx to run it. If you are in good luck, you should see a screen like this:

    CrmWebServiceToolkitTest

I hope that I have covered everything.

Have fun with the toolkit, hope it can make your CRM programming more enjoyable.

[Update - Apr 2, 2010] I started to poke around the toolkit by implementing it in the project at my work recently (I was still using the Helper one) and realized a bug with regard to parsing CRM boolean field when its value is false. I have updated the download package at codeplex site, please download the latest one. I am very sorry for the inconvenience, I should have been the first user of my toolkit. ;)

[Update - May 23, 2010] There has been a new version released (v2.0), which you might want to check out.

Sunday, September 27, 2009

CRM JavaScript Web Service Helper

[UPDATE - Jan 21, 2010] I have released an updated version of script under the name of CRM Web Service Toolkit, please check my latest blog for the details.

In CRM's form customization, we often need to implement business logics based on the information that is not available right away from the crmForm object. In this case, it's common practice to use CRM Web Service to retrieve this type of information from CRM database. This is what I have been doing extensively in my current CRM project, so I spent some of my weekend time to create a re-usable JavaScript CRM Web Service Helper, which you can copy and use.
CrmServiceHelper = function()
{
    /**
     * CrmServiceHelper 1.0
     *
     * @author Daniel Cai
     * @website http://danielcai.blogspot.com/
     * @copyright Daniel Cai
     * @license Microsoft Public License (Ms-PL), http://www.opensource.org/licenses/ms-pl.html
     *
     * This release is provided "AS IS" and contains no warranty or whatsoever.
     *
     * Date: Sep 27 2009
     */

    // Private members
    var DoRequest = function(soapBody, requestType)
    {
        //Wrap the Soap Body in a soap:Envelope.
        var soapXml =
                "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' " +
                "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
                "xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" +
                GenerateAuthenticationHeader() +
                "<soap:Body><" + requestType + " xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" +
                soapBody + "</" + requestType + ">" +
                "</soap:Body>" +
                "</soap:Envelope>";

        var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
        xmlhttp.open("POST", "/MSCRMServices/2007/crmservice.asmx", false);
        xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/" + requestType);

        //Send the XMLHTTP object.
        xmlhttp.send(soapXml);

        var resultXml = xmlhttp.responseXML;

        if (resultXml === null || resultXml.xml === null || resultXml.xml === "")
        {
            if (xmlhttp.responseText !== null && xmlhttp.responseText !== "")
            {
                throw new Error(xmlhttp.responseText);
            }
            else
            {
                throw new Error("No response received from the server. ");
            }
        }

        // Report the error if occurred
        var error = resultXml.selectSingleNode("//error");
        var faultString = resultXml.selectSingleNode("//faultstring");

        if (error !== null || faultString !== null)
        {
            throw new Error(error !== null ? resultXml.selectSingleNode('//description').nodeTypedValue : faultString.text);
        }

        return resultXml;
    };

    var BusinessEntity = function(sName)
    {
        this.name = sName;
        this.attributes = new Object();
    };

    var DataType = {
        String : "string",
        Boolean : "boolean",
        Int : "int",
        Float : "float",
        DateTime : "datetime"
    };

    // Public members
    return {
        BusinessEntity : BusinessEntity,

        DataType : DataType,

        DoRequest : DoRequest,

        Retrieve : function(entityName, id, columns)
        {
            var attributes = "";
            if (typeof attributes !== "undefined")
            {
                for (var i = 0; i < columns.length; i++)
                {
                    attributes += "<q1:Attribute>" + columns[i] + "</q1:Attribute>";
                }
            }

            var msgBody =
                    "<entityName>" + entityName + "</entityName>" +
                    "<id>" + id + "</id>" +
                    "<columnSet xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:ColumnSet'>" +
                    "<q1:Attributes>" +
                    attributes +
                    "</q1:Attributes>" +
                    "</columnSet>";

            var resultXml = DoRequest(msgBody, "Retrieve");
            var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.async = false;
            xmlDoc.loadXML(resultXml.xml);

            var retrieveResult = xmlDoc.selectSingleNode("//RetrieveResult");
            if (retrieveResult === null)
            {
                throw new Error("Invalid result returned from server. ");
            }

            var resultNodes = retrieveResult.childNodes;
            var returnEntity = new BusinessEntity();
            for (var i = 0; i < resultNodes.length; i++)
            {
                var fieldNode = resultNodes[i];
                var field = {};
                field["value"] = fieldNode.text;

                for (var j = 0; j < fieldNode.attributes.length; j++)
                {
                    field[fieldNode.attributes[j].nodeName] = fieldNode.attributes[j].nodeValue;
                }

                returnEntity.attributes[fieldNode.baseName] = field;
            }

            return returnEntity;
        },

        Fetch : function(xml)
        {
            var msgBody = "<fetchXml>" + _HtmlEncode(xml) + "</fetchXml>";

            var resultXml = DoRequest(msgBody, "Fetch");
            var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.async = false;
            xmlDoc.loadXML(resultXml.xml);

            var fetchResult = xmlDoc.selectSingleNode("//FetchResult");
            if (fetchResult === null)
            {
                throw new Error("Invalid result returned from server. ");
            }
            xmlDoc.loadXML(fetchResult.childNodes[0].nodeValue);

            var resultNodes = xmlDoc.selectNodes("/resultset/result");
            var results = [];

            for (var i = 0; i < resultNodes.length; i++)
            {
                var resultEntity = new BusinessEntity();

                for (var j = 0; j < resultNodes[i].childNodes.length; j++)
                {
                    var fieldNode = resultNodes[i].childNodes[j];
                    var field = {};
                    field["value"] = fieldNode.text;

                    for (var k = 0; k < fieldNode.attributes.length; k++)
                    {
                        field[fieldNode.attributes[k].nodeName] = fieldNode.attributes[k].nodeValue;
                    }

                    resultEntity.attributes[fieldNode.baseName] = field;
                }

                results[i] = resultEntity;
            }

            return results;
        },

        Execute : function(request)
        {
            var msgBody = request;

            var resultXml = DoRequest(msgBody, "Execute");
            var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.async = false;
            xmlDoc.loadXML(resultXml.xml);
            return xmlDoc;
        },

        ParseValue : function(businessEntity, crmProperty, type, crmPropertyAttribute)
        {
            if (businessEntity === null || typeof crmProperty === "undefined" || !businessEntity.attributes.hasOwnProperty(crmProperty))
            {
                return null;
            }

            var value = (typeof crmPropertyAttribute !== "undefined")
                    ? businessEntity.attributes[crmProperty][crmPropertyAttribute]
                    : businessEntity.attributes[crmProperty].value;

            switch (type)
                    {
                case DataType.Boolean:
                    return (value !== null) ? (value === "1") : false;
                case DataType.Float:
                    return (value !== null) ? parseFloat(value) : 0;
                case DataType.Int:
                    return (value !== null) ? parseInt(value) : 0;
                case DataType.DateTime:
                    return (value !== null) ? ParseDate(value) : null;
                case DataType.String:
                    return (value !== null) ? value : "";
                default:
                    return (value !== null) ? value : null;
            }

            return null;
        }
    };
}();

The following are a few scenarios that your might find the CRM Web Service Helper userful.
  1. Fetch a list of records or one record, in which case you can use CrmServiceHelper.DoFetchXmlRequest().
    var fetchXml = 
    '<fetch mapping="logical">' +
       '<entity name="account">' +
          '<attribute name="name" />' +
          '<attribute name="primarycontactid" />' +
          '<filter>' +
             '<condition attribute="accountid" operator="eq" value="' + crmForm.all.accountid.DataValue[0].id + '" />' +
          '</filter>' +
       '</entity>' +
    '</fetch>';  
     
    var fetchResult = CrmServiceHelper.Fetch(fetchXml);
    alert(CrmServiceHelper.ParseValue(fetchResult[0], 'name'));
    

  2. Retrieve one record.
    var retrieveResult = CrmServiceHelper.Retrieve('account', crmForm.all.accountid.DataValue[0].id, ['accountid', 'name']);
    alert(CrmServiceHelper.ParseValue(retrieveResult, 'name'));

  3. Execute a request.
    function GetCurrentUserId()
    {
       var request = "<Request xsi:type='WhoAmIRequest' />";
       var xmlDoc = CrmServiceHelper.Execute(request);
     
       var userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
       return userid;
    }

Beyond the above example, you can use CrmServiceHelper.DoRequest() function to make any other CRM service calls including Create, Update, Delete, etc.

A few final notes about Web Service Helper:
  1. CrmServiceHelper is designed as a container object to provide all necessary interfaces to interact with CRM Web Service through JavaScript. By this approach, we don't pollute JavaScript global namespace with a lot of objects.
  2. All functions in CrmServiceHelper throws an error when exception happens, it's your responsibility to handle this type of exception. The common practice is using try/catch block, but it's totally up to you. If you don't use try/catch block, CRM platform will catch it, and give the user an alert warning window.
  3. You should probably consider saving the above script to a file such as CrmServiceHelper.js, and upload the file to your CRM's ISV folder, then use Henry Cordes' load_script() function to load and consume the service helper in the form's onload event.
  4. When you use CrmServiceHelper.GetValueFromFetchResult() function to parse the fetched result, please make sure to specify the datatype correctly.
  5. FetchXML is extremely flexible, it can do almost anything that you may want (except some native SQL functions such as SOUNDEX, etc.) in CRM, FetchXML is usually my first choice when I need to retrieve more than one record from CRM using JavaScript (In C#, you might want to use Query Expression due to the syntax friendship in IDE environment). You might want to consider using Stunnware Tools to help you create FetchXML in a more productive way.

[UPDATE - Jan 21, 2010] I have released an updated version of script under the name of CRM Web Service Toolkit, please check my latest blog for the details.