JulianDavid
JulianDavid

Reputation: 123

Triggering infoWindow click - Uncaught TypeError: Cannot read property '__e3_' of undefined

Note: I've read through five posts on StackOverflow already with the same error and they did not resolve my issue.

I'm working on a class project which is a single-page JavaScript app that uses the Google Maps JavaScript API to show the user gyms in their city. I've got the search working. I've got the infoWindows working(clicking an a marker on the map will bring up an infoWindow with relevant details about the particular gym), but now I'm trying to make the app open up the designated infoWindow when the user clicks on a gym name in the list view. I am looking at the examples from Google in order to build my app. Specifically, I'm trying to emulate features of this one.

The error shows up in the console once the user clicks on a gym name in the list. I believe the line of code that's causing it is in my buildListView() function (about 70% of the way down the height of the file below). I've put a comment to the right of the suspicious line within that function.

Go ahead and play with it yourself to see what I mean. Any help would be appreciated :)

<!DOCTYPE html>
<html>
  <head>
    <title>Place searches</title>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <style>
      html, body, #map-canvas {
        height: 100%;
        margin: 0px;
        padding: 0px
      }
      #pac-input {
        width: 350px; 
        height: 25px;
        font-size: 16px; 
        margin: 10px; 
        padding: 5px
      }
      ul {
        z-index: 2; /* Fixes infoWindow and list view overlap issue */
      }
      li:hover {
        background: gray;
        color: white;
      }
      table {
        font-size: 12px;
      }
      .placeIcon {
        width: 20px;
        height: 34px;
        margin: 4px;
      }
      .hotelIcon {
        width: 24px;
        height: 24px;
      }
      #rating {
        font-size: 13px;
        font-family: Arial Unicode MS;
      }
      .iw_table_row {
        height: 18px;
      }
      .iw_attribute_name {
        font-weight: bold;
        text-align: right;
      }
      .iw_table_icon {
        text-align: right;
      }
    </style>
    <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true&libraries=places"></script>
    <script>
var madison, 
    map, 
    infoWindow, 
    autocomplete, 
    service, 
    controlList, 
    messageDiv; 

var anyInfoWindowSeenAlready = false;  

var markers = [], 
    gyms = [];

var hostnameRegexp = new RegExp('^https?://.+?/');

function initialize() {
  madison = new google.maps.LatLng(43.0667, -89.4000), 
      mapOptions = {
        center: madison,
        zoom: 12, 
        streetViewControl: false, 
        mapTypeControl: false
      }, 
      autocompleteOptions = {
        types: ['(cities)']
      };

  map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

  infoWindow = new google.maps.InfoWindow({
      content: document.getElementById('info-content')
      });

  document.getElementById('info-content').style.display = 'none';

  var input = document.getElementById('pac-input');
  map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

  // Create the DIV to hold the control and call the 
  // ListControl() constructor passing in this DIV.
  var listControlDiv = document.createElement('div');
  var listControl = new ListControl(listControlDiv, map);
  map.controls[google.maps.ControlPosition.LEFT_TOP].push(listControlDiv);

  // Create the 'Find a gym near you!' message div and 
  // push it onto the map canvas as a map control
  messageDiv = document.createElement('div');
  messageDiv = buildMessageDiv(messageDiv);
  map.controls[google.maps.ControlPosition.TOP_CENTER].push(messageDiv);

  autocomplete = new google.maps.places.Autocomplete(input, autocompleteOptions);
  service = new google.maps.places.PlacesService(map);

  var request = {
    location: madison,
    radius: 25000,
    types: ['gym']
  };
  service.nearbySearch(request, callback);
  google.maps.event.addListener(autocomplete, 'place_changed', onPlaceChanged);
}

/**
 * The ListControl adds a control to the map that
 * displays the search results in a list view. This 
 * constructor takes the control DIV as an argument.
 */
function ListControl(controlDiv, map) {

  var madison = new google.maps.LatLng(43.0667, -89.4000);

  // Set CSS styles for the DIV containing the control
  // Setting padding to 5 px will offset the control
  // from the edge of the map.
  controlDiv.style.padding = '10px';
  controlDiv.style.opacity = '0.6';

  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = 'white';
  controlUI.style.borderStyle = 'solid';
  controlUI.style.borderWidth = '1px';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'List of gyms returned by search';
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  controlList = document.createElement('ul');
  controlList.style.listStyleType = 'none'; 
  controlList.style.maxHeight = '500px';
  controlList.style.maxWidth = '300px';
  controlList.style.overflowY = 'auto';
  controlList.style.overflowX = 'hidden';
  controlList.style.fontFamily = 'Arial,sans-serif';
  controlList.style.fontSize = '16px';
  controlList.style.padding = '0px';
  controlList.style.margin = '0px';
  controlUI.appendChild(controlList);

  // Setup the click event listeners
  // google.maps.event.addDomListener(controlUI, 'click', function() {
  //   map.setCenter(madison)
  // });
}

// When the user selects a city, perform a nearbySearch() 
// for gyms in that city
function onPlaceChanged() {
  var place = autocomplete.getPlace();
  if (place.geometry) {
    clearMarkers();
    map.panTo(place.geometry.location);
    map.setZoom(12);
    var request = {
      location: place.geometry.location,
      radius: 25000,
      types: ['gym']
    };
    service.nearbySearch(request, callback);
    messageDiv.style.fontSize = '14px';
    messageDiv.innerHTML = '<h1>Here are the top 20 gyms in your city. Click to see details.</h1>';
  } else {
    document.getElementById('pac-input').placeholder = 'Start typing city name, then select a city from list';
  }
}

function buildMessageDiv(messageDiv) {
  messageDiv.innerHTML = '<h1>Find a gym near you!</h1>';
  messageDiv.style.textAlign = 'center';
  messageDiv.style.fontSize = '20px';
  messageDiv.style.visibility = 'visible';
  return messageDiv;

}


function callback(results, status) {
  if (status == google.maps.places.PlacesServiceStatus.OK) {
    gyms = results;
    for (var i = 0; i < gyms.length; i++) {
      markers[i] = createMarker(gyms[i]);
      markers[i].placeResult = gyms[i];
    }
  }
  buildListView();
}

function createMarker(place) {
  var placeLoc = place.geometry.location;
  var marker = new google.maps.Marker({
    map: map,
    position: placeLoc
  });

  google.maps.event.addListener(marker, 'click', showInfoWindow);

  return marker;
}

function clearMarkers() {
  for (var i = 0; i < markers.length; i++) {
    if (markers[i]) {
      markers[i].setMap(null);
    }
  }
  markers = [];
}

function buildListView() {
  controlList.innerHTML = '';
  for (var i = 0; i < gyms.length; i++) {
    if (gyms[i]) {
      var li = document.createElement('li');
      li.onclick = function() {
        google.maps.event.trigger(markers[i], 'click'); //<<--This line!
      };
      li.innerHTML = gyms[i].name;
      li.style.padding = '15px';
      li.style.cursor = 'pointer';
      li.setAttribute("id", gyms[i].name);
      controlList.appendChild(li);
    }
  }
}

// Get the place details for a hotel. Show the information in an info window,
// anchored on the marker for the hotel that the user selected.
function showInfoWindow() {
  var marker = this;
  service.getDetails({placeId: marker.placeResult.place_id},
    function(place, status) {
      if (status != google.maps.places.PlacesServiceStatus.OK) {
        return;
      } 
      if (anyInfoWindowSeenAlready === false) {  
        document.getElementById('info-content').style.display = 'initial';
      }
      infoWindow.open(map, marker);
      buildIWContent(place);
      anyInfoWindowSeenAlready = true;
    });
}

// Load the place information into the HTML elements used by the info window.
function buildIWContent(place) {
  document.getElementById('iw-icon').innerHTML = '<img class="hotelIcon" ' +
      'src="' + place.icon + '"/>';
  document.getElementById('iw-url').innerHTML = '<b><a href="' + place.url +
      '">' + place.name + '</a></b>';
  document.getElementById('iw-address').textContent = place.vicinity;

  if (place.formatted_phone_number) {
    document.getElementById('iw-phone-row').style.display = '';
    document.getElementById('iw-phone').textContent =
        place.formatted_phone_number;
  } else {
    document.getElementById('iw-phone-row').style.display = 'none';
  }

  // Assign a five-star rating to the hotel, using a black star ('&#10029;')
  // to indicate the rating the hotel has earned, and a white star ('&#10025;')
  // for the rating points not achieved.
  if (place.rating) {
    var ratingHtml = '';
    for (var i = 0; i < 5; i++) {
      if (place.rating < (i + 0.5)) {
        ratingHtml += '&#10025;';
      } else {
        ratingHtml += '&#10029;';
      }
    document.getElementById('iw-rating-row').style.display = '';
    document.getElementById('iw-rating').innerHTML = ratingHtml;
    }
  } else {
    document.getElementById('iw-rating-row').style.display = 'none';
  }

  // The regexp isolates the first part of the URL (domain plus subdomain)
  // to give a short URL for displaying in the info window.
  if (place.website) {
    var fullUrl = place.website;
    var website = hostnameRegexp.exec(place.website);
    if (website == null) {
      website = 'http://' + place.website + '/';
      fullUrl = website;
    }
    document.getElementById('iw-website-row').style.display = '';
    document.getElementById('iw-website').textContent = website;
  } else {
    document.getElementById('iw-website-row').style.display = 'none';
  }
}

google.maps.event.addDomListener(window, 'load', initialize);  

    </script>
  </head>
  <body>

    <input id="pac-input" class="controls" type="text"
      placeholder="Start typing city name, then select a city from list">

    <div id="map-canvas"></div>

    <div id="info-content">
      <table>
        <tr id="iw-url-row" class="iw_table_row">
          <td id="iw-icon" class="iw_table_icon"></td>
          <td id="iw-url"></td>
        </tr>
        <tr id="iw-address-row" class="iw_table_row">
          <td class="iw_attribute_name">Address:</td>
          <td id="iw-address"></td>
        </tr>
        <tr id="iw-phone-row" class="iw_table_row">
          <td class="iw_attribute_name">Telephone:</td>
          <td id="iw-phone"></td>
        </tr>
        <tr id="iw-rating-row" class="iw_table_row">
          <td class="iw_attribute_name">Rating:</td>
          <td id="iw-rating"></td>
        </tr>
        <tr id="iw-website-row" class="iw_table_row">
          <td class="iw_attribute_name">Website:</td>
          <td id="iw-website"></td>
        </tr>
      </table>
    </div>

  </body>
</html>

Upvotes: 0

Views: 973

Answers (1)

geocodezip
geocodezip

Reputation: 161384

This is a common problem defining references to markers in loops. One way to solve it is with function closure (a function that keeps closure on its arguments):

function buildListView() {
    controlList.innerHTML = '';
    for (var i = 0; i < gyms.length; i++) {
        if (gyms[i]) {
            clickableLink(i);
        }
    }
}

function clickableLink(i) {
    var li = document.createElement('li');
    li.onclick = function () {
        google.maps.event.trigger(markers[i], 'click'); //<<--This line!
    };
    li.innerHTML = gyms[i].name;
    li.style.padding = '15px';
    li.style.cursor = 'pointer';
    li.setAttribute("id", gyms[i].name);
    controlList.appendChild(li);

}

More on Function closure

working code snippet:

var madison,
  map,
  infoWindow,
  autocomplete,
  service,
  controlList,
  messageDiv;

var anyInfoWindowSeenAlready = false;

var markers = [],
  gyms = [];

var hostnameRegexp = new RegExp('^https?://.+?/');

function initialize() {
  madison = new google.maps.LatLng(43.0667, -89.4000),
    mapOptions = {
      center: madison,
      zoom: 12,
      streetViewControl: false,
      mapTypeControl: false
    },
    autocompleteOptions = {
      types: ['(cities)']
    };

  map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

  infoWindow = new google.maps.InfoWindow({
    content: document.getElementById('info-content')
  });

  document.getElementById('info-content').style.display = 'none';

  var input = document.getElementById('pac-input');
  map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

  // Create the DIV to hold the control and call the 
  // ListControl() constructor passing in this DIV.
  var listControlDiv = document.createElement('div');
  var listControl = new ListControl(listControlDiv, map);
  map.controls[google.maps.ControlPosition.LEFT_TOP].push(listControlDiv);

  // Create the 'Find a gym near you!' message div and 
  // push it onto the map canvas as a map control
  messageDiv = document.createElement('div');
  messageDiv = buildMessageDiv(messageDiv);
  map.controls[google.maps.ControlPosition.TOP_CENTER].push(messageDiv);

  autocomplete = new google.maps.places.Autocomplete(input, autocompleteOptions);
  service = new google.maps.places.PlacesService(map);

  var request = {
    location: madison,
    radius: 25000,
    types: ['gym']
  };
  service.nearbySearch(request, callback);
  google.maps.event.addListener(autocomplete, 'place_changed', onPlaceChanged);
}

/**
 * The ListControl adds a control to the map that
 * displays the search results in a list view. This
 * constructor takes the control DIV as an argument.
 */
function ListControl(controlDiv, map) {

  var madison = new google.maps.LatLng(43.0667, -89.4000);

  // Set CSS styles for the DIV containing the control
  // Setting padding to 5 px will offset the control
  // from the edge of the map.
  controlDiv.style.padding = '10px';
  controlDiv.style.opacity = '0.6';

  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = 'white';
  controlUI.style.borderStyle = 'solid';
  controlUI.style.borderWidth = '1px';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'List of gyms returned by search';
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  controlList = document.createElement('ul');
  controlList.style.listStyleType = 'none';
  controlList.style.maxHeight = '500px';
  controlList.style.maxWidth = '300px';
  controlList.style.overflowY = 'auto';
  controlList.style.overflowX = 'hidden';
  controlList.style.fontFamily = 'Arial,sans-serif';
  controlList.style.fontSize = '16px';
  controlList.style.padding = '0px';
  controlList.style.margin = '0px';
  controlUI.appendChild(controlList);

  // Setup the click event listeners
  // google.maps.event.addDomListener(controlUI, 'click', function() {
  //   map.setCenter(madison)
  // });
}

// When the user selects a city, perform a nearbySearch() 
// for gyms in that city
function onPlaceChanged() {
  var place = autocomplete.getPlace();
  if (place.geometry) {
    clearMarkers();
    map.panTo(place.geometry.location);
    map.setZoom(12);
    var request = {
      location: place.geometry.location,
      radius: 25000,
      types: ['gym']
    };
    service.nearbySearch(request, callback);
    messageDiv.style.fontSize = '14px';
    messageDiv.innerHTML = '<h1>Here are the top 20 gyms in your city. Click to see details.</h1>';
  } else {
    document.getElementById('pac-input').placeholder = 'Start typing city name, then select a city from list';
  }
}

function buildMessageDiv(messageDiv) {
  messageDiv.innerHTML = '<h1>Find a gym near you!</h1>';
  messageDiv.style.textAlign = 'center';
  messageDiv.style.fontSize = '20px';
  messageDiv.style.visibility = 'visible';
  return messageDiv;

}


function callback(results, status) {
  if (status == google.maps.places.PlacesServiceStatus.OK) {
    gyms = results;
    for (var i = 0; i < gyms.length; i++) {
      markers[i] = createMarker(gyms[i]);
      markers[i].placeResult = gyms[i];
    }
  }
  buildListView();
}

function createMarker(place) {
  var placeLoc = place.geometry.location;
  var marker = new google.maps.Marker({
    map: map,
    position: placeLoc
  });

  google.maps.event.addListener(marker, 'click', showInfoWindow);

  return marker;
}

function clearMarkers() {
  for (var i = 0; i < markers.length; i++) {
    if (markers[i]) {
      markers[i].setMap(null);
    }
  }
  markers = [];
}

function buildListView() {
  controlList.innerHTML = '';
  for (var i = 0; i < gyms.length; i++) {
    if (gyms[i]) {
      clickableLink(i);
    }
  }
}

function clickableLink(i) {
  var li = document.createElement('li');
  li.onclick = function() {
    google.maps.event.trigger(markers[i], 'click'); //<<--This line!
  };
  li.innerHTML = gyms[i].name;
  li.style.padding = '15px';
  li.style.cursor = 'pointer';
  li.setAttribute("id", gyms[i].name);
  controlList.appendChild(li);

}

// Get the place details for a hotel. Show the information in an info window,
// anchored on the marker for the hotel that the user selected.
function showInfoWindow() {
  var marker = this;
  service.getDetails({
      placeId: marker.placeResult.place_id
    },

    function(place, status) {
      if (status != google.maps.places.PlacesServiceStatus.OK) {
        return;
      }
      if (anyInfoWindowSeenAlready === false) {
        document.getElementById('info-content').style.display = 'initial';
      }
      infoWindow.open(map, marker);
      buildIWContent(place);
      anyInfoWindowSeenAlready = true;
    });
}

// Load the place information into the HTML elements used by the info window.
function buildIWContent(place) {
  document.getElementById('iw-icon').innerHTML = '<img class="hotelIcon" ' +
    'src="' + place.icon + '"/>';
  document.getElementById('iw-url').innerHTML = '<b><a href="' + place.url +
    '">' + place.name + '</a></b>';
  document.getElementById('iw-address').textContent = place.vicinity;

  if (place.formatted_phone_number) {
    document.getElementById('iw-phone-row').style.display = '';
    document.getElementById('iw-phone').textContent = place.formatted_phone_number;
  } else {
    document.getElementById('iw-phone-row').style.display = 'none';
  }

  // Assign a five-star rating to the hotel, using a black star ('&#10029;')
  // to indicate the rating the hotel has earned, and a white star ('&#10025;')
  // for the rating points not achieved.
  if (place.rating) {
    var ratingHtml = '';
    for (var i = 0; i < 5; i++) {
      if (place.rating < (i + 0.5)) {
        ratingHtml += '&#10025;';
      } else {
        ratingHtml += '&#10029;';
      }
      document.getElementById('iw-rating-row').style.display = '';
      document.getElementById('iw-rating').innerHTML = ratingHtml;
    }
  } else {
    document.getElementById('iw-rating-row').style.display = 'none';
  }

  // The regexp isolates the first part of the URL (domain plus subdomain)
  // to give a short URL for displaying in the info window.
  if (place.website) {
    var fullUrl = place.website;
    var website = hostnameRegexp.exec(place.website);
    if (website == null) {
      website = 'http://' + place.website + '/';
      fullUrl = website;
    }
    document.getElementById('iw-website-row').style.display = '';
    document.getElementById('iw-website').textContent = website;
  } else {
    document.getElementById('iw-website-row').style.display = 'none';
  }
}

google.maps.event.addDomListener(window, 'load', initialize);
html,
body,
#map-canvas {
  height: 100%;
  margin: 0px;
  padding: 0px
}
#pac-input {
  width: 350px;
  height: 25px;
  font-size: 16px;
  margin: 10px;
  padding: 5px
}
ul {
  z-index: 2;
  /* Fixes infoWindow and list view overlap issue */
}
li:hover {
  background: gray;
  color: white;
}
table {
  font-size: 12px;
}
.placeIcon {
  width: 20px;
  height: 34px;
  margin: 4px;
}
.hotelIcon {
  width: 24px;
  height: 24px;
}
#rating {
  font-size: 13px;
  font-family: Arial Unicode MS;
}
.iw_table_row {
  height: 18px;
}
.iw_attribute_name {
  font-weight: bold;
  text-align: right;
}
.iw_table_icon {
  text-align: right;
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=places&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>

<input id="pac-input" class="controls" type="text" placeholder="Start typing city name, then select a city from list">
<div id="map-canvas"></div>
<div id="info-content">
  <table>
    <tr id="iw-url-row" class="iw_table_row">
      <td id="iw-icon" class="iw_table_icon"></td>
      <td id="iw-url"></td>
    </tr>
    <tr id="iw-address-row" class="iw_table_row">
      <td class="iw_attribute_name">Address:</td>
      <td id="iw-address"></td>
    </tr>
    <tr id="iw-phone-row" class="iw_table_row">
      <td class="iw_attribute_name">Telephone:</td>
      <td id="iw-phone"></td>
    </tr>
    <tr id="iw-rating-row" class="iw_table_row">
      <td class="iw_attribute_name">Rating:</td>
      <td id="iw-rating"></td>
    </tr>
    <tr id="iw-website-row" class="iw_table_row">
      <td class="iw_attribute_name">Website:</td>
      <td id="iw-website"></td>
    </tr>
  </table>
</div>

Upvotes: 1

Related Questions