Friday, December 30, 2011

SC.Menu dynamic positioning with OpenLayers

In one app I needed to allow the user to CTRL-click on an OpenLayers map to popup a tool menu. Since all the rest of the app is Sproutcore based I also wanted the menu to have the same look&feel.

Sproutcore already has a menu view which is called SC.MenuPane. Unfortunately SC.MenuPane can be positioned only relatively to another SC.View, but what if I want to position the menu exactly where I clicked on the map?

The solution turns out to be pretty simple and requires us to override only one MenuPane method.
The method is positionPane and can be overridden at definition time by adding it to the mixin as done below (gotta love javascript, huh?):


Maps.openLayersController.menuPane: SC.MenuPane.create({
 layout: {width: 120},
 itemHeight: 25,
 items: [
  { title: '_geocode'.loc(), icon: 'http://maps.gstatic.com/favicon.ico', action: "geocode" },
  { title: '_streetview'.loc(), icon: 'icon-streetview-16', action: "streetview" }
 ],
 /** @private
  The ideal position for a picker pane is just below the anchor that
  triggered it + offset of specific preferType. Find that ideal position,
  then call fitPositionToScreen to get final position. If anchor is missing,
  fallback to center.
  */
 positionPane: function(useAnchorCached) {
  useAnchorCached = useAnchorCached && this.get('anchorCached');

  var anchor = useAnchorCached ? this.get('anchorCached') : this.get('anchorElement'),
   layout       = this.get('layout');

  if ( anchor && anchor.x && anchor.y ) {
   this.adjust({ width: layout.width, height: layout.height, left: anchor.x, top: anchor.y });
   // if no anchor view has been set for some reason, just center.
  } else {
   this.adjust({ width: layout.width, height: layout.height, centerX: 0, centerY: 0 });
  }
  this.updateLayout();
  return this;
 }
}),

the code for the OpenLayers control that pops-up the menu is the following:


// click control for geocoding and street view
var clickControl = new OpenLayers.Control.ModClick(
 {
  onClick: function(evt){
   // check that CTRL is pressed while clicking
   if(evt.ctrlKey) {
    Maps.openLayersController.menuPane.popup({x:evt.x, y:evt.y});
   }
  }
 }
);