Editing a Gutenberg Core Block
For a recent site, I needed a <span>
tag to wrap the content of a button link.
This could also have been a custom block type, but I was able to alter the
core/button
block instead.
Add the filter
The blocks.getSaveElement
filter allows us to alter the result of the block’s
save function. In other words, what’s displayed on the front end.
import { addFilter } from '@wordpress/hooks';
addFilter(
'blocks.getSaveElement',
'my-wp-theme/button',
updateBlockMarkup
);
Add the function
The filter above calls the function updateBlockMarkup
to alter the save
element. That function takes the element and blockType as parameters.
const updateBlockMarkup = (element, blockType) => {
if (!element || blockType.name !== 'core/button') return element;
};
Since we only want to alter buttons, we check the block name and return the unaltered element if we have a different block type.
Create the span
Right now, the text displayed for the button is the value
prop on the element’s
children
prop. (It looks like element.props.children.props.value
). We want to
retrieve that value and then create a new span element with that value. We’ll use
@wordpress/element
’s createElement
, which is an abstraction layer on top of
React’s createElement.
import { createElement } from '@wordpress/element';
...
const { children } = element.props;
if (!children) return element;
const { value, ...childProps } = children.props;
const spanElem = createElement(
'span',
{
className: 'button-inner',
},
value
);
Recreate the children
You’ll notice ...childProps
in the code above. We need any props on the
button, other than value
, to recreate the a
tag around the span. Cloning
the element doesn’t work because it copies over the value as well, and the value
is rendered instead of the children.
const anchorElem = createElement('a', childProps, spanElem);
Note that this assumes the element is always a link. If we needed to accommodate
buttons that were created as span
or button
, we’d want to revisit this approach.
Clone the element
Finally, we return a clone of the original element (using cloneElement
, which
is likewise an abstraction on top of the React API), using the new anchorElem as the
children instead of the original children.
return cloneElement(element, {}, anchorElem);
So altogether, the updateBlockMarkup
function looks like this:
import { createElement, cloneElement } from '@wordpress/element';
const updateBlockMarkup = (element, blockType) => {
if (!element || blockType.name !== 'core/button') return element;
const { children } = element.props;
if (!children) return element;
const { value, ...childProps } = children.props;
const spanElem = createElement(
'span',
{
className: 'button-inner',
},
value
);
const anchorElem = createElement('a', childProps, spanElem);
return cloneElement(element, {}, anchorElem);
};
Update the attribute
There’s one problem left. The core/button
block gets the button text
from the contents of the a
tag. Since those now include the span, the markup
ends up part of the value, and the saved element doesn’t match what WordPress expects.
To fix that, we can alter the block metadata to use the span tag as the selector instead.
This can be done using the block_type_metadata
filter in functions.php.
function my_wp_theme_block_metadata_registration( $metadata ) {
if ($metadata['name'] === 'core/button') {
$metadata['attributes']['text']['selector'] = 'span';
}
return $metadata;
}
add_filter( 'block_type_metadata', 'my_wp_theme_block_metadata_registration' );
References
- https://css-tricks.com/a-crash-course-in-wordpress-block-filters/
- https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/#blocks-getsaveelement
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-element/
- https://reactjs.org/docs/react-api.html#createelement
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-element/#cloneelement