Friday, November 29, 2019

Google Maps Integration with Microsoft Dynamics CRM


As we all know Microsoft Dynamics CRM by default is integrated with Bing Maps. We can add Bing maps on any entity we want. We can enable or disable the feature in the Settings > Administration > System Settings area.


This post shows you how to integrate Google maps API with Dynamics CRM. Here we will integrate Google maps in Account entity form using HTML web resource and we create a Dashboard where we will show all the accounts of CRM on Google Map.
Before you start, you need Google Map API key to proceed further.
Getting an API key is not complicated and just requires a couple of minutes of your time. In the process, you will need to create a billing account or use credentials of already existing one, which will be used for payment, in case you exceed your limits. This link will guide you on how to get Google Maps API key in 3 easy steps. 
I assume you have basic Dynamics CRM and JavaScript knowledge to perform below guided steps.
Step 1: In order to integrate Google Maps, first we create a HTML web resource.

Code snippets:
<html>
 <head>
  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initialize" defer="" async=""></script>
  <script>
   function initialize() {
    var map_canvas = document.getElementById('map_canvas');
    var map_options = {
      center: new google.maps.LatLng(54.737686, 2.126855),
      zoom: 4,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    }
 
    var map = new google.maps.Map(map_canvas, map_options)
    var geocoder = new google.maps.Geocoder();
    var address = window.parent.Xrm.Page.data.entity.attributes.get('address1_composite').getValue();
    var googleLocation = window.parent.Xrm.Page.data.entity.attributes.get('new_googlelocation').getValue();
    var addressChanged = window.parent.Xrm.Page.data.entity.attributes.get('address1_composite').getIsDirty() ;
    var name = window.parent.Xrm.Page.data.entity.attributes.get('name').getValue();
    var contentString = '<div><strong>' + name + '</strong><br>' + address + '<br></div>';
    geocoder.geocode( { 'address': address}, function(results, status) {
      if (status == google.maps.GeocoderStatus.OK) {
        if(googleLocation == null){
          var latitude = results[0].geometry.location.lat();
          var longitude = results[0].geometry.location.lng();
          var loc = latitude + ',' + longitude;
          window.parent.Xrm.Page.getAttribute("new_googlelocation").setValue(loc);
          window.parent.Xrm.Page.data.entity.save();
        }
        map.setCenter(results[0].geometry.location);
        map.setZoom(14);
        var marker = new google.maps.Marker({
          map: map,
          position: results[0].geometry.location,
          title: address
        });
        
  var infowindow = new google.maps.InfoWindow({
          content:contentString
        });
        google.maps.event.addListener(marker, 'click', function() {
          infowindow.open(map,marker);
        });
      } else {
        //alert("Geocode was not successful for the following reason: " + status);
      }
    });
   }
   google.maps.event.addDomListener(window, 'load', initialize);
  </script>
  <meta charset="utf-8"><meta><meta><meta>
 </head>
 <body style="overflow-wrap: break-word;" dir="LTR" onfocusout="parent.setEmailRange();" lang="en-US">
   <div id="map_canvas" style="width: 100%; height: 100%;"></div>
 </body>
</html>

Understanding the code:

In the code below, the script loads the API from the specified URL.the callback parameter executes the initialize function after the API loads. The async attribute allow the browser to continue rendering the rest of your page while the API loads. The key parameter contains your API key.

<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initialize" defer="" async=""></script>

The code contains in the initialize function that initializes and adds the map when the web page loads. The code below constructs a new Google maps object, and adds properties to the map including the center and zoom level. As I am working for one of the client from United Kingdom, so I choose latitude and longitude location of UK.

var map_options = {
      center: new google.maps.LatLng(54.737686, 2.126855),
      zoom: 4,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    }
    var map = new google.maps.Map(map_canvas, map_options)

Next, we will retrieve composite address of an account record and then using Geocoding we will find out geographic coordinates. Geocoding is the process of converting addresses into geographic coordinates, which can be used to place markers on a map, or position the map. 

Using Geocoding we will get the latitude and longitude for address of an Account record, we will save this details in another custom field called "new_googlelocation"  which is of single line of text. This field value will be useful for us to put marker on Dashboard's map.

var googleLocation = window.parent.Xrm.Page.data.entity.attributes.get('new_googlelocation').getValue();
var addressChanged = window.parent.Xrm.Page.data.entity.attributes.get('address1_composite').getIsDirty();
if(googleLocation == null){
  var latitude = results[0].geometry.location.lat();
  var longitude = results[0].geometry.location.lng();
  var loc = latitude + ',' + longitude;
  window.parent.Xrm.Page.getAttribute("new_googlelocation").setValue(loc);
  window.parent.Xrm.Page.data.entity.save();
}


And using an InfoWindow method we will show location on google map. An InfoWindow displays content in a popup window above the map, at a given location.

geocoder.geocode( { 'address': address}, function(results, status) {
  if (status == google.maps.GeocoderStatus.OK) {
    if(googleLocation == null){
      var latitude = results[0].geometry.location.lat();
      var longitude = results[0].geometry.location.lng();
      var loc = latitude + ',' + longitude;
      window.parent.Xrm.Page.getAttribute("new_googlelocation").setValue(loc);
      window.parent.Xrm.Page.data.entity.save();
    }
    map.setCenter(results[0].geometry.location);
    map.setZoom(14);
    var marker = new google.maps.Marker({
      map: map,
      position: results[0].geometry.location,
      title: address
    });
        
 var infowindow = new google.maps.InfoWindow({
      content:contentString
    });
    google.maps.event.addListener(marker, 'click', function() {
      infowindow.open(map,marker);
    });
  } else {
    //alert("Geocode was not successful for the following reason: " + status);
  }
});

Step 2: Next, we need to add google map on Account form.



Step 3: We have integrated google maps with our Dynamics environment, next we need to show all the accounts on Dashboard using Google Map. For this, we will create another html web-resource for dashboard.


Code snippets:

<html>
  <head>
 <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY;callback=initMap" defer="" async=""></script> <script type="text/javascript">
   function initMap() {
     var map;
  var bounds = new google.maps.LatLngBounds();
  var mapOptions = {
    center: new google.maps.LatLng(54.5260, 15.2551),
    zoom: 4,
         mapTypeId: google.maps.MapTypeId.ROADMAP
  }; 
        
  // Display a google map on the web page
  map = new google.maps.Map(document.getElementById("mapCanvas"), mapOptions);
  map.setTilt(50);
  
  //Show multiple location markers on the google map  
  var infoPopupWindow = new google.maps.InfoWindow(), marker, i;
   
  var req = new XMLHttpRequest();
    
  req.open("GET", parent.Xrm.Page.context.getClientUrl() + "/api/data/v9.1/accounts?$select=name,address1_composite,new_googlelocation&$filter=_ownerid_value eq 106E5F09-48CC-4BF0-BF21-FD7C074991F8", false);
  req.setRequestHeader("OData-MaxVersion", "4.0");
  req.setRequestHeader("OData-Version", "4.0");
  req.setRequestHeader("Accept", "application/json");
  req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
  req.setRequestHeader("Prefer", "odata.include-annotations=\"OData.Community.Display.V1.FormattedValue\"");
  req.setRequestHeader("Prefer", "odata.maxpagesize=10");
  req.onreadystatechange = function () {
    if (this.readyState === 4) {
      req.onreadystatechange = null;
   if (this.status === 200) {
     var results = JSON.parse(this.response);
     for (var i = 0; i < results.value.length; i++) {
       var address = "";
    var name = "";
    var contentString ="";
    var location = "";
    var latitude = "";
    var longitude = "";
    address = results.value[i]["address1_composite"];
    name = results.value[i]["name"];
    location = results.value[i]["new_googlelocation"];
    latitude = location.substring(0, location.indexOf(",")).trim();
    longitude = location.substring(location.indexOf(",")+1, location.len).trim();
    if(address != null && name != null){
             contentString = '<div><strong>' + name + '</strong><br>' + address + '<br></div>';
    }
       
    var position = new google.maps.LatLng(latitude, longitude);
    bounds.extend(position);
    marker = new google.maps.Marker({
      position: position,
      map: map,
      title: name
    });
        
    //Attach click event to the marker.
    (function (marker, contentString) {
      google.maps.event.addListener(marker, "click", function (e) {
        //Wrap the content inside an HTML DIV in order to set height and width of InfoWindow.
        infoPopupWindow.setContent(contentString);
     infoPopupWindow.open(map, marker);
      });
    })(marker, contentString);
        
    //Fitting Automatically center the screen
    map.fitBounds(bounds);      
        }
     // Customize google map zoom level once fitBounds run
     var boundsListener = google.maps.event.addListener((map), '', function(event) {
       this.setZoom(4);
       google.maps.event.removeListener(boundsListener);
     });
   }       
   else {
     alert(this.statusText);
   }
    }
  };
  req.send(); 
   }
   google.maps.event.addDomListener(window, 'load', initMap); 
     
 </script>
 <meta><meta>
  </head>
  <body style="overflow-wrap: break-word;" onfocusout="parent.setEmailRange();">
 <div id="mapCanvas" style="width: 100%; height: 100%;"></div> 
  </body>
</html>


Understanding the code:

We will retrieve all the active account records from organization and using for loop we will mark the geographic location on the map and attach click event to each marker InfoWindow, so that on click of each marker on map it will show Account Name and Address of an account in InfoWindow.

Step 4: Final step, we will create dashboard and add HTML web-resource created for Dashboard.



Alright, we have completed all the customization, its time to see changes on Dashboard.


Hope it helps someone, Cheers !! Happy Learning..

Thursday, November 28, 2019

Download Dynamics 365 version 9.x Software Development Kit (SDK)

From Dynamics 365 version 9.x, Microsoft will no longer provide downloads for the SDK. This post provides information about where you can download the developer tools, assemblies and code samples that are shipped as part of the Software Development Kit (SDK) for Microsoft Dynamics 365 version 9.x.

Below are options available to get the SDK for version 9.x.

  • To download complete package of developer tools, Download
This zip file contains Configuration Migration Tool, Core Tools, Package Deployment, and Plugin Registration.

NOTE: After you download the zip file, you must right-click on zip and go to properties and unblock the files before you extract it.

  • Using PowerShell
You can download tools used in development from NuGet using the powershell script found on
 link.
Hope it helps someone. Cheers!!