Tuesday, April 20, 2010

Attribute Mapping using Java Script

I'm active visitor and answerer of several forums about Microsoft Dynamics CRM. I have seen a lot questions about mappings - how does it work. Mapping works only once - when new child record opened from parent record and when user just fills parent record lookup - of course mapping doesn't work. I've decided to fix this issue.



I've tested my script on mapping between account and contact. I've put following script to Onchange event handler of parentcustomerfield:

MapFields = function(sourceentity, targetentity, sourceentityid, invokeattributename)

{
var xml = "" +
"<?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>" +
" <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>attributemap</q1:EntityName>" +
" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
" <q1:Attributes>" +
" <q1:Attribute>targetattributename</q1:Attribute>" +
" <q1:Attribute>sourceattributename</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Distinct>false</q1:Distinct>" +
"<q1:Criteria>"+
"<q1:FilterOperator>And</q1:FilterOperator>"+
"<q1:Conditions>"+
"<q1:Condition>"+
"<q1:AttributeName>parentattributemapid</q1:AttributeName>"+
"<q1:Operator>Null</q1:Operator>"+
"</q1:Condition>"+
"</q1:Conditions>"+
"</q1:Criteria>"+
" <q1:LinkEntities>" +
" <q1:LinkEntity>" +
" <q1:LinkFromAttributeName>entitymapid</q1:LinkFromAttributeName>" +
" <q1:LinkFromEntityName>attributemap</q1:LinkFromEntityName>" +
" <q1:LinkToEntityName>entitymap</q1:LinkToEntityName>" +
" <q1:LinkToAttributeName>entitymapid</q1:LinkToAttributeName>" +
" <q1:JoinOperator>Inner</q1:JoinOperator>" +
" <q1:LinkCriteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>sourceentityname</q1:AttributeName>" +
" <q1:Operator>Equal</q1:Operator>" +
" <q1:Values>"+
" <q1:Value xsi:type='xsd:string'>"+sourceentity+"</q1:Value>"+
" </q1:Values>"+
" </q1:Condition>" +
" <q1:Condition>" +
" <q1:AttributeName>targetentityname</q1:AttributeName>" +
" <q1:Operator>Equal</q1:Operator>" +
" <q1:Values>"+
" <q1:Value xsi:type='xsd:string'>"+targetentity+"</q1:Value>"+
" </q1:Values>"+
" </q1:Condition>" +
" </q1:Conditions>" +
" </q1:LinkCriteria>" +
" </q1:LinkEntity>" +
" </q1:LinkEntities>" +
" </query>" +
" </RetrieveMultiple>" +
" </soap:Body>" +
"</soap:Envelope>";

var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction"," http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var results = xmlHttpRequest.responseXML.getElementsByTagName('BusinessEntity');

xml = "<?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>"+
"<Retrieve xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+
"<entityName>" + sourceentity + "</entityName>"+
"<id>" + sourceentityid + "</id>"+
"<columnSet xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:ColumnSet'>"+
"<q1:Attributes>";


var mappings = new Array();

for (var i=0; i < results.length; i++)
{
mappings[i] = new Object();
mappings[i].sourceattribute = results[i].selectSingleNode('./q1:sourceattributename').nodeTypedValue;
mappings[i].targetattribute = results[i].selectSingleNode('./q1:targetattributename').nodeTypedValue;

xml += "<q1:Attribute>" + mappings[i].sourceattribute + "</q1:Attribute>";
}


xml += "</q1:Attributes>"+
"</columnSet>"+
"</Retrieve>"+
"</soap:Body>"+
"</soap:Envelope>";

xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Retrieve");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var resultXml = xmlHttpRequest.responseXML;

for(var i = 0; i < mappings.length; i++)
{
try
{
var currentfieldtype = crmForm.all[mappings[i].targetattribute].className;
var sourcefieldvalue = resultXml.selectSingleNode("//q1:" + mappings[i].sourceattribute);
if (sourcefieldvalue != null && mappings[i].targetattribute != invokeattributename)
{
var targetfieldvalue = null;

if (currentfieldtype == "ms-crm-Text" || currentfieldtype == "ms-crm-SelectBox ")
targetfieldvalue = sourcefieldvalue.nodeTypedValue;
else if (currentfieldtype.indexOf("ms-crm-Lookup") == 0)
{
targetfieldvalue = new Array();
targetfieldvalue[0] = new Object();
targetfieldvalue[0].id = sourcefieldvalue.nodeTypedValue;
targetfieldvalue[0].name = sourcefieldvalue.getAttribute("name");
targetfieldvalue[0].type = crmForm.all[mappings[i].sourceattribute].getAttribute("lookuptypes");
}
else if (currentfieldtype == "ms-crm-DateTime")
{
var datetimestring = sourcefieldvalue.nodeTypedValue;
var firstpart = datetimestring.split('T')[0];
var secondpart = datetimestring.split('T')[1].split('+')[0];
var parts = firstpart .split('-');
var parts2 = secondpart.split(':')

targetfieldvalue = new Date();

targetfieldvalue.setYear(parts[0]);
targetfieldvalue.setMonth(parts[1] - 1);
targetfieldvalue.setDate(parts[2]);

targetfieldvalue.setHours(parts2[0]);
targetfieldvalue.setMinutes(parts2[1]);
targetfieldvalue.setSeconds(parts2[2]);
}


crmForm.all[mappings[i].targetattribute].DataValue = targetfieldvalue;
crmForm.all[mappings[i].targetattribute].ForceSubmit = true;
}
}
catch(err) {}
}
}

if (crmForm.all.parentcustomerid.DataValue != null && crmForm.all.parentcustomerid.DataValue[0].typename == "account")
MapFields("account", "contact", crmForm.all.parentcustomerid.DataValue[0].id, "parentcustomerid");

5 comments:

  1. Hi Andriy, thanks for this code. I've used it to get very close to achieving a requirement for 'composite forms' -- a form which displays attribute values from multiple entities.

    However, some attributes don't seem to be mapped. They include: a bit field, an ntext field, int fields, a lookup field (one lookup gets mapped, the other doesn't).

    Do I have to extend your code to get it to work with all data types?

    ReplyDelete
  2. Hello.

    Yes. You are right. To get all required field types you should add some code.

    ReplyDelete
  3. Hi Andriy, I used code inspired by your blog to retrieve attribute values from a parent record, however, I had a problem with date fields. I found that by omitting "targetfieldvalue.setSeconds(parts2[2]);" from my code that it worked for me. Perhaps this is a difference between dateonly and datetime fields?

    Regards, Neil

    ReplyDelete
  4. Hi Andriy, Thanks for the code. It's save a lot of work ;)

    ReplyDelete