Wednesday, January 26, 2011

Bing Map Integration with CRM 2011

Integrating the BING Maps in MS CRM 2011:

1) Create an account in Bing map
2) Download the SDK
3) Understand the REST architecture
4) Integrate the MAP using the REST architecture

Understanding the basics of REST architecture:

What is Rest?
Representational State Transfer : REST is a way to access documents and resources using a simple remote software architecture, represented by an API. The Web (World Wide Web) is an application of the REST architecture.

This architecture is defined as follows:
- States and functions of a remote application are considered as resources.
- Each resource is only accessible using a standard address. These are hyper links. Under HTTP these are URIs.
- Resources have a standard interface in which the transactions and data types are precisely defined. XML or JSON for example, or HTML.

RESTful web services:
A RESTful web service (also called a RESTful web API) is a simple web service implemented using HTTP and the principles of REST. It is a collection of resources, with three defined aspects:

1. the base URI for the web service, such as http://example.com/resources/
2. the Internet media type of the data supported by the web service. This is often JSON, XML or YAML but can be any other valid Internet media type.
3. the set of operations supported by the web service using HTTP methods (e.g., POST, GET, PUT or DELETE).

What is JSON?
JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write.
JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.

JSON is built on two structures:

A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.
These are universal data structures. Virtually all modern programming languages support them in one form or another. It makes sense that a data format that is interchangable with programming languages also be based on these structures.


Ah lots of theory!, Lets start in action.Integrating the Map in CRM 2011.

1) Create a HTML resource file like the one below.(the goal is to show the Account (In CRM terms) on the map)



  

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=8" />
    <title>Bing Maps REST with Jquery</title>
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
    <script type="text/javascript" src="ClientGlobalContext.js.aspx"></script>
    <script type="text/javascript">
/**************************************************************************
This Function is for getting an object Id from the CRM
        IMP: Pass record object-type code and unique identifier as parameter 
        option is selected in the CRM.
By doing this you will get the context obj ID from the CRM.
Parse the URL to retrieve the GUID as in the below code.
**************************************************************************/
        var objId;
        function getParameters(values, unescapeValues) {
            var objArr;
            var parameters = new Array();
            var vals = ('?' == values.charAt(0) ? values.substr(1) : values).split("&");
            for (var i in vals) {
                var entityIdArr;
                objArr = vals[i].split("=");
                if (objArr[0] == 'id') {
                    objId = objArr[1].substr(3, 36);
                }
            }
        }

/**************************************************************************
This Function is for constructing the JSON object and 
        retrieve the result from the web service
**************************************************************************/
        var ODATA_ENDPOINT = "/XRMServices/2011/OrganizationData.svc";
        var context = Xrm.Page.context || GetGlobalContext();
        var serverUrl = context.getServerUrl();
        
/**************************************************************************
Constructing the JSON object and query 
**************************************************************************/
        function loadMapfromCRM() {
            getParameters(location.search, true);
            if (objId != null) {
                retrieveAccountRecord(objId);
            }
        }

        function retrieveAccountRecord(Id) {
            var retrieveAccountReq = new XMLHttpRequest();
            retrieveAccountReq.open("GET", ODATA_ENDPOINT + "/AccountSet(guid'" + Id + "')", true);
            retrieveAccountReq.setRequestHeader("Accept", "application/json");
            retrieveAccountReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            retrieveAccountReq.onreadystatechange = function () {
                retrieveAccountReqCallBack(this);
            };
            retrieveAccountReq.send();
        }

        function retrieveAccountReqCallBack(retrieveAccountReq) {
            if (retrieveAccountReq.readyState == 4 /* complete */) {
                if (retrieveAccountReq.status == 200) {
                    //Success
                    var retrievedAccount = JSON.parse(retrieveAccountReq.responseText).d;
                    address = retrievedAccount.Address1_City + "," + retrievedAccount.Address1_StateOrProvince;
                    map.Find(null, address, null, null, 0, 1, false, false, false, false,
                    function (shapeLayer, results, places, moreResults, error) {
                        var place = places[0];
                        var pushpin = new VEPushpin('1', place.LatLong, //latitude, longitude
                                                    'you can place your icon resource url as you see in the webresource', 
                                                    retrievedAccount.Name, //Title
                                                    address  //Notes
                                                    );
//For adding the pushpins
                        map.AddPushpin(pushpin);

//For setting the Zoom level
                        map.SetCenterAndZoom(place.LatLong, 7);

                    });
                }
                else {
                        errorHandler(retrieveAccountReq);
                        alert("retrieveAccountReqCallBack function failure END");
                }
            }
        }
    </script>
</head>
<body>
    <div id="map" style="position:absolute; top:0px; left:0px; width:100%; height:100%;"/>
</body>
</html>
<script type="text/javascript">
    //Load the map
    var map = new VEMap("map");
    map.SetCredentials("YOUR CREDENTIALS<KEY>");
    map.LoadMap();
    
    //Resize the map
    window.onresize = function (event) { map.Resize(document.body.clientHeight); };
    window.onresize(null);
    loadMapfromCRM();
</script>


2) Go to Customizations -> Web Resources -> Add a HTML new web resource and choose the one you created as above

3) Create a tab in one of the entity forms and add the Web Resource and *IMPORTANT* select an option "Pass record object-type code and unique identifier as parameter"

4) Thats all yours! you can test the MAP.

41 comments:

Anonymous said...

Thanks --- cool it worked well

pearse said...

Hi Chaitanya
Can you please elaborate on step 3.this is the scenerio,I want to display all my active contacts on map on the dashboard. (eventually I would like to display all the leads on the map on the dashboard.
can you please help me out . I created a HTML file with all the code. then I added it to the webresources.after that what do I do?
also is it the same procedure for displaying leads/opportunities/accounts in the map on the dashboard. and How do I test it?
THANKS
pfranklin@stueve.com

pearse said...

IN CONTINUATION TO THE PREVIOUS COMMENT ,I COULD GET THE MAP DISPLAYED ON THE DASHBOARD,BUT IT IS AN EMPTY MAP WITH NO ACCOUNTS PLOTTED ON IT. DO i HAVE TO ENTER THE BING MAP KEY? . I tried on line 99 in map.SetCredentials("mybingkey").IS IT RIGHT?.Also it shows 2 errors on my script. error- xrm is undefined in line 38 and error ' VE Map' is undefined
Thanks

Chaitanya... said...

Hey Pearse,

There are couple things you need to keep in mind.

1) As you have indicated you need to register for a developer account to get a key. Once, you have the key you need to insert in the line number 99.

2) While adding the pushpins you can even include the resource image file, which you would have added in the Web Resource.

pearse said...

Thanks Chaitanya. It works fine. Now I am Trying to plot the leads on a map and display it on the dashboard. Thanks Again.
Pearse

Sebastien said...

guys am trying to play around but am also getting the error VEMAP is undefined.

Pearse / Chaitanya how to resolve this.

what do i have to do with the SDK that you say to download.

please help, am not a programmer

thanks
Sebastien

Chaitanya... said...

Hey Sebastien,

Those are the complete steps to follow it technically.

But, you can skip that and directly use the code as is.

Quick Steps:

1) Create a bing Map Account
2) In the code replace the 'KEY' with the actual key that you have received on registration.
3) Create a resource file
4) Create a section in the form
5) Add the resource file in to the section. Keep in mind to fallow last points as discussed in the blog (#3).

Let me know if you have any issues?

Thanks,
Chaitanya...

Anonymous said...

var retrievedAccount = JSON.parse(retrieveAccountReq.responseText).d;
After this statement, if i try to display retrievedAccount.Name,
it is coming as undefined. Does anyone know why?

Chaitanya... said...

Couple of things that I can think of.

1) Make sure your JSON object is not null. If null, you need to download the JSON file and add the contents of the file at the top in the script section.

2) Try alerting other properties of the account like Addess, city etc...

Let me know, if you still have a problem?

Thanks,
Chaitanya...

Anonymous said...

Can u elaborate on the 1st point? what exactly should i do?
And i tried alerting other fields, it still says undefined. For some reason, JSON.parse is not working..
Please help me.. Have to submit this today.

Chaitanya... said...

Please, fallow the link to copy the JSON.JS contents and paste the whole thing with in the script tag.

URL: http://devpro.it/code/149.html

Thanks,
Chaitanya...

Anonymous said...

How do you go about putting the pushpin in? Also, how can you zoom the map into the location more?

Anonymous said...

HI Chaitanya,
I have been trying to work your code on contacts entity but i am only getting black map without any address. so can you guide me where i may have gone wrong here. Thanks

Chaitanya... said...

Hello,

There are couple of places where you need to modify the code.

1) While, building the ODATA query you need to use the right Entity name. Like, right now, you see the "accountSet" in the sample code above. you, got to change this to the entity you are referring to.

2) Second point being, you need to use the right SCHEMA NAME after retrieving result from the ODATA request.

Let me know if you need any further assitence on this?

Thanks,
Chaitanya...

Anonymous said...

HI,
Thanks for the reply, Yes from the beginning i have given my entity name what i have on my form so as well schema name, However still got the same problem as it's not showing those data on map just a blank map.Can you think of anything else??
Thanks

Anonymous said...

Webpage error details


Message: HTML Parsing Error: Unable to modify the parent container element before the child element is closed (KB927917)
Line: 0
Char: 0
Code: 0

Hi Chaitanya.
I am getting the above error on my page on dashboard so it is displaying just blank map i suppose. can you look on this please?

Thanks

Chaitanya... said...

I think this link might be useful for you.

http://stackoverflow.com/questions/301484/problem-with-html-parser-in-ie

Let, me know, if this is some thing useful.

Thanks,
Chaitanya...

Anonymous said...

Hi Thanks, I have sorted out the problem for the above issue but still having the same problem with displaying all the contacts push pins on the map as it still is showing blank map. I have been trying to show contacts on the form for that i am using contact as entity name instead of AccountSet and address1_city and address1_country as the schema name what i have on my contact Form. I entered the developer key as well from my bing map account. Do i need to change anything else?? Thanks

Chaitanya... said...

Ok, Here are couple of check points:

1) Make sure you enter the correct Entity Name like, in your case it is contactSet (You have to use the Set word after the entity name)

2) Schema names are not same as the Name field, meaning, Name is always in lowercase but, usually schema names are Camel Case. In your case, it should be address1_City, address1_County.

Let, me know if this solves your problem.

Thanks,
Chaitanya...

Anonymous said...

HI
I have checked with the different entity and schema name but getting a blank map only/ The main thing is that it does not show any error on page so cant tell you about what;s going wrong. I think i am unable to get the records coz it;s not all showing any alert i have given in the code/

Anonymous said...

Hi one more thing wanted to ask you in relation to the above queries. Does this code work for crm 2011 online or it is for on premise version???
Thanks

Chaitanya... said...

Yes, It works well with the 2011 Online. Having said that, you have a new version released to work on Maps v7.0. That is pretty much similer to this.

In anycase, if you need further help on this, I need to see your code to provide you any insight on the development.

Thanks,
Chaitanya...

Anonymous said...

Hi,

For the characters limitation i cant not post the whole core here so just posted the one in which i have made changes for the entity. Please have a look. Thanks

Anonymous said...

function retrieveAccountRecord(Id) {
var retrieveAccountReq = new XMLHttpRequest();
retrieveAccountReq.open("GET", ODATA_ENDPOINT + "/contactSet(guid'" + Id + "')", true);
retrieveAccountReq.setRequestHeader("Accept", "application/json");
retrieveAccountReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
retrieveAccountReq.onreadystatechange = function () {
retrieveAccountReqCallBack(this);
};
retrieveAccountReq.send();
}

function retrieveAccountReqCallBack(retrieveAccountReq) {
if (retrieveAccountReq.readyState == 4 /* complete */) {
if (retrieveAccountReq.status == 200) {
//Success
var retrievedAccount = JSON.parse(retrieveAccountReq.responseText).d;
address = retrievedAccount.address1_City + "," + retrievedAccount.address1_Country;
alert("address");
map.Find(null, address, null, null, 0, 1, false, false, false, false,
function (shapeLayer, results, places, moreResults, error) {
var place = places[0];
var pushpin = new VEPushpin('1', place.LatLong, //latitude, longitude
'you can place your icon resource url as you see in the webresource',
retrievedAccount.Name, //Title
address //Notes
);

Anonymous said...

Webpage error details

User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; .NET4.0E)
Timestamp: Sat, 1 Oct 2011 14:12:28 UTC


Message: Object expected
Line: 84
Char: 26
Code: 0
URI: https://crm4.dynamics.com/%7B634530750560000000%7D/WebResources/ut_MultipleRecords?id=%7b7C938AEA-69E4-E011-860E-1CC1DE089875%7d&orglcid=1033&orgname=37ecc6d4b2134f52afbb4d81ffec1248&type=2&typename=contact&userlcid=1033

Anonymous said...

Hi Chaitanya,

I am getting the above error recently. Please have a look.

Thanks

Chaitanya... said...

Hello,

I have added my sample code in the contact and it started working.

Fallowing are things I did.

1) Entity Name: ContactSet (In your case it is spelled contactSet)

2) In my code, if you see the line number 72, you need to set your custom icon to show it as a pushpin. for example 'https://XXXXX.crm.dynamics.com//WebResources/expert_company.png' [NOTE: you should copy this url from the web resource, also, replace the XXXXX with your org name].

3) Setting Zoom Level: look at my code, line number 80. I have provided 7 you can replace that with 15 to view a zoomed map.

I was able to get that working.

If you still have nay issues, please send me your email, I will pass you on the web resource file.

Thanks,
Chaitanya...

Chaitanya... said...

Couple of more things to note:

1) I am using IE 9, When I open a contact form with map, It asks me for Show Content dialog at the bottom. I click on it view the map.

2) Name your map resource with out any root folder structure, meaning HTML/contactmap.html (If you name it like this, you need to change ClientGlobalContext.js.aspx to the same notation). I would suggest you name your map file as contactmap.html.

Thanks,
Chaitanya...

Anonymous said...

Hi Chitanya,
Thanks i have applied all the changes you have mentioned above but i presume there is something wrong in my code, coz still i am getting the above stated error of Object expected. Do i have to include any script file also like jquery.min.js and ClientGlobalContext.js.aspx as a webresource ??? It wud be a really great help if you can send your code on the following email id tandel.p86@gmail.com
Thanks

Anonymous said...

Hi Chaitanya,

I have figured out the problem and it;s working fine with the particular contact form. Thanks a million for Helping,It's the code for showing only one contact info on form. It;s not for showing all the contact records. is it??? Thanks again chaitanya

Chaitanya... said...

Hey No problems, I am happy that you got it working.

Yes, the sample that I have shown, is only for one contact. That being aid, we can tune our ODTAA quefry to retrieve all the Account/Contact records, by NOT specifying the GUID of an entity record.Please, refer the sample as in the below code.

retrieveAccountReq.open("GET", ODATA_ENDPOINT + "/AccountSet", true);

Let me know, if you have any questions?

Thanks,
Chaitanya...

Anonymous said...

Hello Chaitanya,

Yes i have made it changes in the line you have specified above and put it on the Dashboard to show the map but i guess there is some problem on displaying on dashboard for all records? Do we need to make changes anywhere else on the code to display on the dashboard??

Thanks

Anonymous said...

Hi, Chaitanya

How do I add fields to the code? EX: Add zipcode, address so i can pull back all the info, also how to plot all accounts on the map?

Chaitanya... said...

Hello,

Here is the solution,

1) To pull back all the attributes:
By default, the retrieved record should contain all the attributes, you need to access them by their SCHEMA NAME(Very important, usually these are in Camel Case).
If you want to select only few fields and do some sort of filtering, fallow the below sample query(Please change the ORGNAME to your organization name and change the ENTITYNAME to the entity you are requesting, example: AccountSet and add the appropriate fields in the select statement).

https://ORGNAME.crm.dynamics.com//XRMServices/2011/OrganizationData.svc/ENTITYNAMESet/$top=1&$filter=statecode/Value eq 0 &$select=zipcode,statuscode, address1_line1,telephone&$orderby=name desc

2) If you do not pass the entity GUID to an ODATA Query, you can retrieve all the accounts. I think, I have already explained in one of the converzation above.

Let me know, if you still need any help on this?

Thanks,
Chaitanya...

Unknown said...

I am getting map correctly but it is always pointed to specific location in United states. I am using exact same code as mentioned in the blog.
WHen i put in Account form City, State as = Dublin and Country=Ireland

Nothing changes in the map even I save the form.

Any idea why i am getting same map?

Thanks in advance

Chaitanya... said...

You can probably have a look at the list on the Geographical coverage.

Bing Maps Geographic Coverage

http://msdn.microsoft.com/en-us/library/dd435699.aspx

Thanks,
Chaitanya...

Unknown said...

Thanks Chaitanya for coming back on my query.

I am in middle of dublin, Ireland, I do not think I should have issue with the coverage.I am using Dynamics CRM 2011.

Also few things to mention:

1. When i use the code in the web resourcde and try to click Ok I get an error about 'VEMap' is undefined.

2. I used address = retrievedAccount.address1_City + "," + retrievedAccount.address1_Country; alert(address);
a

My alert never appears though?

Any help please?

Chaitanya... said...

Ok, there are two ways I look at this issue,

1) The "VEMap" is not getting loaded, as a result the further code is not getting executed and for the same reason you are not getting any alert windows appearing.
2) Do you have the Bing map Key set in the code? also, try to put a debugger and you could basically debug the JavaScript code to identify the real cause for it, I think, I have it in one of my blog on client side debugging.

Thanks,
Chaitanya...

Unknown said...

thanks for coming back.

I have used the key in:
map.SetCredentials("aaaa");

and i generated the bing key using:
Trial / Not-for-profit

Can you point me to the debugger link if you have somewhere in ur blog if possible please?

Originally i remember i just changed following line as i was getting error on Xrm so just added following to it:

var context = Xrm.Page.context || GetGlobalContext();

to

var context = window.parent.Xrm.Page.context || GetGlobalContext();

Chaitanya... said...

Here is a link that you can make use of:

http://chaitanyaprasadtk.blogspot.in/2013/01/javascript-debugging.html

Thanks,
Chaitanya...

Unknown said...

Thanks Chaitanya - i was on some other project and never debugged it. Ok so finally i debugged it and it appears at 'VEMap' is undefined.

I am not sure what to do now :(

I checked all browser scripting and all are enabled.

Any help?