0

I am doing a small HTML5 project with Google Maps JavaScript API v3 using JavaScript, CSS and HTML.

In this small app, my objective is to add markers to the map, and once each marker is clicked, to show some information about the marker.

My first approach was the following:

var markersArray = [];

// Adds a marker to the map and push to the array.
function addMarker(location) {
    var marker = new google.maps.Marker({
        position: location,
        map: map,
        draggable: true,
        animation: google.maps.Animation.DROP,
        label: markersArray.length + "",
        title: markersArray.length + ""
    });
    markersArray.push(marker);
    marker.addListener('click', function() {
        clickMarkerEvent(markersArray.length - 1);
    });
}

//listener
function clickMarkerEvent(index) {
    alert(markersArray[index].getTitle());
}

However, When I click the marker, I only get information about the last marker placed on the map. The Listener is failing.

I tried correcting this by using scopes in JavaScript, and I even gave a look at binding, however, none of those worked.

In the end, I came up with something completely different:

var markersArray = [];
var lastMarkerClicked;    

function addMarker(location) {
    var marker = new google.maps.Marker({
        position: location,
        map: map,
        draggable: true,
        animation: google.maps.Animation.DROP,
        label: markersArray.length + "",
        title: markersArray.length + ""
    });
    markersArray.push(marker);
    marker.addListener('click', function() {
        clickMarkerEvent();
    });
}

//listener
function clickMarkerEvent() {
    lastMarkerClicked = event.target || event.srcElement;
    alert(markersArray[lastMarkerClicked].title);
}

The problem with this last approach (even though it works) is the following:

Overall my solution works, but I truly dislike it. Is there a better way to do this?

Following is my code, I am open to suggestions !

'use strict'

/*
 Best practices: For the best user experience, only one info window should be 
 open on the map at any one time. Multiple info windows make the map appear 
 cluttered. If you only need one info window at a time, you can create just 
 one InfoWindow object and open it at different locations or markers upon map
 events, such as user clicks. If you do need more than one info window, you 
 can display multiple InfoWindow objects at the same time.
*/
var infowindow;
var contentString;

var lastMarkerClicked;
var markersArray = [];

var map;

// Initializes the map with a marker
function initMap() {

 var myLatLng = {
  lat: -25.363,
  lng: 131.044
 };

 map = new google.maps.Map(document.getElementById('map'), {
  zoom: 4,
  center: myLatLng
 });

 // This event listener calls addMarker() when the map is clicked.
 google.maps.event.addListener(map, 'click', function(event) {
  addMarker(event.latLng);
 });

 addMarker(myLatLng);
}

// Adds a marker to the map and push to the array.
function addMarker(location) {
 var marker = new google.maps.Marker({
  position: location,
  map: map,
  draggable: true,
  animation: google.maps.Animation.DROP,
  label: markersArray.length + "",
  title: markersArray.length + ""
 });
 markersArray.push(marker);
 marker.addListener('click', function() {
  clickMarkerEvent();
 });
}

// Sets the map on all markers in the array.
function setMapOnAll(map) {
 for (var i = 0; i < markersArray.length; i++) {
  setMapOnMarker(i, map);
 }
}

// Removes the markers from the map, but keeps them in the array.
function clearMarkers() {
 setMapOnAll(null);
}

// Shows any markers currently in the array.
function showMarkers() {
 setMapOnAll(map);
}

function setMapOnMarker(markerIndex, map) {
 markersArray[markerIndex].setMap(map);
}

function hideMarker(markerIndex) {
 setMapOnMarker(markerIndex, null);
}

function deleteMarker(markerIndex) {
 hideMarker(markerIndex);
 markersArray[markerIndex] = null;
}

/* 
 Deletes all markers in the array by removing references to them.
 Note that the above method does not delete the marker. It simply removes the 
 marker from the map. If instead you wish to delete the marker, you should remove 
 it from the map, and then set the marker itself to null.
 https://developers.google.com/maps/documentation/javascript/markers#remove
*/
function deleteMarkers() {
 clearMarkers();

 for (var i = 0; i < markersArray.length; i++) {
  markersArray[i] = null;
 }

 markersArray = [];
}

//listeners
function clickMarkerEvent() {

 lastMarkerClicked = event.target || event.srcElement;

 if (markersArray[lastMarkerClicked.title].getAnimation() !== null) {
  markersArray[lastMarkerClicked.title].setAnimation(null);
 }
 else {
  markersArray[lastMarkerClicked.title].setAnimation(google.maps.Animation.BOUNCE);
 }
 
 contentString = '<div id="content">' +
 '<div id="siteNotice">' +
 '</div>' +
 '<h1 id="firstHeading" class="firstHeading">Marker Info</h1>' +
 '<div id="bodyContent">' +
 '<b>Locatoin:</b> <p>' + markersArray[lastMarkerClicked.title].getPosition() + '</p>' + 
 '<b>Title: </b> <p>' + lastMarkerClicked.title + '</p>' + 
 '<button onclick="hideMarkerClickEvent()">Hide Marker</button>' +
 '<button onclick="deleteMarkerClickEvent()">Delete Marker</button>' +
 '</div>' +
 '</div>';
 
 if(infowindow !== null && typeof infowindow !== 'undefined')
  infowindow.close();
 
 infowindow = new google.maps.InfoWindow({
  content: contentString,
  maxWidth: 200
 });
 infowindow.open(map, markersArray[lastMarkerClicked.title]);
}

function deleteMarkerClickEvent() {
 deleteMarker(lastMarkerClicked.title);
}

function hideMarkerClickEvent() {
 hideMarker(lastMarkerClicked.title);
}
html,
body {
 height: 100%;
 margin: 0;
 padding: 0;
}

#map {
 height: 100%;
}
<!DOCTYPE html>
<html>

<head>
 <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
 <meta charset="utf-8">
 <title>Simple markers</title>
 <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
 <div id="map"></div>
 <script type="text/javascript" src="markers.js"></script>
 <script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCGj-Dsa4PtrJiyATE_upQgOkfEkjFXqoQ&callback=initMap">
 </script>
</body>

</html>
Flame_Phoenix
  • 16,489
  • 37
  • 131
  • 266
  • possible duplicate of [Google Maps JS API v3 - Simple Multiple Marker Example](http://stackoverflow.com/questions/3059044/google-maps-js-api-v3-simple-multiple-marker-example) – geocodezip May 18 '16 at 13:03

2 Answers2

2

Your first approach was almost ok, the problem was that at the point when click listener was called, all markers were already in markersArray so markersArray.length - 1 always pointed to the last marker. Just create a variable which value holds the id of the marker inside the scope of the addMarker function. Here is the working code (notice the use of var index = markersArray.length;):

'use strict'


var infowindow;
var contentString;

var markersArray = [];

var map;

// Initializes the map with a marker
function initMap() {

 var myLatLng = {
  lat: -25.363,
  lng: 131.044
 };

 map = new google.maps.Map(document.getElementById('map'), {
  zoom: 4,
  center: myLatLng
 });

 // This event listener calls addMarker() when the map is clicked.
 google.maps.event.addListener(map, 'click', function(event) {
  addMarker(event.latLng);
 });

 addMarker(myLatLng);
}

// Adds a marker to the map and push to the array.
function addMarker(location) {
    var index = markersArray.length;
 var marker = new google.maps.Marker({
  position: location,
  map: map,
  draggable: true,
  animation: google.maps.Animation.DROP,
  label: index + "",
  title: index + ""
 });
 markersArray.push(marker);
 marker.addListener('click', function() {
  clickMarkerEvent(index);
 });
}

// Sets the map on all markers in the array.
function setMapOnAll(map) {
 for (var i = 0; i < markersArray.length; i++) {
  setMapOnMarker(i, map);
 }
}

// Removes the markers from the map, but keeps them in the array.
function clearMarkers() {
 setMapOnAll(null);
}

// Shows any markers currently in the array.
function showMarkers() {
 setMapOnAll(map);
}

function setMapOnMarker(markerIndex, map) {
 markersArray[markerIndex].setMap(map);
}

function hideMarker(markerIndex) {
 setMapOnMarker(markerIndex, null);
}

function deleteMarker(markerIndex) {
 hideMarker(markerIndex);
 markersArray[markerIndex] = null;
}


function deleteMarkers() {
 clearMarkers();

 for (var i = 0; i < markersArray.length; i++) {
  markersArray[i] = null;
 }

 markersArray = [];
}

//listeners
function clickMarkerEvent(index) {

 if (markersArray[index].getAnimation() !== null) {
  markersArray[index].setAnimation(null);
 }
 else {
  markersArray[index].setAnimation(google.maps.Animation.BOUNCE);
 }
 
 contentString = '<div id="content">' +
 '<div id="siteNotice">' +
 '</div>' +
 '<h1 id="firstHeading" class="firstHeading">Marker Info</h1>' +
 '<div id="bodyContent">' +
 '<b>Locatoin:</b> <p>' + markersArray[index].getPosition() + '</p>' + 
 '<b>Title: </b> <p>' + markersArray[index].getTitle() + '</p>' + 
 '<button onclick="hideMarkerClickEvent(' + index + ')">Hide Marker</button>' +
 '<button onclick="deleteMarkerClickEvent(' + index + ')">Delete Marker</button>' +
 '</div>' +
 '</div>';
 
 if(infowindow !== null && typeof infowindow !== 'undefined')
  infowindow.close();
 
 infowindow = new google.maps.InfoWindow({
  content: contentString,
  maxWidth: 200
 });
 infowindow.open(map, markersArray[index]);
}

function deleteMarkerClickEvent(index) {
 deleteMarker(index);
}

function hideMarkerClickEvent(index) {
 hideMarker(index);
}
html,
body {
 height: 100%;
 margin: 0;
 padding: 0;
}

#map {
 height: 100%;
}
<!DOCTYPE html>
<html>

<head>
 <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
 <meta charset="utf-8">
 <title>Simple markers</title>
 <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
 <div id="map"></div>
 <script type="text/javascript" src="markers.js"></script>
 <script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCGj-Dsa4PtrJiyATE_upQgOkfEkjFXqoQ&callback=initMap">
 </script>
</body>

</html>
Matej P.
  • 5,303
  • 1
  • 28
  • 44
  • I have arrived to yet another solution. However, I do believe your's is simpler even though my solution would work for more general cases. For that reason I have elected your answer as the correct one, but I am still posting my solution with hopes of helping someone in the future. – Flame_Phoenix May 18 '16 at 09:36
  • There are already tons of similar questions on SO, eg: http://stackoverflow.com/questions/37112677/how-to-link-each-marker-with-its-own-infowindow, http://stackoverflow.com/questions/31983445/why-are-my-google-maps-event-listeners-not-working-properly, http://stackoverflow.com/questions/5210904/google-map-infowindow-closure ... and many more. People have generally problems with closures when attaching listeners in a loop. Your case was somewhat special because you only needed to pass one integer so that's why I didn't reference you to those articles and instead created ad-hoc solution for you. – Matej P. May 18 '16 at 09:55
1

I have finally found a solution which I like (different from the one I accepted).

After a lot of research, I found this website: https://www.toptal.com/javascript/interview-questions

And I read question number 10:

In what order will the numbers 1-4 be logged to the console when the code below is executed? Why?

(function() {
    console.log(1); 
    setTimeout(function(){console.log(2)}, 1000); 
    setTimeout(function(){console.log(3)}, 0); 
    console.log(4);
})();

If you want to know the answer, feel free to read the original source.

After reading this, I came up with my version using scopes:

var markersArray = [];

// Adds a marker to the map and push to the array.
function addMarker(location) {
    var marker = new google.maps.Marker({
        position: location,
        map: map,
        draggable: true,
        animation: google.maps.Animation.DROP,
        label: markersArray.length + "",
        title: "Marker Number " + markersArray.length
    });

    markersArray.push(marker);
    marker.addListener('click', (function(index) {
        return function() {
            clickMarkerEvent(index);
        };
    })(markersArray.length - 1));
}

//listeners
function clickMarkerEvent(index) {
    alert(index);
}

This is what I was trying to do in the first place, but I have forgotten a few details.

Still, I have decided to accept the other question for its simplicity.

Here is my complete project. I added some more features like custom controls for the markers. It is not perfect, by all means, but I think it is a nice starter point for anyone interested.

'use strict'

/*global google*/

/*
 Best practices: For the best user experience, only one info window should be 
 open on the map at any one time. Multiple info windows make the map appear 
 cluttered. If you only need one info window at a time, you can create just 
 one InfoWindow object and open it at different locations or markers upon map
 events, such as user clicks. If you do need more than one info window, you 
 can display multiple InfoWindow objects at the same time.
*/
var infowindow;
var contentString;

var lastMarkerClicked;
var markersArray = [];

var map;

// Initializes the map with a marker
function initMap() {

 var myLatLng = {
  lat: -25.363,
  lng: 131.044
 };

 map = new google.maps.Map(document.getElementById('map'), {
  zoom: 4,
  center: myLatLng
 });

 // This event listener calls addMarker() when the map is clicked.
 google.maps.event.addListener(map, 'click', function(event) {
  addMarker(event.latLng);
 });

 addMarker(myLatLng);
}

// Adds a marker to the map and push to the array.
function addMarker(location) {
 var marker = new google.maps.Marker({
  position: location,
  map: map,
  draggable: true,
  animation: google.maps.Animation.DROP,
  label: markersArray.length + "",
  title: "Marker Number " + markersArray.length
 });
 
 markersArray.push(marker);
 marker.addListener('click', (function(index) {
  return function() {
   clickMarkerEvent(index);
  };
 })(markersArray.length - 1));
}

// Sets the map on all markers in the array.
function setMapOnAll(map) {
 for (var i = 0; i < markersArray.length; i++) {
  if(markersArray[i] !== null)
  setMapOnMarker(i, map);
 }
}

// Removes the markers from the map, but keeps them in the array.
function clearMarkers() {
 setMapOnAll(null);
}

// Shows any markers currently in the array.
function showMarkers() {
 setMapOnAll(map);
}

/* 
 Deletes all markers in the array by removing references to them.
 Note that the above method does not delete the marker. It simply removes the 
 marker from the map. If instead you wish to delete the marker, you should remove 
 it from the map, and then set the marker itself to null.
 https://developers.google.com/maps/documentation/javascript/markers#remove
*/
function deleteMarkers() {
 clearMarkers();

 for (var i = 0; i < markersArray.length; i++) {
  if(markersArray[i] !== null)
   markersArray[i] = null;
 }
 
 markersArray = [];
}

// Sets a marker with the given map
function setMapOnMarker(markerIndex, map) {
 markersArray[markerIndex].setMap(map);
}

// Hides a single marker
function hideMarker(markerIndex) {
 setMapOnMarker(markerIndex, null);
}

// Deletes a marker. Hides it first.
function deleteMarker(markerIndex) {
 hideMarker(markerIndex);
 markersArray[markerIndex] = null;
}

//listeners
function clickMarkerEvent(index) {

 lastMarkerClicked = markersArray[index];

 if (lastMarkerClicked.getAnimation() !== null)
  lastMarkerClicked.setAnimation(null);
 else 
  lastMarkerClicked.setAnimation(google.maps.Animation.BOUNCE);

 contentString = '<div id="content">' +
  '<div id="siteNotice">' +
  '</div>' +
  '<h1 id="firstHeading" class="firstHeading">Marker Info</h1>' +
  '<div id="bodyContent">' +
  '<b>Locatoin:</b> <p>' + lastMarkerClicked.getPosition() + '</p>' +
  '<b>Title: </b> <p>' + lastMarkerClicked.getTitle() + '</p>' +
  '<button onclick="hideMarkerClickEvent()">Hide Marker</button>' +
  '<button onclick="deleteMarkerClickEvent()">Delete Marker</button>' +
  '</div>' +
  '</div>';

 if (infowindow !== null && typeof infowindow !== 'undefined')
  infowindow.close();

 infowindow = new google.maps.InfoWindow({
  content: contentString,
  maxWidth: 200
 });
 infowindow.open(map, lastMarkerClicked);
}

function deleteMarkerClickEvent() {
 deleteMarker(markersArray.indexOf(lastMarkerClicked));
}

function hideMarkerClickEvent() {
 hideMarker(markersArray.indexOf(lastMarkerClicked));
}
html,
body {
 height: 100%;
 margin: 0;
 padding: 0;
}

#map {
 height: 100%;
}

#floating-panel {
 position: absolute;
 top: 10px;
 left: 25%;
 z-index: 5;
 background-color: #fff;
 padding: 5px;
 border: 1px solid #999;
 text-align: center;
 font-family: 'Roboto', 'sans-serif';
 line-height: 30px;
 padding-left: 10px;
}
<!DOCTYPE html>
<html>

<head>
 <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
 <meta charset="utf-8">
 <title>Simple markers</title>
 <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
 <div id="floating-panel">
  <input onclick="clearMarkers();" type=button value="Hide Markers">
  <input onclick="showMarkers();" type=button value="Show All Markers">
  <input onclick="deleteMarkers();" type=button value="Delete Markers">
 </div>
 <div id="map"></div>
 <script type="text/javascript" src="markers.js"></script>
 <script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCGj-Dsa4PtrJiyATE_upQgOkfEkjFXqoQ&callback=initMap">
 </script>
</body>

</html>
Flame_Phoenix
  • 16,489
  • 37
  • 131
  • 266