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.

20 comments:

  1. Is there anyway to suppress this error?:
    The entity with a name = 'undefined' was not found in the MetadataCache

    I'm using a min.js version if that helps. I searched your code and couldn't find an alert.

    ReplyDelete
  2. @Withers, when you instantiate a BusinessEntity, did you specify the entity name?

    ReplyDelete
  3. Wow man, your code just saved me a ton of work hours. useing that, and the lookup to picklist option, i'm one happy programmer.
    also, very nice and sleek codeing, it was easy to read understand.

    cheers to you

    ReplyDelete
  4. Is there anyway to tell if an N:N relationship already exists? I'm trying to associate a custom entity to an account through an N:N relationship, but I'd like to test if it already exists before I commit it.

    Any ideas?

    ReplyDelete
  5. Sorry, also I'm trying to access a custom entities ExtensionBase table through the Retrieve() function. Is that possible?

    For example, I have a custom entity named "Structure" (cust_structure) with fields Cust_structurename and Cust_PrimaryContactId the Cust_structureExtensionBase table. How do I access those fields?

    Here's my code:

    // Get the Structure GUID
    var structureId = crmForm.all.cust_structureid.DataValue[0].id;

    if (structureId != null)
    {

    var cols = ["Cust_structurename", "Cust_PrimaryContactId"];

    var retrievedContact = CrmServiceToolkit.Retrieve("cust_structure", structureId, cols);

    alert(retrievedContact.getValue("Cust_structurename"));

    alert(retrievedContact.getValue("Cust_PrimaryContactId"));

    }

    ReplyDelete
  6. @Tom,

    For your question regarding the N:N relationship, you can construct FetchXML and use Fetch method to check whether a N:N relationship record exists.

    Your code about using Retrieve method looks fine to me, except that the way you get the Structure GUID needs to be done a little differently.

    // Get the Structure GUID
    var structureId = crmForm.all.cust_structureid.DataValue;

    if (structureId != null)
    {

    var cols = ["Cust_structurename", "Cust_PrimaryContactId"];

    var retrievedStructure = CrmServiceToolkit.Retrieve("cust_structure", structureId[0].id, cols);

    alert(retrievedStructure.getValue("Cust_structurename"));

    alert(retrievedStructure.getValue("Cust_PrimaryContactId"));

    }

    The second alert will give you the contact's GUID, not the name, if you want to show the name, you should do the following:
    alert(retrievedStructure.getValue("Cust_PrimaryContactId", "name"));

    ReplyDelete
  7. Hi Daniel,

    I'm not sure why this didn't work, but for completions sake here's the final code I used for my instance. Maybe this will help somebody.

    try
    {
    // Try to asssociate the Contact designated in Primary Contact of the STRUCTURE
    var structureId = crmForm.all.cust_structureid.DataValue;

    if (structureId != null)
    {
    // Get the primary contact GUID from the structure
    var cols = ["cust_primarycontactid"];
    var retrievedStructure = CrmServiceToolkit.Retrieve("cust_structure", structureId[0].id, cols);

    var contactId = retrievedStructure.getValue("cust_primarycontactid");
    var contactName = retrievedStructure.getValue("cust_primarycontactid", "name");

    // Need to make sure the Structure has a primary contact
    if (contactId != null)
    {
    // Associate it with the accounts N:N relationship with contact
    CrmServiceToolkit.associate("cust_contact_account_NN", "account", accountId, "contact", contactId);
    window.location.reload(true);

    // Link it to the Primary Contact field in the Account and save it
    crmForm.all.primarycontactid.DataValue = COL_CreateLookup(contactId, "contact", contactName);
    crmForm.Save();

    }
    }

    alert("Contact successfully linked.");

    return;


    }
    catch (err)
    {
    alert("\tContact NOT linked.\t\n\n No Primary Contact has been selected for \n either the Primary Structure or this Entity.\t");
    }

    ReplyDelete
  8. And Daniel, sorry, last thing,

    "For your question regarding the N:N relationship, you can construct FetchXML and use Fetch method to check whether a N:N relationship record exists."

    Forgive my ignorance, but how would I go about doing that?

    ReplyDelete
  9. @Tom, by FetchXML, I meant something like this:

    var fetchXml =
    '<fetch mapping="logical" aggregate="true"><entity name="n2nrelationshipname">
    <attribute name="n2nrelationshipnameid" aggregate="count" alias="count" /><filter><condition attribute="entity1idcolumn" operator="eq" value="{entity1id-guid}" /><condition attribute="entity2idcolumn" operator="eq" value="entity2id-guid" /></filter></entity></fetch>';

    var fetchResult = CrmServiceToolkit.Fetch(fetchXml);

    alert(fetchResult[0].getValue('count'));

    Essentially, N:N relationship is represented by a relationship entity in CRM.

    ReplyDelete
  10. Hi Daniel,

    Thank you for your help. I have a question about the asynchronous callbacks for functions other than Fetch. How would you go about doing this for Create(), Delete(), queryByAttribute() etc? Is it something like:

    var fetchResults = CrmServiceToolkit.queryByAttribute(queryOptions, fetchCallback);

    function fetchCallback(fetchedEntityRoles)
    {
    alert(fetchResults[0].getValue(""));
    }

    When I try to do code similiar to the follow the code excutes but nothing happens...

    ReplyDelete
  11. @Tom, your code should be like this:
    var fetchResults = CrmServiceToolkit.queryByAttribute(queryOptions, fetchCallback);

    function fetchCallback(fetchedEntityRoles)
    {
    alert(fetchedEntityRoles[0].getValue("blah.blah"));
    }

    ReplyDelete
  12. Sorry, that was a silly mistake. I suppose I'm more confused as to how to make Create() and Delete() asynchronous to speed up the performance of the CRM. How would I do a Create?

    var createResponse = CrmServiceToolkit.Create(newRecord, fetchCallback);

    function fetchCallback(fetchedEntityRoles)
    {
    return fetchedEntityRoles;
    // or what? I don't need it to do anything but create the record, but want to do it without making the form freeze..
    }

    ReplyDelete
  13. Hi,
    Total Dynamics n00b here -- I am tasked with connecting a "contact us" form on a PHP website to a Dynamics CRM installation. Pretty simple -- just need to add a new contact to Dynamics with the info that someone fills out in the web form.

    Your Javascript library looks like it will be perfect for this task, but I think I'm missing some piece of the puzzle here -- where exactly do I tell the code what the URL for the Dynamics system is, and what the authentication info is (name/password, auth token, etc.)?

    I don't see any .Net-specific code in here, so I'm assuming I can run this from a plain old HTML page (it's all client-side javascript, right?) -- but perhaps I'm wrong and this must be run from an Asp.Net site?

    Thanks for any help you can provide.

    -Jordan

    ReplyDelete
  14. @jordanlev, the toolkit was written and designed to be used in CRM form. Yes, it's possible to use it in other application platform, but it's not recommended because of security concern as you will need to provide security credential in client side in order for it to work.

    In your case, it's possible to write PHP script to talk to CRM server in your server side to mitigate such security risks. Let me know if you need any assistance from me.

    Cheers,
    Daniel

    ReplyDelete
  15. Hi Daniel,
    I would actually love some assistance on this. I am a few years removed from the Microsoft development world so I am not sure what the proper vocabulary for what I want to do is (which makes it hard to find the right help). If you could explain this in non-Dynamics terms (or even non-ASP.net terms), that would be great. Feel free to contact me directly if you want to take this off-line (and discuss compensation if this is a time-consuming task).

    Thank you!

    -Jordan

    ReplyDelete
  16. Hi,
    First of all, congratulations for this toolkit. It saved me hours and hours ...

    I have to duplicate / clone a service activity. Fetching the data is OK with fetchxml but I'm having some problems with the Create ...

    I'm trying to create the record "serviceappointment" and then to loop over the different records of "activityparty" to create them 1 by 1. When trying to create the "activityparty" I get the following error message "The Create method does not support entities of type 'activityparty' ...

    Can someone help me ? What am I doing wrong ?

    Thanks a lot !

    Bernard.

    ReplyDelete
  17. @Bernard, you don't really create "activityparty" record if I understand your requirement correctly. I believe you are actually working with "resources" field, which can contain multiple activityparty records. activityparty record itself can be a lookup to user or facility/equipment entity. I think it would help to provide your code. You may consider using CRM Development Forum for this kind of detailed questions.

    Cheers,
    Daniel

    ReplyDelete
  18. Hello Daniel,

    I have a problem when I try this,

    var listaMarketing = new CrmServiceToolkit.BusinessEntity("list");
    listaMarketing.attributes["listname"] = crmForm.all.name.DataValue; //Nombre igual al nombre de la Acción de Marketing
    listaMarketing.attributes["new_estado"] = 1;
    //listaMarketing.attributes["ownerid"] = crmForm.all.ownerid.DataValue[0].id; //Propietario

    var createResponse = CrmServiceToolkit.Create(listaMarketing);

    It returns this error:

    "0x80040216 An unexpected error occurred. Platform"

    I have located the error when it is doing the _processResponse function in the responseXml.

    Do you know what can be?

    Thank you,
    Jose.

    ReplyDelete
  19. Hi Jose, the toolkit will throw an exception when an error happens at the server end. What you should do in this case, is to turn on CRM server trace log, and find out what actually went wrong.

    One thing that you should be aware when creating a marketing list record is, you need to provide a Member Type value for the new list, the schema name of Member Type field is createdfromcode.

    In addition, you should make sure that 1 is a valid value for new_estado field.

    Hope this helps.
    Daniel

    ReplyDelete
  20. Hi Daniel,

    I know this is an old thread, but I am using the "crmservicetoolkit" to create an account record, but I'm having difficulty with the "Owner" field - the Create() method is not accepting the GUID string that I am passing it.
    I am asuming that this is because the "ownerid" field is not a simply look field, as I am also having problems with the "parentcustomerid" field of the contact entity (These lookup fields can be multiple entity types).
    Is there a special way of setting these lookup field values with the crmservicetoolkit.Create() method?
    I have already tried various different incarnations of the code below:
    newCustomer.attributes["ownerid"] = { $value: strOwnerID, type: "systemuser" };
    but to no avail, as the serialize() method seems to simply parse this into "[object] [object]", which the create method also doesn't recognise.
    Can you assist me please?


    James Hathaway
    Dynamics CRM Developer for Saint-Gobain

    ReplyDelete