Back to all articles
ACF Google Map Integration Using Vanilla JavaScript
Tutorial

ACF Google Map Integration Using Vanilla JavaScript

Updated on December 21, 2023

Looking for a new website?

Get in touch
Save your eyes, enable dark-mode!

When it comes to developing websites on WordPress, the ACF (Advanced Custom Fields) plugin is an invaluable tool. It allows you to easily add and manage custom fields on your website. While the ACF documentation provides comprehensive guidance, it primarily focuses on jQuery, which is included with WordPress by default.

But what if you’re working on a theme that doesn’t utilize jQuery? In this article, we’ll walk you through an example of integrating the ACF Google Map field using plain JavaScript, making it accessible in your website if you’re using bundlers.

acf google map

Introduction

This post will not learn you how to work with the ACF Map element or any other ACF features. This is solely a jQuery to vanilla js translation of the Google Map element because I needed it myself. So now I can come back to this post at any time and hope someone else could benefit from this as well.

ACF Map without jQuery

At our agency, we recently switched from jQuery-based templates, back to vanilla javascript to better support OOP programming and switch to more modern tools like ViteJS.

Javascript Setup

To ensure a clean and organized code structure, we will be utilizing an object-oriented programming (OOP) approach. In this example, we’ll create an exportable class called “Contact” to handle the Google Map functionality specifically for the contact page. However, if you plan to use a map on multiple pages, it’s recommended to create a more generic “Map” class instead.

Using classes allows us to encapsulate related functionality, making it easier to manage and reuse code throughout our project. With that in mind, this would be our base setup for the Contact.js class file:

export default class Contact {
  // currently this is it
}

In the ACF docs, a CDN is used to load the Google Maps API. However, in this example, we will take advantage of the js-api-loader package from npm, which allows us to easily install and use the API code within our class.

npm install @googlemaps/js-api-loader

// or with Yarn
yarn add @googlemaps/js-api-loader

Once it’s installed and you don’t have any errors, we can import the library and make the first steps for the Class setup:

import { Loader } from '@googlemaps/js-api-loader';

export default class Contact {
  constructor() {
    this.loader = new Loader({
      apiKey: 'YOUR_API_KEY',
      version: 'weekly',
    });
  }
}

That’s all there’s necessary to load the Google Maps script through NPM. With the constructor set up, we can now focus on identifying the HTML div element where our map will be displayed. In case we need to handle multiple maps, we’ll loop over all instances. For this, we’ll use the $el variable, which follows the ACF Maps jQuery documentation closely.

Our final constructor in the Contact class will look like this after we’ve included our map-div:

import { Loader } from '@googlemaps/js-api-loader';

export default class Contact {
  constructor() {
    this.$el = document.querySelectorAll('.location__map');
    this.map = null;

    this.loader = new Loader({
      apiKey: 'YOUR_API_KEY',
      version: 'weekly',
    });
    
    this.$el.forEach($el => {
      this.initMap($el);
    })
  }
}

Render a Google Map into a selected element (initMap function)

In the last edit to our constructor, we are looping over all .location__map divs on the page. For each of those divs, we now want to initialize the Google Map script to load the Google Map inside that div. For that, we’ll be using the initMap function. Translated from the ACF Map docs, the initMap function in plain javascript looks like this:

async initMap($el) {
  const $markers = $el.querySelectorAll('.marker');

  const mapOptions = {
    zoom: parseInt($el.dataset.zoom) || 16,
    mapTypeId: 'roadmap',
  };

  await this.loader.load();

  this.map = new google.maps.Map($el, mapOptions);
  this.map.markers = [];

  $markers.forEach($marker => {
    this.initMarker($marker, this.map);
  });

  this.centerMap(this.map);
}

This function needs to be async as we’re using the await function to wait till the Google Maps Loader is ready. After the await this.loader.load() function, we have access to the google variable. Otherwise, our script will complain that “google” is undefined. (This is also the reason why we use mapTypeId: 'roadmap' in our mapOptions, as google is not yet available there).

The initMap function is done, but now we’ve introduced 2 new functions that we don’t have yet, the initMarker function and the centerMap function. Let’s start with initMarker().

Create a marker for the given element in our map (initMarker function)

This function will require 2 parameters, the marker element with the location info for the marker and the map element so we know on which map we want to place this marker. The initMarker function will look like this:

initMarker($marker, map) {
  const lat = parseFloat($marker.dataset.lat);
  const lng = parseFloat($marker.dataset.lng);
  const latLng = { lat, lng };

  const marker = new google.maps.Marker({
    position: latLng,
    map: map,
  });

  map.markers.push(marker);

  if($marker.innerHTML) {
    const infowindow = new google.maps.InfoWindow({
      content: $marker.innerHTML,
    });

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

Aaand we’re done with the initMarker function. Why doesn’t this need to be an async function? Because we’re not waiting on something. InitMarker is only called in our initMap() function, which is asynchronous. But this makes the google object always available in this function too – as it is running after the Loader was called in initMap().

The final step:

Center Google Map to show all markers in a view (centerMap)

The last function being used in the ACF Map documentation is the centerMap function when you’re rendering multiple markers in one map – and you would like to show all of them in one view when the map loads.

The centerMap():

centerMap(map) {
  const bounds = new google.maps.LatLngBounds();

  map.markers.forEach(marker => {
    bounds.extend({
      lat: marker.position.lat(),
      lng: marker.position.lng(),
    });
  });

  if(map.markers.length === 1){
    map.setCenter(bounds.getCenter());
  } else {
    map.fitBounds(bounds);
  }
}

With the centerMap function, we’re done with the translation for the ACF Map documentation from jQuery to vanilla javascript. Also here google is already available, so this function does not need to be asynchronous)

The ACF Map in jQuery translation to vanilla Javascript (OOP)

To have a clear overview of our script, here’s the full Contact class that should be an exact jQuery to vanilla javascript translation from the installation script used on the ACF Map explanation:

import { Loader } from '@googlemaps/js-api-loader';

export default class Contact {
  constructor($el = null) {
    this.$el = document.querySelectorAll('.location__map');

    this.loader = new Loader({
      apiKey: "YOUR_API_KEY",
      version: "weekly",
    });

    this.map = null;
    this.$el.forEach($el => {
      this.initMap($el);
    });
  }
  
  /** initMap */
  async initMap($el) {
    const $markers = $el.querySelectorAll('.marker');

    const mapOptions = {
      zoom: parseInt($el.dataset.zoom) || 16,
      mapTypeId: 'roadmap',
    };

    await this.loader.load();

    this.map = new google.maps.Map($el, mapOptions);

    this.map.markers = [];

    $markers.forEach($marker => {
      this.initMarker($marker, this.map);
    });

    this.centerMap(this.map);
  }

  /** initMarker */
  initMarker($marker, map) {
    const lat = parseFloat($marker.dataset.lat);
    const lng = parseFloat($marker.dataset.lng);
    const latLng = { lat, lng };

    const marker = new google.maps.Marker({
      position : latLng,
      map: map,
    });

    map.markers.push(marker);

    if($marker.innerHTML){
      const infowindow = new google.maps.InfoWindow({
        content: $marker.innerHTML
      });

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

  /** centerMap */
  centerMap(map) {
    const bounds = new google.maps.LatLngBounds();
    map.markers.forEach(marker => {
      bounds.extend({
        lat: marker.position.lat(),
        lng: marker.position.lng(),
      });
    });

    if(map.markers.length === 1){
      map.setCenter(bounds.getCenter());
    } else {
      map.fitBounds(bounds);
    }
  }
}

ACF Google Map PHP requirements

You also need to install Google Maps in your functions.php so also the WordPress backend can use Google Maps. When you use only the npm installation from earlier in this post, the map will only work on the front end. To register your Google API key in your WordPress website, please us one of the following methods:

// Method 1: Filter.
function my_acf_google_map_api($api){
  $api['key'] = 'xxx';
  return $api;
}
add_filter('acf/fields/google_map/api', __NAMESPACE__ . 'my_acf_google_map_api');

// Method 2: Setting.
function my_acf_init() {
  acf_update_setting('google_api_key', 'xxx');
}
add_action('acf/init', __NAMESPACE__ . 'my_acf_init');

Some CSS to style the Google Map

.location__map {
    width: 100%;
    height: 400px;
    border: #ccc solid 1px;
    margin: 20px 0;
}

.location__map img {
   max-width: inherit !important;
}

Let me know if this worked for you or if you’re having other problems. I’m interested in finetuning this script and maybe we can make it into an npm library. As Weichie.com uses ACF maps regularly in our projects, having an acf-google-map library would be nice to have.

Leave a Reply

Your email address will not be published. Required fields are marked *

2 Comments