Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Integrating jQuery Context Menu Plugin with Bing Maps AJAX Control V7

5.00/5 (1 vote)
17 Sep 2012CPOL3 min read 18K   226  
Integrating jQuery Context Menu Plugin with Bing Maps AJAX Control V7

Introduction

While working with the V7 Bing Maps AJAX control, I needed to implement a context menu for various elements on the map. Context menus are not part of the default functionality, so I decided to integrate the jQuery Context Menu Plugin instead.  

Background 

This article assumes that you have basic knowledge regarding Bing Map AJAX Control and JavaScript.

Using the code 

I had to make a few modification to the jQuery Context Menu Plugin source code in order to make it work nicely with Bing Maps. They are documented below. 

jquery.contextMenu.css 

The default width of 120px crops menu items that are too long. I changed the width attribute to min-width so the menu items are no longer cropped: 

jquery.ContextMenu.js 

The source code is set up to ignore mouse event propagation on elements that are bound to a Context Menu. This behavior will break the default mouse handling of Bing Maps. So I modified the source code to only ignore right mouse clicks:  

Bing Maps

Now we are ready to use the Context Menu plugin in Bing Maps. In order to enable the context menu on a map element, for instance a Pushpin, you need to be able to find the Pushpin in the DOM. The default API gives you a way to assign custom class names to Pushpins, which provides a way for you to build your jQuery Selector to find the Pushpin. To assign a custom class name to a Pushpin, you use the typeName attribute in PushpinOptions. The following code will assign a class name of mypinclass to the pushpin DOM element when the Pushpin appears on the map.  

JavaScript
function addDefaultPushpin() {
    var pushpin = new Microsoft.Maps.Pushpin(map.getCenter(), {
        typeName: 'mypinclass'
    });
    map.entities.push(pushpin);
}

Also, you can assign multiple class names to the typeName attribute, separated by spaces. You can use this feature to assign unique class names to the Pushpins that can serve as individual IDs, so you can know which Pushpin's context menu was invoked. Alternatively, you can just use single class names that are unique.   

Setting up the Context Menu    

Setting up the menu is easy. First define your menu however you'd like as documented in the jQuery Context Menu Plugin docs .

XML
<ul id="myMenu" class="contextMenu">
    <li class="edit"><a href="#edit">Edit</a></li>
    <li class="copy"><a href="#copy">Copy</a></li>
    <li class="paste"><a href="#paste">Paste</a></li>
</ul>

Binding the Context Menu is tricky mainly because I could not figured out an effect way of getting notified when a map element is inserted into the DOM. Namely, when you insert something into the entities collection on the map, the actual DOM elements appear asynchronously sometime afterwards. I could not figure out a good way to get notified of this, so I took a less elegant approach. I try to find the map element in the DOM. If it is not found, try again some time later, and repeat for a set number of tries.  

function initPushpinContextMenu(selector, numTimesToTry) {
    if (numTimesToTry < 0) {
        return 0;
    }
    if ($(selector).length === 0) {
        setTimeout(function () {
            initPushpinContextMenu(selector, numTimesToTry--);
        }, 1000);
    }
    else {
        $(selector).contextMenu({
            menu: 'myMenu'
        },
			function (action, el, pos) {
			    if (action === "edit") {
			        alert("edit invoked on " + selector);
			    } else if (action === "copy") {
			        alert("copy invoked on " + selector);
			    } else if (action === "paste") {
			        alert("paste invoked on " + selector);
			    } else {
			        alert(
            'Action: ' + action + '\n\n' +
                'Element ID: ' + $(el).attr('id') + '\n\n' +
                    'X: ' + pos.x + ' Y: ' + pos.y + ' (relative to element)\n\n' +
                        'X: ' + pos.docX + '  Y: ' + pos.docY + ' (relative to document)'
        );
			    }
			});
    }
    return 0;
}     

You can then call initPushpinContextMenu() like so:

JavaScript
var selector= "." + "mypinclass";
initPushpinContextMenu(selector, 3); 

Bring it all together 

So, say if we wanted to have 10 randomly generated pushpins that all have Context Menus associated with them, we can do something like the following:

JavaScript
function addPushpins() {
    var limit = 10;
    var bounds = map.getBounds();
    latlon = bounds.getNorthwest();
    var lat = latlon.latitude - bounds.height / 4;
    var lon = latlon.longitude + bounds.width / 4;
    var latDiff = bounds.height / 2;
    var lonDiff = bounds.width / 2;
    var typeName = 'mypinclass';
    for (var i = 0; i < limit; i++) {
        var individualTypeName = typeName + i;
        var pushpin = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(
              lat - (latDiff * Math.random()), lon + (lonDiff * Math.random())), {
            typeName: individualTypeName
        });
        map.entities.push(pushpin);
        var selector = "." + individualTypeName;
        initPushpinContextMenu(selector, 3);
    }
}

Which will look something like this:

This all works very well with the rest of the map in terms of functionality and user interaction, and I find this a lot easier to work with compared to trying to implement the context menu using the native Infobox elements.

This approach should be extendable to other elements on the map, as long as you have a way to select them using jQuery.   

I hope you will find this helpful, and happy coding! 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)