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.
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 .
<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:
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:
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!