If you are wanting the completed version of this just to install I will be putting this on codeplex for free download. Or you can download the managed and unmanaged solutions from below.
http://crmmapping.codeplex.com
First thing that is needed is to add a button to the advanced find ribbon. The following XML copied into an exported customisations.xml file will provide a new Map button in the Show group in the MSCRM.AdvancedFind tab.
<RibbonDiffXml>
<CustomActions>
<CustomAction Id="AdvancedFind.OpenMap.CustomAction" Location="Mscrm.AdvancedFind.Groups.Show.Controls._children" Sequence="75">
<CommandUIDefinition>
<Button Id="AdvancedFind.OpenMap.Button.Map" Command="AdvancedFind.OpenMap.Button.Map.Command" LabelText="Map" ToolTipTitle="Map" ToolTipDescription="Open results in map" Image32by32="$webresource:boggle_EarthIcon32" TemplateAlias="o1" Image16by16="$webresource:boggle_EarthIcon16" />
</CommandUIDefinition>
</CustomAction>
</CustomActions>
<Templates>
<RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
</Templates>
<CommandDefinitions>
<CommandDefinition Id="AdvancedFind.OpenMap.Button.Map.Command">
<EnableRules />
<DisplayRules />
<Actions>
<JavaScriptFunction FunctionName="DisplayMap" Library="$webresource:boggle_AdvancedFindSearch.js" />
</Actions>
</CommandDefinition>
</CommandDefinitions>
<RuleDefinitions>
<TabDisplayRules />
<DisplayRules />
<EnableRules />
</RuleDefinitions>
<LocLabels />
</RibbonDiffXml>
The CustomAction element contains the location reference to the advanced find tab show group, as noted by Mscrm.AdvancedFind.Groups.Show.Controls._children.
The Button element references the custom command defined in the CommandDefintions element and specifies the TemplateAlias, without the correct alias defined the button will not display.
The JavaScriptFunction references the method DisplayMap and the library in the webresources called boggle_AdvancedFindSearch.js.
The resulting button looks like this screenshot below:
The next thing that is needed is a way to process the results into a generic array for adding to a map. The following code has been added to a webresource named boggle_AdvancedFindSearch.js.
var resultsArray;
function DisplayMap() {
//Get the fetchXml from the advanced find window.
var fetchXml = advFind.FetchXml;
//Create a new XML doc.
var fetchXmlDoc = new ActiveXObject("Microsoft.XMLDOM");
fetchXmlDoc.async = "false";
fetchXmlDoc.loadXML(fetchXml);
//Clear out the existing attribute nodes, we want all attributes
var attribs = fetchXmlDoc.getElementsByTagName("attribute");
for (var i = 0; i < attribs.length; i++)
attribs[i].parentNode.removeChild(attribs[i]);
//Add the all-attributes element if it isn't there
var existingAllAttribs = fetchXmlDoc.getElementsByTagName("all-attributes");
if (existingAllAttribs == null || existingAllAttribs.length == 0) {
var entityElem = fetchXmlDoc.getElementsByTagName("entity")[0];
var allAttribElem = fetchXmlDoc.createElement("all-attributes");
entityElem.insertBefore(allAttribElem, entityElem.childNodes[0]);
}
//Get the results from the crm service
var results = FetchResultsXml(fetchXmlDoc.xml);
//Process the results from the fetch into an array
resultsArray = GetArrayFromFetchResults(results);
//Open the map webresource
var mapWindow = window.open("/WebResources/boggle_bingMapsControl", "mapWindow", "toolbar=0,menubar=0,directories=0");
}
function GetArrayFromFetchResults(fetchResults) {
//Instantiate a new array
var entityResults = new Array();
//Get all the entity elements
var entityElems = fetchResults.getElementsByTagName("a:Entity");
for (var i = 0; i < entityElems.length; i++) {
//Create a new array object to load the results into
entityResults[i] = new Object();
//Get the field values for the object
entityResults[i].Name = GetFieldValue(entityElems[i], "name");
entityResults[i].Street = GetFieldValue(entityElems[i], "address1_line1");
entityResults[i].City = GetFieldValue(entityElems[i], "address1_city");
entityResults[i].Country = GetFieldValue(entityElems[i], "address1_country");
}
//Return the array
return entityResults;
}
function GetFieldValue(entity, fieldName) {
var vals = entity.getElementsByTagName("a:KeyValuePairOfstringanyType");
//Find the field that matches the name
for (var j = 0; j < vals.length; j++) {
if (vals[j].getElementsByTagName("b:key")[0].firstChild.nodeValue == fieldName) {
if (vals[j].getElementsByTagName("b:value").length > 0)
return vals[j].getElementsByTagName("b:value")[0].firstChild.nodeValue;
else
return "";
}
}
}
function EncodeXml(xml) {
return xml.replace(/\&/g, '&' + 'amp;').replace(/</g, '&' + 'lt;')
.replace(/>/g, '&' + 'gt;').replace(/\'/g, '&' + 'apos;').replace(/\"/g, '&' + 'quot;');
}
function FetchResultsXml(fetchXml) {
//Create the fetch Xml request
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
"<soapenv:Body>" +
"<RetrieveMultiple xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<query i:type=\"a:FetchExpression\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">" +
"<a:Query>" + EncodeXml(fetchXml) + "</a:Query>" +
"</query>" +
"</RetrieveMultiple>" +
"</soapenv:Body>" +
"</soapenv:Envelope>";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/XRMServices/2011/Organization.svc/web", false);
xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
//Send the request to CRM
xmlHttpRequest.send(xml);
//Load the results into the xml var
var resultXml = xmlHttpRequest.responseXML;
return resultXml;
}
The method DisplayMap loads the fetchXml from the advanced find form and removes the attribute elements and adds the all-attributes element to ensure address fields are retrieved. The fetchxml is used for a retrievemultiple request by passing the fetchxml to the FetchResultsXml method. The results are then processed using the GetArrayFromFetchResults method to get the results in an easier to use form to be passed onto the map. The map window is then opened using the window.open method, the bing maps window will look at the var resultsArray to get the results retrieved from the advanced find.
The next part we need is an html webresource for displaying the bing maps with the results, here is the code that loads the required elements for the map.
<HTML xmlns:v = "urn:schemas-microsoft-com:vml"><HEAD><TITLE></TITLE>
<META content="text/html; charset=utf-8" http-equiv=Content-Type>
<SCRIPT type=text/javascript src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&s=1"></SCRIPT>
<SCRIPT src="ClientGlobalContext.js.aspx"></SCRIPT>
<SCRIPT type=text/javascript src="boggle_bingMapsJScript.js"></SCRIPT>
<SCRIPT type=text/javascript src="boggle_AdvancedFindSearch.js"></SCRIPT>
<SCRIPT type=text/javascript src="boggle_bingMapsJScript.js"></SCRIPT>
</HEAD>
<BODY contentEditable=true onload=GetMap();>
<DIV style="POSITION: relative; WIDTH: 100%; HEIGHT: 100%" id=myMap></DIV></BODY></HTML>
The part that displays the result are in the javascript webresource named boggle_bingMapsJScript.js. This library adds pins to the map based on the addresses in the entities and centres on the last pin. The code for the library is below.
//Instantiate the variables
var map = null;
var resultsArray = null;
//If there is a parent window with the resultsArray variable set the var results array
if (window.opener != null && window.opener.resultsArray != null)
resultsArray = window.opener.resultsArray;
function GetMap() {
//Load the map
map = new VEMap('myMap');
map.LoadMap();
//Load the results if there are any
if (resultsArray != null && resultsArray != undefined) {
for (var i = 0; i < resultsArray.length; i++) {
resultsArray[i].id = i;
FindLocation(resultsArray[i]);
}
}
}
function FindLocation(entity) {
if (entity != null && entity != undefined) {
var address = entity.Street + ", " + entity.City + ", " + entity.Country;
//Find the address in bing maps
map.Find(null, address, null, null, 0, 1, false, false, true, false, function (shapeLayer, findresults, places, moreresults, error) {
if (error != null && error != "") {
alert(error);
return;
}
//Create the pin and setup its values
var pin = new VEShape(VEShapeType.Pushpin, places[0].LatLong);
pin.SetTitle(entity.Name);
pin.SetDescription(address);
map.AddShape(pin);
map.SetCenterAndZoom(places[0].LatLong, 10);
});
}
}
Once all this is implemented a page like the screenshot below will have the results displayed on it.
No comments:
Post a Comment