miguel.nz

Creating custom blocks with ACF, Gutenberg and native attributes and components

February 4, 2024   |   4 minutes read.

In this article I am going to summarise how I have been using ACF to create custom block that can be integrated with core attributes and components.

The first thing we do when creating a block on ACF is the block.json:

{
  "name": "tag-add-on",
  "title": "Tag On",
  "description": "White button with icon and text.",
  "editorStyle": [ "file:./styles.css" ],
  "category": "ndnb",
  "icon": "admin-comments",
  "keywords": ["button", "tag", "add-on"],
  "supports": {
    "multiple": false,
    "mode" : false,
    "jsx": true
  },
  "acf": {
    "mode": "preview",
    "renderTemplate": "render.php"
  },
  "align": "full"
}

In this case I am creating a custom tag with an image an text. I am assuming you know how to create blocks. You can read more here.

Then my render looks like this:

<?php

$template = array('core/paragraph');
$has_icon = isset($block['IconField']) && $block['IconField'] ? 'wp-block-tag-on-has-icon' : '';
$block_attributes = get_block_wrapper_attributes([
  'class' => 'wp-block-tag-add-on ' . $has_icon,
]);

?>

<div <?php echo $block_attributes; ?>>
  <div class="tag-add-on">
    <?php if ( isset($block['IconField']) && $block['IconField'] ): ?>
      <figure>
        <img src="<?php echo $block['IconField']; ?>">
      </figure>
    <?php endif; ?>
    <InnerBlocks allowedBlocks="<?php echo esc_attr( wp_json_encode( $template ) ) ?>" />
  </div>
</div>

There are few things to understand here:

$template = array('core/paragraph');

Here I define the templates available on my InnerBlocks

$has_icon = isset($block['IconField']) && $block['IconField'] ? 'wp-block-tag-on-has-icon' : '';
$block_attributes = get_block_wrapper_attributes([
  'class' => 'wp-block-tag-add-on ' . $has_icon,
]);

Here I handle if the icon has been added. If that is happening I am adding an additional CSS class. get_block_wrapper_attributes is a function from WordPress Core.

...
<div <?php echo $block_attributes; ?>>
...

Here I render the attributes from the block wrapper.

  <div class="tag-add-on">
    <?php if ( isset($block['IconField']) && $block['IconField'] ): ?>
      <figure>
        <img src="<?php echo $block['IconField']; ?>">
      </figure>
    <?php endif; ?>
    <InnerBlocks allowedBlocks="<?php echo esc_attr( wp_json_encode( $template ) ) ?>" />
  </div>

This is the rest of my block, it basically renders an image if exists and allows an InnerBlocks to be rendered. For now this InnerBlocks will only accepts core/paragraphs.

Creating an attribute with the core of WordPress

The first part is to create an attribute on our block. This requires some Javascript and you need to do the following:

  1. We will create a file to handle the JS part. Let’s say wp-tag-on-fields.js
  2. We will create an attribute called IconField
  3. The attribute type will be set as string
  4. The method addIconField will be added via wp.hooks.addFilter method
function addIconField(settings, name) {
  
  if (typeof settings.attributes !== 'undefined') {
    
    if (name == 'acf/tag-add-on') {
      settings.attributes = Object.assign(settings.attributes, {
        IconField: {
          type: 'string',
        }
      });
    }
  
  }
  
  return settings;

}

wp.hooks.addFilter(
  'blocks.registerBlockType',
  'mnz/tag-on-fields',
  addIconField
);

Handling the attribute via wp.compose.createHigherOrderComponent

What is next is to check when acf/tag-add-on is rendered and add our attribute on the InspectorControl. In the same way as with the method addIconField WordPress uses React components to do the work. The following code allow us to handle IconField, add an image, replace or delete it.

const IconField = wp.compose.createHigherOrderComponent((BlockEdit) => {
  return (props) => {
    const { MediaUpload } = wp.editor;
    const { Fragment } = wp.element;
    const { PanelBody, Button } = wp.components;
    const { InspectorControls } = wp.blockEditor;
    const { attributes, setAttributes, isSelected } = props;
    const { image } = attributes;

    const onSelectImage = (media) => {
        setAttributes({ IconField: media.url });
    };

    return (
      <Fragment>
        <BlockEdit {...props} />
        {isSelected && (props.name == 'acf/tag-add-on') && 
          <InspectorControls>
            <PanelBody>
              <p>Select an Icon:</p>
              <MediaUpload
                onSelect={onSelectImage}
                type="image"
                value={image}
                render={({ open }) => (
                  <>
                    {attributes.IconField ? (
                        <>
                          <p><img src={attributes.IconField} alt="Selected" style={{ maxWidth: '50%', height: 'auto' }} /></p>
                          <p><a href="#" onClick={open}>Replace Image</a> | <a href="#" onClick={() => { setAttributes({ IconField: null }) }}>Delete Image</a></p>
                        </>
                    ) : (
                        <Button className="components-button is-primary" onClick={open}>Select Image</Button>
                    )}
                  </>
                )}
              />
            </PanelBody>
          </InspectorControls>
        }
      </Fragment>
    );
  };
}, 'IconField');

wp.hooks.addFilter(
  'editor.BlockEdit',
  'mnz/tag-add-on-fields',
  IconField
);

That is all. If you have registered your ACF Block accordingly, you should be able to see the attributes and handle everything with the wordpress core. As WordPress is reaching some kind of maturity with gutenberg, I find it interesting to play around with their core capabilities and functionalities. This also makes it easy for the user to manage and preview their content.

Extra: Managing React Assets

It is true that react components need to be prepared for the browser. You can use your own tools. In my case, as I use Laravel Mix for my assets and task management. I find this package quite useful: https://laravel-mix.com/extensions/wp-blocks.

Your webpabk.mix.js should look like this:

mix.block(
  [
    `/block-editor-assets/src/wp-tag-on-fields.js`
  ], 
  `/block-editor-assets/dist/custom-block-editor-assets.js`
);

This post has been partially based on: https://awhitepixel.com/add-custom-settings-to-existing-wordpress-gutenberg-blocks/ as a reference.