Bring GoogleMaps InfoWindow to front - google-maps

I have a GoogleMaps APIv3 application in which multiple InfoWindows can be open at any one time. I would like to be able to bring an obscured InfoWindow to the front of all other InfoWindows if any part of it is clicked - similar to the behaviour of windows in MS Windows OS.
I had thought to add an onclick event handler which increases the z-index of the InfoWindow, but the event handler does not appear to be firing.
ZIndex is a global variable that keeps increasing as InfoWindows are clicked - or thats the theory anyway.
Can anyone help ?
Here is my code:-
var ZIndex=1;
var iw = new google.maps.InfoWindow({ content:contentString });
google.maps.event.addListener(iw, 'click', handleInfoWindowClick(iw) );
function handleInfoWindowClick(infoWindow) {
return function() {
infoWindow.setZIndex(ZIndex++);
}
}

there is no click-event for an infoWindow, it's a little bit more difficult.
you'll need to use an element(not a string) as content for the infowindow, because you need a DOMListener instead a listener for the infowindow-object
when domready-fires, you must apply the click-DOMListener to the anchestor of this content-node that defines the infowindow
The following code will do this for you, add this to your page:
google.maps.InfoWindowZ=function(opts){
var GM = google.maps,
GE = GM.event,
iw = new GM.InfoWindow(),
ce;
if(!GM.InfoWindowZZ){
GM.InfoWindowZZ=Number(GM.Marker.MAX_ZINDEX);
}
GE.addListener(iw,'content_changed',function(){
if(typeof this.getContent()=='string'){
var n=document.createElement('div');
n.innerHTML=this.getContent();
this.setContent(n);
return;
}
GE.addListener(this,'domready',
function(){
var _this=this;
_this.setZIndex(++GM.InfoWindowZZ);
if(ce){
GM.event.removeListener(ce);
}
ce=GE.addDomListener(this.getContent().parentNode
.parentNode.parentNode,'click',
function(){
_this.setZIndex(++GM.InfoWindowZZ);
});
})
});
if(opts)iw.setOptions(opts);
return iw;
}
Instead of google.maps.InfoWindow() you must call now google.maps.InfoWindowZ()
It also returns a genuine InfoWindow, but with the mentioned listener applied to it. It also creates the node from the content when needed.
Demo: http://jsfiddle.net/doktormolle/tRwnE/
Updated version for visualRefresh(using mouseover instead of click) http://jsfiddle.net/doktormolle/uuLBb/

Related

Google maps on mobile with gestureHandling = cooperative still fires click event

When on a mobile device, if I want to scroll the page by clicking somewhere on the Google map and scrolling down, I still receive a click event.
I see the "use two fingers to move the map" message, and the page is scrolling as expected but I receive a click event after. And I use this click event to add a marker. So at this point, it's messing the behavior of the whole page.
Here is a simple jsfiddle to reproduce (on mobile of course, or in mobile mode on chrome https://developers.google.com/web/tools/chrome-devtools/device-mode/):
Just scroll down while clicking on the map and an alert with the text "Click Event" will popup.
google.maps.event.addListener(map, 'click', function (evt) {
alert("Click Event");
});
https://jsfiddle.net/83o1my1p/
I believe this problem has already been reported in Google issue tracker. Have a look at the following bug:
https://issuetracker.google.com/issues/64586414
Feel free to add a star in the bug to express your interest and subscribe to notifications. Also, note that the person who filed the bug also found a workaround:
The clicks can be prevent after the scroll, the same way they they are prevented after dragend events when using on a desktop. As I said above, I worked around it myself by handling the mousedown event, and comparing the location of it, to the location of the click event.
I hope this helps!
Here is the work around I used.
var mouseDownPos = null;
google.maps.event.addListener(map, 'mousedown', function(e) { mouseDownPos = e.pixel });
google.maps.event.addListener(map, 'click', function(e) {
var mouseUpPos = e.pixel;
var distance = Math.sqrt(Math.pow(mouseDownPos.x - mouseUpPos.x, 2) + Math.pow(mouseDownPos.y - mouseUpPos.y, 2));
if(distance > 10) return; // Adjust for tolerance
// Do what you need here
});
My solution is to use a var and change it state on touch events. Then use it in the click event:
var dragged = false;
google.maps.event.addListener(map, 'click', function (evt) {
if (!dragged) {
// Do stuff here
}
});
google.maps.event.addDomListener(canvas, 'touchmove', function (event) {
if (event.touches.length == 1) {
dragged = true;
}
});
google.maps.event.addDomListener(canvas, 'touchend', function(event) {
dragged = false;
});

Google Map API V3 — How to prevent mouse click event for a marker when user actually has double clicked

I have a marker on the map to which I want to bind two events:
click
dblclick
I want to do the following:
When user clicks on the marker, map should zoom-in and will show
more detailed map.
I want to bind 'dblclick' event to the same marker so that it will
load some third-party reports in adjacent 'div' element.
In other words, I want it to behave differently when user clicks or dblclicks. But the problem is, when I bind both these event to marker and user 'double clicks' the marker, 'click' handler is getting fired, which I don't want to let it happen.
Is it true that, when user double-clicks, click event is also fired? If so, how to prevent it from triggering 'click' event when user actually double-clicked?
Is there any way so that I can do different things on either click and double-click event of the marker?
It's a known nuance of the api, you need to install a click counter timeout, like this:
function createMap2() {
var infoWindow = new google.maps.InfoWindow();
var map = new google.maps.Map(document.getElementById("map2"), myOptions);
var doubleClicked=false;
var clickEvent;
google.maps.event.addListener(map, 'dblclick', function(event) {
doubleClicked=true;
});
function handleClick() {
if (!doubleClicked) {
infoWindow.setPosition(clickEvent.latLng);
infoWindow.setContent(createInfo(clickEvent));
infoWindow.open(map);
}
}
google.maps.event.addListener(map, 'click', function(event) {
clickEvent = event;
doubleClicked = false;
window.setTimeout(handleClick, 250);
});
}
Above code extracted from http://www.william-map.com/20100506/1/v3click.htm
Check out these links for more info:
https://groups.google.com/forum/?fromgroups=#!topic/google-maps-js-api-v3/YRAvYHngeNk
https://groups.google.com/forum/?fromgroups=#!topic/google-maps-js-api-v3/2MomDiLMEiw
You can use a pre-handler function that separates single from double clicks. In this case, the second click must come within 500 miliseconds of the first one:
//Global vars
var G = google.maps;
var clickTimeOut = null;
G.event.addListener(marker,'click',mClick);
function mClick(mev) {
if (clickTimeOut) {
window.clearTimeout(clickTimeOut);
clickTimeOut = null;
doubleClick(mev);
}
else {
clickTimeOut = window.setTimeout(function(){singleClick(mev)},500);
}
}
function doubleClick(mev) {
// handle double click here
}
function singleClick(mev) {
window.clearTimeout(clckTimeOut);
clickTimeOut = null;
// handle single click here
}
mev is the mouseEvent object that the event handlers receive as parameter.

Do not fire 'zoom_changed' event when calling fitBounds function on map

Is there a way to prevent the zoom_change event from being triggered if it occurs due to fitBounds() ?
I am having an issue where I need to do a search on the server from client when there is a zoom change to map but every time I call fitBounds() it causes zoom_change to trigger which causes the client to do another search on the server. I am only interested in zoom_change done by users and not programmatically using fitBounds.
When you do a fitBounds in your program, set a global flag. When the zoom_changed event fires, if the flag is set, clear it, otherwise send your request off to the server.
After many frustrating hours, here is my solution:
var tiles_listener = google.maps.event.addListenerOnce(map, 'tilesloaded', function() {
var zoom_listener = google.maps.event.addListener(map, 'zoom_changed', function() {
reloadMarkers();
});
});
Note that I am calling addListenerOnce for tilesloaded to ensure that the zoome_changed listener is only added once, after the first fitBounds completes.
It's an old question, but it may be useful to others. I had the same problem, zoom_changed been triggered every time I called fitBounds() when adding several markers to a map. What I did was to add the listener to the map_changed event after the map was completely loaded, like this:
google.maps.event.addListener(map, 'tilesloaded', function() {
google.maps.event.addListener(map, 'zoom_changed', function() {
There is another way to do that by removing that that event. I think it would be much easier and cleaner than adding a global variable.
vm.map.events.bounds_changed = function() {
google.map.event.removeListener(zoom_changed);
}
Another way (forgotten to add that):
vm.map.events.bounds_changed = function() {
google.map.event.addEventListener(zoom_changed,function(mapper,eventName,args){});
}
In my app, I may fire off zoom change events programmatically, either via setZoom(), fitBounds(), or setOptions().
MAN = {};
MAN.globalMap = google.maps.Map(document.getElementById('map'));
I then route all programmatic calls to set the zoom through wrapper functions that flip a flag to false before they go on to set the zoom.
MAN.savedZoom = MAN.globalMap.getZoom();
MAN.saveZoomFlag = true;
// we want it to always be true, unless zoom was
// changed programatically.
MAN.setZoom = function setZoom(zoomLevel) {
console.log("won't save that I'm setting zoomLevel to " + zoomLevel);
this.saveZoomFlag = false;
MAN.globalMap.setZoom(zoomLevel);
};
MAN.fitBounds = function fitBounds(bounds) {
console.log("setting bounds, won't save zoomlevel.");
this.saveZoomFlag = false;
MAN.globalMap.fitBounds(bounds);
};
MAN.setOptions = function setOptions(options) {
console.log("setting options, won't save zoomlevel.");
this.saveZoomFlag = false;
MAN.globalMap.setOptions(options);
};
I then declare a listeners. At first I was declaring only the first, and was puzzled that it wasn't working:
google.maps.event.addListener(
MAN.globalMap,
'zoom_changed',
function zoom_changed_listener() {
console.log(
"zoom changed to " +
MAN.globalMap.getZoom() + "; " +
(MAN.saveZoomFlag ? "saving." : "not saving.")
);
if (MAN.saveZoomFlag) {
console.trace("saving");
MAN.savedZoom = MAN.globalMap.getZoom();
}
MAN.saveZoomFlag = true;
}
);
you may also find the idle event helpful, however, if you're just trying to avoid the initial set. See more about the maps events here: https://developers.google.com/maps/documentation/javascript/events
I suppose this should be simple. Have a variable say
var z = map.getZoom(); //scope this variable appropriately(somewhere before you call the fitbounds()
and then after the map.fitbounds() immediately do
map.setZoom(z); //this automatically changes the zoom level back to the previous level

How to make the infowindow appear depending upon the space it already have and not move the map

I am displaying few locations in google maps. I refresh these locations if a user zoom in/out or drags the map. Now the issue is if i click on the marker of a location then the info window opens up and drags the map if it doesn't have any space to show. This dragging causes my locations to be refreshed and hence my marker vanishes.
I tried disableAutoPan: true but then the info window is not seen only. Is there any way that the info window auto adjust itself. is there any way to sort this out?
Use a global variable to indicate when the refresh should be ignored, for example:
// Global var
var ignoreRefresh = false;
google.maps.addListener(marker,'click', function(){
ignoreRefresh = true;
infoWindow.setContent('Hello');
infoWindow.open(map,marker);
})
function refresh(){
if (ignoreRefresh) {
ignoreRefresh = false;
return;
}
//...
//do your normal refresh of data
//...
}

Open Google Maps InfoWindow based on certain criteria

I want to open a Google Maps InfoWindow based on whether or not one of my buildings is throwing a building alarm. The buildings I have the markers on all have alarm states (on or off), and if they are in alarm state, I am changing the color of the marker to yellow or red, depending on the severity of the alarm. When the alarms are "red" alarms, the marker is animated with the google.maps.Animation.BOUNCE effect.
The bounce effect is sometimes not enough to garner attention (we leave this screen open on a wall, and the data in the $(this).children(".alarm-count") div below changes dynamically due to another script we have running on the site in the background.
I already know how to change the markers based on the alarm state, can I also open an InfoWindow within the same condition? I have tried this:
google.maps.event.addListener(map,'idle',(function(marker,i){
return function(){
infowindow.setContent(
'<div class="infowindow-inner">'+
'<a href="'+bldgGfx[i]+'" onclick="window.open('+bldgGfx[i]+');return false;" target="_blank" title="'+bldgName[i]+' ('+bldgAddr[i]+')">'+
'<h2>'+bldgName[i]+'</h2>'+
'<h4>'+bldgAddr[i]+'</h4>'+
'<p>'+mainMeter[i]+' kW</p>'+
'<p>'+alarmCount[i]+' Alarms</p>'+
'</div>'
);infowindow.open(map,marker);
}
})(marker,i));
but it doesn't seem to be working.
The long and short of it is I need to evaluate one value per marker in my page, and open (or not open) the InfoWindow for each building based on that value.
Here is my code:
$(".building").each(function(i){
bldgNo[i] = $(this).children(".bldg-no").html().slice(1);
bldgName[i] = $(this).children(".bldg-name").html();
bldgAddr[i] = $(this).children(".bldg-address").html();
bldgGfx[i] = $(this).children(".bldg-graphic").html();
mainMeter[i] = $(this).children(".main-meter").html();
alarmCount[i] = $(this).children(".alarm-count").html();
latitude[i] = $(this).children(".latitude").html();
longitude[i] = $(this).children(".longitude").html();
if (alarmCount[i]!="N/A"){alarmCount[i]=alarmCount[i].slice(0,-3);}
if (alarmCount[i]>"0" && alarmCount[i]!="N/A"){
marker=new google.maps.Marker({position:new google.maps.LatLng(latitude[i],longitude[i]),map:map,shadow:shadow,icon:redIcon,title:bldgName[i]+" \n"+bldgAddr[i],optimized:false});marker.setAnimation(google.maps.Animation.BOUNCE);
////
//// THE COMMAND TO OPEN THE INFOWINDOW WILL GO HERE, RIGHT?
////
}
else if ($(this).hasClass("new")||(mainMeter[i]=="N/A")||(!isNumber(mainMeter[i]))) {
marker=new google.maps.Marker({position:new google.maps.LatLng(latitude[i],longitude[i]),map:map,shadow:shadow,icon:yellowIcon,title:bldgName[i]+" \n"+bldgAddr[i],optimized:false});marker.setAnimation(google.maps.Animation.NULL);}
else {
marker=new google.maps.Marker({position:new google.maps.LatLng(latitude[i],longitude[i]),map:map,shadow:shadow,icon:greenIcon,title:bldgName[i]+" \n"+bldgAddr[i],optimized:false});marker.setAnimation(google.maps.Animation.NULL);}
markersArray.push(marker);
google.maps.event.addListener(marker,'click',(function(marker,i){
return function(){
infowindow.setContent(
'<div class="infowindow-inner">'+
'<a href="'+bldgGfx[i]+'" onclick="window.open('+bldgGfx[i]+');return false;" target="_blank" title="'+bldgName[i]+' ('+bldgAddr[i]+')">'+
'<h2>'+bldgName[i]+'</h2>'+
'<h4>'+bldgAddr[i]+'</h4>'+
'<p>'+mainMeter[i]+' kW</p>'+
'<p>'+alarmCount[i]+' Alarms</p>'+
'</div>'
);infowindow.open(map,marker);
}
})(marker,i));
i++;
});
Since many buildings may be "on alert", you'll want a InfoWindow array (in my demo it's a global because I use an inline call); however, the screen may get cluttered very easily. I wrote a Z-Index routine to bring a clicked InfoWindow to front. You might also want to consider MarkerWithLabel or InfoBubble because in my opinion they look better than the vanilla InfoWindow.
Please see the demo
demo side-by-side with code
I'll only copy some of the parts that are very different.
var infowindows = [];
function initialize() {
map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
// MANY LINES SKIPPED
// ...
$(this).children(".longitude").html();
infowindows[i] = new google.maps.InfoWindow({
content:'<div class="infowindow"
onclick="bringToFront('+i+')">'+bldgName[i]+'</div>'});
if (alarmCount[i]>0 && alarmCount[i]!="N/A"){
// marker=new google.maps.Marker({position:new google.maps.LatLng(latitude[i],longitude[i]),map:map,shadow:shadow,icon:redIcon,title:bldgName[i]+" \n"+bldgAddr[i],optimized:false});marker.setAnimation(google.maps.Animation.BOUNCE);
marker = new google.maps.Marker({position:new google.maps.LatLng(latitude[i],longitude[i]),map:map,title:"red"});
infowindows[i].open(map,marker);
//// THE COMMAND TO OPEN THE INFOWINDOW WILL GO HERE, RIGHT?
}
...
google.maps.event.addListener(marker,'click',(function(marker,i){
return function(){
infowindows[i].open(map,marker);
bringToFront(i);
}
})(marker,i));
});
}
function bringToFront(windowIndex) {
console.log(windowIndex);
for (var i = infowindows.length-1, n = 0; i >= n; i--) {
infowindows[i].setZIndex(0);
}
infowindows[windowIndex].setZIndex(1);
}