Back

MooseDog Studios

Here in Vermont, Convention Takes A Back Seat To Quality

MooseDog Studios Autumn Hero Picture

Multiple Google Maps In One Element

Published: Jun 24, 2023 Updated: Feb 20, 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

TOP

SHARE