I am trying to create an infowindow with an IIFE. I am having quite a bit of trouble passing information into it.
As you can see I retrieved an array of places using the searchBox Class, which was instantiated and correctly implemented in order to properly use the getPlaces() method.
Places is an array of objects and I use the information to create a marker and infowindow. The markers show up! However, I can't seem to pass the object, places[i], into the IIFE.
There's NOT even an error in the console, which is why I am in desperate need of your help.
My ultimate goal is to have an infowindow open on the clicked marker with the the corresponding information.
Can anyone spot what I am doing wrong here?
var places = searchBox.getPlaces(); // array of place objects
var placeMarkers = [];
var placeInfoWindow = new google.maps.InfoWindow();
var placeBounds = new google.maps.LatLngBounds();
if (!places || places === null || places.length === 0) {
alert("No Places Founds");
} else {
for (var i = 0, l = places.length; i < l; i++) {
// MARKER
var loc = places[i].geometry.location;
var placeMarker = new google.maps.Marker({
position: loc,
title: places[i].name,
animation: google.maps.Animation.DROP,
icon: places[i].icon
});
placeMarkers.push(placeMarker); // save each marker
placeMarker.setMap(map); // display marker immediately
// extend boundary to include each marker location
placeBounds.extend(loc);
//REGISTER MARKER CLICK HANDLER WITH CURRENT places[i] INFO
placeMarker.addListener("click", (function(placeCopy) {
return function() {
var infoWindowContentStr = '<div>';
if (placeCopy.name) {
infoWindowContentStr += '<div>' + placeCopy.name + '</div><br>';
}
if (placeCopy.formatted_address) {
infoWindowContentStr += '<div>' + placeCopy.formatted_address + '</div>';
}
infoWindowContentStr += '</div>';
placeInfoWindow.setContent(infoWindowContentStr);
placeInfoWindow.open(map, this);
};
}), places[i]);
}
map.fitBounds(placeBounds);
}
You have a typo in your IIFE function definition, it isn't executing the function and getting closure until the click happens.
related question: Google Maps API Places Library SearchBox (used to create the MCVE)
placeMarker.addListener("click", (function(placeCopy) {
return function() {
var infoWindowContentStr = '<div>';
if (placeCopy.name) {
infoWindowContentStr += '<div>' + placeCopy.name + '</div><br>';
}
if (placeCopy.formatted_address) {
infoWindowContentStr += '<div>' + placeCopy.formatted_address + '</div>';
}
infoWindowContentStr += '</div>';
placeInfoWindow.setContent(infoWindowContentStr);
placeInfoWindow.open(map, this);
};
})(places[i])); // you had }), places[i]);
proof of concept fiddle
code snippet:
function initialize() {
map = new google.maps.Map(document.getElementById('map_canvas'), {
mapTypeId: google.maps.MapTypeId.ROADMAP,
streetViewControl: false
});
var defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(41.7033177, -93.0573533),
new google.maps.LatLng(41, -93)
);
map.fitBounds(defaultBounds);
var input = document.getElementById('target');
var searchBox = new google.maps.places.SearchBox(input);
searchBox.setBounds(defaultBounds);
var markers = [];
service = new google.maps.places.PlacesService(map);
google.maps.event.addListener(searchBox, 'places_changed', function() {
var places = searchBox.getPlaces(); // array of place objects
var placeMarkers = [];
var placeInfoWindow = new google.maps.InfoWindow();
var placeBounds = new google.maps.LatLngBounds();
if (!places || places === null || places.length === 0) {
alert("No Places Founds");
} else {
for (var i = 0, l = places.length; i < l; i++) {
// MARKER
var loc = places[i].geometry.location;
var placeMarker = new google.maps.Marker({
position: loc,
title: places[i].name,
animation: google.maps.Animation.DROP,
icon: places[i].icon
});
placeMarkers.push(placeMarker); // save each marker
placeMarker.setMap(map); // display marker immediately
// extend boundary to include each marker location
placeBounds.extend(loc);
//REGISTER MARKER CLICK HANDLER WITH CURRENT places[i] INFO
placeMarker.addListener("click", (function(placeCopy) {
return function() {
var infoWindowContentStr = '<div>';
if (placeCopy.name) {
infoWindowContentStr += '<div>' + placeCopy.name + '</div><br>';
}
if (placeCopy.formatted_address) {
infoWindowContentStr += '<div>' + placeCopy.formatted_address + '</div>';
}
infoWindowContentStr += '</div>';
placeInfoWindow.setContent(infoWindowContentStr);
placeInfoWindow.open(map, this);
};
})(places[i]));
}
map.fitBounds(placeBounds);
}
});
google.maps.event.addListener(map, 'bounds_changed', function() {
var bounds = map.getBounds();
searchBox.setBounds(bounds);
});
}
google.maps.event.addDomListener(window, 'load', initialize);
html,
body,
#map_canvas {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
#search-panel {
position: absolute;
top: 5px;
left: 50%;
margin-left: -180px;
width: 350px;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
}
#target {
width: 345px;
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=places"></script>
<div id="search-panel">
<input id="target" type="text" placeholder="Search Box">
</div>
<div id="map_canvas"></div>
I can't figure out how to add Marker Clusters to this map. It's used with Wordpress, so that the user can add locations to it from the Wordpress dashboard. I'm pretty new to Google API and I'm not having success with any examples that I find. Any help is appreciated!
<?php // Index template
get_header(); ?>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
<div class="twelve column">
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<div class="intro">
<?php the_excerpt(); ?>
<hr>
</div>
<?php the_content(); ?>
<header class="clearfix"></header>
<div id="mapcanvas"></div>
<?php
// For creating multiple, customized loops.
// http://codex.wordpress.org/Class_Reference/WP_Query
$custom_query = new WP_Query('post_type=locations'); // exclude category 9
while($custom_query->have_posts()) : $custom_query->the_post(); ?>
s <?php if(get_field('link')): ?>
<div>
<?php while(has_sub_field('link')): ?>
<div>
<p><?php the_sub_field('url'); ?></p>
</div>
<?php endwhile; ?>
</div>
<?php endif; ?>
<?php endwhile; ?>
<?php wp_reset_postdata(); // reset the query ?>
<?php
function get_single_term($post_id, $taxonomy)
{
$terms = wp_get_object_terms($post_id, $taxonomy);
if(!is_wp_error($terms)) {
return ''.$terms[0]->name.'';
}
}
$i = 0;
// For creating multiple, customized loops.
// http://codex.wordpress.org/Class_Reference/WP_Query
$custom_query = new WP_Query('post_type=location&posts_per_page=-1');
while($custom_query->have_posts()) : $custom_query->the_post();
$title = get_the_title(); // Location title
$map = get_field('location'); // ACF location contains address and coordinates
$terms = strip_tags( get_the_term_list( $post->ID, 'distributors', '', ' & ' )); // Get distributor terms and rm links
$info = '<strong>' . $title . '</strong><br>' . $map['address']; // Info window content
$link = get_field('link');
if($link){
$info .= '<br>'. $link .'';
}
$location[$i][0] = $title; // Store the post title
$location[$i][1] = $map['coordinates']; // Store the ACF coordinates
$location[$i][2] = json_encode($info); // Store info window content
$location[$i][3] = strip_tags( get_single_term( $post->ID, 'distributors' )); // Get first term for marker icon
$i ++;
endwhile; ?>
<?php wp_reset_postdata(); // reset the query ?>
<script>
var geocoder;
var map;
$(function initialize() {
geocoder = new google.maps.Geocoder();
// Center map on our main location
var myLatLng = new google.maps.LatLng(41.583013,-93.63701500000002);
var bounds = new google.maps.LatLngBounds();
// https://developers.google.com/maps/documentation/javascript/styling
// Create an array of styles.
var styles = [
{
stylers: [
{ saturation: -99.9 }
]
}
];
// Create a new StyledMapType object, passing it the array of styles,
// as well as the name to be displayed on the map type control.
var styledMap = new google.maps.StyledMapType(styles, {name: 'exile'});
// Create a map object, and include the MapTypeId to add
// to the map type control.
var mapOptions = {
mapTypeId: 'roadmap',
center: myLatLng,
zoom: 14,
disableDefaultUI: false,
scrollwheel: true,
draggable: true
};
// Display a map on the page
map = new google.maps.Map(document.getElementById("mapcanvas"), mapOptions);
map.setTilt(45);
//Associate the styled map with the MapTypeId and set it to display.
map.mapTypes.set('exile', styledMap);
map.setMapTypeId('exile');
// Marker icons
typeObject = {
"Others" : {
"icon" : new google.maps.MarkerImage('http://exilebrewing.com/site/img/beer-mug.png', new google.maps.Size(18,26), new google.maps.Point(0,0), new google.maps.Point(9,26)),
"shadow" : new google.maps.MarkerImage('http://maps.google.com/mapfiles/shadow50.png', new google.maps.Size(40,34))
}
}
// http://wrightshq.com/playground/placing-multiple-markers-on-a-google-map-using-api-3/
// Multiple Markers
var markers = [
["Exile Brewing Co", 41.583013,-93.63701500000002,"Others"],
<?php
if (count($location)>0) {
foreach ($location as $key => $value){
if ($key < (count($location)-1)){
echo '["' . $location[$key][0] . '",' . $location[$key][1] . ',"' . $location[$key][3] . '"], ' . "\n";
} else {
echo '["' . $location[$key][0] . '",' . $location[$key][1] . ',"' . $location[$key][3] . '"]';
}
}
}
?>
];
// Info Window Content
var infoWindowContent = [
["<strong>Exile Brewing Co.</strong><br>1514 Walnut Street, Des Moines"],
<?php
if (count($location)>0) {
foreach ($location as $key => $value){
if ($key < (count($location)-1)) {
echo '[' . $location[$key][2] . '], ' . "\n";
} else {
echo '[' . $location[$key][2] . ']';
}
}
}
?>
];
// Display multiple markers on a map
var infoWindow = new google.maps.InfoWindow(), marker, i;
// Loop through our array of markers & place each one on the map
for( i = 0; i < markers.length; i++ ) {
var position = new google.maps.LatLng(markers[i][1], markers[i][2]); // ACF coordinates
var icon = typeObject[markers[i][3]]['icon'];
var shadow = typeObject[markers[i][3]]['shadow'];
bounds.extend(position);
marker = new google.maps.Marker({
position: position,
map: map,
title: markers[i][0],
icon: icon,
shadow: shadow
});
// Allow each marker to have an info window
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
infoWindow.setContent(infoWindowContent[i][0]);
infoWindow.open(map, marker);
}
})(marker, i));
// Automatically center the map fitting all markers on the screen
//map.fitBounds(bounds);
}
function codeAddress() {
var address = document.getElementById('address').value;
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});
} else {
alert('Geocode was not successful for the following reason: ' + status);
}
});
}
// Override our map zoom level once our fitBounds function runs (Make sure it only runs once)
var boundsListener = google.maps.event.addListener((map), 'bounds_changed', function(event) {
this.setZoom(15);
google.maps.event.removeListener(boundsListener);
});
});
</script>
<div id="panel">
<input id="address" type="textbox" value="Sydney, NSW">
<input type="button" value="Geocode" onclick="codeAddress()">
</div>
<div id="map-canvas"></div>
<footer class="clearfix"></footer>
<?php endwhile;?>
<?php endif; ?>
</div>
</div>
<?php get_footer(); ?>
Some
There can be three kind of clustering of markers as follows
Grid-Based Clustering
Grid-based clustering is probably the most common approach for clustering markers. It will divide the map into a grid and group all markers within each square into a cluster. Although an efficient technique, it has some obvious limitations since it can lead to unwanted results. Two markers that are really close together but in separate squares will, for example, not be grouped into the same cluster.
Distance-Based Clustering
This technique looks at each individual marker and checks whether it’s nearby other markers. If it’s close enough to another marker, the two of them will be grouped into a cluster.
Distance-based clustering also has its drawbacks. Since the clusters will appear at random
locations depending on where a cluster is formed they may not make sense for the user.
Regional Clustering
A third technique is regional clustering. What this means is that you define different geographical regions, such as counties or states. All markers in each region will be grouped into a cluster. You also define at which zoom level the cluster will break up into separate markers (or smaller clusters). The advantage of this technique is that you can create clusters that make more sense to the user. The drawback is that it requires more effort and can’t as easily be automated as the other clustering techniques.
Example
Shows the complete code. When you run this page, it will add 100 markers at random
locations at the visible part of the map. If you want to try adding a different number of markers, just change the number in the for loop.
(function() {
window.onload = function(){
// Creating a map
var options = {
zoom: 3,
center: new google.maps.LatLng(37.09, -95.71),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById('map'), options);
google.maps.event.addListenerOnce(map, 'bounds_changed', function() {
// Getting the boundaries of the map
var bounds = map.getBounds();
// Getting the corners of the map
var southWest = bounds.getSouthWest();
var northEast = bounds.getNorthEast();
// Calculating the distance from the top to the bottom of the map
var latSpan = northEast.lat() - southWest.lat();
// Calculating the distance from side to side
var lngSpan = northEast.lng() - southWest.lng();
// Creating a loop
for (var i = 0; i < 100; i++) {
// Creating a random position
var lat = southWest.lat() + latSpan * Math.random();
var lng = southWest.lng() + lngSpan * Math.random();
var latlng = new google.maps.LatLng(lat, lng);
// Adding a marker to the map
new google.maps.Marker({
position: latlng,
map: map
});
}
});
};
})();
Try this
<script type="text/javascript">
function initialize() {
var myLatlng = new google.maps.LatLng(13.696516,-89.208765);
var myOptions = {
zoom: 17,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var contentString = '<h1>Aditivos de El salvador</h1><p>33 Av. Sur #661, Colonia Flor Blanca, San Salvador El Salvador.</p>';
var infowindow = new google.maps.InfoWindow({
content: contentString
});
var image = '<?php echo bloginfo('template_url'); ?>/images/iconmapa.png';
var beachMarker = new google.maps.Marker({
position: myLatlng,
map: map,
icon: image
});
google.maps.event.addListener(beachMarker, 'click', function() {
infowindow.open(map,beachMarker);
});
}
</script>
Hello and thanks in advance for your help!
The following test code:
1) Uses PHP to get lat/lng data from a mySQL database, and then uses that data to form a JavaScript array with initialization data. WORKS FINE.
2) Uses the initialized JavaScript array to create markers. WORKS FINE.
3) Allows (left) clicks to create new markers (more code will be added later to add those new marker locations back into the database). WORKS FINE.
4) Allows RIGHT-clicks to delete a marker using marker.setMap(null) - either the preloaded markers from the database, or newly created user markers. USER MARKERS DELETE PROPERLY, PRE-LOADED MARKERS DO NOT DELETE PROPERLY, AS DESCRIBED BELOW.
5) Both the preloaded markers, and the newly created ones are stored in the array "markers" using the statement markers.push(marker). SEEMS TO BE OK.
Everything works, except the right-click deletion of the PRELOADED markers. (The right-click deletion of user created markers works fine.) Any right-click on any preloaded marker, only deletes the LAST preloaded marker. It's as if the delete event listener were outside and after the loop that sets the preloaded markers, but it is inside that loop.
I think the faulty section is the one with the leading comment "show prev clicks in database". Any ideas would be greatly appreciated! I'm very new at Google Maps API v3, so it's probably something obvious that I'm just missing or misunderstanding. Thanks again!
<!DOCTYPE html>
<html>
<head>
<title>Marker Test</title>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
#map-canvas, #map_canvas {
height: 100%;
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
<?
// Generate JavaScript array initialization from database
$username="REDACTED";
$password="REDACTED";
$database="REDACTED";
$con=mysql_connect(localhost,$username,$password);
#mysql_select_db($database,$con) or die( "Unable to select database");
$query="SELECT * FROM pool where (record_id >= 1765) AND (record_id <= 1769)";
/*
$query="SELECT * FROM pool where (record_id <= '$marker_end') AND (record_id >= '$marker_start')";
$query="SELECT * FROM pool where session = '$session'";
*/
$result=mysql_query($query,$con);
$length=mysql_num_rows($result);
$length_count=1;
if ($result) {
echo "var PreviousClicks = [\n";
while($row = mysql_fetch_array($result)) {
$_lat=$row['google_lat'];
$_lng=$row['google_lng'];
$_record_id=$row['record_id'];
echo "{\n";
echo "lat: $_lat,\n";
echo "lng: $_lng,\n";
echo "title: \"$_record_id\"\n";
echo "}";
if($length_count<$length) {echo ",\n";} else {echo "\n";}
$length_count++;
} // end while
echo "];";
} // end if
mysql_close();
?>
</script>
<script type="text/javascript">
var map;
var markers = [];
function addMarker(location) {
var marker = new google.maps.Marker({
position: location,
title: "A",
map: map
});
google.maps.event.addListener(marker, 'rightclick', function(event) {
marker.setMap(null);
});
markers.push(marker);
} //end addMarker function
function initialize() {
// currently manual center initialization
var startLoc = new google.maps.LatLng(33.037380,-117.090431);
var mapOptions = {
zoom: 16,
center: startLoc,
mapTypeId: google.maps.MapTypeId.TRAFFIC
};
map = new google.maps.Map(document.getElementById('map-canvas'),mapOptions);
google.maps.event.addListener(map, 'click', function(event) {
addMarker(event.latLng);
});
// show prev clicks in database ------------------------------------------------------------------
for (i = 0; i < PreviousClicks.length; i++) {
var marker = new google.maps.Marker({
position: new google.maps.LatLng(PreviousClicks[i].lat, PreviousClicks[i].lng),
title: PreviousClicks[i].title,
map: map
});
google.maps.event.addListener(marker, 'rightclick', function(event) {
marker.setMap(null);
});
markers.push(marker);
} // end for
// end "show prev clicks in database" section ----------------------------------------------------
} // end function initialize
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map-canvas">
</div>
</body>
</html>
Inside a marker's event handler, this refers back to the marker. Therefore :
this.setMap(null);
will remove the marker from the map.
But more can be done with this code. In particular :
Avoid repeating code by using addMarker() to add both new and previous markers.
When markers are removed, also remove them from the markers array.
Both can be achieved as follows :
function addMarker(event) {
var marker = new google.maps.Marker({
position: event.latLng,
title: event.title || "A",
map: map
});
google.maps.event.addListener(marker, 'rightclick', function(event) {
this.setMap(null);
//Remove the marker from the markers array.
for(i=0; i<markers.length; i++) {
if(markers[i] == this) {
removed = markers.splice(i, 1);
break;
}
}
});
markers.push(marker);
}
function initialize() {
...
google.maps.event.addListener(map, 'click', addMarker);
...
for (i = 0; i < PreviousClicks.length; i++) {
addMarker({
latLng: new google.maps.LatLng(PreviousClicks[i].lat, PreviousClicks[i].lng);
title: PreviousClicks[i].title
});
}
}
A wee bit out of my depth here, I hope someone can help me resolve this...
I have a Google map which loads data from an array and also populates a sidebar list of the items.
A click on the sidebar item opens the corresponding infowindow as expected o.k.
When I mouseenter a sidebar item it pans to if necessary and highlights the corresponding marker (and closes any infowindow currently open ).
(.locs is the sidebar container, .loc is an item)
$(document).on("mouseenter",".loc",function() {
var thisloc = $(this).data("locid");
for(var i=0; i<markers.length; i++) {
if(markers[i].locid == thisloc) { //get the latlong
if(lastinfowindow instanceof google.maps.InfoWindow) lastinfowindow.close();
map.panTo(markers[i].getPosition());
markers[i].setIcon('../mapIcons/mini-white.png');
}
}
});
This bit works fine as I'm just setting a fixed image.
However, when I mouseleave the marker fails to revert back to its original state.
If I use the setIcon for a specic image it works fine, but I want to refer back to the original icon from the dataset item.
markers[i].setIcon("currmark");
currmark is the var I used to hold the original icon.
$(document).on("mouseleave",".loc",function() {
var thisloc = $(this).data("locid");
var currmark = getIcon("mapData.type");
for(var i=0; i<markers.length; i++) {
if(markers[i].locid == thisloc) { //get the latlong
map.panTo(markers[i].getPosition());
markers[i].setIcon("currmark");
}
}
});
An alert set on 'currmark', the var I used to hold the original icon, shows only the last icon set by the mouseenter setIcon.
This seems to idicate to me that the original icon from the dataset (mapdata.type) has been forgotten / replaced by the last setting applied to markers[i].
I think I need to reaccess the dataset to remind the var of the original state when I set
var currmark = getIcon("mapData.type");
Some added info....
I'm using a utility function that translates a given 'type' in the dataset to an icon, hence the getIcon(type) function.
function getIcon(type) {
switch(type) {
case "bar": return "../mapIcons/mini-blue.png";
case "restaurant": return "../mapIcons/mini-red.png";
case "cafe": return "../mapIcons/mini-yellow.png";
default: return "../mapIcons/mini-white.png";
}
}
An example of a data entry...
var data = [
{address:'Rua Calheta',detail:'Restaurant',title:'Dolphin',type:'restaurant',ico:'red',lat:"37.08570947136275",long:"-8.731633722782135"}
];
I hope I've explained this half sensibly. Can anyone assist me, as I was dead chuffed I'd got this far.
2nd Additional Info for Beetroot showing how my marker and sidebar are initially setup....
data.forEach(function(mapData,idx) {
var marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(mapData.lat,mapData.long),
title: mapData.title,
icon: getIcon(mapData.type)
});
var link=('<a href="#"' + mapData.title +'">');
var contentHtml = "<div id='iw' class='iw2'><h3>" + link + mapData.title+"</a></h3>" + mapData.detail+"<br>"+mapData.address+"</div>";
var infowindow = new google.maps.InfoWindow({
content: contentHtml
});
google.maps.event.addListener(marker, 'click', function() {
if(lastinfowindow instanceof google.maps.InfoWindow) lastinfowindow.close();
marker.infowindow.open(map, marker);
lastinfowindow = marker.infowindow;
});
marker.locid = idx+1;
marker.infowindow = infowindow;
markers[markers.length] = marker;
var spot=('<img src="' + (getIcon(mapData.type)) + '" />');
var sideHtml = '<p class="loc" data-locid="'+marker.locid+'">'+spot+mapData.title+'</p>';
$("#locs").append(sideHtml);
if(markers.length == data.length) doFilter();
});
EDIT 02/07
The complete source after incorporating Beetroot-Beetroot's slick changes. Here in its entirety as I'm now vary aware that he can only help if he has the whole picture!
Now attempting to debug after learning I'm not such a smartass.
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false& style=feature:all|element:labels|visibility:off">
</script>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript">
var map;
var markers = [];
var lastinfowindow;
var locIndex;
//Credit: MDN
if ( !Array.prototype.forEach ) {
Array.prototype.forEach = function(fn, scope) {
for(var i = 0, len = this.length; i < len; ++i) {
fn.call(scope, this[i], i, this);
}
}
}
//my data ~ cutdown and local for testing
var data = [
{address:'Rua Calheta',detail:'Restaurant',title:'Dolphin',type:'restaurant',icotyp:'red',lat:"37.08570947136275",long:"-8.731633722782135"},
{address:'Rua Calheta',detail:'Cafe',title:'Cafe Calheta',type:'cafe',icotyp:'yellow',lat:"37.0858150589029",long:"-8.731550574302673"},
{address:'Rua Calheta',detail:'Bar',title:'Kellys',type:'bar',icotyp:'red',lat:"37.08583217639933",long:"-8.731239438056945"},
{address:'Rua 2nd Abril',detail:'Bar',title:'Godots',type:'bar',icotyp:'blue',lat:"37.08602046860496",long:"-8.731470108032226"}
];
// translate data type: to icon
function getIcon(type) {
switch(type) {
case "bar": return "../mapIcons/mini-blue.png";
case "restaurant": return "../mapIcons/mini-red.png";
case "cafe": return "../mapIcons/mini-yellow.png";
default: return "../mapIcons/mini-white.png";
}
}
function initialize() {
var latlng = new google.maps.LatLng(37.08597981464561, -8.730670809745788);
var myOptions = {
zoom: 18,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
// revised beetroot-beetroot 'loop' function to set markers and sidebar at the same time
var $locs = $("#locs");//Used repeatedly inside the loop.
$.each(data, function(idx, mapData) {
//Note: critical assignment of the new marker as a property of `mapData`.
mapData.marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(mapData.lat,mapData.long),
title: mapData.title,
icon: getIcon(mapData.type)
});
var link = '' + mapData.title + '';
var contentHtml = "<div id='iw' class='iw2'><h3>" + link + "</h3>" + mapData.detail + "<br>" + mapData.address + "</div>";
//What was `marker` must now be referred to as `mapData.marker`
mapData.marker.infowindow = new google.maps.InfoWindow({
content: contentHtml
});
google.maps.event.addListener(mapData.marker, 'click', function() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
//`this` refers back to the clicked element; the appropriate marker
this.infowindow.open(map, this);
lastinfowindow = this.infowindow;
});
var spot = '<img src="' + (getIcon(mapData.type)) + '" />';
//Here, it's convenient to create what was `sideitem` as a jQuery object, so we can apply several jquery methods (without making an assignment).
$('<p class="loc" />')
.html(spot + mapData.title)
.appendTo($locs)
.data('mapData', mapData);//<<<<<<<< critical
});
doFilter(); //It should be possible to execute this statement when the loop is finished.
$(document).on("mouseenter", ".loc", function() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
var mapData = $(this).data("mapData");
map.panTo(mapData.marker.getPosition());//<<<< direct reference to the marker avoids the need to loop.
mapData.marker.setIcon(getIcon(''));//get the default icon.
}).on("mouseleave", ".loc", function() {
var mapData = $(this).data("mapData");
mapData.marker.setIcon(getIcon(mapData.type));
}).on("click",".loc",function() {
var mapData = $(this).data("mapData");
mapData.marker.infowindow.open(map, mapData.marker);
lastinfowindow = mapData.marker.infowindow;
});
/*
Run on every change to any of the checkboxes
*/
function doFilter() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close(); //Close last info windows if still open and new filter applied
if(!locIndex) {
locIndex = {};
//I reverse index markers to figure out the position of loc to marker index
for(var x=0, len=markers.length; x<len; x++) {
locIndex[markers[x].locid] = x;
}
}
//what's checked?
var checked = $("input[type=checkbox]:checked");
var selTypes = [];
for(var i=0, len=checked.length; i<len; i++) {
selTypes.push($(checked[i]).val());
}
for(var i=0, len=data.length; i<len; i++) {
var sideDom = "p.loc[data-locid="+(i+1)+"]";
//Only hide if length != 0
if(checked.length !=0 && selTypes.indexOf(data[i].type) < 0) {
$(sideDom).hide();
markers[locIndex[i+1]].setVisible(false);
} else {
$(sideDom).show();
markers[locIndex[i+1]].setVisible(true);
}
}
}
$(document).on("click", "input[type=checkbox]", doFilter);
}
</script>
<style type="text/css">
#container {width:920px;height:500px; margin-left:auto;margin-right:auto;}
#map_canvas {width: 700px; height: 500px;float:left;border:1px gray solid;margin-right:10px;}
/* sidebar styling */
#locs {margin-top: 0px;padding-top: 0px;margin-left: 5px;height: 500px;width:200px;overflow-y: auto;overflow-x: hidden;}
#locs img {float:left;padding-right:5px;}
.loc {border: 0;width: 200px;padding: 2px;cursor: pointer;margin:0;text-transform:uppercase;font-family:Arial, Helvetica, sans-serif;font-size:0.90em;}
/* checkbox styling */
#form {margin-top: 0px;padding-top: 0px;height: 29px;}
.label {width: 32.5%;margin: 0;padding: 4px 0 4px 4px;display: inline-block;font-family: "Trebuchet MS", Verdana, Arial, sans-serif;text-transform: uppercase;font-size: 0.75em;font-weight: bold;}
.red {color:white;background-color:red;}
.yellow {color:yellow;background-color:black;}
.blue {color:white;background-color:blue;}
/* Map info styling */
#map_canvas div#iw {width:250px;height:65px;overflow:hidden;}
#map_canvas h3 {font-size:1em; margin:0; padding:3px;}
#map_canvas .iw2 a {text-decoration:none;border-bottom:thin blue dashed;}
#map_canvas .iw2 a:hover {text-decoration:none;border-bottom:thin red solid;}
</style>
</head>
<body onLoad="initialize()">
<div id="container">
<div id="map_canvas"></div> <!-- map //-->
<div id="locs"></div> <!-- sidebar //-->
<div id="form" style="margin:0;padding:0;border:thin gray solid;width:910px">
<span class="label blue"><input type="checkbox" name="bars" value="bar">Bars</span>
<span class="label yellow"><input type="checkbox" name="cafes" value="cafe">Cafes</span>
<span class="label red"><input type="checkbox" name="restaurants" value="restaurant">Restaurants</span>
</div>
</div> <!-- end of container //-->
</body>
</html>
EDIT 3 - 03/07 - Revised initialise marker / sidebar
// BB - revised each loop function to set markers and sidebar at the same time
var $locs = $("#locs");//Used repeatedly inside the loop.
$.each(data, function(idx, mapData) {
//BB - Note: critical assignment of the new marker as a property of `mapData`.
mapData.marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(mapData.lat,mapData.long),
title: mapData.title,
icon: getIcon(mapData.type)
});
var link = '' + mapData.title + '';
var contentHtml = "<div id='iw' class='iw2'><h3>" + link + "</h3>" + mapData.detail + "<br>" + mapData.address + "</div>";
//What was `marker` must now be referred to as `mapData.marker`
mapData.marker.infowindow = new google.maps.InfoWindow({
content: contentHtml
});
google.maps.event.addListener(mapData.marker, 'click', function() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
//BB - `this` refers back to the clicked element; the appropriate marker
this.infowindow.open(map, this);
lastinfowindow = this.infowindow;
});
//BB - Here, it's convenient to create what was `sideitem` as a jQuery object, so we can apply several jquery methods (without making an assignment).
var spot = '<img src="' + (getIcon(mapData.type)) + '" />';
$('<p class="loc" />')
.html(spot + mapData.title)
.appendTo($locs)
.data('mapData', mapData);//<<<<<<<< critical
//BB - At the bottom of the $.each() loop in initialize(), create and populate one array per category, as properties of markers ://
if(!markers[mapData.type]) markers[mapData.type] = [];
markers[mapData.type].push(mapData.marker);
});
// End of for/each loop
EDIT 4 - 07/07 - Revised full script for comment
<script type="text/javascript">
var map;
//var markers = [];
//object, not array. BB
var markers = {};
var lastinfowindow;
//var locIndex;
//Credit: MDN
if ( !Array.prototype.forEach ) {
Array.prototype.forEach = function(fn, scope) {
for(var i = 0, len = this.length; i < len; ++i) {
fn.call(scope, this[i], i, this);
}
}
}
//my data ~ cutdown and local for testing
var data = [
{address:'Rua Calheta',detail:'South African & Portuguese Restaurant',title:'Dolphin',type:'restaurant',lat:"37.08570947136275",long:"-8.731633722782135"},
{address:'Rua Calheta',detail:'Poruguese Cafe / Bar',title:'Cafe Calheta',type:'cafe',lat:"37.0858150589029",long:"-8.731550574302673"},
{address:'Rua Calheta',detail:'English Bar',title:'Kellys',type:'bar',lat:"37.08583217639933",long:"-8.731239438056945"},
{address:'Rua 2nd Abril',detail:'English Bar',title:'Godots',type:'bar',lat:"37.08602046860496",long:"-8.731470108032226"},
{address:'Rua 2nd Abril',detail:'Chinese Restaurant',title:'Royal Garden',type:'restaurant',lat:"37.086164896968405",long:"-8.731738328933715"},
{address:'Rua 2nd Abril',detail:'English Bar',title:'Clives',type:'bar',lat:"37.086125",long:"-8.731374"},
{address:'Rua 2nd Abril',detail:'English Restaurant',title:'The Lime Tree',type:'restaurant',lat:"37.086125877750365",long:"-8.731588125228881"},
{address:'Praia da Luz',detail:'Portuguese Restaurant',title:'Alloro',type:'restaurant',lat:"37.09158",long:"-8.724850"},
{address:'Rua Calheta',detail:'English Cafe / Bistro',title:'Jakes',type:'cafe',lat:"37.0862601125538",long:"-8.732671737670898"},
{address:'Av dos Pescadores',detail:'English & Portuguese Restaurant',title:'Chaplins',type:'restaurant',lat:"37.08550480361006",long:"-8.730005621910095"},
{address:'Av dos Pescadores',detail:'English & Portuguese Restaurant',title:'Atlantico',type:'restaurant',lat:"37.085425634814705",long:"-8.729941248893737"},
{address:'Av dos Pescadores',detail:'Indian Restaurant',title:'Saffron',type:'restaurant',lat:"37.08534432623613",long:"-8.729884922504425"},
{address:'Av dos Pescadores',detail:'Indian Restaurant',title:'Pashmina',type:'restaurant',lat:"37.08526729697597",long:"-8.729839324951171"},
{address:'Rua Calheta',detail:'English Bar',title:'The Bull',type:'bar',lat:"37.085652442494",long:"-8.73089075088501"},
{address:'Av dos Pescadores',detail:'English & Portuguese Restaurant',title:'Galley',type:'restaurant',lat:"37.08571577732778",long:"-8.729227781295776"},
{address:'Av dos Pescadores',detail:'English & Portuguese Bar',title:'Carlos',type:'bar',lat:"37.0856306176238",long:"-8.729227781295776"}
];
// translate data.type: to icon
function getIcon(type) {
switch(type) {
case "bar": return "../mapIcons/mini-blue.png";
case "restaurant": return "../mapIcons/mini-red.png";
case "cafe": return "../mapIcons/mini-green.png";
default: return "../mapIcons/mini-white.png"; // used as current selection indicator
//default: return "../mapIcons/marker-yellow-dot.png"; // will use instead of white later
}
}
function initialize() {
var latlng = new google.maps.LatLng(37.08597981464561, -8.730670809745788);
var myOptions = {
zoom: 17,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
//-----------------------------------------------------------------------------------------
/*
Start of for/each loop
*/
var $locs = $("#locs");//Used repeatedly inside the loop.
$.each(data, function(idx, mapData) {
//BB - Note: critical assignment of the new marker as a property of `mapData`.
// section 1 - Marker construct
mapData.marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(mapData.lat,mapData.long),
title: mapData.title,
icon: getIcon(mapData.type)
});
// section 1 end
// section 2 - Infowindow construct
var link = '' + mapData.title + ''; // make a link of name
var contentHtml = "<div id='iw' class='iw2'><h3>" + link + "</h3>" + mapData.detail + "</div>"; // style info window and add detail
// What was `marker` must now be referred to as `mapData.marker`
mapData.marker.infowindow = new google.maps.InfoWindow({
content: contentHtml,
});
// section 2 end
// section 3 - Map marker click function
google.maps.event.addListener(mapData.marker, 'click', function() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close(); // close any open infowindows
map.setZoom(18); // zoom in
map.setCenter(mapData.marker.getPosition());// centre on selection
//BB - `this` refers back to the clicked element; the appropriate marker
this.infowindow.open(map, this); // open infowindow
lastinfowindow = this.infowindow; // remember for closing this infowindow, if still open when selection changes
});
// section 3 end
// section 4 - Display Map marker and sidebar item
// BB - Here, it's convenient to create what was `sideitem` as a jQuery object, so we can apply several jquery methods (without making an assignment).
var spot = '<img src="' + (getIcon(mapData.type)) + '" />'; // sidebar copy of marker for display
$('<p class="loc" />')
.html(spot + mapData.title) // sidebar icon and name
.appendTo($locs) // add to sidebar container
.data('mapData', mapData); //<<<<<<<< critical
// BB Remove non matching from sidebar
marker.loc = $('<p class="loc" />').addClass(mapData.type).html(spot + mapData.title).data('marker', {m:marker, type:mapData.type}).appendTo($locs).get(0);
// BB - At the bottom of the $.each() loop in initialize(), create and populate one array per category, as properties of markers ://
if(!markers[mapData.type]) markers[mapData.type] = [];
markers[mapData.type].push(marker);
});
// End of for/each loop
//-----------------------------------------------------------------------------------------
} // end of initialise (?)
//-----------------------------------------------------------------------------------------
/*
Sidebar Functions
*/
$(document).on("mouseenter", ".loc", function() {
//if(lastinfowindow && lastinfowindow.close) lastinfowindow.close(); // moved to on click, avoids closing when accidently moving mouse
var mapData = $(this).data("mapData");
map.setZoom(18); // zoom in a bit
map.setCenter(mapData.marker.getPosition());// centre on selection
//map.panTo(mapData.marker.getPosition());// direct reference to the marker avoids the need to loop.
mapData.marker.setIcon(getIcon(''));//get the default icon ie. larger yellow spot.
}).on("mouseleave", ".loc", function() {
var mapData = $(this).data("mapData");
mapData.marker.setIcon(getIcon(mapData.type));//reset marker to correct type
map.setZoom(17); //rest zoom to default
map.setCenter(marker.getPosition()); // centre on map middle
}).on("click", ".loc", function() { // opens corresponding info window on map
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
var mapData = $(this).data("mapData");
//map.setZoom(19); // zoom in more maybe
mapData.marker.infowindow.open(map, mapData.marker); // open infowindow
lastinfowindow = mapData.marker.infowindow; // remember for closing this infowindow, if still open when selection changes
})
//-----------------------------------------------------------------------------------------
/*
Run on every change to any of the checkboxes
*/
function doFilter() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close(); // close last infowindow if still open
alert ("filter triggered");
$("input[type=checkbox]").each(function(i, checkbox) {
var markersArr = markers[checkbox.value];
if(markersArr) {
$.each(markersArr, function(i, marker) {
marker.loc.style.display = ['none', 'block'][Number(checkbox.checked)]; // don't display non selected types
marker.setVisible(checkbox.checked); // display selected type(s)
});
};
});
}
//-----------------------------------------------------------------------------------------
/*
Run on initial setup
*/
doFilter();
//-----------------------------------------------------------------------------------------
/*
fires on every checkbox change
*/
$("#form").on("click", ".cat", doFilter)
</script>
If, in the loop where the markers are created, you give each sidebar item a reference to its marker instead of the locid, then there's no need to loop to rediscover the marker on mouseenter/mouseleave.
ie, instead of setting :
//My best guess at what the statement must be.
thisloc.data("locid", id);
//Keep this line if thisloc.data("locid") is used elsewhere.
set (within the sidebar item/marker creation loop) :
var marker = ....;
var thisloc = ...;
...
data[i].marker = marker;//add a .marker property to the original dataset.
thisloc.data("itemData", data[i]);//associate the original dataset with the sidebar item
Now the mouseenter/mouseleave handlers should simplify as follows:
$(document).on("mouseenter", ".loc", function() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
var data = $(this).data("itemData");
map.panTo(data.marker.getPosition());//<<<< direct reference to the marker avoids the need to loop.
data.marker.setIcon(getIcon(''));//get the default icon.
}).on("mouseleave", ".loc", function() {
var data = $(this).data("itemData");
data.marker.setIcon(getIcon(data.type));
});
Note: You shouldn't need to pan to the marker again on mouseleave.
This should answer the question posed plus other advantages.
EDIT:
OK, now I've seen the main loop, it can be revised something like this, with various mods all through :
var $locs = $("#locs");//Used repeatedly inside the loop.
$.each(data, function(idx, mapData) {
//Note: critical assignment of the new marker as a property of `mapData`.
mapData.marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(mapData.lat,mapData.long),
title: mapData.title,
icon: getIcon(mapData.type)
});
var link = '' + mapData.title + '';
var contentHtml = "<div id='iw' class='iw2'><h3>" + link + "</h3>" + mapData.detail + "<br>" + mapData.address + "</div>";
//What was `marker` must now be referred to as `mapData.marker`
mapData.marker.infowindow = new google.maps.InfoWindow({
content: contentHtml
});
google.maps.event.addListener(mapData.marker, 'click', function() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
//`this` refers back to the clicked element; the appropriate marker
this.infowindow.open(map, this);
lastinfowindow = this.infowindow;
});
var spot = '<img src="' + (getIcon(mapData.type)) + '" />';
//Here, it's convenient to create what was `sideitem` as a jQuery object, so we can apply several jquery methods (without making an assignment).
$('<p class="loc" />')
.html(spot + mapData.title)
.appendTo($locs)
.data('mapData', mapData);//<<<<<<<< critical
});
doFilter();//It should be possible to execute this statement when the loop is finished.
And the corresponding mouseenter/mouseleave handlers would be :
$(document).on("mouseenter", ".loc", function() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
var mapData = $(this).data("mapData");
map.panTo(mapData.marker.getPosition());//<<<< direct reference to the marker avoids the need to loop.
mapData.marker.setIcon(getIcon(''));//get the default icon.
}).on("mouseleave", ".loc", function() {
var mapData = $(this).data("mapData");
mapData.marker.setIcon(getIcon(mapData.type));
});
All untested, so may well need debugging.
EDIT2 :
The way I left it above, the array markers wasn't being populated. Now I have seen the function doFilter() I can see that markers should be populated, however it is more convenient for it to be an object rather than an array.
markers = {};//in the same scope as before
At the bottom of the $.each() loop in initialize(), create and populate one array per category, as properties of markers :
if(!markers[mapData.type]) markers[mapData.type] = [];
markers[mapData.type].push(mapData.marker);
With the markers populated in this way, you have a very simple means of identifying all markers in each category, and doFilter() will simplify enormously to :
function doFilter() {
if(lastinfowindow && lastinfowindow.close) lastinfowindow.close();
$("input[type=checkbox]").each(function(i, checkbox) {
var markersArr = markers[checkbox.value];
if(markersArr) {
$.each(markersArr, function(i, marker) {
marker.setVisible(checkbox.checked);
});
}
});
}
EDIT3
To get the sidebar entries to show/hide in sympathy with the category controls, initialize() should be as follows :
function initialize() {
var latlng = new google.maps.LatLng(37.08597981464561, -8.730670809745788);
var myOptions = {
zoom: 18,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var $locs = $("#locs");//Used repeatedly inside the loop.
var marker;
$.each(data, function(idx, mapData) {
//Note: critical assignment of the new marker as a property of `mapData`.
marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(mapData.lat, mapData.long),
title: mapData.title,
icon: getIcon(mapData.type)
});
var link = '' + mapData.title + '';
var contentHtml = "<div id='iw' class='iw2'><h3>" + link + "</h3>" + mapData.detail + "<br>" + mapData.address + "</div>";
marker.infowindow = new google.maps.InfoWindow({
content: contentHtml
});
google.maps.event.addListener(marker, 'click', function() {
closeLastInfoWindow();
//`this` refers back to the clicked element; the appropriate marker
this.infowindow.open(map, this);
lastinfowindow = this.infowindow;
});
var spot = '<img src="' + (getIcon(mapData.type)) + '" />';
//Here, it's convenient to create what was `sideitem` as a jQuery object, so we can apply several jquery methods.
marker.loc = $('<p class="loc" />')
.addClass(mapData.type)
.html(spot + mapData.title)
.data('marker', {m:marker, type:mapData.type})
.appendTo($locs).get(0);
if(!markers[mapData.type]) markers[mapData.type] = [];
markers[mapData.type].push(marker);
});
$(document).on("mouseenter", ".loc", function() {
closeLastInfoWindow();
var m = $(this).data("marker").m;
map.panTo(m.getPosition());//<<<< direct reference to the marker avoids the need to loop.
m.setIcon(getIcon(''));//get the default icon.
}).on("mouseleave", ".loc", function() {
var data = $(this).data("marker");
data.m.setIcon(getIcon(data.type));
}).on("click",".loc",function() {
var m = $(this).data("marker").m;
m.infowindow.open(map, m);
lastinfowindow = m.infowindow;
});
$("#form").on("click", ".cat", doFilter);
doFilter();
}
markers[i].setIcon("currmark") won't work (sets the icon to the string "currmark"). Use the variable: markers[i].setIcon(currmark).
$(document).on("mouseleave",".loc",function() {
var thisloc = $(this).data("locid");
var currmark = getIcon("mapData.type");
for(var i=0; i<markers.length; i++) {
if(markers[i].locid == thisloc) { //get the latlong
map.panTo(markers[i].getPosition());
markers[i].setIcon(currmark);
}
}
});