A responsive, CMS-driven, multi-pin Google Map

This fully-customizable, fully-responsive, CMS-driven Google Map works great for real estate projects, multi-location clients, point-of-interest maps, member community maps, & more.

Bon Ton

674 Myrtle St NE
Atlanta, GA 30308
+14049966177
bon-ton
33.7729939
-84.3826027
Bon Ton
674 Myrtle St NE
Atlanta, GA 30308

The Earl

488 Flat Shoals Ave SE
Atlanta, GA 30316
+14045223950
the-earl
33.7409816
-84.3480989
The Earl
488 Flat Shoals Ave SE
Atlanta, GA 30316

The Plaza

1051 Ponce De Leon Ave NE
Atlanta, GA 30306
the-plaza
33.773441
-84.3552265
The Plaza
1051 Ponce De Leon Ave NE
Atlanta, GA 30306

Daddy D'z

264 Memorial Dr SE
Atlanta, GA 30312
+14042220206
daddy-dz
33.7470644
-84.3811636
Daddy D'z
264 Memorial Dr SE
Atlanta, GA 30312

Bookhouse

736 Ponce De Leon Ave NE
Atlanta, GA 30306
+14042541176
bookhouse
33.7744016
-84.3671737
Bookhouse
736 Ponce De Leon Ave NE
Atlanta, GA 30306

Home Grown

968 Memorial Dr SE
Atlanta, GA 30316
+14042220455
home-grown
33.7466893
-84.3603607
Home Grown
968 Memorial Dr SE
Atlanta, GA 30316

Star Bar

437 Moreland Ave NE
Atlanta, GA 30307
+14043903062
star-bar
33.7662834
-84.3510849
Star Bar
437 Moreland Ave NE
Atlanta, GA 30307

Brick + Mortar

1170 Howell Mill Rd
Atlanta, GA 30318
+14044929207
brick-mortar
33.7859225
-84.4166867
Brick + Mortar
1170 Howell Mill Rd
Atlanta, GA 30318

Wax N' Facts

432 Moreland Ave NE
Atlanta, GA 30307
+14045252275
wax-n-facts
33.7660851
-84.3515813
Wax N' Facts
432 Moreland Ave NE
Atlanta, GA 30307

Midtown Art Cinemas

931 Monroe Dr NE
Atlanta, GA 30308
+14048790160
midtown-art-cinemas
33.7791324
-84.3694441
Midtown Art Cinemas
931 Monroe Dr NE
Atlanta, GA 30308

Glad + Young

675 Ponce De Leon Ave NE N 219 Floor 2
Atlanta, GA 30308
glad-young
33.7727683
-84.3831062
Glad + Young
675 Ponce De Leon Ave NE N 219 Floor 2
Atlanta, GA 30308

Fred's Meat & Bread

99 Krog St NE
Atlanta, GA 30307
+14046883733
freds-meat-bread
33.756846
-84.3661228
Fred's Meat & Bread
99 Krog St NE
Atlanta, GA 30307

Highland Inn & Ballroom

644 North Highland Avenue Northeast #130
Atlanta, GA 30306
+14048745756
highland-inn-ballroom
33.7719531
-84.354964
Highland Inn & Ballroom
644 North Highland Avenue Northeast #130
Atlanta, GA 30306

Kudzu

2928 E Ponce de Leon Ave
Decatur, GA 30030
+14043736498
kudzu
33.7828029
-84.2759739
Kudzu
2928 E Ponce de Leon Ave
Decatur, GA 30030

Oxford Comics

2855 Piedmont Rd NE
Atlanta, GA 30305
+14042338682
oxford-comics
33.769513
-84.3857433
Oxford Comics
2855 Piedmont Rd NE
Atlanta, GA 30305

Ok Yaki

714 Moreland Ave SE Suite D
Atlanta, GA 30316
+14049999254
ok-yaki
33.7348112
-84.3510175
Ok Yaki
714 Moreland Ave SE Suite D
Atlanta, GA 30316

Implementation notes

Last updated: 1/28/2004

Step 1 - Creating the collections

The name, latitude (plain text), & longitude (plain text) fields are all required. Any other information intended to display either in the location cards or in the location info window will also need to be included. This particular project also includes a second collection for location categories, which are displayed as icons here.

Step 2 - Building the UI

The bulk of the UI is made up of the cards in the sidebar, which display the basic information about each location. Critically, the link to trigger the map info window (Location Link inside of the Map Link embed) is javascript, so it has to be created in an embed. This means there are 3 options for situation the link: custom code all the content of the card so that it can be wrapped in the custom link element, embed a clickable link element inside the card, or embed a link element outside of the card & position it absolutely on top of the card. For this project, we've chosen the third so that the entire card is clickable, but the content can still be edited in javascript.

<!-- Link to open corresponding info window -->
<a href="javascript:google.maps.event.trigger(gmarkers['{{slug}}'],'click');" class="location-link position-absolute---full"></a>

The Map window needs a #map ID for the javascript to inject the Google Map into. And the location card Collection Item needs the class Location Item in order to be targeted by the javascript.

Step 3 - Create hidden data

An embed needs to live hidden inside the location card Collection Item which will contain the slug & latitude/longitude variables referenced in the javascript.

<div class="data---slug w-dyn-bind-empty">{{slug}}</div>
<div class="data---latitude w-dyn-bind-empty">{{latitude}}</div>
<div class="data---longitude w-dyn-bind-empty">{{longitude}}</div>

The content intended to display in the Info Window will also need to live inside a Data - Info Window div nested inside the hidden div.

Step 4 - Include the Google Maps API javascript

Paste the following code at the end of your page's <body>.

<script async defer src="https://maps.googleapis.com/maps/api/js?key[your-API-key]&callback=initMap" type="text/javascript"></script>

Step 5 - Include the custom javascript

Paste the following code after the Google Maps API javascript.

<script>

  // This script is adapted from Anna Sabatini's dynamic Google Maps project
  // https://discourse.webflow.com/t/tutorial-adding-several-markers-on-one-google-map-from-dynamic-collection/114410

  window.addEventListener('load', function () {
    var locations = [];
    var dynPlaces = document.querySelectorAll('.w-dyn-item.location-item');

    dynPlaces.forEach(function (elem) {
      var place = [];
      var dataTitle = elem.querySelector('.data---slug').innerText;
      var dataLat = Number(elem.querySelector('.data---latitude').innerText);
      var dataLong = Number(elem.querySelector('.data---longitude').innerText);
      var infoWindowContent = elem.querySelector('.data---info-window').innerHTML;

      place.push(dataTitle, infoWindowContent, dataLat, dataLong);
      locations.push(place);
    });

    var map = new google.maps.Map(document.getElementById('map'), {
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      streetViewControl: false,
      mapTypeControl: false,
      fullscreenControl: false,
      scrollwheel: false,
      styles: [
        // Add your provided styles here
        {
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#faf4e8"
            }
          ]
        },
        {
          "elementType": "labels.icon",
          "stylers": [
            {
              "visibility": "off"
            }
          ]
        },
        {
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#2e2a2a"
            }
          ]
        },
        {
          "elementType": "labels.text.stroke",
          "stylers": [
            {
              "color": "#faf4e8"
            }
          ]
        },
        {
          "featureType": "administrative.land_parcel",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#a0938c"
            }
          ]
        },
        {
          "featureType": "administrative.province",
          "elementType": "geometry.stroke",
          "stylers": [
            {
              "color": "#7e7371"
            },
            {
              "visibility": "on"
            },
            {
              "weight": 1.5
            }
          ]
        },
        {
          "featureType": "poi",
          "stylers": [
            {
              "color": "#7e7371"
            },
            {
              "visibility": "on"
            }
          ]
        },
        {
          "featureType": "poi",
          "elementType": "geometry.fill",
          "stylers": [
            {
              "color": "#faede1"
            }
          ]
        },
        {
          "featureType": "poi",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#504949"
            }
          ]
        },
        {
          "featureType": "poi",
          "elementType": "labels.text.stroke",
          "stylers": [
            {
              "color": "#faf4e8"
            }
          ]
        },
        {
          "featureType": "poi.park",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#f2e0ce"
            }
          ]
        },
        {
          "featureType": "poi.park",
          "elementType": "labels.text.fill",
          "stylers": [
                        {
              "color": "#706665"
            }
          ]
        },
        {
          "featureType": "road",
          "stylers": [
            {
              "visibility": "simplified"
            }
          ]
        },
        {
          "featureType": "road",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#ffffff"
            }
          ]
        },
        {
          "featureType": "road.arterial",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#504949"
            }
          ]
        },
        {
          "featureType": "road.highway",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#eddbc9"
            }
          ]
        },
        {
          "featureType": "road.highway",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#2e2a2a"
            }
          ]
        },
        {
          "featureType": "road.local",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#706665"
            }
          ]
        },
        {
          "featureType": "transit.line",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#f2e0ce"
            }
          ]
        },
        {
          "featureType": "transit.station",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#faede1"
            }
          ]
        },
        {
          "featureType": "water",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#e6d4c3"
            }
          ]
        },
        {
          "featureType": "water",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#706665"
            }
          ]
        }
      ]
    });

    var infowindow = new google.maps.InfoWindow();

    function createMarker(latlng, html) {
      var marker = new google.maps.Marker({
        position: latlng,
        map: map,
        icon: "https://cdn.prod.website-files.com/65aeaafb5fffb1901c9957c3/65b6a61b255d3d0c963339f0_Google_Maps_pin.svg"
      });

      google.maps.event.addListener(marker, 'click', function() {
        infowindow.setContent(html);
        infowindow.open(map, marker);
      });
      return marker;
    }

    gmarkers = [];
    for (var i = 0; i < locations.length; i++) {
      gmarkers[locations[i][0]] =
        createMarker(new google.maps.LatLng(locations[i][2], locations[i][3]),
                                locations[i][1]);
    }

    var bounds = new google.maps.LatLngBounds();
    for (var i = 0; i < locations.length; i++) {
      bounds.extend(new google.maps.LatLng(locations[i][2], locations[i][3]));
    }
    map.fitBounds(bounds);
    map.setCenter(bounds.getCenter());
  }, false);

</script>

Step 6 - Customize the map

Optionally, you can update the map styling with Google's Styling Wizard. Replace the json in the styles section of the javascript. Alternatively, you can simply update the hexadecimal values in the script.