Inline function and global variable issue in Javascript - javascript

I have some code here : http://bitbucket.org/natim/lo53_tp1/src/tip/part3/camions/medias/js/tracking.js
That I use to draw some information about trucks direction.
The problem come from a function defined in a for loop like this one :
...
for(i = 0; i < nb_trucks; i++)
{
...
contentString = '<div id="content">'+ trucks[i]['name'] + '</div>';
current_window = new google.maps.InfoWindow({
content: contentString
});
infosWindow.push(current_window);
current_marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(trucks[i]['end']['lat'], trucks[i]['end']['lon']),
draggable: false,
title: trucks[i]['name']
});
markers.push(current_marker);
google.maps.event.addListener(current_marker, 'click', function() {
current_window.open(map, current_marker);
});
}
In this code, you can see the last block
google.maps.event.addListener(current_marker, 'click', function() {
current_window.open(map, current_marker);
});
And my problem is that current_marker in the addListener parameters is different from the one inside the function.
The current_window and the current_marker inside the function is overide at each loop turn.
How can I get it right ?
Thanks

Wrap it in a closure (just this little section, no other changes) so you get the variable you want, like this:
(function(marker) { //a copy is passed, accessible as marker inside this function
google.maps.event.addListener(marker, 'click', function() {
current_window.open(map, marker);
});
})(current_marker); //pass in the current value
This doesn't reference the same variable that's changing every loop, a copy of it is passed into the closure, so each time you run this it gets a copy of what current_marker is that that time passed in. If you're more curious about this, there are some great answers explaining closures in this question.

Related

Iterating over an array and adding google maps event to each?

so I have a project that's working with Google Maps, using KnockoutJS. I define the markers like so:
for (i = 0; i < markerData.length; i++) {
// Grab marker data so we only have to call it once
var m = markerData[i];
// Define everything
var position = new google.maps.LatLng(m.location.lat, m.location.lng);
var title = m.title;
// etc etc
// Push all this info to the google maps marker
var marker = new google.maps.Marker({
map: map,
position: position,
title: title
// etc with rest
});
And then in the same for loop, I try to define an info window, simplified for reading:
google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(this.title + '<br>' + this.address + '<br><a target="_blank" href="' + this.directions + '">Get Directions</a>');
infoWindow.open(map, this);
map.setCenter(this);
});
While it works exactly as I like it, linters are saying not to make a function inside the for loop. With that being said - is there a way I can move this outside the for loop and still get it to work effectively? Everything I've tried thus far has essentially turned into another for loop in one way or another, which still fires up the linter.
I feel like I'm missing something blatantly apparent here. Any thoughts or ideas to how I can properly apply this?
As a side note - all markers are in the KO vm viewModel.gMarkers(). Greatly appreciate any help, thanks!
Specifically the linter is complaining about the anonymous function google.maps.event.addListener(marker, 'click', function() {}; within the for loop. You could try putting it into its own function and calling it within the loop with the marker passed in.
var m, position, title, marker;
for (i = 0; i < markerData.length; i++) {
// Grab marker data so we only have to call it once
m = markerData[i];
// Define everything
position = new google.maps.LatLng(m.location.lat, m.location.lng);
title = m.title;
// etc etc
// Push all this info to the google maps marker
marker = new google.maps.Marker({
map: map,
position: position,
title: title
// etc with rest
});
doThis(marker);
}
function doThis(marker) {
google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(this.title + '<br>' + this.address + '<br><a
target="_blank" href="' + this.directions + '">Get Directions</a>');
infoWindow.open(map, this); // Not sure where map is coming from
map.setCenter(this);
});
}

text input to a global infowindow

This question extends an answer here that uses domready. The extension is an attempt to include a second infowindow with a slightly different name: inwindow vs infowindow. The first question is, given the answerer's first "rule" -- "Create only one instance of the infowindow object and use setContent() method to modify its content." -- can there be a second infowindow object?
If so, then what is wrong with my attempt at creating inwindow as shown below? inwindow appears when the map is clicked, but clicking its "Submit" button does not seem to do anything.
A working JSFiddle.com version is here (at least it shows a map).
Some global vars and objects are next.
var map
var infowindow = new google.maps.InfoWindow();
var inwindow = new google.maps.InfoWindow();
var markers = [];
var counter = 0;
initialize() is excerpted next.
google.maps.event.addListener(map, 'click', function (event) {
addMarker(event.latLng);
});
google.maps.event.addListener(inwindow, 'domready', function () {
var button = document.getElementById('inputButton');
var input = document.getElementById('nameinput').value;
button.onsubmit = function() {
marker.title = input;
inwindow.close();
};
});
google.maps.event.addListener(infowindow, 'domready', function () {
var button = document.getElementById('deleteButton');
var id = parseInt(button.getAttribute('data-id'));
button.onclick = function() {
deleteMarker(id);
};
});
}
function addMarker(location) {
counter++;
var inputForm = 'Name: <input type="text" id="nameinput" size="31" maxlength="31" tabindex="1"/>' + '<input type="button" id="inputButton" value="Submit">';
var marker = new google.maps.Marker({
position: location,
map: map,
id: counter
});
inwindow.setContent(inputForm);
inwindow.open(map, marker);
markers.push(marker);
var deleteButton = '<button id="deleteButton" data-id="' + counter + '">Delete</button>';
google.maps.event.addListener(marker, 'rightclick', function () {
infowindow.setContent(deleteButton);
infowindow.open(map, marker);
});
}
function deleteMarker(markerId) {
for (var i=0; i<markers.length; i++) {
if (markers[i].id === markerId) {
markers[i].setMap(null);
}
}
}
You can have as many infowindows as you want. The rules I gave you there were more like hints (hence the italic font). You can always adapt it to your needs.
Now you have a few issues in your code:
onsubmit event is triggered when the submit button in a form is clicked. The action should be on the form, not on the button. But you don't have a form here. You can use onclick event instead (like for the delete button).
You get the input value when the infowindow is ready (domready) not after the user has filled the field. Therefore it will always be empty.
You set marker.title = xxx but marker is not available here, plus, you should use the setTitle() method to change a marker title.
Why didn't you use the same technique that I (and you) used for the delete button action? (using the Id, etc.) I suggest that you try to understand what happens there and adapt it to the part where you set the marker title.
If you are still stuck, let me know and I will explain further!
Edit:
JSFiddle demo
Note that there is no tabindex defined on the inputs and buttons, and that I used .focus() to set the focus on the input or button when the infowindow opens.
Hope this helps.

Trying to refactor my listener callback for google maps in backbone.js app

I'm trying to refactor a google.maps.event.addListener call's anonymous function. Here is what I originally have, which works perfectly fine. Note: The initialize function is run through a loop in some other view, which creates these Truck views. Remember, this code works perfectly and manages to loop through all my markers and set the event listener accordingly.
FoodTruckFinder.Views.TruckView = Backbone.View.extend({
initialize: function(options) {
var marker = new google.maps.Marker({
map: options['map'],
animation: google.maps.Animation.DROP,
optimized: false, //stops marker from flashing
position: options['foodPos']//LatLng
});
var infoWindow = options['infoWindow'];
var self = this;
google.maps.event.addListener(marker, 'mouseover', function() {
// bounce once
marker.setAnimation(google.maps.Animation.BOUNCE);
marker.setAnimation(null);
var content = '<div><strong>'+this.model.get('applicant')+
'</strong><p>'+this.model.get('address')+
'</p><p>'+this.model.get('fooditems')+'</p></div>';
infoWindow.setContent(content);
infoWindow.open(this.map, marker);
});
}
})
This is the refactored code, which doesn't seem to work. My goal was to place the anonymous function into some other chunk of code because that would make it more readable.
FoodTruckFinder.Views.TruckView = Backbone.View.extend({
initialize: function(options) {
this.marker = new google.maps.Marker({
map: options['map'],
animation: google.maps.Animation.DROP,
optimized: false, //stops marker from flashing
position: options['foodPos']//LatLng
});
this.infoWindow = options['infoWindow'];
var populateMarkers = this.populateMarkers.bind(this);
google.maps.event.addListener(marker, 'mouseover', populateMarkers);
},
populateMarkers: function() {
this.marker.setAnimation(google.maps.Animation.BOUNCE);
this.marker.setAnimation(null);
var content = '<div><strong>'+this.model.get('applicant')+
'</strong><p>'+this.model.get('address')+
'</p><p>'+this.model.get('fooditems')+'</p></div>';
this.infoWindow.setContent(content);
this.infoWindow.open(this.map, marker);
}
})
This doesn't work. What happens is only one marker ends up showing up on the map (even though I should have many) and the callback doesn't do what it is supposed to with the infoWindow. In short, it doesn't work.
I was wondering if someone more experienced could see a big flaw in why this doesn't work.
I am not sure if that is your problem, but you are not using the scope correctly.
You are assigning var self = this but never use it in your anon function.
var content = '<div><strong>' + self.model.get('applicant') +
'</strong><p>' + self.model.get('address') +
'</p><p>' + self.model.get('fooditems')+'</p></div>';
Also, consider using a template engine to prevent XSS, and to gain more maintainability:
var tempalte = _.template([
'<div>',
'<strong><%- applicant %></strong>',
'<p><%- address %></p>',
'<strong><%- fooditems %></strong>',
'</div>'
].join(''));
var content = template(self.model.toJSON());

google maps multiple markers clickevent

Im looping and placing some markers, but when i click a marker, they all respond with the same value
Here is my code
for(a=0; a < prod.length; a++){
// we add marker to map
var myLatlng = new google.maps.LatLng(prod[a]['lat'],prod[a]['lon']);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title: prod[a]['name']+" \n"+prod[a]['description'],
icon: image
});
google.maps.event.addListener(marker, "click", function() {
show_details(a);
});
}
function show_details, a always has the same value
I have looked at the other answers here but that didnt solve my problem.
Tipical problem in async programming/scripting. The a variable passing, when the click event runs, so , the value of that is what is after the for loop finishes.
You should create an inner function scope, and save the value of a in a variable, what lives only in that scope.
The solution:
(function(z){
google.maps.event.addListener(marker, "click", function() {
show_details(z);
});
})(a);
The a variable lives outside the callback function too. So if you modify the value of a ( or the for loop modify that) and when the event handler is called, it see the modified a.
Help link: http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/ .

Issue trying to set google maps info window content with

So i have several markers appearing from an array, but i'm having a lot of difficulty setting the content of the InfoWindows that appear
I put the titles in which works great, but when I try to set the content, it doesn't set the content, but still shows the title.
What am I doing wrong?
for (index = 0; index < data.length; ++index) {
locationName = data[index].LocationName;
locationID = data[index].LocationID;
locationX = data[index].XCord;
locationY = data[index].YCord;
var infoContent = '<div><h3>' + data[index].LocationName + '</h3></div><div>' + data[index].LocationID + '</div>';
var mapLocation = new google.maps.LatLng(locationX,locationY);
var marker = new google.maps.Marker({ // Set the marker
position: mapLocation, // Position marker to coordinates
icon: image, //use our image as the marker
map: map, // assign the marker to our map variable
title: data[index].LocationName,
content: infoContent
//draggable:true //Have a guess what this does
});
// Allow each marker to have an info window
google.maps.event.addListener(marker, 'click', (function (marker, index) {
return function () {
infoWindow.setContent(infoContent);
infoWindow.setContent(this.title);
infoWindow.open(map, this);
}
})(marker, index));
var infoWindow = new google.maps.InfoWindow({
content: infoContent
}), marker, index;
}
You are calling setContent twice:
infoWindow.setContent(infoContent);
infoWindow.setContent(this.title);
I guess the last one should be removed.
Edit:
To get the correct infoWindow content, try moving the creation of the eventListener to a function:
// Allow each marker to have an info window
updateInfoWindowOnMarkerClick(marker, infoContent);
//Somewhere after the loop
function updateInfoWindowOnMarkerClick(marker, infoContent){
google.maps.event.addListener(marker, "click", function () {
infoWindow.setContent(infoContent);
infoWindow.open(map, this);
});
}
Have a look at JavaScript closure inside loops – simple practical example for more information about function scope.

Categories

Resources