Pages

Wednesday, November 4, 2009

Easy to use JavaScript Retrieve and RetrieveMultiple functions

function MischiefMayhemSOAP(xmlSoapBody, soapActionHeader) {
  var xmlReq = "<?xml version='1.0' encoding='utf-8'?>"
    + "<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>"
    + xmlSoapBody
    + "  </soap:Body>"
    + "</soap:Envelope>";

  var httpObj = new ActiveXObject("Msxml2.XMLHTTP");
    
  httpObj.open('POST', '/mscrmservices/2007/crmservice.asmx', false);

  httpObj.setRequestHeader('SOAPAction', soapActionHeader);
  httpObj.setRequestHeader('Content-Type', 'text/xml; charset=utf-8');
  httpObj.setRequestHeader('Content-Length', xmlReq.length);

  httpObj.send(xmlReq);

  var resultXml = httpObj.responseXML;

  var errorCount = resultXml.selectNodes('//error').length;
  if (errorCount != 0) {
    var msg = resultXml.selectSingleNode('//description').nodeTypedValue;
    alert("The following error was encountered: " + msg);
    return null;
  } else {
    return resultXml;
  }
}
function RetrieveRecord(entityName, entityId, attrArray) {
  var xmlSoapBody = ""
    + "    <Retrieve xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"
    + "      <entityName>" + entityName + "</entityName>"
    + "      <id>" + entityId + "</id>"
    + "      <columnSet xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:ColumnSet'>"
    + "        <q1:Attributes>";

  for (index in attrArray) {
    xmlSoapBody += "          <q1:Attribute>" + attrArray[index] + "</q1:Attribute>";
  }

  xmlSoapBody += ""
    + "        </q1:Attributes>"
    + "      </columnSet>"
    + "    </Retrieve>";

  var resultXml = MischiefMayhemSOAP(xmlSoapBody, 'http://schemas.microsoft.com/crm/2007/WebServices/Retrieve');

  if (resultXml != null) {
    var resultArray = new Array();

    for (index in attrArray) {
      if (resultXml.selectSingleNode("//q1:" + attrArray[index]) != null) {
        resultArray[index] = resultXml.selectSingleNode("//q1:" + attrArray[index]).nodeTypedValue;
      } else {
        resultArray[index] = null;
      }
    }

    return resultArray;
  } else {
    return null;
  }
}
function RetrieveMultiple(entityName, attrArray, distinct, criteriaXml)  {
  var xmlSoapBody = ""
    + "    <RetrieveMultiple xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"
    + "      <query xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:QueryExpression'>"
    + "        <q1:EntityName>" + entityName + "</q1:EntityName>"
    + "        <q1:ColumnSet xsi:type='q1:ColumnSet'>"
    + "          <q1:Attributes>";

  for (index in attrArray) {
    xmlSoapBody += "            <q1:Attribute>" + attrArray[index] + "</q1:Attribute>";
  }

  xmlSoapBody += ""
    + "          </q1:Attributes>"
    + "        </q1:ColumnSet>"
    + "        <q1:Distinct>" + distinct + "</q1:Distinct>"
    + criteriaXml
    + "      </query>"
    + "    </RetrieveMultiple>";

  var resultXml = MischiefMayhemSOAP(xmlSoapBody, 'http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple');

  if (resultXml != null) {
    var resultArray = new Array();
    var entityNodes = resultXml.selectNodes("//RetrieveMultipleResult/BusinessEntities/BusinessEntity");

    if (entityNodes.length > 0) {
        for (var index = 0; index < entityNodes.length; index++) {
          var entityNode = entityNodes[index];
          resultArray[index] = new Array();
    
          for (attrIndex in attrArray) {
            if (entityNode.selectSingleNode("q1:" + attrArray[attrIndex]) != null) {
              resultArray[index][attrIndex] = entityNode.selectSingleNode("q1:" + attrArray[attrIndex]).nodeTypedValue;
            } else {
              resultArray[index][attrIndex] = null;
            }
          }
        }
        
        return resultArray;
    } else {
      return null;
    }
  } else {
    return null;
  }
}
These functions are built around SOAP implementations of the Retrieve and RetrieveMultiple messages. I haven't annotated my code, so forgive any obvious lack of description of what the code is doing. I'll try to briefly summarize here:
MischiefMayhemSOAP
Parameters:
xmlSoapBody - (string) XML containing the body of the SOAP call
soapActionHeader - (string) The 'SOAPAction' specifying the type of SOAP call
Purpose:
Serves as a generic XMLHTTP interface to CRM's SOAP functions.
Returns:
(string) XML result from the CRM server.
RetrieveRecord
Parameters:
entityName - (string) The CRM-platform name of the desired entity
entityId - (string) The GUID of the record to retrieve
attrArray - (string[]) An Array of attributes to retrieve for the record
Purpose:
Easily retrieves a single record from CRM.
Returns:
(string[] or null) An Array of values from CRM, index-matching the attributes provided in attrArray. Returns null if there is an error with, or invalid return from, the SOAP call.
RetrieveMultiple
Parameters:
entityName - (string) The CRM-platform name of the desired entity
attrArray - (string[]) An Array of attributes to retrieve for the records
distinct - (string:"true" or "false") Directs CRM to return with or without duplicate records
criteriaXml - (string) XML containing any combination of Criteria or LinkEntities elements pertaining to the "RetrieveMultiple" SOAP message
Purpose:
Easily retrieves multiple records from CRM which match the rules defined in the criteriaXml.
Returns:
(string[n][] or null) An Array, with n matching-record elements, of an array of values from CRM, index-matching the attributes provided in attrArray. Returns null if there is an error with, or invalid return from, the SOAP call.

8 comments:

  1. Great Post mate :)

    ReplyDelete
  2. Hi, Theres a bug in your Retrieve Multiple Method.
    Firstly a quick warning, on initially looking at the following bug report and fix, you won't believe me (i wouldn't have either if I was you), but please believe me (give it a go if you like)
    the 2 lines "...selectSingleNode("//q1:" actually always return the first record retrieved, so the method will return the correct number of records, however they will all contain the same values. After some reading, even though you are performing this query on the correct sub-node the double slash results in it magically inspecting the full xml docment.
    The fix is nice and easy, simply replace the // on the 2 selectsinglenode lines with ./ (you could drop the ./ altogether if you wanted)

    Otherwise its a lovely handy bit of reusable code that more people should use.

    Jon.

    ReplyDelete
  3. Holy crap! Thanks for finding that bug, Jon! I can't believe I did that. I'm posting the correction now.

    ReplyDelete
  4. I've found this article to be very handy. Thanks Dave for posting this up and thank you Jon for catching the bug. :)

    ReplyDelete
  5. Wow.This piece of code works like magic. Very impressive. Need a favor though, could you please add the examples of calling functions for both single record and multiple.

    ReplyDelete
  6. I have since updated these functions in the CRM Javascript Library, and haven't yet made the time to properly document them on that page. Though I hate to do so, unless I can update them soon, you may find Daniel Cai's web-service toolkit and his documentation more forthcoming: http://danielcai.blogspot.com/2010/05/mscrm4-web-service-toolkit-for.html

    ReplyDelete
  7. Thank you; this is great!

    ReplyDelete
  8. Great post, Very impressive, it will be gud if u can add some more examples using such functions for better understanding

    ReplyDelete

Unrelated comments to posts may be summarily disposed at the author's discretion.