注意:我已经阅读了StackOverflow上的五篇帖子,但是他们没有解决我的问题。
我正在开发一个类项目,该项目是一个单页JavaScript应用程序,它使用Google Maps 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 ('✭')
// to indicate the rating the hotel has earned, and a white star ('✩')
// 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 += '✩';
} else {
ratingHtml += '✭';
}
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>
答案 0 :(得分:1)
这是定义循环中标记引用的常见问题。解决它的一种方法是使用函数闭包(一个保持其参数闭包的函数):
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 ('✭')
// to indicate the rating the hotel has earned, and a white star ('✩')
// 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 += '✩';
} else {
ratingHtml += '✭';
}
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"></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>