I have a json object infoCentros which I use to construct the map, like this:
for ( var i = 0; i < infoCentros.length; i++ ) {
var centro = infoCentros[i];
var lat = centro.cordenadas.lat;
var lon = centro.cordenadas.long;
if (lat && lon) {
c++;
latlon = new google.maps.LatLng(lat, lon);
var moptions = {
position: latlon,
map: $project.gmap
}
moptions.icon = theme_uri + '/images/marker.png';
var marker = new google.maps.Marker(moptions);
$project.mapMarkers.push(marker);
google.maps.event.addListener(marker, 'click', function() {
$project.mapInfoWindow.setContent(
'<div class="sescam-info-window">' +
'<h3>' + centro.nombre + '</h3>' +
'<p>' + centro.lugar + '</h3>' +
'<p>Coordinador</p>' +
'<p>' + centro.coordinador.nombre + '</p>' +
'<p>' + centro.coordinador.email + '</p>' +
'<p>Responsable</p>' +
'<p>' + centro.responsable.nombre + '</p>' +
'<p>' + centro.responsable.email + '</p>'
+ '</div>'
);
$project.mapInfoWindow.open($project.gmap, marker);
});
$project.mapBounds.extend(latlon);
}
}
It seems to work fine, but if I have 5 markers, It doesn't matter which one I click, the infowindow always corresponds to the last item (position and content),
Any idea what am I missing? I thought that passing marker to the addListener would Do the trick..
Problem is with third argument of google.maps.event.addListener. That anonymous function contains the variable from the parent scope which when evaluated with marker click will always take the last value assigned. Look for closure for more details. However you may able to get the desired behaviour by using "bind" feature of javascript function prototype in below manner:
//inside for loop
google.maps.event.addListener(marker, 'click', handleMarkerClick.bind(undefined, marker, i));
//other codes if any...
defining handleClick
function handleMarkerClick(marker, index) {
if (typeof infowindow === 'undefined') {
infowindow = new google.maps.InfoWindow({});
}
var data = infoCentros[index]//helpful data
//create content with dynamic data
infowindow.setContent("dynamic content");
infowindow.open(marker.getMap(), marker);//modify as per your requirement
}
Duncan is right, he means you have to attach the listener outside of your for loop. It's not specific to Google Maps, it's how javascript works. Try searching for "javascript closures for loop" or visit this link - Closures in Javascript for a simple explanation.
Related
I have my javascript in my view and I am trying to load an image from my database into an info window for my google maps but the image does not load.
Javascript code in view:
$.get("#Url.Action("GetMarkers","Donator")", function (json, status) {
//Global infoWindow object that will be reused by all markers
var infoWindow = new google.maps.InfoWindow();
var marker, data;
//Loop through the json data
for (var i = 0; i < json.length; i++) {
data = json[i];
var latLng = new google.maps.LatLng(data.Latitude, data.Longitude)
//Creating marker and putting it on the map
marker = new google.maps.Marker({
position: latLng,
map: map,
title: data.NpoName + ' says Click Me!'
});
// Creating a closure to retain the correct data, notice how I pass the current data in the loop into the closure (marker, data)
(function (marker, data) {
// Attaching a click event to the current marker
google.maps.event.addListener(marker, "click", function (e) {
var imageUrl = '<%=Url.Content(' + data.VerificationIcon + ')%>';
infoWindow.setContent('<h3 class="info-window-title">' + data.NpoName + '</h3>' + '<hr />' + '<p>Npo type: ' + data.NpoType + '</p>' + '<p>' + data.VerificationStatus + '<img src="' + imageUrl + '" alt="verification icon" class="verification-icon" />' + '</p>' + 'Click here to donate!');
infoWindow.open(map, marker);
});
})(marker, data);
};
});
Code in controller:
public JsonResult GetMarkers()
{
//Disable lazy loading
db.Configuration.ProxyCreationEnabled = false;
//Map marker section
var DynamicMapMarkers = db.tblNpo.Include(zz => zz.tblVerification).Include(xx => xx.tblNpo_Type);
//Set map marker values
foreach (var item in DynamicMapMarkers)
{
DonatorUserViewModel MapMarkers = new DonatorUserViewModel();
MapMarkers.NpoID = item.npo_id;
MapMarkers.NpoName = item.npo_name;
MapMarkers.NpoType = item.tblNpo_Type.description;
MapMarkers.VerificationStatus = item.tblVerification.description;
MapMarkers.VerificationIcon = item.tblVerification.verification_icon;
MapMarkers.Longitude = Convert.ToDouble(item.longitude);
MapMarkers.Latitude = Convert.ToDouble(item.latitude);
MapMarkerList.Add(MapMarkers);
}
var json = MapMarkerList;
return Json(json, JsonRequestBehavior.AllowGet);
}
Main focus area from the code above:
var imageUrl = '<%=Url.Content(' + data.VerificationIcon + ')%>';
infoWindow.setContent('<h3 class="info-window-title">' + data.NpoName + '</h3>' + '<hr />' + '<p>Npo type: ' + data.NpoType + '</p>' + '<p>' + data.VerificationStatus + '<img src="' + imageUrl + '" alt="verification icon" class="verification-icon" />' + '</p>' + 'Click here to donate!');
The format of the way the image is stored in the database ~/Images/DbImages/verified.png
I am not sure why the image isn't loading, or how to work around it. Any help would be appreciated.
For now, on a button click I have it so that it takes in data from two textboxes, and uses it to
1) append tweets to a panel, and
2) drop pins on a map.
My next step is to have it so that on the button click, it geodecodes a location, and does the same thing. I feel like my jquery.click function is getting really big, and wanted to know if there was a standard way to "separate" it out to make it look prettier and more readable. Can you typically have javascript functions within a jquery file that are called upon, or what is the way to go?
Here is my current jquery file. As you can see it's very big but what happens is straight forward: searchbutton on click takes some values, and sets up a new map in that location, then I access my web server's information, append it to a panel, and also drop pins on a map.
$(function () {
$("#search-button").click(function() {
// variables for google maps
var LatValue = parseFloat($("#searchLat").val());
var LonValue = parseFloat($("#searchLon").val());
var myLatLng = {lat: LatValue, lng: LonValue};
var map = new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 12,
center: myLatLng
});
$.getJSON(
"http://localhost:3000/tw",
{
geoSearchWord: $("#searchme").val(),
geoSearchWordLat: $("#searchLat").val(),
geoSearchWordLon: $("#searchLon").val(),
geoSearchWordRad: $("#searchRadius").val()
}
).done(function (result) {
$("#fromTweets").empty();
console.log(result);
for (i = 0; i < result.statuses.length; i++) {
//Print out username and status
$("#fromTweets").append('<b>' + "Username: " + '</b>' + result.statuses[i].user.screen_name + '<br/>');
$("#fromTweets").append('<b>' + "Tweet: " + '</b>' + result.statuses[i].text + '<br/>');
$("#fromTweets").append('<b>' + "Created at: " + '</b>' + result.statuses[i].created_at + '<br/>');
if (result.statuses[i].geo !== null) {
//Print out the geolocation
$("#fromTweets").append('<b>' + "GeoLocation: " + '</b>' + "Lat: " + result.statuses[i].geo.coordinates[0] + " Lon: " + result.statuses[i].geo.coordinates[1] + '<br/>'+ '<br/>');
//dropping a new marker on the map for each tweet that has lat/lon values
//Multiplying by i * 0.0005 to space them out in case they are from the same gelocation while still holding
//the integrity of their location.
LatValue = parseFloat(result.statuses[i].geo.coordinates[0] + i*0.0005);
LonValue = parseFloat(result.statuses[i].geo.coordinates[1] + i*0.0005);
myLatLng = {lat: LatValue, lng: LonValue};
var newMarker = new google.maps.Marker({
position: myLatLng,
map: map,
animation: google.maps.Animation.DROP,
});
} else {
$("#fromTweets").append('<b>' + "GeoLocation: " + '</b>' + "Cannot be identified" + '<br/>' + '<br/>')
}
}
});
});
The most simple and obvious thing you can do it so split your code by extracting independent logical blocks to functions:
Just something like this:
var map;
function combineTweetsAjaxRequestData()
{
return {
geoSearchWord: $("#searchme").val(),
geoSearchWordLat: $("#searchLat").val(),
geoSearchWordLon: $("#searchLon").val(),
geoSearchWordRad: $("#searchRadius").val()
};
}
function createGMap()
{
return new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 12,
center: {
lat: parseFloat($("#searchLat").val()),
lng: parseFloat($("#searchLon").val())
}
});
}
function createGMarker(coords)
{
var coordsFixed = {
lat: parseFloat(coords[0] + i * 0.0005),
lng: parseFloat(coords[1] + i * 0.0005)
};
return new google.maps.Marker({
position: coordsFixed,
map: map,
animation: google.maps.Animation.DROP,
});
}
function clearInfo() {
$("#fromTweets").empty();
}
function appendInfo(title, text)
{
$("#fromTweets").append('<b>' + title + ':</b> ' + text + '<br/>');
}
function processTweet(tw)
{
appendInfo('Username', tw.user.screen_name);
appendInfo('Tweet', tw.text);
appendInfo('Created at', tw.created_at);
if (tw.geo !== null) {
var twCoords = tw.geo.coordinates;
appendInfo('GeoLocation', "Lat: " + twCoords[0] + " Lon: " + twCoords[1]);
createGMarker(twCoords);
} else {
appendInfo('GeoLocation', "Cannot be identified")
}
}
function loadTweets() {
$.getJSON(
"http://localhost:3000/tw",
combineTweetsAjaxRequestData()
).done(function (result) {
clearInfo();
console.log(result);
result.statuses.forEach(processTweet);
});
}
$(document).ready(function () {
map = createGMap();
$("#search-button").click(function() {
loadTweets();
});
});
Now, it can be easily read as a text. Your code should be readable and understandable from the first glance. Even better, if a non-developer can read it and understand some basic concepts.
What happens when the page is loaded? We create a Google map control and load tweets
How do we load tweets? We make a AJAX request by combining request data from inputs
What happens when it is loaded? We clear out current information and process every tweet
How do we process a single tweet? We output some basic information. Then, we output geolocation if it is available. Otherwise, we output an error.
Now, if you need to add information to another source, you won't extend or modify your loadTweets method - you will extend or modify appendInfo method, because the logics of information output is encapsulated here.
I'm trying to create a group of markers with info box attached. However, no matter which marker I click, it always opens the info box of the last detailmarker. Anyone know why? Please help.
var stepDisplay = new google.maps.InfoWindow();
function AddDetailMarker(map, itinerary) {
var markers = [];
for (var i = 1; i < itinerary.Legs.length; i++) {
var position = new google.maps.LatLng(itinerary.Legs[i].BusStop.Latitude, itinerary.Legs[i].BusStop.Longitude);
var title = itinerary.Legs[i].BusStop.Code + ": " + itinerary.Legs[i].BusStop.Location + " " + itinerary.Legs[i].BusStop.Street + ", Quận " + itinerary.Legs[i].BusStop.Ward;
var detailmarker = new google.maps.Marker({
position: position,
map: map,
title: title,
icon: "/Content/img/customized_marker/" + "blue" + "/" + "bus-stop2" + ".png"
});
google.maps.event.addListener(detailmarker, 'click', function () {
stepDisplay.setContent(title);
stepDisplay.open(map, detailmarker);
});
markers[i-1] = detailmarker;
}
}
Edit: possible dublicate of Google maps infowindow showing on wrong marker. I've tried all the solutions I found here and none works.
Yes, this is exactly the same problem as the other one you linked to, and the solution for your code is the same - put the code to create each marker into a function, and call that function in your loop:
var stepDisplay = new google.maps.InfoWindow();
function AddDetailMarker(map, itinerary) {
for (var i = 1; i < itinerary.Legs.length; i++) {
addLegMarker( map, itinerary.Legs[i] );
}
}
function addLegMarker( map, leg ) {
var position = new google.maps.LatLng(leg.BusStop.Latitude, leg.BusStop.Longitude);
var title = leg.BusStop.Code + ": " + leg.BusStop.Location + " " + leg.BusStop.Street + ", Quận " + leg.BusStop.Ward;
var detailmarker = new google.maps.Marker({
position: position,
map: map,
title: title,
icon: "/Content/img/customized_marker/" + "blue" + "/" + "bus-stop2" + ".png"
});
google.maps.event.addListener(detailmarker, 'click', function () {
stepDisplay.setContent(title);
stepDisplay.open(map, detailmarker);
});
}
Do you see why that fixes it? The title and detailmarker are now specific to each invocation of addLegMarker(). In the original code, there was only a single copy of each of these variables, shared among all markers.
I have the following code and everytime I click on one of the markers, I get the first infowindow. I have 181 infowindows and 181 markers. Any help appreciated.
var i=0;
$.each(in_locations,function(k,v){
console.log(v);
var marker_latlng = new google.maps.LatLng(v.latitude, v.longitude);
markers[i] = new google.maps.Marker({
position: marker_latlng,
map: map,
title:"here is my title" + v.id
});
infowindows[i] = new google.maps.InfoWindow({
content: "here is some content string" + v.id,
});
google.maps.event.addListener(markers[i], 'click', function() {
alert('number of infowindows: ' + infowindows.length + ' and value is: ' + i);
infowindows[i].open(map,markers[i]);
});
i++;
});
thx
Does the alert tell you that the value is always 181? You need to wrap your handler declaration in a closure to stop it using the external value of i (which will always be 181 once the loop completes)
(function(i){
google.maps.event.addListener(markers[i], 'click', function() {
alert('number of infowindows: ' + infowindows.length + ' and value is: ' + i);
infowindows[i].open(map,markers[i]);
});
}(i));
Edit: Because you're using jQuery's $.each function, which gives each iteration its own scope (because each iteration is calling a function) you can also get around your issue by creating a local variable, say j, which grabs the external value of i.
var j = i;
google.maps.event.addListener(markers[i], 'click', function() {
alert('number of infowindows: ' + infowindows.length + ' and value is: ' + j);
infowindows[j].open(map,markers[j]);
});
(This works because every iteration a new j is created, whereas before you only had 1 i because you declared it outside the iterating function.)
UPDATED
demo: http://jsbin.com/obunap/5
$.each(in_locations, function(i, item){
console.log(i);
var marker_latlng = new google.maps.LatLng(item.latitude, item.longitude);
var markers = new google.maps.Marker({
position: marker_latlng,
map: map,
title:"here is my title" + item.id
});
var infowindows = new google.maps.InfoWindow({
content: "here is some content string" + item.id,
});
google.maps.event.addListener(markers, 'click', function() {
alert('number of infowindows: ' + in_locations.length + ' and value is: ' + i);
infowindows.open(map, markers);
});
});
explanation? you are already inside a loop $.each so you don't need to increment an i++ iterator
I've been fighting with this for a while, so I think it's better to ask the big guys.
I have the following function which I use to create GMarkers with some information
function createMarker(data, html) {
var marker = new GMarker(new GLatLng(data.latlng.y, data.latlng.x));
var html = "Provider: "+ data.name.data + "<br/>" +
"Address: " + data.address.data + "<br/>" +
"Phone: " + data.phone.data + "<br/>" +
'<a href="javascript:zoomit(' + data.latlng.y + ',' + data.latlng.x + ')">Zoom<\/a>';
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(html);
});
return marker;
}
As you can see, I have a link in the info window to zoom the map, and this is the part I'm having problems now. I want to zoom the map to a specific zoom level when the user clicks on that link.
Any Ideas?
I do this on my mapping project site. I have some javascript to do the fixed zoom:
function centerAndZoom (CenterLat, CenterLng)
{
var CenterPoint = new GLatLng (CenterLat, CenterLng);
map.setCenter(CenterPoint);
map.setZoom (9);
}
I handle the onClick event for the image in the info window:
html += '<img height="24" onClick="centerAndZoom('+lat+','+lng+')"
style="cursor: pointer" src="pics/magnify_glass_small.png">'