in Gutenberg, WordPress

Interacting with Gutenberg’s RichText Component

…by inserting a shortcode (using a button).

As of the latest WordPress update we all now know about the Gutenberg editor, and whether you love or hate it, it’s here to stay.

But, as it’s so new, it means the APIs are still being worked on – which (naturally) means some things are just not possible or documented yet.

That being said, there are plenty of great resources out there at the moment for creating Gutenberg Blocks.  This post assumes you know how to:

  1. Create a Gutenberg Block Plugin
    ~ check out create-guten-block
  2. Add Toolbars to Blocks
    official docs have got you covered
  3. Add the RichText component to a Block
    ~ check out the excellent video by Zac Gordon

The Problem

What if we want to make a word red, or add a shortcode into our RichText Component with a single click? …and how about from a button on the toolbar?

Sure, we can roll our own and create any kind of editor in our block (we have total flexibility to integrate TinyMCE however we want), but surely there’s a way to do it with the built in RichText component… After all its what the core blocks use, and we like doing things the WP way right?

This is something that all my googling couldn’t unearth…

After some days digging around and a lot of hacking at code I’ve found there is indeed a way.

The Solution

Before we get to that, lets get up and running with some familiar Gutenberg Block code.

1.  Create a Block with RichText and BlockControls (Toolbar) Components

This post assumes you already know how to create a Gutenberg Block with a RichText component. If not, you should familiarise yourself with the resources above.  Your code should look something like this:

View the Code

Nothing out of the ordinary – we’re using registerBlockType to setup our block, adding RichText and  BlockControls components to our edit function, with a couple of functions handling the changes and setting the content.

If you’re using create-guten-block, then replace src/block/block.js with the above Gist.

2. Accessing the editor

Using `onSetup`

Although it’s not documented yet we can see onSetup is a callback we can utilise which will return a copy of the TinyMCE editor (RichText components use TinyMCE).  However for me at least this was not the tricky part.

Disclaimer: I’m pretty new to React + ES6 and still getting my head around some of their concepts – which is probably why I found this more difficult to understand than some might.

My first idea was to setup a callback which receives the TinyMCE editor, and then stores a reference to that in the current object:

function onEditorSetup( editor ) {
    this.editor = editor;
}

Fail… What’s wrong with this?  …Scope.

One thing I’ve learnt is, the edit function is returned for each instance of our block in the post editor view, but the parent object with the edit function is invoked only once when setting up the block as a block type (should be pretty obvious really, took me a minute).

This means that anything we want to use in our edit function, must live only inside the edit function (pretty much) so that each instance is self contained, and interacts with its own objects…

Hmm… Where to go from here… where do we store the reference to editor?

Extending the `edit` function

If edit is a function, then I could see no reliable way to keep track of each instance of our editor and controls. I did think about wrapping RichText in a React Component so I could keep a reference to the editor, within a certain scope, but then realised that I’d run in to the same problem when trying to get the toolbar button to interact with it. It was a bit beyond me.

Cue examining the Gutenberg source code (again). Sure enough, there is a Gutenberg component that utilises the onSetup callback and provides a great example of how to handle this scenario correctly.

Turns out my thinking was a bit limited, what I needed to understand was that edit just needs to return a React Component.

So what they’ve done with the List Component is create a React Component on edit, and return the RichText and all the trimmings together within the Component, so they can access shared local variables, functions etc.

Great stuff, this means we can get our button talking to the editor within the same scope.

Lets see what edit might look like, using inspiration from the Gutenberg source (check the inline comments):

edit: class extends Component {
    
    //standard constructor for a component
    constructor() {
        super( ...arguments );
        
        //make sure we bind `this` to the current component within our callbacks
        this.setupEditor = this.setupEditor.bind( this );
        this.onChangeContent = this.onChangeContent.bind( this );

        this.state = {
            //we don't need our component to manage a state in this instance
        };
    }
    
    //same as before, except `this` actually references this component
    setupEditor( editor ) {
        this.editor = editor;
    }
    
    //no change here again, except the binding of `this`
    onChangeContent( newContent ) {
        this.props.setAttributes( { content: newContent } );
    }
    
    //slightly different pattern of syntax here, we're returning a function
    onClickShortcodeButton() {
        return () => {
            
            //the content we want to insert
            var myContent = '[myshortcode][/myshortcode]';
            
            if ( this.editor ) {
                //execCommand is a TinyMCE function
                this.editor.execCommand( 'mceInsertContent', false, myContent );
            }
        };
    }
    
    //all react components require render - this is what will be returned by our component
    render() {
        const {
            attributes,
            setAttributes,
            className,
        } = this.props;
        
        return (
            <Fragment>
                <BlockControls
                    controls={ [
                        {
                            icon: 'edit',
                            title: __( 'Insert Shortcode' ),
                            onClick: this.onClickShortcodeButton(),
                        },
                    ] }
                />
                <RichText
                    //getSettings={ this.getEditorSettings } //a useful callback for adding params to TinyMCE on setup
                    onSetup={ this.setupEditor }
                    key = { 'editable' }
                    tagName = { 'p' }
                    className = { className }
                    onChange =  { this.onChangeContent }
                    value = { attributes.content}
                />
            </Fragment>
        );
    }
},

Remember edit is now a React + ES6 Component, so the syntax is fairly standard, but not what you might be used to seeing in a Block.

What we’ve done here is port everything (our click handlers, return elements) over so that it uses the correct syntax/structure for a React Component.  One thing to note is, if we’re using a React Component it must properly return the Component in the render method, which looks a lot like the return of our old edit function.

There is one addition though, now that we have this.editor working within the scope of the Component, I’ve added the final functionality allowing the button to interact with it:

onClickShortcodeButton() {
	return () => {
		
		//the content we want to insert
		var myContent = '[myshortcode][/myshortcode]';
		
		if ( this.editor ) {
			//execCommand is a TinyMCE function
			this.editor.execCommand( 'mceInsertContent', false, myContent );
		}
	};
}

This just updates the click callback to interact with this.editor, and in this case we wanted to insert a shortcode using execCommand.

And that’s it! Everything should be working together nicely now.

From here, the world is your oyster – you have access to the TinyMCE editor which means you can now develop the same functionality into your Gutenberg Blocks as we can with the Classic editor.

NB – you’ll notice in the source a commented getSettings callback, this allows you to modify the settings object that gets passed into TinyMCE when being initialised – might be useful!

Files

View the Final Code

Code as a CGB (create-guten-block) Plugin

Conclusion

There are plenty of possibilities now that we’re able to access TinyMCE in our custom blocks and indeed this will be useful for many right away…

But I’m sure there are a few others thinking, why do all this now? – what we really want to do is add this kind of functionality to the core blocks…! –  and that’s also my thought process…

I think in all likelihood when Gutenberg allows us to extend core blocks, they’re going to have to add a way to access the TinyMCE editors within them, much like when we use onSetup now within our own blocks.

Hopefully, when the time comes and the APIs get updated, we’ll have code that’s pretty much ready to go and had some testing in the Gutenberg  environment at least.

That’s all for now – I would love to hear about things that you’ve made as well as any thoughts on the article.