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).
Why it exists
Section titled “Why it exists”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.
Entry points
Section titled “Entry points”| 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 |
How it works
Section titled “How it works”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]Configuration
Section titled “Configuration”- Per office: the ACF fields listed above.
office_latitude/office_longitudeare 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,31andblocks/location-tabs/block-location-tabs.js:19,54— and therefore also in the built files underassets/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’sgoogle_api_keyto an empty string, which disables the ACF Google Map field type.
Invariants and gotchas
Section titled “Invariants and gotchas”map-blockhardcodesid="map"(block-map-block.php:16). Two map-blocks on one page produce duplicate IDs and only the first map initialises.location-tabsavoids this by numbering (map-1,map-2, …).- Coordinates are strings from data attributes. They are passed to Mapbox as
[lng, lat]withoutparseFloat(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-blockusestel:, notmailto:(block-map-block.php:48).location-tabshas it right (block-location-tabs.php:97). Clicking an office email inmap-blockattempts a phone call. location-tabsinstantiatesBPOfficethree times per office (background, tab, pod) — three sets ofget_field()calls each. Fine at four offices, worth knowing if the office list grows.podContainerOffsetis 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
clientHeightat init, so a pod whose content loads late can overflow its container. officeis non-public, so office records have no front-end URL. Thepage_linkfield exists to point at a real page instead;BPOffice::$linkis 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 everyservicepage — 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.
Changing it safely
Section titled “Changing it safely”- New office → add the
officepost, fill lat/lng and contact fields, then add it to the relevant blocks. Nothing enumerates offices automatically except thelocation-tabsfallback 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 thanmap-block’s fixedid="map". - If Mapbox’s header enqueue becomes a performance problem, the right fix is to
make
single-service.php:28conditional on the block actually rendering, not to move the enqueue into the block (ACF’senqueue_assetsruns 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 withlocation-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.