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?
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
Related
I want to capture events in Javascript like link click, input type, button hit/submit etc and send it to an application (recording user actions). Later I will play those user events and do automation testing.
I have a specific case, that I do not know how to capture the user event. When I bring mouse cursor over menu region, a pop up menu shows up. Let's take an example. In dev.lmtools.com site, bring mouse cursor to "Test Environment" tab. You will see smething like image below. In that image, you can see the highlighted menu link "Calculate Endpoints" that user will click.
My job is to record user event when popup menu shows up and as well when user clicks link "Calculate Endpoints". I have explored mouseover, mouseexit, mouseenter, mouseleave event handlers without success. Mouseover generates so many events, so this I want to exclude. Mouseenter fires at page start, when it goes to menu area event does not fire.
I am interseted to know which event I should listen for, what is the unique identifier of element that pops up, identifier of link thats clicked by user, so that I can play afterwards accordingly.
Any help in this regard highly appreciated.
You can try Intersection Observer
The Intersection Observer API provides a way to asynchronously observe
changes in the intersection of a target element with an ancestor
element or with a top-level document's
For example, you can add a IDto the popup container and then, ( Note - I did not ran this code. May have some syntax errors )
// definition
function popupObserver(el, cb) {
let opt = {
root: document.documentElement
}
let observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
cb(entry.intersectionRatio > 0);
});
}, opt);
observer.observe(el);
}
then you use above function like,
popupObserver(document.querySelector("#popup_container_id"), trackPopup);
function trackPopup(isVisible){
let linkEl = null;
if(linkEl) {
linkEl.removeEventListener("click", trackMouseClick)
}
if(isVisible) {
// User hover over the link. capture popup. below code is a dummy code
tracker.track(EVENT.POPUP_HOVER);
linkEl = document.querySelector("#link_id");
linkEl.addEventListener("click", trackMouseClick)
}
}
function trackMouseClick() {
// dummy code to track link click
tracker.track(EVENT.LINK_CLICKED)
}
I am using AmCharts and would like to know how to stop the clickGraphItem event propagating to a clickGraph event.
The reason I want to stop it is this:
When I click on a bubble in an XY chart I use Mustache to render a little form under the graph with info contained in the dataContext of the clicked item.
As there are many bubbles on the plot I would like to be able to highlight the selected item on the bubble chart after it is clicked so as to let the user know what point the info is being edited for.
However if the user clicks in empty space on the graph (the clickGraph event on it's own) I want to fire event.chart.validateData(); so that the clicked bubble goes back to original colors.
The problem I am encountering is that when I click on the item I get first the clickGraphItem event firing but that also triggers the clickGraph event.
I have tried to put event.event.stopPropagation() at the end of the "Render Mustache and change fill of bubble" method but this does nothing.
The function I am using to handle both events is below:
function showPointForm(event) {
var ev = event
if (ev.item && ev.item.dataContext.data != undefined) {
var bullet = ev.item.bulletGraphics.node;
bullet.setAttribute("stroke",colors.info);
bullet.setAttribute("fill",colors.warning);
$(document).find('#form-area').html(
Mustache.render($(document).find('#form-template').html(),{
data : ev.item.dataContext.data
})
)
}else{
$(document).find('#form-area').html('')
event.chart.validateData();
}
ev.event.stopPropagation()
}
var chart = AmCharts.makeChart('div',configObject)
chart.addListener("clickGraphItem",showPointForm);
chart.addListener("clickGraph",showPointForm);
The idea here is that if the event is from an item, it will change the fill of the bullet and the stroke, and then render a form with some data prop from the item. If the event does not have an item and data, the rendered form get whiped and the chart is redrawn to original styles.
The stopping of event propagation does nothing here to stop the clickGraph event being fired
Any ideas on what I am doing wrong here?
Thanks
SOLUTION:
It turns out that what I was attempting to achieve isn't supported through the standard AmCharts API. The clickGraph event will not fire in empty space on a bubble/XY chart. Instead I attached an event in to the click event of the .amcharts-plot-area.
function showPointForm(event) {
var ev = event
if (ev.item && ev.item.dataContext.data != undefined) {
$(document).find('.highlight').each(function(){
$(this).attr('class', 'amcharts-graph-bullet');
})
var bullet = ev.item.bulletGraphics.node;
bullet.setAttribute("class",'highlight amcharts-graph-bullet');
$(document).find('#form-area').html(
Mustache.render($(document).find('#form-template').html(),{
data : ev.item.dataContext.data
})
)
}else{
$(document).find('#form-area').html('')
event.chart.validateData();
}
}
function reset(){
$(document).find('#form-area').html('')
timelineChart.validateData();
}
var chart = AmCharts.makeChart('div',configObject)
chart.addListener("clickGraphItem",showPointForm);
$(document).on("click",'.amcharts-plot-area',reset);
So now when I click on a bubble, all bubbles that were previously styled with the highlight class have that class removed. Then the clicked bubble has it added.
When there is a click event in the amcharts-plot-area however, the reset() function is called which removes all the highlighted classes again.
This solves my issue but as stated below the stopping of event propagation is not possible.
There isn't a way to prevent chart event propagation. The chart event name is passed in the type property, so you can use that to determine which flow you want. It's not pretty, but that's all you can do:
function showPointForm(event) {
if (event.type == "clickGraphItem") {
// logic for clickGraphItem only
}
else {
// logic for clickGraph
}
}
You could also set a custom flag inside the chart when the clickGraphItem is fired to indicate that it was just triggered so you know not to perform any additional logic if it was bubbled, for example:
function clickGraphHandler(event) {
if (event.type == "clickGraphItem") {
event.chart.clickGraphItemFired = true;
console.log('clickGraphItem')
} else {
if (event.chart.clickGraphItemFired) {
event.chart.clickGraphItemFired = false;
console.log('clickGraph - bubbled from clickGraphItem');
} else {
console.log('clickGraph - not bubbled from clickGraphItem')
}
}
}
Demo
I have hooked up a simple long touch function that after 500ms uses the "open" API command to open the context menu. The menu opens. However, on "touchend" the menu disappears. It only stays if I touchmove over the context menu before "touchend". Is there a way to prevent this sort of behaviour? From the source code, only a "touchstart" in a different part of the dom should trigger a close event.
Code is below, in case useful. Not that a delegate of tr is required by my context menu - to explain the targetTr variable use below.
var mobDevice_onLongTouch,
mobDevice_touchTimer,
mobDevice_longPressDuration = 500; //length of time we want the user to touch before we do something
//handle long press on the datatable
var touchArea = document.querySelector("#table");
touchArea.addEventListener("touchstart", touchAreaTouchStart, false);
touchArea.addEventListener("touchend", touchAreaTouchEnd, false);
function touchAreaTouchStart(e) {
var targetTr = $(e.target).closest('tr');
mobDevice_touchTimer = setTimeout(function () { touchArea_onLongTouch(targetTr) }, mobDevice_longPressDuration)
};
function touchAreaTouchEnd(e) {
if (mobDevice_touchTimer) {
clearTimeout(mobDevice_touchTimer) //reset the clock
}
};
function touchArea_onLongTouch(target) {
$('#table').contextmenu('open', target);
};
I solved this. ContextMenu was working fine, but the DOM control I was touching on registered a change event (to highlight a table row) on touchend. So the context menu popped up during touch and hold, then got cleared by a DOM change at touchend.
The solution was to manually add the highlight table row event to touchstart and preventDefault on touchend (when the touch target was inside the table)
I've created a fiddle to reproduce the problem.
https://jsfiddle.net/rvwp47Lz/23/
callback: function (key, option) {
console.log("You clicked the test button", this);
// Need the iframe contents to regain focus so the mouse events get caught
setTimeout(function () {
$iframe[0].contentWindow.focus();
}, 100);
}
Basically, what I want to happen is the mouse move events to be caught after closing the context menu.
I can call focus on the iFrame's body or document but it doesn't seem to have any effect.
After you right click one of the items within the iframe and select an item, the mousemove event on the iframes body is no longer called (you can also notice that the hover CSS effect on the items are no longer working).
Ideas?
After some debugging and playing around with jQuery.contextMenu's code it seems the issue actually comes from the itemClick function. I added comments to the code and will add an issue to their github for a possible fix (unless there's some reason they're disabling default here)
// contextMenu item click
itemClick: function (e) {
var $this = $(this),
data = $this.data(),
opt = data.contextMenu,
root = data.contextMenuRoot,
key = data.contextMenuKey,
callback;
// abort if the key is unknown or disabled or is a menu
if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-submenu, .context-menu-separator, .' + root.classNames.notSelectable)) {
return;
}
// This line is causing the issue since it's preventing the default actions which puts
// mouse events back into place. Chrome must disable mouse move events when the contextmenu event
// gets triggered to improve performance.
//e.preventDefault();
e.stopImmediatePropagation();
Is it possible to make Virtual Earth pushpin infobox display respond from onclick instead of mouseover?
Currently I'm using something like this:
...
var pin = new VEShape(
VEShapeType.Pushpin,
[location]
);
pin.SetCustomIcon(icon_url);
pin.SetTitle(title);
pin.SetDescription(info_window_template);
map.AddShape(pin);
document.getElementById(pin.GetPrimitive().iid).onmouseover = EventHandlerOnMouseOver;
}
var EventHandlerOnMouseOver = function(e) {
// Handle content loading...
}
...
However, if I attempt to change the onmouseover to onclick, VE picks up onclick and disables the infobox entirely.
You can accomplish what describing through the use of events... note that I'm using Virtual Earth 6.2.
The trick is to suppress the onmouseover event and the subscribe to the onclick event. When you have figured out if the user clicked on a shape or not you can call the ShowInfoBox method on the map control to force it to show the info box.
And here is teh codez =)
// Begin by supressing the onmouseover event. Returning true
// from an event handler disables the default Virtual Earth action
map.AttachEvent('onmouseover', function(e) { if(e.elementID) return true; });
// Subscribe to the onclick event and react whenever we get an element id
map.AttachEvent("onclick", function(e) {
// elementID is null when someone clicks on the map and not on a shape
if(e.elementID) {
var shape = map.GetShapeByID(e.elementID);
if(shape) map.ShowInfoBox(shape);
}
});
Note that the infobox will show even if you right click on the shape; to avoid this you would look at the leftMouseButton or rightMouseButton properties on the event object.
References:
VEMap.onclick Event
VEMap.onmouseover Event