Multiple Google Maps In One Element
Updated: Apr 19, 2024
Submitted for your consideration: several different Google maps, of different areas, with different markers, each dynamically displayed on the click of a button. You are about to enter another dimension. A dimension not only of sight and sound, but of mind. A journey into a wondrous land of imagination (felt obliged to do that, sorry not sorry :) ).
Here's a decent mock-up of the upcoming markup:
Let's start, as one always should, with some HTML, which Eleventy is wonderfully capable of, so...
On To The Show
regional-maps.njk<section class="regional-maps">
<div id="map0" class="regional-map"></div>
<div id="map1" class="regional-map"></div>
<div id="map2" class="regional-map"></div>
<div id="map3" class="regional-map"></div>
</section>
Note that by saying "in one element", I have meant the "section" element above. Each regional map is destined for the appropriate "div" element. Still, quite reasonable markup for the best performance.
Let's start examinnig this with arguably the simplest part, the CSS. The first class defines the height of the containing "section", the second class defines the height of the map-containing "div". Note Google requires you do declare an explicit height for it's targeted map divs, or the browser displays nothing. This is why there is no change in visibility for each map div, as the not showing it has been handled for us by changing the height.
regional-maps.css.regional-maps {
height: 95%;
}
.regional-map-active {
height: 100%;
}
Next to define is the data that goes into placing the markers for each location in the map. I use a json file that holds all the data I need. As you can see on my cider-trail, there's more than a few entries, so here I'll pare down the json file for demonstration purposes.
cider-data.json[
{
"name": "Stowe Cider",
"latitude": 44.475908081446896,
"longitude": -72.70179481660405,
"region": "northcentral"
},
{
"name": "Champlain Orchards",
"latitude": 43.85703964139849,
"longitude": -73.35300664546972,
"region": "southcentral"
},
{
"name": "Eden Ciders",
"latitude": 44.93614295874543,
"longitude": -72.2091493435647,
"region": "north"
},
{
"name": "Little City Cider Company",
"latitude": 42.89612480654151,
"longitude": -73.19045707461014,
"region": "south",
}
]
The heart of this matter moving forward is the javscript. And bonus, no outside help is needed as it's all vanilla javascript. It's broken into two main efforts: (1) handling stuff for and with Google, and (2) handling the button clicking
regional-maps-google-stuff.js// the name of this function must match the name provided to Google Maps as the callback in your script element
// everything happens inside here
function initMap() {
// retreive the json data needed
async function getCider() {
const response = await fetch('/data/cider.json');
const ciderData = await response.json();
return ciderData;
};
// deal with the json data to create the maps
async function handleCiders() {
// here's the data
let ciders = await getCider();
// let's establish some Google related stuff here
let map, marker, count;
let infoWindow = new google.maps.InfoWindow({});
// on to our vanilla javascript manipulating our data
// initialize each region
let northMap = [];
let northCentralMap = [];
let southCentralMap = [];
let southMap = [];
// state-wide array of regional arrays
let bigArray = [];
// loop through the json data to fill the individual regional arrays
// created above
for (let i = 0; i < ciders.length; i++) {
switch(ciders[i].region) {
case 'north':
northMap.push(ciders[i]);
break;
case 'northcentral':
northCentralMap.push(ciders[i]);
break;
case 'southcentral':
southCentralMap.push(ciders[i]);
break;
case 'south':
southMap.push(ciders[i]);
break;
};
}; // end for
// bring each regional array into a state-wide bigArray
bigArray.push(
northMap,
northCentralMap,
southCentralMap,
southMap
);
//
// loop through the big array of regional arrays to put together everything google needs
// to have four maps on one page in the same <section>
//
for (let i = 0; i < bigArray.length; i++) {
// by establishing a different mapTarget on each iteration, we are creating a different map/mapTarget for each <div id="mapX">
const mapTarget = document.getElementById('map' + i);
const mapOptions = {
// this happens to be the center of Vermont, yours will be different of course
center: {lat: 44.0407, lng: -72.7093},
zoom: 8,
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
zoomControl: true,
streetViewControl: false,
mapTypeId: google.maps.MapTypeId.TERRAIN,
// this is optional, but it's possible to define your own map styles, this resource is pretty cool:
//styles courtesy: https://snazzymaps.com/style/61/blue-essence
styles:[]
};
// create new map for each region on each trip through the bigArray
// this is mostly magic by Google
map = new google.maps.Map(mapTarget, mapOptions);
//
// inner for...loop to go through each regional array
// note the index variable is a different letter!
//
for (let j = 0; j < bigArray[i].length; j++) {
// establish each establishment as an object to be used
let ciderObject = bigArray[i][j];
//create content for Google's marker popup infowindow
function windowContent() {
var content = document.createElement('div');
content.setAttribute('class', 'cideries-map-infoslider');
// all the data here, ${object.xyz} comes from the json file,
// but for this purpose I truncated that out of the demo json file above
content.innerHTML = `<div class="cideries-map-infoslider-image">
<img src=".${ciderObject.cider100}" alt="${ciderObject.selector}-logo"/>
</div>
<div class="cideries-map-infoslider-details">
<p class="cideries-map-infoslider-name">${ciderObject.name}</p>
<p class="cideries-map-infoslider-address">${ciderObject.address}</p>
<p class="cideries-map-infoslider-address">${ciderObject.city}, VT</p>
<a class="cideries-map-infoslider-link" href="./cideries/${ciderObject.selector}">View Cidery Details</a>
</div>`;
return content;
}; // end create windowContent function
// create markers for each of the maps
marker = new google.maps.Marker({
position: new google.maps.LatLng(
ciderObject.latitude,
ciderObject.longitude
),
map:map,
icon: {url: "https://maps.google.com/mapfiles/ms/icons/blue-dot.png"},
title: 'Cidery Info:'
}); // end marker creation
// then add listener for click on marker to pop up info window
let markerCount = [i][j];
google.maps.event.addListener(marker, 'click',
(function (marker, markerCount) {
return function () {
infoWindow.setContent(windowContent(
ciderObject.name,
ciderObject.address,
ciderObject.city,
ciderObject.selector,
ciderObject.cider100
));
infoWindow.open(map, marker);
}
})(marker, markerCount),{passive:true});
};// end inner for..loop (j)
}; //end bigArray for loop (i)
}; // end handleCider
handleCiders();
}; // end initMap
Nothing to say here as the javascript is fully documented and explained above. So now we'll move on to handling the region buttons I mocked up above. I've put it in the same file as above.
regional-maps-google-stuff.js// let's start with an iife, so fun, so safe
(() => {
'use strict';
// everything happens inside here
const buttonBehavior = {
// first things first
init() {
let mapChoose = document.querySelector('.regional-choose');
let mapSection = document.querySelector('.regional-maps');
let mapElements = mapSection.querySelectorAll('.regional-map');
// note the event listener is not on each button, but on the section (.regional-choose) they're in
// the click is captured during the act of bubbling
// that's javascript magic to you and me
mapChoose.addEventListener('click', (event) => {
// sending the information collected to be used in the named function
buttonBehavior.initializeTargetMap(event.target, mapElements);
});
},
// find exactly which button was clicked
initializeTargetMap (clickedButton, mapElements) {
const buttonClasses = clickedButton.classList;
// extract the class name string to be used by the switch expression
// I happen to know that the class name I'm looking for is the second one in the classList
// your situation may well be different
let region = buttonClasses.item(1);
// switch for the win
// each case sends data to adjust the final on-screen render:
// note each mapElement[] matches our markup, specifically the id for each target div
// and the entire nodeList of target divs
switch (region) {
case 'north':
buttonBehavior.adjustClasses(mapElements[0], mapElements);
break;
case 'north-central':
buttonBehavior.adjustClasses(mapElements[1], mapElements);
break;
case 'south-central':
buttonBehavior.adjustClasses(mapElements[2], mapElements);
break;
case 'south':
buttonBehavior.adjustClasses(mapElements[3], mapElements);
break;
};
},
// un-render and render the appropriate target divs
adjustClasses(mapElement, mapElements) {
// run through all the target divs in the nodeList
mapElements.forEach(mapItem => {
// remove active class if it exists
if (mapItem.classList.contains('regional-map-active')) {
mapItem.classList.remove('regional-map-active');
}
});
// add active class to proscribed map div element
mapElement.classList.add('regional-map-active');
}
} // end buttonBehavior const
buttonBehavior.init();
})();
Again, the above javascript should be fully documented.
There's one last item to take care of here, and that is to involve Google herself. The script element in your footer:
footer.njk<script async
src="https://maps.googleapis.com/maps/api/js?key=ABCXYZ&callback=initMap">
</script>
So To Recap
- A counter-intuitive problem to solve
- Plain ol' javascript is your friend
- All kinds of maps, all in one place!
TOP