Build a block: Adding configurable attributes (for a map block)

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:

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close