Search code examples
javascriptgoogle-mapsgoogle-maps-api-3

Google Maps API v3: allow default context menu on custom OverlayView


I've got a map with custom overlays (based on https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup).

The custom overlay content includes a link/anchor tag and I would like to allow the user to right click the link and select "Open in new tab", however right clicks are cancelled by the map and I am unable to figure out how to prevent that behaviour.

If you compare the sample of the custom overlay linked above with the default Info Window https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/infowindow-simple you can notice that the custom overlay does NOT show the context menu when right clicking on the "Hello World" text, whereas the Info Window DOES show the context menu. In the dev tools I noticed an event handler on the Info Window that somehow allows the context menu (removing that handler stops the context menu coming up), however as it's in the minified Google maps code I'm not able to make sense of it.

I have tried the following:

google.maps.event.addListener(map, 'rightclick', function (e) {
    var event = e.ya;
    var element = event.target;
    if (element.nodeName === "A") {
        event.stopImmediatePropagation();
        event.stopPropagation();
        return true;
    }
});

Code is executed, but there's still no context menu. Instead it breaks something on the map as the map then moves with the mouse as if I still had the mouse down (looks like I prevented the mouseup handler).

I also tried to set preventMapHitsFrom on the custom overlay (https://developers.google.com/maps/documentation/javascript/reference/overlay-view#OverlayView.preventMapHitsAndGesturesFrom), which made the above not fire any more, but still no context menu.

I was also able to attach an event handler myself (excuse the jQuery):

$(document).on("contextmenu", ".map-popup__link", function (e) {
    e.stopImmediatePropagation();
    return true;
});

But again not sure how to prevent the event being cancelled. I also tried to trigger a new event on the same element, but that just creates a loop (obviously) without solving the problem.

Based on https://stackoverflow.com/a/7414594/1397352 I have modified the Popup.prototype.onAdd function to

Popup.prototype.onAdd = function () {
    this.getPanes().floatPane.appendChild(this.containerDiv);

    this.getPanes().overlayMouseTarget.appendChild(this.containerDiv);

    // set this as locally scoped var so event does not get confused
    var me = this;

    // Add a listener - we'll accept clicks anywhere on this div, but you may want
    // to validate the click i.e. verify it occurred in some portion of your overlay.
    google.maps.event.addDomListener(this.containerDiv, 'contextmenu', function () {
        google.maps.event.trigger(me, 'contextmenu');
    });
};

Breakpoint in event handler gets hit, but again no context menu shows.

Has anyone got this to work with custom overlays?


Solution

  • Thanks to MrUpsidown's comment I was able to find a solution in the (archived) infobox library: https://github.com/googlemaps/v3-utility-library/blob/master/archive/infobox/src/infobox.js#L231

    It looks like I was close in my first attempt, but should have set event.cancelBubble = true;

    Final solution:

    Popup.prototype.onAdd = function () {
        this.getPanes().floatPane.appendChild(this.containerDiv);
    
        // This handler allows right click events on anchor tags within the popup
        var allowAnchorRightClicksHandler = function (e) {
            if (e.target.nodeName === "A") {
                e.cancelBubble = true;
                if (e.stopPropagation) {
                    e.stopPropagation();
                }
            }
        };
        this.contextListener_ = google.maps.event.addDomListener(this.containerDiv, "contextmenu", allowAnchorRightClicksHandler);
    };
    
    Popup.prototype.onRemove = function () {
        if (this.contextListener_) {
            google.maps.event.removeListener(this.contextListener_);
            this.contextListener_ = null;
        }
        if (this.containerDiv.parentElement) {
            this.containerDiv.parentElement.removeChild(this.containerDiv);
        }
    };
    

    see https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup for remainder of Popup code