Open Search

Getting Gutenberg: How I’m using WordPress’ new editor

November 7, 2019 3:42 pm
Categorised in:
Reading Time: 10 minutes

Gutenberg, for those unaware (and this is a mighty weird article to read if you aren’t aware), is the new WordPress article creation tool. I say new, it’s been around about a year now. But from what I’ve gathered during my journey to this point as a theme developer, it’s still a relatively feared dark art in the WordPress world. If you are here for a code example of creating some Gutenberg components utilising child blocks in ES5, you might find it useful to skip down the article.

Actually now I’ve broken through the initial fog of confusion that comes from trying to adopt and bend it to my will for a theme I’m building, I can already really appreciate it and see it probably is the giant leap forward people have been talking about. Especially as it pertains to website developers, because the way I’m developing with it, I see the opportunity to reduce a lot of more boring, post-launch work after you’ve released a new site.

Prior to Gutenberg, and lick me all over if this sounds familiar to you, a lot of my themes tended to be template and custom meta block heavy. By that I mean, quite a few templates and then supporting options and toggles under the editor to wrangle the page to a clients requirements. This would then gain weight as clients in time would ask for new odds and ends. Even typing this now, having used Gutenberg, this seems insane, but previously I actually found all this quite elegant.

Gutenberg changes all of that around allowing you as a developer to focus less on template creation and more on componentization. Gutenberg itself can be used as a very powerful layout tool, you feed that some custom components with set rules and suddenly you can create infinite variations on pages with really rich deep content, with a very minimal amount of templates.

And when I say you, I mean the client. That’s what you can do here. Reduce the amount of times someone needs to come back to you as a developer and say we need a new template of XYZ which uses A, B, & C bits from other pages and has this text and image instead. Gutenberg really will allow a client, designer, account manager to safely compose these pages from your components and you can be reasonably sure that what they’ve done will operate within the rules that you defined.

How I’m working with Gutenberg

Conceptually, Gutenberg seems to want to veer WordPress away from it’s PHP roots and replant them in React. So if you hadn’t bothered to learn React (like me) you’re gonna come at this and be a bit lost. Especially as everyone else seems to like to document in ESNext and I’m not using that. However, if you know your Javascript and like to play with JSON, you will easily pick up the pattern. I’ve relied heavily on Render Callbacks so I offload back out of React on change to what were previously PHP partials of templates.

I’ve done this so that these components can be reused outside the editor environment if there are instances where I’d want to specifically add a block the old way. Say for instance, I had an upsell block which I wanted to add to the bottom of every blog page, with contents populated via an Options page in the admin. So, this method allows for consistent component reuse throughout WordPress, not just with Gutenberg.

I’ve now refactored these old partials into classes, still doing the same thing, spitting out a partial, but the encapsulation allows me to think more clearly about the attributes I’m getting back from theReact in ┬áthe Editor on save and applying them to the partial. Previously, this would have been where I’d grab some custom post meta values that had been saved and populate the partial, but now, just get it out of the React callback. Effectively, it’s made me clean up my PHP standards a bit, and everything feels better for it.

The JS function you use to create a block is quite simple on the surface but can have a lot of depth. Certainly more thank I’ve shown in the examples below. For example you can create a component which contains user definable child blocks (Inner Blocks). So if you create an accordion component, you can create a top level container block which houses a defined title and intro paragraph but which also has an area where a user can create as many or as few child blocks, which are separately composed blocks as they require.

What’s really cool is these inner blocks can be scoped to certain types, so you can only allow the user to add an accordion block into an accordion container. Which really adds to that degree of safety that I mentioned earlier. No stray p tag randomly getting in there and breaking formatting or something. Previously in the editor, you could attempt something like this but it’d be incredibly fragile and you inevitably end up having to tweak where a stray tag had got in. Not now, now it’s clean markup, perfect every time. This alone is an insane leap forward.

Example Code to create a Gutenberg Component

This example is to create an accordion component as mentioned earlier. I’m providing the JS in ES5 markup as there doesn’t appear to be a lot of that about and for me this would have been really helpful (c’mon Google give me those mad SEO hits for people looking for ES5 ). WordPress examples tend to show these being created in a plugin, but you can just do it in your theme.

Setting up blocks for the Editor

so first things first, jump into your theme and drop this into your functions.php:


/// Block: Section - Accordion

/// Register Block
function gutenberg_blocks_sections_accordion_register_block() {
    /// First we register the script for our block, 
    /// The array parameter will be passed in our script
    wp_register_script('section-accordion',
        get_template_directory_uri().'/js/gutenberg-blocks/section-accordion.js',
        array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components' )
    );
    /// Then we register the block type, to refer to our registered script.
    /// note the render callback, our js file returns attributes and content to this callback
    register_block_type( 'gutenberg-sections/section-accordion', array(
        'editor_script' => 'section-accordion',
        'render_callback' => 'gutenberg_blocks_sections_accordion_render_callback'
    ) );
}
/// add the register block on init
add_action( 'init', 'gutenberg_blocks_sections_accordion_register_block' );

This sets up the block our container for WordPress, from here we go to our js file that was referenced in wp_register_script to create the options for this section:


/// These parameters we passed in during register in functions.php
( function( blocks, editor, element, components ) {
    /// Used to create elements
    var el = element.createElement;
    /// So we can have rich text
    var RichText = editor.RichText;
    /// So we can create innerBlocks
    var InnerBlocks = editor.InnerBlocks;
    /// register the block type
    blocks.registerBlockType( 'gutenberg-sections/section-accordion', {
        /// User facing title shown when selecting a block in the Gutenberg Editor
        title: 'Section: Accordion',
        /// User facing icon shown when selecting a block in the Gutenberg Editor
        icon: 'universal-access-alt',
        /// which category this block lives in the Gutenberg Editor selection thing
        category: 'communityAssist_blocks',
        /// Attributes are the data that gets passed back in an array to the render callback
        attributes: {
            /// Here we will pass back a string for the title of the section
            title: {
                type: 'string',
                default: 'I am a title'
            },
        },
        /// This fires whenever you edit something, 
        edit: function( props ) {
            /// gets our current title from the attributes
            var title = props.attributes.title;
            /// callback used whenever the title is edited, which then sets the title attribute
            function onChangeTitle( obj ) {
                console.log(obj);
                props.setAttributes( { title: obj } );
            }
            /// the return of the function should be the contents that we want visible to edit within the block 
            return [
                /// For our block we want an editable area for the title, and we want an area where users can add as many blocks as they require
                /// here we add the title element, note the callback to onChangeTitle and that we use our title var to provide the current attribute title
                el(RichText,
                    {
                        tagName: 'h2',
                        className: props.className,
                        onChange: onChangeTitle,
                        value: title,
                    }
                ),
                /// This creates an area for inner blocks
                el(InnerBlocks,
                    {
                        /// Use allowed blocks to limit which components can be used within the inner blocks
                        allowedBlocks:['gutenberg-components/accordion-block'],
                        /// Here we are providing a template with two inner blocks as a starter, the user can add more
                        template: [
                            ['gutenberg-components/accordion-block',{},[]],
                            ['gutenberg-components/accordion-block',{},[]]
                        ]
                })
            ];
        },
        /// When the user saves the document 
        save: function( ) { 
            /// Normally we'd return null here as we are using a render callback, and we'd just want the attributes to be passed back to our render callback however, as we are using inner blocks we want to return the content of the inner blocks
            return el(InnerBlocks.Content)
        }
    } );
}(
    window.wp.blocks,
    window.wp.editor,
    window.wp.element,
    window.wp.components
) );

This handles the editor side of WordPress, we’ll get to the front end rendering in a bit, but for now we want to repeat the process to create the accordion container block. For brevity, I won’t comment unless there is specific things to note. So back in functions.php we register what will become our inner accordion blocks:


/// Block: Component - Accordion block

/// Register Block
function gutenberg_blocks_component_accordion_block_register_block() {
    /// First we register the script for our block, 
    /// The array parameter will be passed in our script
    wp_register_script('accordion-block',
        get_template_directory_uri().'/js/gutenberg-blocks/accordion-block.js',
        array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components' )
    );
    /// Then we register the block type, to refer to our registered script.
    /// note the render callback, our js file returns attributes and content to this callback
    register_block_type( 'gutenberg-components/accordion-block', array(
        'editor_script' => 'accordion-block',
        'render_callback' => 'gutenberg_blocks_component_accordion_block_render_callback'
    ) );
}
/// add the register block on init
add_action( 'init', 'gutenberg_blocks_component_accordion_block_register_block' );

Very similar to previous, these registrations don’t change much. But note the change to the render callback and obviously the script names/paths.

This inner block will contain a title and content. So our JS file looks like:


( function( blocks, editor, element, components ) {
    var el = element.createElement;
    var RichText = editor.RichText;
    blocks.registerBlockType( 'gutenberg-components/accordion-block', {
        title: 'Accordion Block',
        icon: 'universal-access-alt',
        category: 'communityAssist_blocks',
        /// Set this as child to the section accordion
        parent: ['gutenberg-sections/section-accordion'],
        attributes: {
            title: {
                type: 'string',
                default: 'I am a title'
            },
            /// Note the additional attribute for the content in this inner block
            content: {
                type: 'string',
                default: 'I am the content'
            },
        },
        edit: function( props ) {
            var title = props.attributes.title;
            /// Note the additional var
            var content = props.attributes.content;
            function onChangeTitle( obj ) {
                //console.log(obj);
                props.setAttributes( { title: obj } );
            }
            /// Note the additional callback
            function onChangeContent( newContent ) {
                props.setAttributes( { content: newContent } );
            }
            return [
                el(RichText,
                    {
                        tagName: 'h2',
                        className: props.className,
                        onChange: onChangeTitle,
                        value: title,
                    }
                ),
                /// Note the additional rich text element
                el(RichText,
                    {
                        tagName: 'p',
                        className: props.className,
                        onChange: onChangeContent,
                        value: content,
                    }
                )
            ];
        },
        /// here we return null as we are composing our block in the render callback. The rendered output will then be sent to the parent container, somewhat automagically
        save: function() { 
            return null
        }
    } );
}(
    window.wp.blocks,
    window.wp.editor,
    window.wp.element,
    window.wp.components
) );

If this has all worked correctly, in your editor you may see something like the image below and selecting your new accordion block should show the following UI:


As you can see, we have our section title, then two inner blocks as specified in our accordion block InnerBlock template and then an option add more accordion blocks.

Rendering blocks on the front end

To render the output of the saved attributes our content is passed to the render callback we designated in our registration of the block. So for our inner accordion block, our render callback looks like this in our functions.php:


/// Render Callback
function  gutenberg_blocks_component_accordion_block_render_callback($attributes, $content){
    /// the attributes we set up in our accordion-block js are passed here
    /// I pass them into an args array that I use to populate the contents of my partial
    $args = array(
        'title' =>; $attributes["title"],
        'content' =>; $attributes["content"],
    );
    /// handle creation of my partial
    include_once get_template_directory().'/partials/sections/component-accordion-block.php';
    $block = new component_accordion_block($args);
    Then return the finished partial
    return $block->create();
}

In the above render callback, we don’t use the returned rendered $content, we only need to attributes to compose the output. However, for the render callback for the section container, we will use that $content as that is the precomposed accordion blocks we have are creating here:


/// Render Callback
function  gutenberg_blocks_sections_accordion_render_callback($attributes, $content){
    //var_dump($content);
    //var_dump($attributes);
    $args = array(
        'title' =>; $attributes["title"],
        /// Note this is precomposed html output code of the accordion blocks
        'accordionBlocks' =>; $content
    );
    /// Create full width image container
    include_once get_template_directory().'/partials/sections/section-six-up-accordion-blocks.php';
    $accordion = new section_accordion_blocks($args);
    return $accordion->create();
}

The render callbacks feed into individual classes for composing these partials i.e, the container for the section looks like this.

The section container

Note how the $accordionBlocks var is taking precomposed content supplied from the render callback and just dumping it straight into the string. Again, this all helps to keep everything nice and encapsulated.


class section_accordion_blocks{

    private $title = "Title";
    private $content = "lorem ipsum dolor sit amet";
    private $accordionBlocks = '';
    public function __construct($args) {
        if (!is_null($args)){
            if (array_key_exists('title', $args)){
                $this->title =  $args["title"];  
            }
            if (array_key_exists('content', $args)){
                $this->content =  $args["content"];  
            }
            if (array_key_exists('accordionBlocks', $args)){
                $this->accordionBlocks =  $args["accordionBlocks"];  
            }
        }
    }

    public function create(){
        $title = $this->title;
        $content = $this->content;
        $imgDiv = $this->getImageFromID();
        $accordionBlocks = $this->accordionBlocks;
        $composed = "<section id=\"section-points-block\" class=\"accordion-block\">
                        <div class=\"content\">
                                <h2 class=\"dark-text\">".$title."</h2>
                                <div class="block-grid">".$accordionBlocks."</div>
                        </div>
                    </section>"; 
        return $composed; 
    } 
}

The accordion block

Again, very straight forward, with the accordion block we compose the partial purely from the attributes we set up earlier


class component_accordion_block{
    private $title = "Title";
    private $content = "lorem ipsum dolor sit amet";
    public function __construct($args) {
        if (!is_null($args)){
            if (array_key_exists('title', $args)){
                $this->title =  $args["title"];  
            }
            if (array_key_exists('content', $args)){
                $this->content =  $args["content"];  
            }
        }
    }

    public function create(){
        $title = $this->title;
        $content = $this->content;
        $block = '<details open class="block">
                    <summary><h3 class="dark-text">'.$title.'</h3></summary>
                    <p>'.$content.'</p>
                </details>';
        return $block;
    }
}

Creating a category for your blocks

It’s probably handy for your users to have a way of finding theme specific components you’ve created. Remember earlier when we were in our individual registerBlockType js files? There was a key for category at the top level. Example uses for this would be an extended paragraph block, you might add that to the ‘core’ category. Or you can make your own. Before we can have new category displayed in the Gutenberg drop downs, we need to register it. So add this to your functions.php:


function communityAssist_block_category( $categories, $post ) {
    return array_merge(
        array(
	    array(
		'slug' => 'communityAssist_blocks',
                'title' => __( 'Community Assist Blocks', 'communityAssist_blocks' ),
	    ),
        ),
        $categories
    );
}
add_filter( 'block_categories', 'communityAssist_block_category', 10, 2);

This adds a filter which creates our new category and merges that array into the existing array of block categories. If we go back into our editor and try to add a new block, you should now see your new category listed just after most used.

Result

When all is done, and with a bit of CSS Grid and Javascript to handle some front end requirements for making it accordion I get a lovely accordion at mobile and contents displayed open on desktop


In Conclusion

Lovely stuff. So with that, we’ve made a simple, yet flexible, yet strict, component which is pretty difficult to break, and retains high levels of encapsulation. If you have a lot of bits and pieces that work already as components in PHP this seems to be the way to go for me, prior to Gutenberg these might be good instances to take and convert into these sorts of components. I’ve found this method has meant there is less cognitive overhead in having to work out the rendering ins and outs of React, and ESNext, whilst still getting your feet wet enough to feel a bit comfortable. As I’ve mentioned, Gutenberg seems incredibly powerful and ultimately the future of WordPress. I foresee a day when Widgets evolve, making way for a Gutenberg component-esque Widget panel. It’s a huge leap forward, and the basics can be grasped and wrangled within a few days work. Excited to see what else I can do with it going forward.

This joint was penned by @elmarko