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. In part 2, I explained how to add a Mapbox map. My next goal was to add configurable attributes so that the map location / style / zoom and other features could be selected from the settings sidebar.
Here’s how this looked.
The main PHP file
The is_token_valid
attribute is now removed from register_post_meta
due to a change in how it is being used (see the previous article to see where this had been added). The two instances of wp_localize_script
have now been changed to wp_add_inline_script
, more appropriate for the array of data now being added to the inline script. The third parameter in wp_add_inline_script
is ‘before’, indicating the script should be added before the handle.
The biggest change is the addition of an array of data with default values that can be added to that inline script, in order to be used for the front-end map. Those values are replaced if any of the attributes have changed, and that new data then passed via the inline script. The demo_plugin_block_assets
function now has the following content added to it, in place of the two instances of wp_localize_script
:
global $post;
$blocks = parse_blocks( $post->post_content );
$new_arr = array();
$default_attributes = [
'mapbox_api_token_field' => '',
'is_token_valid' => false,
'mapStyle' => 'mapbox://styles/mapbox/satellite-streets-v12',
'zoom' => 13,
'longitude'=> -3.647891,
'latitude' => 57.120085,
'pitch' => 80,
'bearing' => 10,
'mapbox_api_token_field' => get_post_meta( get_the_ID(), 'mapbox_api_token_field', true) ? get_post_meta( get_the_ID(), 'mapbox_api_token_field', true) : null,
];
foreach( $blocks as $block ) {
if ( $block['blockName'] === 'demo-block/map-demo-block') {
$attributes = $block['attrs'];
$new_arr = array_replace($default_attributes, $attributes);
}
}
wp_add_inline_script( 'mapbox_block-build', 'let myBlockLocalize =' . json_encode( $new_arr ), 'before' );
Edit.js
Starting from the top of Edit.js, the first change is to what we import – in this case @wordpress/components
has expanded to include several more components:
import { TextControl, RangeControl, ToggleControl, SelectControl } from '@wordpress/components';
Underneath the first useEffect
in Edit.js (where we check for changes to the token validity), we have a new useEffect
which runs the runMaxboxMap
function every time an attribute is changed (so attributes
is added as a dependency). This makes sure the map updates in real-time if any of the attributes are changed in the settings sidebar.
useEffect(() => {
runMapboxMap( attributes );
}, [attributes]);
The biggest change on Edit.js is to the InspectorControls component, where all the additional attributes have been added (and main control labels are wrapped in a translation function). The first few lines underneath this component have also changed so I’ve included them all here (namely the Map component now passes attributes
as a prop instead of a destructured mapboxBlockProp, and similarly we check for the existence of the Mapbox token using attributes
, not mapboxBlockToken
.
const inspectorControls = (
<InspectorControls key="setting">
<div id="block-plugin-controls">
<TextControl
label={ __( "First Settings Input" ) }
value={ attributes.first_settings_input || '' }
onChange={ ( val ) => setAttributes( { first_settings_input: val } ) }
/>
<RangeControl
label={ __("Zoom Level" ) }
value={ attributes.zoom }
onChange={ ( val) => setAttributes( { zoom: Number( val) } ) }
min={ 1 }
max={ 22 }
/>
<RangeControl
label={ __( "Pitch" ) }
value={ attributes.pitch }
onChange={ ( val) => setAttributes( { pitch: Number( val ) } ) }
min={ -90 }
max={ 90 }
/>
<RangeControl
label={ __( "Bearing" ) }
value={ attributes.bearing }
onChange={ ( val ) => setAttributes( { bearing: Number( val ) } ) }
min={ 0 }
max={ 360 }
/>
<TextControl
label={ __( "Longitude" ) }
value={ attributes.longitude }
onChange={ ( val ) => setAttributes( { longitude: Number( val ) } ) }
/>
<TextControl
label={ __( "Latitude" ) }
value={ attributes.latitude }
onChange={ ( val ) => setAttributes( { latitude: Number( val ) } ) }
/>
<SelectControl
label={ __( "Map Style" ) }
value={ attributes.mapStyle }
options={ [
{ label: 'Satellite', value: 'mapbox://styles/mapbox/satellite-v9' },
{ label: 'Satellite Streets', value: 'mapbox://styles/mapbox/satellite-streets-v12' },
{ label: 'Street View', value: 'mapbox://styles/mapbox/streets-v12' },
{ label: 'Outdoors', value: 'mapbox://styles/mapbox/outdoors-v12' },
{ label: 'Navigation Day', value: 'mapbox://styles/mapbox/navigation-day-v1' },
] }
onChange={ ( val) => setAttributes( { mapStyle: val } ) }
__nextHasNoMarginBottom
/>
</div>
</InspectorControls>
);
const mapControls = (
<Map blockProps={attributes} />
);
if (attributes.is_token_valid && attributes.mapbox_api_token_field !== '' ) {
Save.js
In line with the changes in edit.js
, where attributes
are passed rather than MapboxBlockProp
or mapboxBlockToken
, the only change in save.js
is passing attributes
into the Map component instead of destructured mapboxBlockProp
:
<Map blockProps={attributes}/>
Index.js
Apart from block.json
which also includes updated attributes, index.js
is the main JS file with changes needed to attributes themselves. Here is the updated registerPlugin
function including all the new attributes with their defaults:
registerBlockType( metadata.name, {
attributes: {
first_settings_input: {
type: 'string',
},
is_token_valid: {
type: 'boolean',
default: false,
},
mapbox_api_token_field: {
type: 'string',
},
mapStyle: {
type: 'string',
default: 'mapbox://styles/mapbox/satellite-streets-v12'
},
zoom: {
type: 'integer',
default: 13,
},
longitude: {
type: 'number',
default: -3.647891
},
latitude: {
type: 'number',
default: 57.120085
},
pitch: {
type: 'number',
default: 80,
},
bearing: {
type: 'number',
default: 10,
},
},
/**
* @see ./edit.js
*/
edit: Edit,
/**
* @see ./save.js
*/
save,
} );
The map
There is just one change in map.js
– passing through the attributes
when running the runMapboxMap
function:
componentDidMount() {
const mapAttributes = this.props.blockProps;
runMapboxMap( mapAttributes );
}
The map code in map-block.js
has changed significantly. Here is the full file:
function runMapboxMap( attributes ) {
let mapboxMap = document.getElementById( 'mapbox-map' );
let mapAttributes = [];
// If the map block hasn't even been added to the page / post yet, leave early.
if ( typeof myBlockLocalize === 'undefined' ) {
return;
} else if ( attributes ) {
mapAttributes = attributes;
} else {
mapAttributes = myBlockLocalize;
}
if ( mapboxMap ) {
if ( mapAttributes.is_token_valid ) {
mapboxgl.accessToken = mapboxgl.accessToken ? mapboxgl.accessToken : mapAttributes.mapbox_api_token_field;
let map = new mapboxgl.Map({
container: 'mapbox-map',
style: mapAttributes.mapStyle,
zoom: mapAttributes.zoom,
center: [ `${mapAttributes.longitude}`, `${mapAttributes.latitude}` ],
pitch: mapAttributes.pitch,
bearing: mapAttributes.bearing,
});
map.on('style.load', () => {
map.addSource('mapbox-dem', {
'type': 'raster-dem',
'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
'tileSize': 512,
'maxzoom': 14
});
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();
After these three stages (here was the first article on adding text input fields to a settings sidebar and plugin sidebar, and here was the second on adding a Mapbox map), now I have a block plugin allowing me to add a Mapbox Map with a valid key, and I can change the location showing on the map, as well as various attributes including the map location and zoom. Here is what the editor and settings sidebar looks like now:
