One of this things I really like about working with JavaScript in
CRM 2011 is that you can now have shared libraries of re-usable code for each
form AND you can build FetchXML and send it through to the server for queries.
We have a LOT of customizations on our Case forms which require
several behind the scenes queries to get data to show on the form as the user
is interacting with it. One example is that when the user selects a Contact
record, there are a couple of fields on the form where we show the phone
number(s) to call them, as well as their email. It's just for reference, but it
helps the user experience.
The good news with CRM 2011
is that you don’t really have to know all the FetchXML codes and write it
yourself. You can build an advanced find
query and then export the FetchXML results to a text file.
Here's a good example of a shared function I use on several forms:
function GetContactData(sContactId) {
var _oService;
var _sOrgName = Xrm.Page.context.getOrgUniqueName();
var _sServerUrl = GetServerURL();
var _sOrgName = Xrm.Page.context.getOrgUniqueName();
var _sServerUrl = GetServerURL();
var sFetch = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>" +
"<entity name='contact'>" +
"<attribute name='fullname' />" +
"<attribute name='telephone1' />" +
"<attribute name='emailaddress1' />" +
"<attribute name='mobilephone' />" +
"<attribute name='parentcustomerid' />" +
"<attribute name='contactid' />" +
"<attribute name='address1_line1' />" +
"<attribute name='address1_line2' />" +
"<attribute name='address1_city' />" +
"<attribute name='address1_stateorprovince' />" +
"<attribute name='address1_postalcode' />" +
"<attribute name='address1_country' />" +
"<order attribute='lastname' descending='false' />" +
"<filter type='and'>" +
"<condition attribute='statecode' operator='eq' value='0' />" +
"<condition attribute='contactid' operator='eq' uitype='contact' value='" + sContactId + "' />" +
"</filter></entity></fetch>";
"<entity name='contact'>" +
"<attribute name='fullname' />" +
"<attribute name='telephone1' />" +
"<attribute name='emailaddress1' />" +
"<attribute name='mobilephone' />" +
"<attribute name='parentcustomerid' />" +
"<attribute name='contactid' />" +
"<attribute name='address1_line1' />" +
"<attribute name='address1_line2' />" +
"<attribute name='address1_city' />" +
"<attribute name='address1_stateorprovince' />" +
"<attribute name='address1_postalcode' />" +
"<attribute name='address1_country' />" +
"<order attribute='lastname' descending='false' />" +
"<filter type='and'>" +
"<condition attribute='statecode' operator='eq' value='0' />" +
"<condition attribute='contactid' operator='eq' uitype='contact' value='" + sContactId + "' />" +
"</filter></entity></fetch>";
_oService = new FetchUtil(_sOrgName, _sServerUrl);
// sync query
var results = _oService.Fetch(sFetch);
var results = _oService.Fetch(sFetch);
// Use for Async query
// _oService.Fetch(sFetch, myCallBack);
// _oService.Fetch(sFetch, myCallBack);
if (results != null && results.length > 0) { return results; }
else { return ''; }
else { return ''; }
};
In
order to use this on the case/incident form, here is an example to alert the
user with the contact data returned:
function AlertContactData() {
if (Xrm.Page.getAttribute('responsiblecontactid').getValue() != null) {
var results = GetContactData(Xrm.Page.getAttribute('responsiblecontactid').getValue()[0].id);
if (results != '' && results.length > 0) {
alert("Email address: " + results[0].attributes["emailaddress1"];
alert("Telephone1: " + results[0].attributes["telephone1"];
alert("Mobilephone: " + results[0].attributes["mobilephone"];
}
else { Alert(“Contact Not Found! “);}
}
};
if (Xrm.Page.getAttribute('responsiblecontactid').getValue() != null) {
var results = GetContactData(Xrm.Page.getAttribute('responsiblecontactid').getValue()[0].id);
if (results != '' && results.length > 0) {
alert("Email address: " + results[0].attributes["emailaddress1"];
alert("Telephone1: " + results[0].attributes["telephone1"];
alert("Mobilephone: " + results[0].attributes["mobilephone"];
}
else { Alert(“Contact Not Found! “);}
}
};
So
there are a couple of things needed to make this work, namely the FetchUtil
code. As much as I would like to have brilliantly
come up with this on my own… alas I am not that talented. But I am talented enough to copy good code
when I see it. This particular code was
lifted from another blogger at:
var XMLHTTPREADY = 4;
this.org = sOrg;
this.server = sServer;
if (typeof (ORG_UNIQUE_NAME) != "undefined") {
this.org = ORG_UNIQUE_NAME;
}
}
if (sServer == null) {
this.server = window.location.protocol + "//" + window.location.host;
}
};
FetchUtil.prototype._ExecuteRequest = function (sXml, sMessage, fInternalCallback, fUserCallback) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
if (fUserCallback != null) {
//asynchronous: register callback function, then send the request.
var crmServiceObject = this;
xmlhttp.onreadystatechange = function () {
fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
};
xmlhttp.send(sXml);
} else {
//synchronous: send request, then call the callback function directly
xmlhttp.send(sXml);
return fInternalCallback.call(this, xmlhttp, null);
}
};
//asynchronous: register callback function, then send the request.
var crmServiceObject = this;
xmlhttp.onreadystatechange = function () {
fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
};
xmlhttp.send(sXml);
} else {
//synchronous: send request, then call the callback function directly
xmlhttp.send(sXml);
return fInternalCallback.call(this, xmlhttp, null);
}
};
FetchUtil.prototype._HandleErrors = function (xmlhttp) {
/// <summary>(private) Handles xmlhttp errors</summary>
if (xmlhttp.status != XMLHTTPSUCCESS) {
var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
alert(sError);
return true;
} else {
return false;
}
};
/// <summary>(private) Handles xmlhttp errors</summary>
if (xmlhttp.status != XMLHTTPSUCCESS) {
var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
alert(sError);
return true;
} else {
return false;
}
};
FetchUtil.prototype.Fetch = function (sFetchXml, fCallback) {
/// <summary>Execute a FetchXml request. (result is the response XML)</summary>
/// <param name="sFetchXml">fetchxml string</param>
/// <param name="fCallback" optional="true" type="function">(Optional) Async callback function if specified. If left null, function is synchronous </param>
/// <summary>Execute a FetchXml request. (result is the response XML)</summary>
/// <param name="sFetchXml">fetchxml string</param>
/// <param name="fCallback" optional="true" type="function">(Optional) Async callback function if specified. If left null, function is synchronous </param>
var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
request += "<s:Body>";
request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' + '<request i:type="b:RetrieveMultipleRequest" ' + ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' + ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' + '<b:KeyValuePairOfstringanyType>' + '<c:key>Query</c:key>' + '<c:value i:type="b:FetchExpression">' + '<b:Query>';
request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);
request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';
request += '</s:Body></s:Envelope>';
request += "<s:Body>";
request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' + '<request i:type="b:RetrieveMultipleRequest" ' + ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' + ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' + '<b:KeyValuePairOfstringanyType>' + '<c:key>Query</c:key>' + '<c:value i:type="b:FetchExpression">' + '<b:Query>';
request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);
request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';
request += '</s:Body></s:Envelope>';
return this._ExecuteRequest(request, "Fetch", this._FetchCallback, fCallback);
};
};
FetchUtil.prototype._FetchCallback = function (xmlhttp, callback) {
///<summary>(private) Fetch message callback.</summary>
//xmlhttp must be completed
if (xmlhttp.readyState != XMLHTTPREADY) {
return;
}
///<summary>(private) Fetch message callback.</summary>
//xmlhttp must be completed
if (xmlhttp.readyState != XMLHTTPREADY) {
return;
}
//check for server errors
if (this._HandleErrors(xmlhttp)) {
return;
}
if (this._HandleErrors(xmlhttp)) {
return;
}
var sFetchResult = xmlhttp.responseXML.selectSingleNode("//a:Entities").xml;
var resultDoc = new ActiveXObject("Microsoft.XMLDOM");
resultDoc.async = false;
resultDoc.loadXML(sFetchResult);
resultDoc.async = false;
resultDoc.loadXML(sFetchResult);
//parse result xml into array of jsDynamicEntity objects
var results = new Array(resultDoc.firstChild.childNodes.length);
for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
var oResultNode = resultDoc.firstChild.childNodes[i];
var jDE = new jsDynamicEntity();
var obj = new Object();
var results = new Array(resultDoc.firstChild.childNodes.length);
for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
var oResultNode = resultDoc.firstChild.childNodes[i];
var jDE = new jsDynamicEntity();
var obj = new Object();
for (var j = 0; j < oResultNode.childNodes.length; j++) {
switch (oResultNode.childNodes[j].baseName) {
case "Attributes":
var attr = oResultNode.childNodes[j];
switch (oResultNode.childNodes[j].baseName) {
case "Attributes":
var attr = oResultNode.childNodes[j];
for (var k = 0; k < attr.childNodes.length; k++) {
// Establish the Key for the Attribute
var sKey = attr.childNodes[k].firstChild.text;
var sType = '';
var sKey = attr.childNodes[k].firstChild.text;
var sType = '';
// Determine the Type of Attribute value we should expect
for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
sType = attr.childNodes[k].childNodes[1].attributes[l].text;
}
}
for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
sType = attr.childNodes[k].childNodes[1].attributes[l].text;
}
}
switch (sType) {
case "a:OptionSetValue":
var entOSV = new jsOptionSetValue();
entOSV.type = sType;
entOSV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entOSV;
break;
case "a:OptionSetValue":
var entOSV = new jsOptionSetValue();
entOSV.type = sType;
entOSV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entOSV;
break;
case "a:EntityReference":
var entRef = new jsEntityReference();
entRef.type = sType;
entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
obj[sKey] = entRef;
break;
var entRef = new jsEntityReference();
entRef.type = sType;
entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
obj[sKey] = entRef;
break;
default:
var entCV = new jsCrmValue();
entCV.type = sType;
entCV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entCV;
break;
}
var entCV = new jsCrmValue();
entCV.type = sType;
entCV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entCV;
break;
}
}
jDE.attributes = obj;
break;
break;
case "Id":
jDE.guid = oResultNode.childNodes[j].text;
break;
jDE.guid = oResultNode.childNodes[j].text;
break;
case "LogicalName":
jDE.logicalName = oResultNode.childNodes[j].text;
break;
jDE.logicalName = oResultNode.childNodes[j].text;
break;
case "FormattedValues":
var foVal = oResultNode.childNodes[j];
var foVal = oResultNode.childNodes[j];
for (var m = 0; m < foVal.childNodes.length; m++) {
// Establish the Key, we are going to fill in the formatted value of the already found attribute
var sKey2 = foVal.childNodes[m].firstChild.text;
jDE.attributes[sKey2].formattedValue = foVal.childNodes[m].childNodes[1].text;
}
break;
}
// Establish the Key, we are going to fill in the formatted value of the already found attribute
var sKey2 = foVal.childNodes[m].firstChild.text;
jDE.attributes[sKey2].formattedValue = foVal.childNodes[m].childNodes[1].text;
}
break;
}
}
results[i] = jDE;
}
}
//return entities
if (callback != null) callback(results);
else return results;
if (callback != null) callback(results);
else return results;
};
function jsDynamicEntity(gID, sLogicalName) {
this.guid = gID;
this.logicalName = sLogicalName;
this.attributes = new Object();
};
function jsCrmValue(sType, sValue) {
this.type = sType;
this.value = sValue;
};
this.type = sType;
this.value = sValue;
};
function jsEntityReference(gID, sLogicalName, sName) {
this.guid = gID;
this.logicalName = sLogicalName;
this.name = sName;
this.type = 'EntityReference';
};
this.guid = gID;
this.logicalName = sLogicalName;
this.name = sName;
this.type = 'EntityReference';
};
function jsOptionSetValue(iValue, sFormattedValue) {
this.value = iValue;
this.formattedValue = sFormattedValue;
this.type = 'OptionSetValue';
};
this.value = iValue;
this.formattedValue = sFormattedValue;
this.type = 'OptionSetValue';
};
No comments:
Post a Comment