< Retour au blog

Adding options and controls to an existing Gutenberg block

Publié le

dans la catégorie

,

Lire la version en français

This article explains how to add options and controls to existing blocks in the WordPress editor, Gutenberg, and save them in the attributes.

The procedure is different from adding controls when creating custom blocks, as we will only use filters to hook to existing blocks. The filters available in the editor work like PHP filters (add_filter, apply_filters).

It’s best if you have a basic knowledge of React and/or JavaScript to understand this tutorial. I won’t go into details about the concepts related to components, etc.

I invite you to consult the documentation on filters before reading this article.

All the functional code is available as a plugin.

Steps to add a control

Adding a custom control consists of four steps:

  1. Define a custom attribute to store the value of the control
  2. Add the control to the desired location
  3. Manipulate the rendering of the block
  4. Save the block

For this first example, we will add a button to the Toolbar of the paragraph block. The whole code is visible on the plugin repository.

Define a custom attribute

If you want to add a control to a block, it is probably because you want to save its value along with the other values of the block.

Just like custom fields in a post, the attributes of a block must be declared first. When creating a custom block, this is done at block declaration time.
On the other hand, if we wish to add an attribute to an existing block (native or not), we must use a filter: blocks.registerBlockType.

const enableToolbarButtonOnBlocks = [
    'core/paragraph'
];

const setToolbarButtonAttribute = ( settings, name ) => {
    // Do nothing if it's another block than our defined ones.
    if ( ! enableToolbarButtonOnBlocks.includes( name ) ) {
        return settings;
    }

    return Object.assign( {}, settings, {
        attributes: Object.assign( {}, settings.attributes, {
            paragraphAttribute: { type: 'string' }
        } ),
    } );
};
wp.hooks.addFilter(
    'blocks.registerBlockType',
    'custom-attributes/set-toolbar-button-attribute',
    setToolbarButtonAttribute
);

In the code above, in setToolbarButtonAttribute we add the paragraphAttribute, specifying that it is a string. You will customize paragraphAttribute according to your control.

Next, we add this to the paragraph block by hooking into wp.hooks.addFilter, blocks.registerBlockType, which is a filter performed on each block declaration.

A word about the condition at the beginning of the code:

if ( ! enableToolbarButtonOnBlocks.includes( name ) ) {
   return settings;
}

It simply checks that the current block is part of an authorized list that is defined in enableToolbarButtonOnBlocks.

Note that if your custom control is to be saved on an existing attribute (for example the align attribute), you can skip this first step.

Add the control to the block

Once the attribute is defined, we add the control to the block so that the attribute can be modified in the editor. In this first example, we add a button to the Toolbar:

const withToolbarButton = createHigherOrderComponent( ( BlockEdit ) => {
    return ( props ) => {

        // If current block is not allowed
    	if ( ! enableToolbarButtonOnBlocks.includes( props.name ) ) {
            return (
                <BlockEdit { ...props } />
            );
        }

        const { attributes, setAttributes } = props;
        const { paragraphAttribute } = attributes;

        return (
            <Fragment>  
                <BlockControls group="block">
                    <ToolbarGroup>
                        <ToolbarButton
                            icon="format-status"
                            label={ __( 'Custom Button', 'core-block-custom-attributes' ) }
                            isActive={ paragraphAttribute === 'custom' }
                            onClick={ () => {
                                if ( paragraphAttribute === 'custom' ) {
                                    setAttributes( { paragraphAttribute: false } )
                                } else {
                                    setAttributes( { paragraphAttribute: 'custom' } )
                                }
                            } }
                        />
                    </ToolbarGroup>
                </BlockControls>
                <BlockEdit { ...props } />
            </Fragment>
        );
    };
}, 'withToolbarButton' );
wp.hooks.addFilter(
    'editor.BlockEdit',
    'custom-attributes/with-toolbar-button',
    withToolbarButton
);

First, we use createHigherOrderComponent, which allows to modify an existing component and to access its properties, in order to modify the Toolbar.

Then we use BlockControls to position ourselves in the Toolbar, then ToolbarGroup, and finally we create our button with the ToolbarButton component. This is where the addition of the custom value of the attribute, and its removal are managed, in the onClick method.

This time we use the editor.BlockEdit filter, which is performed in the Edit functions of the blocks.

About the group="block" attribute on the BlockControls: it allows to be positioned at the same level as the controls added to the block (in its declaration).

If you remove it, your control will be added in a container following these controls.

Manipulating the render of the block in the editor

At this point, our control is displayed and the attribute is added or removed when the button is clicked.

This is not visible, as the attribute itself does not change anything. If you want to use the attribute via HTML, you have to add a CSS class to the block according to the attribute.

To do this, we will use the editor.BlockListBlock filter which allows us to modify the properties (props) of the block wrapper, and therefore to add a CSS class or an HTML attribute, for example.

const withToolbarButtonProp = createHigherOrderComponent( ( BlockListBlock ) => {
    return ( props ) => {

        // If current block is not allowed
        if ( ! enableToolbarButtonOnBlocks.includes( props.name ) ) {
            return (
                <BlockListBlock { ...props } />
            );
        }

        const { attributes } = props;
        const { paragraphAttribute } = attributes;

        if ( paragraphAttribute && 'custom' === paragraphAttribute ) {
            return <BlockListBlock { ...props } className={ 'has-custom-attribute' } />
        } else {
            return <BlockListBlock { ...props } />
        }
    };
}, 'withToolbarButtonProp' );

wp.hooks.addFilter(
    'editor.BlockListBlock',
    'custom-attributes/with-toolbar-button-prop',
    withToolbarButtonProp
);

In this code, we simply check that our paragraphAttribute contains the value “custom”, and if so, we add a has-custom-attribute CSS class. If not, we return the original properties of the block.

Save the block

This is the last step, we now need to save the block.

This is quite similar to the previous step: if our attribute is present and contains the value “custom”, we add the has-custom-attribute class to the wrapper.

This time we are on the blocks.getSaveContent.extraProps filter, which allows us to save custom properties (here, a CSS class) for the block.

const saveToolbarButtonAttribute = ( extraProps, blockType, attributes ) => {
    // Do nothing if it's another block than our defined ones.
    if ( enableToolbarButtonOnBlocks.includes( blockType.name ) ) {
        const { paragraphAttribute } = attributes;
        if ( paragraphAttribute && 'custom' === paragraphAttribute ) {
            extraProps.className = classnames( extraProps.className, 'has-custom-attribute' )
        }
    }    

    return extraProps;

};
wp.hooks.addFilter(
    'blocks.getSaveContent.extraProps',
    'custom-attributes/save-toolbar-button-attribute',
    saveToolbarButtonAttribute
);

To summarize: we have added a button to the Toolbar of the Paragraph block. This button allows to add or remove the “custom” value of the attribute named paragraphAttribute, and when it has this value, the Paragraph will have the CSS class “has-custom-attribute”.

Second example

In this second example, we add a selector in the Sidebar of the Image block. The goal is to have a list of options available, and that the value of the option is used to build a dynamic CSS class.

Again the complete code is visible on the repository.

The declaration of the attribute does not change compared to the first example, except for the name of the methods and the key of the attribute.

However, the addition of the control varies, since this time we add our control in the Sidebar and not in the Toolbar:

return (
    <Fragment>
        <BlockEdit { ...props } />
        <InspectorControls>
            <PanelBody
                title={ __( 'Image Custom Attributes' ) }
            >
                <SelectControl
                    label={ __( 'Custom Attribute' ) }
                    value={ imageAttribute }
                    options={ [
                        {
                            label: __( 'None' ),
                            value: ''
                        },
                        {
                            label: __( 'One' ),
                            value: 'one'
                        }
                    ] }
                    onChange={ ( value ) => {
                        setAttributes( {
                            imageAttribute: value,
                        } );
                    } }
                /> 
            </PanelBody>
        </InspectorControls>
    </Fragment>
);

For this we use the InspectorControls component, which allows us to position ourselves in the Sidebar, then we add a Panel component and finally our Select. It is in the latter that the value of the attribute is defined according to the chosen option.

The management of the editing of the block wrapper and its saving does not change much compared to the first example, we still assign a class to the block wrapper, and it consists of a dynamic part: the option chosen in the Select.

className={ 'has-option-' + imageAttribute }

So when we choose the “One” option, the Image block will have the “has-option-one” class.

To go further

If you want to modify the plugin created for this tutorial, to develop your own controls for example, download the plugin folder, install the dependencies (npm install) and use the documentation to know the available commands.

Finally, don’t hesitate to leave your comment on this article! Thanks.

Translated with deepl.com

7 responses to “Adding options and controls to an existing Gutenberg block”

  1. It’s Really very Helpfull, Thanks for this amazing Explaination.

    I implemented it, but I just had one issue like whenever I select Multiple Same Blocks at a time using [CTRL + A] the Custom Toolbar Button Added is visible, but if I select Multiple Different Blocks then the Toolbar Button is not visible.

    Can you please suggest a solution for that?

  2. Brilliant, thank you very much! I needed the dropdown for the sidebar and your site was the first result on google, a very lucky find! I find asking clients to type in CSS classes manually doesn’t work in real life, so having a dropdown is a realistic approach to adding classes. I wish WP would make a Class dropdown a default, with a filter where classes can be set in functions.php but this is the next best thing.

      • Hi Marie, oh wow, thank you! I hadn’t noticed that before, yes, that’s a great solution!

        Can I ask – in your plugin you add a dropdown. What is the benefit of the dropdown compared to Bock Style?

        Or to ask differently: What can a sidebar dropdown do that’s not possible with Block Style?

        • Hi Edith,

          Block Styles is relevant if you just need to add a class. And it’s convenient because WordPress itself handle adding/removing classes. Plus, no need of JavaScript, just PHP.

          But, Block Style is not relevant if your selectable option is not related to block style, or if it’s contain a lot of options, or if you want to add something else than a class.

          Imagine an Icon Block, which let you to select the icon to display, and its font weight (regular, bold, thin).
          The icon font weight is a style, limited to 3 options, so it will be managed by the Block Style.
          But the icon select, which may contain dozens of options, will be more relevant in a separate dropdown.

          So your Icon Block HTML markup will look like this :

          <div class="wp-block-icon is-style-$weight" data-icon="icon-$icon-name">...</div>

          Where data-icon will be added by your custom code, and $icon-name take the value of the selected icon.
          And the classname is-style-$weight will be added by WordPress itself.

          I hope my explanation is clear 🙂

Leave a Reply

Your email address will not be published.

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