ACF PRO Maps - best practise for the "Bedrock" "Sage" way


#1

I’ve been over the forum but can’t see a complete answer.

I’m including ACF Pro in my project simply by installing the plugin in app/mu-plugins/

Step 1:
In setup.php I’m including the key so the map works in the admin:
// Google maps api key for ACF
function acf_google_map_api($api) {
$api[‘key’] = ‘API_KEY_HERE’;
return $api;
}
add_filter(‘acf/fields/google_map/api’, NAMESPACE . ‘\acf_google_map_api’);

Step 2:
I’ve added the SCSS into styles/components/map/_map.scss

Step 3
I’m registering the script with the API key for the frontend… I will wrap this in conditionals like if is_page etc… unless it’s better doing this via routes? if so an example would be appreciated.

wp_register_script('googlemap', "https://maps.googleapis.com/maps/api/js?key=KEY_HERE", false, false, false);
wp_enqueue_script('googlemap');

Step 4
I’m using a partial to setup the map:
public function property_map()
{
return get_field(‘property_map’);
}

So that I can access it in the view like so:

@if ($property_map)

@endif

Step 5
I can’t for the life of me work out how to include the ACF JS into the common.js scripts… I’ve got an example that copies the ACF code with a comment at the top:

/* eslint-disable no-unused-vars */

However the code I found tries to load google like so:
import google from ‘google’;

I don’t feel I need to do that but I’m guessing I need to tell webpack that google isn’t coming from an NPM module?

Can someone help shed some light please or let me know if I’m making a hash of it!

I can get it working with all the steps mentioned above and the ACF code inline on my single property page to help test the first 4 steps are good. It’s just the packaging of the ACF JS code I’m unsure of now.


#2

Sods law… got it working by creating a map.js route, adding a “map” class to the body as I feel thats a sensible way to bring the code in. And then used /*eslint-disable */ at the top of my map.js file.

I think the eslint comment I was copying and pasting was to force an error if undefined vars.

If anyone can comment on if this is messy or if I can improve my enqueue script process to watch for a body class too that’d be great or I’ll just use the default wp if is_page etc…


#3

I use this which I find useful, you won’t need to enqueue the script then.

https://www.npmjs.com/package/google-maps

yarn add google-maps

Then in google-maps.js module for example

/* global google */
import GoogleMapsLoader from 'google-maps';

// https://developers.google.com/maps/documentation/javascript/get-api-key
GoogleMapsLoader.KEY = 'KEY';

function addMap(el)
{
    // do things here
}

function init() {
  GoogleMapsLoader.load(google => {
      let els = document.querySelectorAll('.js-google-map');

      if (!els) {
          return;
      }

      Array.from(els).map(el => google.map = addMap(el));
}

#4

Thanks for the quick reply - sorry a couple of stupid questions incoming…

1, the code you’ve supplied would I simple add that within my routes/maps.js file as that will get triggered when a page has a “map” body class

2, where it says // do things here - is that where I’d place my ACF map code?

Thank you


#5

…one more!

  1. How would I then enqueue this in the admin as that doesnt look for a map class in the body.

#6

1

I wouldn’t add the body class.

Rather, create a JS component/module.

I would place the above JS in a file called scripts/comps/google-maps.js

Then export an init function, like above (this would be at the bottom of google-maps.js)

export default {
  init,
}

Then, in your routes file, in my case I just use scripts/routes/common.js

import googlemaps from '../comps/google-maps';

Then just call the function in common.js

googlemaps.init();

I like to develop my JS according to classes that exist in the DOM, which is what this does in google-maps.js init function

if (!els) {
      return;
}

If there are no DOM elements with js-google-map I just return and stop that component from running. Now you don’t need to worry about route body classes, etc.

2

That’s where you run Google Maps JS functions. ACF just returns the coordinates, you would need to pass those on to the JS via html data attributes. Then you can add logic/markers, etc in the JS.


#7

The backend is dealt with by ACF, just add the Google Key to ACF

/**
 * Google Map API
 * @link https://developers.google.com/maps/documentation/javascript/get-api-key
 */
add_action('acf/init', function () {
    acf_update_setting('google_api_key', '');
});

#8

I just realised that when I went to get a drink… doh! thanks for the detailed reply - super helpful


#9

Here’s my whole JS file if this helps. This is all in ‘vanilla’ JS.

comps/google-maps.js

/* global google */
import GoogleMapsLoader from 'google-maps';

// https://developers.google.com/maps/documentation/javascript/get-api-key
GoogleMapsLoader.KEY = 'KEY';

// map
let map;
let zoom = 15;

// https://snazzymaps.com
let styles = [{'featureType':'water','elementType':'geometry','stylers':[{'color':'#e9e9e9'},{'lightness':17}]},{'featureType':'landscape','elementType':'geometry','stylers':[{'color':'#f5f5f5'},{'lightness':20}]},{'featureType':'road.highway','elementType':'geometry.fill','stylers':[{'color':'#ffffff'},{'lightness':17}]},{'featureType':'road.highway','elementType':'geometry.stroke','stylers':[{'color':'#ffffff'},{'lightness':29},{'weight':0.2}]},{'featureType':'road.arterial','elementType':'geometry','stylers':[{'color':'#ffffff'},{'lightness':18}]},{'featureType':'road.local','elementType':'geometry','stylers':[{'color':'#ffffff'},{'lightness':16}]},{'featureType':'poi','elementType':'geometry','stylers':[{'color':'#f5f5f5'},{'lightness':21}]},{'featureType':'poi.park','elementType':'geometry','stylers':[{'color':'#dedede'},{'lightness':21}]},{'elementType':'labels.text.stroke','stylers':[{'visibility':'on'},{'color':'#ffffff'},{'lightness':16}]},{'elementType':'labels.text.fill','stylers':[{'saturation':36},{'color':'#333333'},{'lightness':40}]},{'elementType':'labels.icon','stylers':[{'visibility':'off'}]},{'featureType':'transit','elementType':'geometry','stylers':[{'color':'#f2f2f2'},{'lightness':19}]},{'featureType':'administrative','elementType':'geometry.fill','stylers':[{'color':'#fefefe'},{'lightness':20}]},{'featureType':'administrative','elementType':'geometry.stroke','stylers':[{'color':'#fefefe'},{'lightness':17},{'weight':1.2}]}];

// google maps callback
function addMap(el) {
  // config
  let config = {
    zoom,
    center: new google.maps.LatLng(0, 0),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    scrollwheel: false,
    styles,
  };

  // markers
  let markers = el.querySelectorAll('.js-google-map-marker');

  // init
  map = new google.maps.Map(el, config);

  // funcs
  addMapMarkers(markers);
  centerMap();

  // return map
  return map;
}

function addMapMarkers(markers) {
  // assign markers to map
  map.markers = [];

  // add marker
  markers.forEach((marker) => {
    // data from data- attributes
    let data = {
      lat: marker.getAttribute('data-lat'),
      lng: marker.getAttribute('data-lng'),
      icon: marker.getAttribute('data-icon'),
    };

    // position
    let position = new google.maps.LatLng(data.lat, data.lng);

    let icon = {
      url: data.icon,
      size: new google.maps.Size(71, 71),
      origin: new google.maps.Point(0, 0),
      anchor: new google.maps.Point(17, 34),
      scaledSize: new google.maps.Size(25, 25),
    };

    // create instance
    let instance = new google.maps.Marker({
      position,
      map,
      icon,
    });

    // add to map.markers array
    map.markers.push(instance);
  });
}

function centerMap() {
    // bounds
    let bounds = new google.maps.LatLngBounds();

    // get bounds for each marker
    map.markers.forEach((marker) => {
      let latlng = new google.maps.LatLng(marker.position.lat(), marker.position.lng());
      bounds.extend(latlng);
    });

    // determine if single or multiple
    if (map.markers.length === 1) {
      // set center and zoom
      map.setCenter(bounds.getCenter());
      map.setZoom(zoom);
    } else {
      // fit markers into map
      map.fitBounds(bounds);
    }
}

function init() {
  GoogleMapsLoader.load(google => {
      let els = document.querySelectorAll('.js-google-map');

      if (!els) {
        return;
      }

      Array.from(els).map(el => google.map = addMap(el));
  });
}

export default {
  init,
}

Then the Blade view, you would get data-lat, data-lng from the acf field of course.

<div class="google-map js-google-map">
  <div 
    class="google-map-marker js-google-map-marker" 
    data-lat="-33.9190454" 
    data-lng="18.3904372"
    data-icon="http://maps.google.com/mapfiles/ms/icons/blue.png">
  </div>
</div>

#10

Perfect thank you - theres lots of helpful information on this forum but seeing a full solution is always 100% easier to digest - I’m sure it’ll help more than just me!


#11

Also, if you are using Bedrock, you can store your Google Maps API key in your .env:

GOOGLE_MAPS_API='xxxxxx'

In your PHP, you can retreive it using: env('GOOGLE_MAPS_API') (Bedrock recommends using the env library). Adding that to Darren’s code above would look like:

/**
 * Google Map API
 * @link https://developers.google.com/maps/documentation/javascript/get-api-key
 */
add_action('acf/init', function () { 
  acf_update_setting('google_api_key', env('GOOGLE_MAPS_API')); 
});

#12

Thank you - I’ve just done that now based on your recommendation I had spotted it but hadn’t clicked that it was to come from the .env file - all updated!


#13

Very good recommendation, thanks knowler