var num = rez.data.length;
for(var key=0;key<num;key++)
{
var marker = [];
var point = new GLatLng(rez.data[key].latitude, rez.data[key].longitude);
marker[key] = new GMarker(point, {icon: iconS});
GEvent.addListener(marker[key], "click", function()
{
marker[key].openInfoWindowHtml('xxxxxx');
});
map.getMap().addOverlay(marker[key]);
}
I need help here, can anyone tell me why is marker[key] in line marker[key].openInfoWindowHtml('xxxxxx'); undefined? I defined it here: marker[key] = new GMarker(point, {icon: iconS});
Apart from what the other answers indicate, you have another problem.
Your key variable is scoped outside your inner event handler function. What happens is that each time you increment key, you are changing the value that will be used in the event handler.
Effectively, let's say num equals 10. All click event handlers will end up calling the following code:
function() {
marker[10].openInfoWindowHtml('xxxxxx');
}
One way to fix this is to scope the loop contents inside another function and invoke it immediately:
var num = rez.data.length;
var marker = [];
for(var key=0;key<num;key++)
{
var point = new GLatLng(rez.data[key].latitude, rez.data[key].longitude);
marker[key] = new GMarker(point, {icon: iconS});
function(key) {
GEvent.addListener(marker[key], "click", function() {
marker[key].openInfoWindowHtml('xxxxxx');
});
}(key);
map.getMap().addOverlay(marker[key]);
}
Edit: To clarify. What I do is that I declare an anonymous function, which I immediately invoke. Another way to see more clearly what is happening, the
function(key) {
GEvent.addListener(marker[key], "click", function() {
marker[key].openInfoWindowHtml('xxxxxx');
});
}(key);
can be replaced by:
function temp(key) {
GEvent.addListener(marker[key], "click", function() {
marker[key].openInfoWindowHtml('xxxxxx');
});
};
temp(key);
I.e., first declare a temporary function called temp, then invoke it the row after.
You should define the marker array before going into that loop...
var num = rez.data.length;
var marker = [];
for(var key=0;key<num;key++)
{
var point = new GLatLng(rez.data[key].latitude, rez.data[key].longitude);
marker[key] = new GMarker(point, {icon: iconS});
GEvent.addListener(marker[key], "click", function()
{
marker[key].openInfoWindowHtml('xxxxxx');
});
map.getMap().addOverlay(marker[key]);
}
...otherwise it will be reseted every time the loop is run and there will be only one marker in the array at index num - 1.
key is accessed from the closure - all the listener functions will share the same key. After the for loop is over, key should be equal to num, and obviously marker[num] would be undefined. You can verify this with alert(key) in the handler.
You want the specific key used when the listener function is defined. You can do it by creating the listener function in a context where the input is stable, for example like this:
function createListener(marker) {
return function() {
marker.openInfoWindowHtml('xxxxxx');
};
}
var num = rez.data.length;
var markers = [];
for(var key=0;key<num;key++)
{
var point = new GLatLng(rez.data[key].latitude, rez.data[key].longitude);
var marker = new GMarker(point, {icon: iconS});
markers[key] = marker;
GEvent.addListener(marker, "click", createListener(marker));
map.getMap().addOverlay(marker);
}
1: Are you sure new GMarker(point, {icon: iconS}) returned a valid result? You dont seem to check for validity
2: marker[key] is only defined in the base loop. When you call GEvent.addListener you basically create an anonymous function. The marker variable may not be defined in that scope. Either use bind() or declare it globally.
Related
I am trying to write a method that creates Leaflet markers with a function updateRoute which should be called whenever the marker is moved.
My problem is that I can't find the index of the marker that was moved as the wayPointIteration variable is evaluated as the total number of markers. I understand why it doesn't work like that now but I don't know how the method can be executed with knowledge of the iteration of that marker.
function updateWayPointsOnMap() {
let options = {draggable: true};
let wayPointIteration = 1;
getRouteWayPoints().forEach(wayPoint => {
let newMarker = L.marker([wayPoint.lat, wayPoint.lng], options).bindTooltip("Way point " + wayPointIteration).addTo(map)
.on('move', function updateRoute(move) {
getRouteWayPoints()[wayPointIteration - 1] = move.latlng;
updateMap();
});
routeMarkers.push(newMarker);
wayPointIteration ++;
});
}
function pushMarkers() {
locInfo = new google.maps.InfoWindow();
for (var i = 0; i < model.length; i++) {
var loc = model[i].places;
var title = model[i].title;
model[i].marker = new google.maps.Marker({
position: loc,
title: title,
animation: google.maps.Animation.DROP,
map: map,
id: i
});
console.log(model[i].title);
model[i].marker.addListener('click', function() {
console.log('InfoWindow Loop entered');
toggleBounce(this);
populateInfoWindow(this, locInfo);
});
wikiLink(model[i].marker);
}
}
Its not advisable to make functions within a loop so i want to make the function outside the loop.
You shouldn't make multiple functions at all, in point of fact, you should just make it once and then attach it as a listener in the loop. such that:
function handleMarkerClick() {
console.log('InfoWindow Loop entered');
toggleBounce(this);
populateInfoWindow(this, locInfo);
}
model[i].marker.addEventListener('click', handleMarkerClick);
Note that if you need to pass contextual information to that handleMarkerClick function that's only available inside the loop, let's say you want it to know the index (i) for some reason, you can bind it:
model[i].marker.addEventListener('click', handleMarkerClick.bind(model[i].marker, i);
and then the signature of the function would need to be function handleMarkerClick(i, event){}
Everytime I run this loop, each marker in the markers array has it's icon overwritten by the results from let icon = iconLoader.getIcon(data[index][5]);.
leaving each marker having the last loaded icon, instead of the icon loading during each pass of the for loop.
I thought that passing icon into the closure would essentially pass it by value, preventing it from being overwritten outside the scope of the closure, but this doesn't seem to be working for me. What am I missing?
var markers = []
for (var index in data) {
let icon = iconLoader.getIcon(data[index][5]);
var createMarker = (function (i) {
return function () {
var marker = new L.marker([data[index][2], data[index][3]])
.setIcon(i)
markers.push(marker);
}
})(icon);
createMarker();
}
var iconLoader = (function () {
var icon = L.icon({
// options
});
return {
getIcon: function (iconName) {
// do stuff to get icon
return icon;
}
};
}());
JSFiddle
So, as I mentioned in my original comment, JavaScript objects and arrays are always passed by reference unless you explicitly create and pass a copy. Nothing in your code is inherently wrong and is not causing this issue - it is actually an issue with how leaflet is handling the object references internally. The way to avoid this is to do a deep copy on the result from iconLoader.getIcon(). If you are using jQuery, you can do it very simply by using $.extend().
for (var index in data) {
let icon = $.extend(true, {}, iconLoader.getIcon(data[index][2]));
var marker = new L.marker([data[index][0], data[index][1]])
.setIcon(icon);
markers.push(marker);
}
If not, you can look into non-jQuery solutions - it's not ideal, but they're everywhere.
I was typing this up as mhodges was writing his answer. I'll go ahead and post it as it's a different solution that also solved the issue for me.
After looking at mhodges' working demo, I noticed he had set up the iconloader a bit differently than I had. He had the iconloader as an object...
var iconLoader = {
getIcon: function (elem) {
return elem;
}
}
while mine was set up as a closure...
var iconLoader = (function () {
var icon = L.icon({
// options
});
return {
getIcon: function (iconName) {
// do stuff to get icon
return icon;
}
};
}());
I thought that maybe I could try setting it up a bit differently and see if that made a difference, and VOILA!
const proto = {
getIcon (iconName) {
var icon = L.icon({
// options
});
// do stuff to get icon
return icon;
}
};
function factoryIcon() {
return Object.create(proto);
}
and then grabbed the icon with
const iconFactory = factoryIcon();
let icon = iconFactory.getIcon(data[index][5]);
I use the following script to generate this page
function initialize() {
var mapCanvas = document.getElementById('map');
var mapOptions = {center:new google.maps.LatLng(latitudeMid,longitudeMid),zoom:15,mapTypeId:google.maps.MapTypeId.ROADMAP,streetViewControl:false,mapTypeControl:true,scaleControl:true,scaleControlOptions:{position:google.maps.ControlPosition.TOP_RIGHT}};
var map = new google.maps.Map(mapCanvas, mapOptions);
var i;
var insertion;
var previousMarker;
// -------------------------------
//show locations on the map
// -------------------------------
for (i = 0; i < fotoCount; i++) {
var myLatLng=new google.maps.LatLng(Latituden[i], Longituden[i]);
var marker = new StyledMarker({styleIcon:new StyledIcon(StyledIconTypes.MARKER,{color:'00ff00',text:Letters[i]}),position:myLatLng,map:map});
marker.set('zIndex', -i);
insertion='<img src=\"http://www.pdavis.nl/Ams/'.concat(Bestanden[i],'.jpg\"></img>');
insertion=insertion.concat('<table class=width100><tr><td>Bestand: ',Bestanden[i],'</td><td class=pright>Lokatie: ',Latituden[i],' °N., ',Longituden[i],' °E. (',Letters[i],')</td>');
insertion=insertion.concat('<td class=pright>Genomen: ',Datums[i],'</td></tr><td colspan=3>Object: ',Objecten[i],'</td></table>');
google.maps.event.addListener(marker, 'click', function() {
$('#photo').html(insertion);
this.styleIcon.set('color', 'ff0000');
if(previousMarker!=null){previousMarker.styleIcon.set('color', '00ff00')};
previousMarker=this;
});
}
Clicking a marker should do two things: (i) turn the marker red (and any existing red marker green) and (ii) show the appropriate photo with information in the right-hand panel. The first does work, but the second always shows the photo corresponding to the last marker. Using
"alert(insertion);" shows that this is correct for each marker.
You can't do it this way because at the end of the loop, "i" will always be the last index. And of course when you click on a marker, "i" value inside the callback is the last index, so you should always have the last picture displayed.
Just put insertion code inside your click callback isn't enough because of the i value. You didn't bind anything to fix value in your callback so you will have the same problem.
The following solution use the marker object to bind the "i" value, like this you can use it in your callback.
Script tested on your page :).
Adapt it as you want !
function initialize() {
var mapCanvas = document.getElementById('map');
var mapOptions = {center:new google.maps.LatLng(latitudeMid,longitudeMid),zoom:15,mapTypeId:google.maps.MapTypeId.ROADMAP,streetViewControl:false,mapTypeControl:true,scaleControl:true,scaleControlOptions:{position:google.maps.ControlPosition.TOP_RIGHT}};
var map = new google.maps.Map(mapCanvas, mapOptions);
var i;
var previousMarker;
// -------------------------------
//show locations on the map
// -------------------------------
for (i = 0; i < fotoCount; i++) {
var myLatLng=new google.maps.LatLng(Latituden[i], Longituden[i]);
var marker = new StyledMarker({styleIcon:new StyledIcon(StyledIconTypes.MARKER,{color:'00ff00',text:Letters[i]}),position:myLatLng,map:map});
marker.set('zIndex', -i);
marker.myIndex = i;
google.maps.event.addListener(marker, 'click', function() {
var insertion = "";
insertion='<img src=\"http://www.pdavis.nl/Ams/'.concat(Bestanden[this.myIndex],'.jpg\"></img>');
insertion=insertion.concat('<table class=width100><tr><td>Bestand: ',Bestanden[this.myIndex],'</td><td class=pright>Lokatie: ',Latituden[this.myIndex],' °N., ',Longituden[this.myIndex],' °E. (',Letters[this.myIndex],')</td>');
insertion=insertion.concat('<td class=pright>Genomen: ',Datums[this.myIndex],'</td></tr><td colspan=3>Object: ',Objecten[this.myIndex],'</td></table>');
$('#photo').html(insertion);
this.styleIcon.set('color', 'ff0000');
if(previousMarker!=null){previousMarker.styleIcon.set('color', '00ff00')};
previousMarker=this;
});
}
}
insertion should be an array. This way when you iterate, in eacj iteration you are just overwriting the content of insertion. In the end you have last value from image array as insertion.
var insertionArr = [];
...
insertion=insertion.concat('<td class=pright>Genomen: ',Datums[i],'</td></tr><td colspan=3>Object: ',Objecten[i],'</td></table>');
insertionArr[marker] = insertion; // Add it to the array
google.maps.event.addListener(marker, 'click', function() {
$('#photo').html(insertionArr[this]);// get it from the array
...
});
This is not tested code.
I have the following code, and having read this, i understand it wont work because the getJSON call is asynchronous. How do i need to change this so that the MarkerClusterer function gets triggered with a full set of markers? I've tried putting the MarkerClusterer function inside the getJSON call but with no luck...
var mcOptions = {gridSize: 50, maxZoom: 9};
var markers = [];
function parse_json(json) {
if (json.length > 0) {
for (i=0; i<json.length; i++) {
var report = json[i];
var latLng = new google.maps.LatLng(report.latitude, report.longitude);
markers[i] = new google.maps.Marker({
position: latLng,
title: report.name + ' ' + report.surf_size_ft_round,
url: "/place/"+report.slug
});
google.maps.event.addListener(markers[i], 'click', function() {
window.location.href = markers[i].url;
});
markers.push(markers[i]);
}
}
};
$.getJSON('<%= request.fullpath + ".json" %>', function(stream) {
if (stream.length > 0) {
parse_json(stream);
alert(markers[1].title); //sanity check - gives result
}
});
alert(markers[5].title); // sanity check - empty
var mc = new MarkerClusterer(map, markers, mcOptions);
Why not put this code snippet:
mc = new MarkerClusterer(map, markers, mcOptions);
inside the anonymous callback function in your $.getJSON? Just declare var mc; somewhere outside the $.getJSON scope to be able to have access to it elsewhere.
Alternatively, you can fire an event at the end of your parse_json function, listen to that event and then fire up another function that creates your MarkerClusterer object when the event has fired. Check this out: How to trigger event in JavaScript?
EDIT:
Upon inspecting your code a bit more, I can see that you set markers[i] to a new Marker instance and then push onto the markers array that same instance. You probably want to either set markers[i] to a new Marker instance or you want to create a var marker, setting it to a new Marker instance and then pushing on the markers array.
Maybe you need to put it inside the success function you give as an input to $.getJSON?
$.getJSON('<%= request.fullpath + ".json" %>', function(stream) {
if (stream.length > 0) {
parse_json(stream);
alert(markers[1].title); //sanity check - gives result
mc = new MarkerClusterer(map, markers, mcOptions);
}
});
alert(markers[5].title); // sanity check - empty