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

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

注意:我已经通读了 Whosebug 上的五篇帖子,它们都出现了同样的错误,但它们都没有解决我的问题。

我正在开发一个 class 项目,它是一个单页 JavaScript 应用程序,它使用 Google 地图 JavaScript API 来显示用户在他们所在城市的健身房。我已经开始搜索了。我已经让 infoWindows 正常工作(单击地图上的一个标记将显示一个 infoWindow,其中包含有关特定健身房的相关详细信息),但现在我试图让应用程序在用户单击时打开指定的 infoWindow列表视图中的健身房名称。我正在查看 Google 中的示例以构建我的应用程序。具体来说,我正在尝试模拟 this one 的功能。

一旦用户单击列表中的健身房名称,控制台中就会出现错误。我相信导致它的代码行在我的 buildListView() 函数中(大约低于下面文件高度的 70%)。我在该函数中可疑行的右侧添加了注释。

继续 play with it yourself 明白我的意思。任何帮助将不胜感激:)

<!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>

这是在循环中定义对标记的引用的常见问题。解决它的一种方法是使用函数闭包(一个在其参数上保持闭包的函数):

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);

}

更多关于Function closure

工作代码片段:

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>