Skip to content

Offices and maps

Office locations are held as a private office post type and rendered two ways: a single office with a Mapbox map (map-block), and a tabbed multi-office switcher where each tab has its own map (location-tabs).

The firm has several offices and wants “solicitors in town” pages to show a real location with contact details. Offices are content, not hardcoded markup, so the same records feed both blocks, service pages and the footer without duplication.

Mapbox is used rather than Google Maps, and a custom Mapbox style is referenced, so maps match the brand palette. The ACF Google Maps API key is explicitly set to an empty string (inc/acf.php:4), which is consistent with Google Maps not being used.

Path What it does
wp-content/themes/bpcollins/inc/cpt.php:228 office post type — public => false, show_ui => true, title + thumbnail only
wp-content/themes/bpcollins/inc/classes/BPOffice.php Accessor for every office field
wp-content/themes/bpcollins/blocks/map-block/block-map-block.php Single office: map on the left, details on the right
wp-content/themes/bpcollins/blocks/location-tabs/block-location-tabs.php Multi-office tab switcher with per-tab map and background image
wp-content/themes/bpcollins/inc/register-blocks.php:102-105 Enqueues Mapbox GL JS (and the directions plugin) for exactly these two blocks
wp-content/themes/bpcollins/single-service.php:28 Also enqueues Mapbox GL JS unconditionally on service pages

Data. Each office carries ACF fields office_telephone_number, office_email_address, office_address, office_map_link, office_additional_information, office_latitude, office_longitude and page_link. Coordinates are entered manually as lat/lng — there is no geocoding step. BPOffice also exposes the featured image and the post slug.

Mapbox loading. register_acf_block_types() special-cases the two block slugs and enqueues mapbox-gl.js v2.8.1 plus mapbox-gl-directions v4.1.0 from the Mapbox CDN in the header, not the footer (inc/register-blocks.php:102-105). The matching CSS is enqueued from inside each block template (block-map-block.php:2-3, block-location-tabs.php:2). The Mapbox access token and the custom style URL are hardcoded in the two block JS files — see Configuration.

map-block. Takes one office (location field), renders a #map div carrying data-lat/data-lng, then the details column. The JS reads the attributes, builds the map at zoom 15, adds a custom div.marker, disables scroll zoom, and adds fullscreen + navigation controls (block-map-block.js:21-45).

location-tabs. Takes a locations list. On a service single with no explicit list it falls back to all published offices ordered by menu_order descending (block-location-tabs.php:14-28), and adds a “Service solicitors in…” heading. For each office it renders a full-bleed background image, a tab in the list, and a pod containing a map plus contact details. First item gets active.

The JS initialises one Mapbox map per pod, equalises pod container height to the tallest pod (recomputed on resize), fades the matching background image on tab hover, switches active classes on click, and on narrow viewports scrolls to the pod container and disables map drag-pan (block-location-tabs.js:29-140).

flowchart TD
A[office CPT: lat/lng, phone, email, address] --> B{block}
B -->|map-block| C[one #map div with data-lat/lng]
B -->|location-tabs| D[one pod per office, each with its own map div]
D --> E[service pages with no list → all offices by menu_order DESC]
C --> F[Mapbox GL JS from CDN, enqueued by register-blocks.php]
D --> F
F --> G[map at zoom 15, custom marker div, scroll zoom disabled]
  • Per office: the ACF fields listed above. office_latitude / office_longitude are required — an office without them renders an empty map container.
  • Per block: location (map-block), locations (location-tabs), plus the standard BlockMeta fields.
  • Mapbox credentials and style: the public Mapbox access token and the custom style ID are hardcoded in blocks/map-block/block-map-block.js:19,31 and blocks/location-tabs/block-location-tabs.js:19,54 — and therefore also in the built files under assets/js/. They are not read from an option or an environment variable. If the token is ever rotated, both source files and the built assets must be updated.
  • acf_google_maps_key() (inc/acf.php:3-6) sets ACF’s google_api_key to an empty string, which disables the ACF Google Map field type.
  • map-block hardcodes id="map" (block-map-block.php:16). Two map-blocks on one page produce duplicate IDs and only the first map initialises. location-tabs avoids this by numbering (map-1, map-2, …).
  • Coordinates are strings from data attributes. They are passed to Mapbox as [lng, lat] without parseFloat (block-map-block.js:25). Mapbox coerces them, but note the order — lng first. A swapped pair lands the pin in the wrong hemisphere with no error.
  • The email link in map-block uses tel:, not mailto: (block-map-block.php:48). location-tabs has it right (block-location-tabs.php:97). Clicking an office email in map-block attempts a phone call.
  • location-tabs instantiates BPOffice three times per office (background, tab, pod) — three sets of get_field() calls each. Fine at four offices, worth knowing if the office list grows.
  • podContainerOffset is measured once at init (block-location-tabs.js:25). If anything above the block changes height afterwards — a lazy image, a cookie banner — the mobile scroll-to-tab lands in the wrong place.
  • Pod height equalisation reads clientHeight at init, so a pod whose content loads late can overflow its container.
  • office is non-public, so office records have no front-end URL. The page_link field exists to point at a real page instead; BPOffice::$link is a permalink to a non-public post and should not be output as a link.
  • Mapbox JS loads in the header ($in_footer = false) on any page with either block, and unconditionally on every service page — a render-blocking third party request even where no map exists.
  • The directions plugin (mapbox-gl-directions) is enqueued but no code initialises it. It appears to be a leftover from a planned feature.
  • New office → add the office post, fill lat/lng and contact fields, then add it to the relevant blocks. Nothing enumerates offices automatically except the location-tabs fallback on service pages.
  • Rotating the Mapbox token or changing the style → edit both block JS files and rebuild with gulp; grep assets/js/ afterwards to confirm no stale copy of the old token remains in a built file.
  • New map surface → reuse location-tabs’ numbered-ID pattern rather than map-block’s fixed id="map".
  • If Mapbox’s header enqueue becomes a performance problem, the right fix is to make single-service.php:28 conditional on the block actually rendering, not to move the enqueue into the block (ACF’s enqueue_assets runs too late for a header script).
  • Deliberately not abstracted: the two blocks duplicate their map bootstrap code. They differ in ID strategy, mobile behaviour and pod layout, so sharing them is more work than it looks.
  • Verify: a page with map-block, a page with location-tabs, and a service page with no explicit office list — check every pin lands on the right building, tab switching works, and the mobile scroll-to-pod behaves.

None. No automated tests exist in this repository. Map positioning has to be checked visually.