Archive

Archive for the ‘Bing Map’ Category

Dynamics CRM: How to add a map to an Entity form

November 13th, 2013 No comments

    Let’s say we have an Entity with Latitude and Longitude attributes and we want to display a pushpin at this point on a map when opening the Entity‘s form. Briefly, I offer to add a section to the form (the section is a placeholder for the map) and bind the form’s OnLoad event to a custom JavaScript, which finds the section, adds Bing Map Control to it, gets Latitude and Longitude attributes of a current record and displays the appropriate point on the map.

1. Add JavaScript to Web Resources

Let’s assume we have the custom unmanaged solution named Test Solution that provides with the Bus Stop Entity containing Latitude and Longitude attributes. Open the solution and create a JavaScript (JScript) Web Resource with the code listed below. Read the comments within the code as they explain what happens inside.

//If the dotNetFollower namespace object is not defined, create it
if (typeof (dotNetFollower) == "undefined") {
    dotNetFollower = {};
}

dotNetFollower.BingMapLib = (function () {
    var self = this; // save reference to itself
	
	self.bingKey = "put here your key";
    self.map     = null; // map control   
    self.mapDiv  = null; // div, a placeholder for Map Control   

	// dynamically adds a script to the page
    self.addScript = function(url, callback) {
		var script   = document.createElement('script');
		script.src   = url;
		script.type  = 'text/javascript';
		script.defer = false;
	 
		if (typeof callback != "undefined" && callback != null) {
	 
			// IE only, connect to event, which fires when JavaScript is loaded
			script.onreadystatechange = function() {
				if (this.readyState == 'complete' || this.readyState == 'loaded') {
					this.onreadystatechange = this.onload = null; // prevent duplicate calls
					callback();
				}
			}
	 
			// FireFox and others, connect to event, which fires when JavaScript is loaded
			script.onload = function() {
				this.onreadystatechange = this.onload = null; // prevent duplicate calls
				callback();
			};
		}
	 
		var head = document.getElementsByTagName('head').item(0);
		head.appendChild(script);
	};
	
	// checks if a passed string is Null or Empty
	self.isNullOrEmpty = function (str) {
        return !str || $.trim(str) === ""; // the trim method is provided by jQuery
    };
	
	// checks if Microsoft.Maps.Map is available 
	self.scriptsReady = function () {
        return typeof (Microsoft)          != 'undefined' &&
        	   typeof (Microsoft.Maps)     != 'undefined' && 
        	   typeof (Microsoft.Maps.Map) != 'undefined' && 
        	   typeof (Xrm)                != 'undefined';
    };
	
	// adds a div to the section within the form, the div is supposed to host a Bing Map Control
	self.prepareMapPlaceholder = function() {
		var mapWidth  = '100%';
		var mapHeight = '400px';
		
		// get the section by label Map
		var section = $('table[label="Map"]');
		
		// if section isn't found, that could mean that we deal with 
        // the CRM 2013. So, let's try to find the section using another selector
        if(section.length == 0)
        	section = $(":header:contains('Map')").parents("table");
		
        var td = section.find('td[colspan="2"]:last');		
		
		// add a div
		td.html('<div id="mapPlaceholder" style="width:' + mapWidth + 
                '; height: ' + mapHeight + ';" />');
		self.mapDiv = td.find('#mapPlaceholder')[0];
	};
    
    // creates a Bing Map Control hosted in the passed args.div
    self.loadMap = function (args) {
    	// check if Microsoft.Maps.Map is available 
        if (self.scriptsReady()) {
        
            if (self.map == null) {
                var div = $(args.div);
                
                // create a Bing Map Control
                self.map = new Microsoft.Maps.Map(args.div, {
                    credentials: self.bingKey,
                    showDashboard: false,
                    showMapTypeSelector: false,
                    showScalebar: false,
                    mapTypeId: Microsoft.Maps.MapTypeId.auto,
                    zoom: args.zoom,
                    width: div.parent().width(),
                    height: div.height(),
                    center: new Microsoft.Maps.Location(args.lat, args.lon),
                    enableSearchLogo: false,
                    enableClickableLogo: false
                });
                
                // hook up to resize event to adjust the map's size in time
                $(window).resize(function () {
                    self.map.setOptions({ width: div.parent().width(), height: div.height() });                    
                });                
            }
            
            // call the callback
            args.callback();
        }
        else
        	// in spite of calling the loadMap function right away after 
        	// the Bing Map script is loaded, 
        	// the Microsoft.Maps.Map still may be undefined, 
        	// so wait for a while in this case
            setTimeout(self.loadMap, 1000, args);
    };
    
    // displays a pushpin on the map
    self.showBusStop = function() {
		
	  self.prepareMapPlaceholder();
		
	  // dynamically add the Bing Map script
	  self.addScript("https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&s=1", 
       function() {
		// this code is executed once the Bing Map script has been loaded
		
		// get lat/lon of the current record
		var lat  = Xrm.Page.getAttribute("testpub_latitude").getValue();
		var lon  = Xrm.Page.getAttribute("testpub_longitude").getValue();        
        var zoom = 15;
        
        // if lat or/and lon are null or empty, 
        // use the default location namely the center of the USA
        if(self.isNullOrEmpty(lat) || self.isNullOrEmpty(lon)) {
        	lat  = 39.8282;
        	lon  = -98.5795;
        	zoom = 1;
        }
        
        // create Map Control hosted in the self.mapDiv
		self.loadMap({lat: lat, lon: lon, zoom: zoom, div: self.mapDiv, callback: function() {
			
			// this code is executed once the Microsoft.Maps.Map gets defined
			
			// create a pushpin
			var location = new Microsoft.Maps.Location(lat, lon);
         	var pushpin  = new Microsoft.Maps.Pushpin(location);
            	
         	// add the pushpin to the map
            self.map.entities.push(pushpin);
		}});
	  });
	};
    
    return self;
})();

The showBusStop function is an entry point that will be called on the Entity form’s OnLoad event (I’ll show later how to bind the event to the showBusStop). The showBusStop prepares the section for holding the map (how to add the section to the form will be described later as well) and dynamically includes the Bing Map script into the page by means of the addScript function described in details here – How to add a script-tag dynamically. Once the Bing Map script has been loaded, we get the Latitude and Longitude and initiate Map Control creation by calling the loadMap function. Note that the Bing Map script dynamically includes other scripts, and one of those is responsible for Microsoft.Maps.Map definition. So, the loadMap waits until the Microsoft.Maps.Map gets available by recalling itself through the setTimeout. Once the Map Control has been created, we create a pushpin and add it to the map.

The Map Control will adjust its width to fill the available space whenever the window’s size is changed.

The above script largely uses jQuery, so the jQuery has to be added to Web Resources as well. In my case the Web Resources have been named testpub_insertMap and testpub_jQuery respectively:

Crm Web Resources

2. Add a section to Entity form.

Open the Bus Stop Entity‘s main form for editing and add a One Column section, for example, to the General tab. Open the section’s properties (double click on the section) and set the Label property to Map (the word “Map” is used by the testpub_insertMap script to locate the section in the DOM tree). The picture below illustrates these steps:

Add Section To Entity Form

3. Bind Entity Form’s OnLoad event to the custom JavaScript function

Keep the Bus Stop Entity‘s main form open for editing and click on Form Properties. There first of all we need to make the custom scripts available on the form. Technically it means that our JavaScript Web Resources will be dynamically included into the page being loaded. So, in the Form Libraries section (at the Event tab), add testpub_jQuery and testpub_insertMap to the list. In our case, the order of the scripts is important: the testpub_jQuery must come first, i.e. be above the testpub_insertMap.

The second step is to specify what function from one of the added JavaScript Web Resources should be called on form’s OnLoad event. In the Event Handlers section, add a new event handler by choosing the testpub_insertMap as a Library and setting the dotNetFollower.BingMapLib.showBusStop as a Function. Note that no parameters are required to be passed into the dotNetFollower.BingMapLib.showBusStop. After these two steps you should have something like this:

Add Scripts To Entity Form OnLoad

If you have done everything right, the result view of the Bus Stop Entity‘s form should look like the following:

Map On Entity Form

The sample unmanaged solution you can download here – testsolution_1_0.zip