In part 1 of my personal project to build a block plugin, I shared how I created a couple of text inputs to the plugin and settings sidebars. My next goal was to use one of the inputs to collect a Mapbox API token, and show a map both in the post / page editor and on the front-end.
There were several moving parts to this so I’ll break it down into the different files (and new files), along with what was added and why.
The main PHP file
Expanding on main-plugin-file.php
from the previous article, the main additions were in terms of the enqueued scripts and styles, along with the now renamed sidebar_plugin_meta_block_field
(which is now more appropriately named mapbox_api_token_field
). This was the text input field used to collect the Mapbox API token. There was also one additional meta field registered: is_token_valid
, in the same function as where all the other meta fields were registered.
register_post_meta( 'post', 'is_token_valid', array(
'show_in_rest' => true,
'single' => true,
'type' => 'boolean',
) );
As for the assets, the snippet below shows the mapbox-gl-js
CSS and JS being enqueued, as well as a new file dist/map-block.js
which includes the Mapbox JS needed to load the map on the front-end. That mapbox_block-buil
script has the mapbox-gl-js
JS as a dependency.
The two instances of wp_localize_script
are helping make PHP data available to our scripts (needed mainly for running the map on the front-end – elsewhere we make use of the block attributes). In the first instance, we’re giving a JavaScript object the name mapboxBlock
. The information in the third parameter is what we’re assigning to this object, which is the Mapbox API token if it is set from the mapbox_api_token_field
meta field (or null otherwise). In the second instance, an object named mapboxTokenValidity
is assigned to the value in the is_token_valid
meta field, or false if it is not set.
function save_live_track_block_assets() {
wp_enqueue_style(
'mapbox_block-mapbox-style',
'https://api.mapbox.com/mapbox-gl-js/v2.11.0/mapbox-gl.css',
array( 'wp-editor' ),
'2.11.0',
);
wp_enqueue_script(
'mapbox_block-mapbox-gl-js',
'https://api.mapbox.com/mapbox-gl-js/v2.11.0/mapbox-gl.js',
array(),
'2.11.0',
);
// The map build script.
wp_enqueue_script(
'mapbox_block-build',
plugins_url( 'dist/map-block.js', __FILE__ ),
array('mapbox_block-mapbox-gl-js'),
null,
true
);
// Making the API token and validity available to build the map on the front-end.
wp_localize_script( 'mapbox_block-build', 'mapboxBlock', [
'accessToken' => get_post_meta( get_the_ID(), 'mapbox_api_token_field', true) ? get_post_meta( get_the_ID(), 'mapbox_api_token_field', true) : null
] );
wp_localize_script( 'mapbox_block-build', 'mapboxTokenValidity', [
'is_token_valid' => get_post_meta( get_the_ID(), 'is_token_valid', true) ? get_post_meta( get_the_ID(), 'is_token_valid', 'true') : 'false'
] );
}
add_action( 'enqueue_block_assets', 'save_live_track_block_assets' );
Edit.js
Below is the entirety of the edit.js
file, including all current imports and the Inspector Controls covered in the last article.
For the initial variable initialization, there is a mapboxBlock variable which holds the mapbox_api_token_field
value. The mapboxBlockProp
variable holds that same value but the curly brackets let the JS parser know that the value should be interpreted as JS rather than a string.
The useEffect hook is making sure that only once on each editor page load the is_token_valid attribute is assigned to the correct value, which is true or false depending on whether the mapboxAPIRequest returns a 401 or not. It’s also then saving the page at this stage, to prevent an additional save being needed.
The Inspector Controls were explained in the last article but this example only includes the first_settings_input
. The Map
component is then included within the mapControls
component, to be used slightly later. The Mapbox API token is passed to the Map component via mapboxBlockProp
.
We then move to the returned output. There are two return scenarios – if we have a valid Mapbox API token and if we don’t. We can check for that with attributes.is_token_valid && mapboxBlock !== ''
– if the API token is valid (and the mapbox_api_token_field
is not empty), then we’ll return a block with the map, and then underneath some text from the block plugin settings sidebar’s first text input field (first_settings_input
).
If the above conditions are not met, we’ll output some text explaining how to get a Mapbox API token and add it to the plugin sidebar.
import { TextControl } from '@wordpress/components';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import Map from './map';
import { __ } from '@wordpress/i18n';
import { useEffect } from "@wordpress/element";
import './editor.scss';
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
const mapboxBlockToken = wp.data.select('core/editor').getEditedPostAttribute('meta')[
'mapbox_api_token_field'
];
const mapboxBlockProp = { mapboxBlockToken };
const mapboxAPIRequest = `https://api.mapbox.com/geocoding/v5/mapbox.places/rndstrasdf.json?access_token=${mapboxBlockToken}`;
useEffect(() => {
fetch(mapboxAPIRequest)
.then(response => {
if (!response.ok)
setAttributes({ is_token_valid: false });
wp.data.dispatch('core/editor').savePost();
} else {
setAttributes({ is_token_valid: true });
wp.data.dispatch('core/editor').savePost();
}
});
}, []);
const inspectorControls = (
<InspectorControls key='setting'>
<div id="block-plugin-controls">
<TextControl
label="First text input"
value={attributes.first_settings_input || ''}
onChange={ ( val ) => setAttributes( { first_settings_input: val } ) }
/>
</div>
</InspectorControls>
);
const mapControls = (
<Map {...mapboxBlockProp } />
);
if (attributes.is_token_valid && mapboxBlockToken !== '' ) {
return (
<div { ...blockProps }>
{ inspectorControls}
<TextControl
{ ...blockProps }
label={ __( 'My block plugin' ) }
value={ attributes.message }
onChange={ ( val ) => setAttributes( { message: val } ) }
/>
<p>{attributes.first_settings_input}</p>
{ mapControls}
</div>
);
}
else {
return (
<div className='mapbox-block-token'>
<h2>Map Block Setup</h2>
<p>To use this block, you need to sign up for a Mapbox account and generate your token.</p>
<a href='https://www.mapbox.com/' className='mapbox-block-token-cta'>Create a Mapbox Token here</a>.
<p>Once you have a valid token, add it to the Mapbox token field in the plugin sidebar.</p>
</div>
);
}
}
Save.js
For the frontend, we want a save.js
file which which will output much the same as what we see in the editor (if there is a map to show). In this file we want to import our Map component. Within the save
function, we create the mapboxBlockProp
variable which holds the Mapbox API token. Then, if the API token is valid, we’ll output an outer div that encloses the Map component and a paragraph field to include the same text input as in the editor (first_settings_input
). If the token is not valid, we’ll just return an empty div.
import { useBlockProps } from '@wordpress/block-editor';
import Map from './map';
export default function save( { attributes } ) {
const blockProps = useBlockProps.save();
const mapboxBlockProp = attributes.mapbox_api_token_field;
if (attributes.is_token_valid === true) {
return (
<div { ...blockProps } className='map-block'>
<Map { ...mapboxBlockProp }/>
<p>{attributes.first_settings_input}</p>
</div>
);
}
else {
return (
<div></div>
)
}
}
Plugin-sidebar.js
In comparison to the built out plugin-sidebar.js
from the previous article, the changes here are with the first meta field (now named MapBoxAPIField), which mainly also includes a few field and variable name changes to more accurately reflect what is happening.
const MapboxAPIField = (props) => {
const metaFieldValue = useSelect(function (select) {
return select('core/editor').getEditedPostAttribute(
'meta'
)['mapbox_api_token_field'];
}, []);
const editPost = useDispatch('core/editor').editPost;
return (
<div>
<TextControl
label='Mapbox API Token'
value={metaFieldValue}
onChange={ function(content) {
editPost({
meta: { mapbox_api_token_field: content },
});
}
}
/>
<p>Refresh after update to view changes.</p>
</div>
);
};
The map
Now to the map. The component itself is created in map.js
. From here we call the runMapboxMap function which is located in dist/map-block.js
.
export default class Map extends React.Component {
render() {
return (
<div>
<div id='mapbox-map' />
</div>
);
}
componentDidMount() {
runMapboxMap();
}
}
In dist/map-block.js
the map is created. The Mapbox docs cover in much more detail the different options available (but make sure the JS and CSS you’re importing is new enough to include any newer styles that you may want to include). However before the map is created, as this file is used in both the front and back-end blocks we need to make sure we’re not trying to add the block if the parent mapbox-map container ID is not present, nor if the token isn’t valid.
function runMapboxMap( ) {
let mapboxMap = document.getElementById('mapbox-map');
if ( mapboxMap ) {
if ( mapboxTokenValidity.is_token_valid ) {
let map = new mapboxgl.Map({
container: 'mapbox-map', // container ID
style: 'mapbox://styles/mapbox/satellite-streets-v12',
zoom: 12,
center: [-3.647891, 57.120085],
pitch: 80,
bearing: 10,
});
map.on('style.load', () => {
map.addSource('mapbox-dem', {
'type': 'raster-dem',
'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
'tileSize': 512,
'maxzoom': 14
});
// add the DEM source as a terrain layer with exaggerated height
map.setTerrain({ 'source': 'mapbox-dem', 'exaggeration': 1.5 });
});
map.on('style.load', () => {
map.setFog({});
});
} else {
if ( document.getElementsByClassName('map-block')[0] ) {
document.getElementsByClassName('map-block')[0].style.display = 'none';
}
}
}
runMapboxMap();
That’s what it took to get a Mapbox map added to a block plugin, allowing for an API key to be added. This was certainly more challenging than the first step of adding just text inputs, as there were a few different ways to approach how and where to add the key, and how best to render the map (as well as avoiding having two map builds – one for the back end and one for the front end), plus check for key validity.
As I continue with the plugin and refactor further, I’ll revisit this article and keep it up to date with further improvements.
In the end, this was the result in the editor, with the same map showing on the front-end:
