Adding custom toggle control to Gutenberg Block Editor

in Wordpress


Requirement. Every header (core/heading) block needs to have a toggle control for adding or removing a custom class name to/from the resulting element.

Usage example. Building an automatic table of contents generator, which will pick only the headers having the custom class name. The admin needs to easily select headers that should be used for the table of contents.

(Related: here's how to define a custom static Block element acting as a placeholder where the table of contents, generated with Javascript, will be inserted: https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/writing-your-first-block-type/.)

The following PHP code goes under wp-content/plugins/exampleplugin/exampleplugin.php:

<?php
/**
 * @package ExamplePlugin
 * @version 1.0
 */
/*
Plugin Name: ExamplePlugin
Plugin URI: http://example.com/
Description: Custom
Author: ExamplePlugin
Version: 1.0
Author URI: http://example.com/
*/

function enqueue_block_editor_adjustments() {
    wp_register_script(
        'block_editor_adjustments',
        plugin_dir_url( __FILE__ ) . 'block_editor_adjustments.js',
        [ 'wp-blocks', 'wp-dom', 'wp-dom-ready', 'wp-edit-post' ],
        filemtime( plugin_dir_path( __FILE__ ) . 'block_editor_adjustments.js' )
    );
    wp_enqueue_script( 'block_editor_adjustments' );
}

add_action( 'enqueue_block_editor_assets', 'enqueue_block_editor_adjustments' );

The above adds our custom Javascript file in the editor.

And here is the wp-content/plugins/exampleplugin/block_editor_adjustments.js file:

// Register our property at core/heading
wp.hooks.addFilter(
    'blocks.registerBlockType',
    'ExamplePlugin/block_editor_adjustments', // our custom namespace
    function(settings, name) {
        if (name !== 'core/heading') { // skip other core blocks
            return settings;
        }
        settings.attributes = Object.assign(settings.attributes, {
            showInTableOfContents: { // showInTableOfContents is our property name
                type: 'boolean',
                default: false
            }
        });
        return settings;
    }
);

// Display the custom ToggleControl for every header; toggle the custom property
wp.hooks.addFilter(
    'editor.BlockEdit',
    'ExamplePlugin/block_editor_adjustments',
    wp.compose.createHigherOrderComponent(function(BlockEdit) {
        return function(props) {
            if (props.name !== 'core/heading') { // skip other core blocks
                return wp.element.createElement( BlockEdit, props );
            }
            console.log('here are the props', props);
            return wp.element.createElement(
                wp.element.Fragment,
                {},
                wp.element.createElement( BlockEdit, props ),
                wp.element.createElement(
                    wp.blockEditor.InspectorControls,
                    {},
                    wp.element.createElement(
                        wp.components.PanelBody,
                        {},
                        wp.element.createElement(
                            wp.components.ToggleControl,
                            {
                                label: 'Show in Table of Contents',
                                checked: props.attributes.showInTableOfContents,
                                onChange: ( value ) => {
                                    props.setAttributes( { showInTableOfContents: value } );
                                },
                            }
                        )
                    )
                )
            );
        };
    })
);

// Convert the custom property value into CSS class name on every save
wp.hooks.addFilter(
    'blocks.getSaveContent.extraProps',
    'ExamplePlugin/block_editor_adjustments',
    function(extraProps, blockType, attributes) {
        if (blockType.name !== 'core/heading') { // skip other core blocks
            return extraProps;
        }
        if (attributes.showInTableOfContents) {
            extraProps.className = extraProps.className + ' fq_contents_header';
        }
        return extraProps;
    }
);

General idea and guidance:

Documentation:

Other, helpful:

#wordpress #wordpress-gutenberg