2

TL;DR

How can I change the edit and save result of a core block without having to re-implement most of its functionality in my own code?

Question

I'm familiar with the block filters and am already able to use them to add attributes or new InspectorControls. However, it bugs me that it seems I cannot update the JSX/HTML of the edit/save without having to overwrite the whole block.

Imagine the following scenario: I want to add a setting to the core/button block to append an icon after the text. So the final HTML should not be

<div class="wp-block-button">
    <a class="wp-block-button__link">Hello World</a>
</div>

but instead the desired result is

<div class="wp-block-button">
    <a class="wp-block-button__link">Hello World</a>
    <span class="wp-block-button__icon">X</span>
</div>

For save I could probably use wp.element.cloneElement to add my own element as an additional child.

But what for edit? My current understanding is that as long as the block does not use SlotFills, I don't really have an option.

In the usual setup like this

const withInspectorControls = createHigherOrderComponent((BlockEdit) => {
  return (props) => {
    const { name, attributes, setAttributes } = props
    if (name !== 'core/button') {
      return <BlockEdit {...props} />
    }

    const { showIcon } = attributes

    return (
      <Fragment>
        <BlockEdit {...props} />
        <InspectorControls>
          <PanelBody title={__('Show Icon?', 'my-plugin')}>
            <ToggleControl
              label={__('Show Icon?', 'my-plugin')}
              checked={showIcon}
              onChange={(showIcon) => setAttributes({ showIcon })}
            />
          </PanelBody>
        </InspectorControls>
      </Fragment>
    )
  }
})

addFilter('editor.BlockEdit', 'my-plugin/add-icon-inspector', withInspectorControls)

I could drop the <BlockEdit {..props} /> part and re-implement the button. But looking at the source of the edit I know that I'd have to either re-implement the complete ButtonEdit method, which I would like to avoid.

Various guides as well as other answers on this stack imply that it makes more sense to create a new block (and de-register/blacklist the core one), is this still valid for 2021?

7
  • Is there a particular reason you have to do it with a span tag? Would CSS not work? Adding the icon attribute is aa different question from using/displaying the attribute to add the markup, by combining both questions into a single question you severely reduce your chances of an answer ( e.g. I know how to add an option to add set and save an additional attribute, but I don't know how to modify the edit/save component to add the new tag )
    – Tom J Nowell
    Jan 28, 2021 at 16:59
  • Also, buttons can have inline images, so the solution to your problem might actually be unrelated to anything you've mentioned, e.g. I can see a way to do this purely by registering a richtext format
    – Tom J Nowell
    Jan 28, 2021 at 17:01
  • @TomJNowell This is just an example, I tried to minimize it as much as possible. The goal is to change the actual DOM/markup, not just add classes/data-attributes. Tomorrow I can try to rephrase it to "How can I change the rendered markup in the edit?" (afaik cloneElement gives me enough freedoms for save).
    – kero
    Jan 28, 2021 at 17:16
  • I believe the example you want can be done by registering a format, complete with the span tag
    – Tom J Nowell
    Jan 28, 2021 at 17:19
  • 1
    No, I'd actually argue that modifying the edit component is hacky, and may actually be a dead end that causes block validation to fail
    – Tom J Nowell
    Jan 28, 2021 at 17:25

1 Answer 1

0

You would do this with the format API, for example, this code from the handbook will add a <samp> tag around text:

import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';
 
const MyCustomButton = ( props ) => {
    return <RichTextToolbarButton
        icon='editor-code'
        title='Sample output'
        onClick={ () => {
            props.onChange( toggleFormat(
                props.value,
                { type: 'my-custom-format/sample-output' }
            ) );
        } }
        isActive={ props.isActive }
    />;
};
 
registerFormatType(
    'my-custom-format/sample-output', {
        title: 'Sample output',
        tagName: 'samp',
        className: null,
        edit: MyCustomButton,
    }
);

Likewise, you can use the same tricks the image format uses to insert a single element without it wrapping text:

https://github.com/WordPress/gutenberg/blob/master/packages/format-library/src/image/index.js

Specifically, it imports insertObject:

                            insertObject( value, {
                                type: name,
                                attributes: {
                                    className: `wp-image-${ id }`,
                                    style: `width: ${ Math.min(
                                        imgWidth,
                                        150
                                    ) }px;`,
                                    url,
                                    alt,
                                },
                            } )

Where name is the name of the format that was registered. The result is an inline <img> tag.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.