In our last post we set up a build environment and got our MyCanvas component loading in the editor.  My plans for this post is to get this block functioning as a very simple doodle pad.

This is a long post.  I don’t mind this though, because I want it to represent how I work through these kinds of processes.  I hope it’s helpful.

If you want to pick up right here, here is the repo from part 1: https://github.com/JimSchofield/guty-paint-tutorial/tree/master

How we will store data

The canvas element is a great way to draw 2d graphics.  We’re going to be using it to render our the doodling that the user can do.  The downside is that we need to store all of the X-Y points that the user clicks on the canvas.

On top of that, we need a way to store those points on the DOM so that when the view is loaded, the view can pick those points up again and render out the canvas stuffs on the view.

So here is our strategy: We’re going to store data in attributes as our single source of truth.  BUT we will also be saving points as a data attribute in our save() function.  Our goal is to be able to reuse MyCanvas in the editor and in the front end view.

Let’s actually start building toward that…

Creating a clickable canvas

Let’s have our MyCanvas component render out a canvas.  We want React to re-render the canvas whenever data is updated, so we have to set up an update function that should run when data is updated…

import React from 'react';

export default class MyCanvas extends React.Component {

    // Whenever component mounts or update, re-render the canvas
    componentDidMount() {
        this.updateCanvas();
    }
    componentDidUpdate() {
        this.updateCanvas();
    }

    // This should always re-render the canvas as if its a blank slate
    // This means we need to store a "history" of all strokes that the user
    // creates in the block
    updateCanvas() {
        // Find the width/height of the canvas to use later
        const width = this.refs.canvas.width;
        const height = this.refs.canvas.height;

        // We're going to access the canvas as a ref so we can do things directly
        // to the canvas.  If you aren't familiar with context, take a look at
        // a canvas tutorial.  Basically, it's what you do most of your
        // painting and work on
        const canvas = this.refs.canvas;
        const ctx = canvas.getContext("2d");

        // We're going to start by filling the canvas with orange
        ctx.fillStyle = 'orange'
        ctx.fillRect(0, 0, width, height);
    }

    render() {
        return (
            <canvas 
                width="400"
                height="400"
                ref="canvas"/>
        )
    }
}

Okay, so we have an orange box.  Yay!

Collecting user created paths

Alright, so it’s going to go something like this:

  1. The user clicks and holds down on the canvas (we triggers an ‘isDrawing’ boolean)
  2. As the user moves, if the ‘isDrawing’ boolean is turned on, we will collect the coordinates of the moves in an array
  3. On mouse up, we check to make sure there was more than one point, and then we save the array as a “stroke” in Gutenberg attributes.
  4. We pass the stroke list to the canvas component to render the stroke.

Easier said than done.  Let’s do this:

1 – On user mouse down

Now we need state- a temporary place to store coordinates until we save the coordinates as a stroke.  We also need to toggle ‘isDrawing’ when someone clicks down on the canvas.  So let’s add these things to MyCanvas.js

constructor(props) {
    super(props);

    this.state = {
        isDrawing: false
    }

    // need to bind this to the method so we can do
    // things like 'setState'
    this.startDrawing = this.startDrawing.bind(this);
}

...

startDrawing() {
    this.setState({ isDrawing: true });
}

render() {
    return (
        <canvas 
            width="400"
            height="400"
            ref="canvas"
            onMouseDown={startDrawing}
            />
    )
}

2 – Collecting coordinates

Now we have to start collecting mouse positions and end the drawing on mouse up.  Next, let’s just console log the (x,y) points on mouse move, and then turn off ‘isDrawing’ on mouse up.

// need to bind this to the method so we can do
// things like 'setState'
this.startDrawing = this.startDrawing.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.endDrawing = this.endDrawing.bind(this);

...

startDrawing() {
    this.setState({ isDrawing: true });
}

handleMouseMove(event) {
    if (this.state.isDrawing) {
        console.log(event);
    }
}

endDrawing() {
    this.setState({ isDrawing: false });
}

render() {
    return (
        <canvas 
            width="400"
            height="400"
            ref="canvas"
            onMouseDown={this.startDrawing}
            onMouseMove={this.handleMouseMove}
            onMouseUp={this.endDrawing}
            />
    )
}

Cool.  Now, if you load the block in the editor and click-drag on the canvas, you will start to see the event being logged to the window.

Let’s tweak the mouse move method a little bit so that we only log the x/y position in relation to the canvas element.  Remember that (x,y) given in an event is from the top and from the left of the object.

handleMouseMove(event) {
    if (this.state.isDrawing) {
        console.log(`${event.clientX},${event.clientY}`);
    }
}

Now if you click drag you can see `x,y` points being printed out.  The problem, though, is that if you go to the upper left of the orange box you should see 0,0, but I see instead something like 310,267.  The x,y we’re getting is in relation to the client viewport.  So the solution is to get the left/top dimensions of the canvas, and subtract those away.  Remember, we only want the x,y points in relation to the top left of our canvas.

handleMouseMove(event) {
    if (this.state.isDrawing) {
        const domRect = event.target.getBoundingClientRect();
        const top = domRect.top;
        const left = domRect.left;
        const point = { x: event.pageX - left, y: event.pageY - top };
        console.log(point);
    }
}

Now if you click and drag near the upper right corner you should see numbers close to 0,0.  Ahhhhh, very nice.

3 – Saving the stroke as an attribute

First, we need to be collecting these points as an array of x,y coordinates, so quickly…

handleMouseMove(event) {
    if (this.state.isDrawing) {
        const domRect = event.target.getBoundingClientRect();
        const top = domRect.top;
        const left = domRect.left;
        const point = { x: event.pageX - left, y: event.pageY - top };
        
        // Merge current point with previous points
        this.setState({
            inProgressPath: [...this.state.inProgressPath, point],
        })
    }
}

endDrawing() {
    this.setState({ isDrawing: false });
    console.log(this.state.inProgressPath);
}

If you’re following this far, you should get one array printed out after you click and drag on the canvas for a bit.

That is the array of points we need to save to attributes.  We will create an ‘addStroke’ function in our gutenberg block (index.js) and pass this function to MyCanvas to use when the drawing is ended.  While we’re doing this, we will create a ‘strokeList’ attribute to save all the strokes.

import MyCanvas from './MyCanvas'; // new!

const { registerBlockType } = wp.blocks;

registerBlockType("guty-paint/block", {
    title: "Guty Paint",
    icon: "admin-customizer",
    category: "common",

    attributes: {
        strokeList: {
            type: Array,
            default: []
        }
    },

    edit(props) {

        const { setAttributes } = props;
        const { strokeList } = props.attributes;

        function addStroke(stroke) {
            setAttributes({
                strokeList: [...strokeList, stroke],
            });
        }

        return (
            <MyCanvas
                addStroke={addStroke}
                strokeList={strokeList}
                />
        );
    },

    save(props) {
        return (
            <MyCanvas />
        );
    }
});

Now ‘strokeList’ is accessible as a prop in MyCanvas and we can trigger adding a stroke because addStroke is available as a prop as well!  Let’s store the stroke and reset the inProgressPath when we do:

...
endDrawing() {
    this.props.addStroke(this.state.inProgressPath);
    this.setState({ isDrawing: false, inProgressPath: [] }, () =>
        console.log(this.props.strokeList)
    );
}
}
...

(I did the console log callback just so we can see the props after state updates.  I’m not going to keep that.)  So, if you save and load the block again, after you click drag on the canvas, you should see an array of points- our stroke- show up in your console.  And rest assured, we have a stroke added to our attributes in our Gutenberg block and it will be saved with the block!  Now!  Let’s get those strokes actually painting on our canvas!

4 – Rendering all the strokes!

Cool, so what we need to do now is render strokes in MyCanvas if there are strokes to render.  In MyCanvas.js:

// inside updateCanvas() ...
// We're going to start by filling the canvas with orange
ctx.fillStyle = "orange";
ctx.fillRect(0, 0, width, height);

// draw all the strokes
this.props.strokeList.forEach(stroke => {
    console.log(stroke);
    this.drawStrokes(stroke, ctx);
});
...

// as its  own method in MyCanvas...

drawStrokes(strokes, context) {
    const firstPoint = strokes[0];
    context.moveTo(firstPoint.x, firstPoint.y);

    for (let index = 1; index < strokes.length; index++) {
        const nextPoint = strokes[index];
        context.lineTo(nextPoint.x, nextPoint.y);
    }

    context.stroke();
}

Now, if you click drag and release, you should see a path painted on your canvas!

Satisfying, no?

Okay, so that’s cool, but you may have noticed that while you’re drawing you don’t see the path.  The solution is actually pretty easy, let’s add another little bit to our updateCanvas method.

...
// draw any stroke that is in progress
if (this.state.isDrawing && this.state.inProgressPath.length > 1) {
    this.drawStrokes(this.state.inProgressPath,ctx);
}
...

So why the ‘this.state.inProgressPath.length > 1’ thing?  If we want to draw a line, we need to have at least two points, otherwise our drawStrokes method will error!

Hopefully now You can see those strokes as you draw them:

Getting this to show up on the front end

Okay, so sweet: we have a block loading and if you hit “update” it reloads if you refresh the editor.  You may have already gone to view the post and noticed that nothing happens.

In fact, you will see a <canvas> element being rendered, but nothing is being painted on it.  That’s because we haven’t yet set up our React component MyCanvas to run, and we need to pass the paths to the front end so that MyCanvas knows what to paint.

Setting up MyCanvas to run on the front end

The problem is that the save() method in Gutenberg blocks is only meant to render static html.  So while our MyCanvas element is run when we save, it’s never called when we look at the front end.

We’re going to save the strokeList as a data attribute on a div, and then manually load MyCanvas through a front end script.

First, to save the strokeList as props

let’s change index.js save() method to this:

save(props) {

    const { strokeList } = props.attributes;

    return (
        <div 
            class="paint-me"
            data-stroke-list={JSON.stringify(strokeList)}>
        </div>
    );
}

Now if you reload the paint-me block and save, when you inspect the post markup you should see a long array of points saved in the data-stroke-list value.

Second, enqueue a front-end script

I call these view scripts.  It will only run on the front end.  Let’s add ‘paint-me.view.js’ in the src folder.

console.log('hi on the front end!');

We’re going to add a separate webpack config so that this file is still compiled, but it will be treated separately since it should only be added on the front end.

// A node thingy that helps with paths... not sure on specifics
const path = require("path");

// Config for regular blocks
module.exports = [
    { //config for regular block scripts
        // entry points to the main js file for our build
        entry: "./assets/src/index.js",
        // and output lets us specify where it should be built to
        output: {
            path: path.resolve(__dirname, "./assets/dist"),
            filename: "build.js"
        },
        module: {
            rules: [
                {
                    // If Webpack sees a .js file, use babel when it builds
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: "babel-loader"
                }
            ]
        },
        stats: {
            // Pretty colors in messages!
            colors: true
        },
        externals: {
            // We don't want webpack to bundle React in our files (that would make our
            // files huge!) so we say that whenever a file requires "React",
            // Webpack won't bundle it.  We're promising, though, that we're providing
            // React somewhere else (maybe a CDN or on the window)
            // so luckily we can through WordPress.  React is in core now, yay!
            react: "React"
        }
    }, // Config for view scripts
    {
        entry: "./assets/src/paint-me.view.js",
        output: {
            path: path.resolve(__dirname, "assets/dist"),
            filename: "build.view.js"
        },
        module: {
            rules: [
                {
                    test: /.js/,
                    loader: "babel-loader"
                }
            ]
        },
        stats: {
            colors: true
        },
        externals: {
            react: "React",
            "react-dom": "ReactDOM"
        }
    }
];

And now we’re going to add to the guty-paint.php file to enqueue the newly build javascript.

...

/**
 * Enqueue view scripts
 */
function guty_paint_plugin_view_scripts() {
    if ( is_admin() ) {
        return;
    }

    wp_enqueue_script(
		'guty-paint/view-scripts',
		plugins_url( '/assets/dist/build.view.js', __FILE__ ),
        array( 'wp-blocks', 'wp-element', 'react', 'react-dom' )
    );
}

add_action( 'enqueue_block_assets', 'guty_paint_plugin_view_scripts' );

If you reload the post, you should see our new Javascript.  It should only run when viewing the site.

Third and last step: pick up the props and run MyCanvas

Instead of explaining everything, I’m going to annotate the view script below

// Even though they're not bundled, they need to be imported
import React from 'react';
import ReactDOM from 'react-dom';

// Import our MyCanvas to call on later
import MyCanvas from './MyCanvas';

ready(() => {
    // There may be many of these, so query them all
    const containers = document.querySelectorAll(".paint-me");
    // turn into array instead of node list
    const containersArray = Array.prototype.slice.call(containers);

    containersArray.forEach((element) => {
        // get the props from this div
        const strokeList = JSON.parse(element.dataset.strokeList);

        // Call react!
        ReactDOM.render(
            <MyCanvas strokeList={strokeList} />, // call MyCanvas and pass the strokeList as props
            element // need to specify the element to render on
        )
    })
});

// Thank you http://youmightnotneedjquery.com/
// Very much like $.ready() from jQuery
function ready(fn) {
    if (
        document.attachEvent
            ? document.readyState === "complete"
            : document.readyState !== "loading"
    ) {
        fn();
    } else {
        document.addEventListener("DOMContentLoaded", fn);
    }
}

If all goes well, you can load your post, and you will see your block rendering nicely!

Reflections

A few things:

I tried to sprint to the end of a minimum “viable” block.  My main goals were to show how to use the canvas element, and how to run a block that calls components in the editor and the post view.

The completed block source code is located in a repo here: https://github.com/JimSchofield/paint-me-tutorial

In my guty-paint example from my first post I added a bunch of features, like stroke color, stroke width, background color, etc.  Take a look there if you want to extend this block further.

Also, there are some bugs with this block that we don’t have time to address right now.  for example, if you drag off the canvas you won’t trigger the end stroke method. Also, I never turned off the addStroke functionality when the block is loaded in the post, so if you click on the post you will see console logs and errors.  I suggest taking a look at my more advanced block https://github.com/JimSchofield/paint-me-gutenberg-block to see how I overcame some of these things.

I know that this was a lot and this was a long post.  I feel, though, that the strategies used here are very useful for loading live javascript on the front end.  I think there will be a lot of work done to make it easy to edit a block in the editor, and then run a block on the front end.

There may be a much easier way.  I’m going to try to redo this block using SVG instead.  With SVG we would be able to render out the lines without calling a view script on the front end.

Please let me know what you think in the comments, or let me know on twitter

I just jumped back into Gutenberg block world.  There was a tweet floating around where someone wrote a block that functioned a lot like paint (after about 10 minutes searching around for the demo I gave up- let me know if you know what I’m talking about and where I could link to).  It got me thinking if I could do something similar…

Demo of “paint-me”, a WordPress Gutenberg block that allows you to paint.

The repo can be seen at https://github.com/JimSchofield/paint-me-gutenberg-block if you just want to try it out.

I learned a lot, and I want to share my process

I’m going to try to outline how you could also create a slightly less complicated block using the canvas element.  I’ve structured this article into the following sections:

In this post:

  • Simplifying the build environment
  • Setting up/installing the block as a WordPress plugin
  • Styles and stylesheets v.s. inline styles
  • Setting up a component

In the part 2 post:

  • Strategy for storing data and using components
  • Running Javascript on the view side of WordPress

Simplifying the build environment

The biggest downside to Gutenberg blocks is that creating a front end build environment can seem intense, especially for those who are used to just editing a script and seeing the changes appear without having to go through setting up a big scaffold of build tools and dependencies.

I don’t think there’s a good way to get back to the “good ‘ol days” of just editing a script.  I think we need to embrace a lot of the powerful tools to build Javascript.  In the end, it makes things more reusable and easier to reason about.

With that being said, here’s a (slightly) simpler setup to create a Gutenberg block…

First: We need to have a WordPress environment started up (I recommend local by flywheel), and a folder created in the WordPress plugin directory.  I’m going to name mine ‘guty-paint’.  I’m going to structure my files like so…

Second: Let’s install our javascript dependencies.  In your terminal, cd into ‘guty-paint’ and…

npm init -y

(-y allows us to skip all the npm questions). Now for the actual dependencies:

npm install --save-dev webpack webpack-cli @babel/core @babel/preset-env @babel/preset-react babel-loader

This is where many get sad.  Let me run through what these are before you get too sad:

  • webpack – This will be the processor that processes all of our files and bundles our executable javascript into one tidy javascript file
  • webpack-cli is a handy tool so we can run webpack using simpler commands
  • @babel/core is the main javascript transpiler in use these days.  It allows us to write in the newest javascript syntax, and…
  • @babel/preset-env and @babel/preset-env are the settings that specify that, yes, we can use JSX and, yes, we can use es6/es7/<insert super advanced fancy syntax here>
  • babel-loader ties babel together with webpack to make everything work nicely

Third:  Let’s configure our webpack file and a .babelrc file.

Here is an annotated webpack config.  You don’t have to understand all that goes on here, but it would be nice to know if you have to tweak things.

I’ve created a ‘webpack.config.js’ file in the ‘guty-paint’ directory and inside:

// A node thingy that helps with paths... not sure on specifics
const path = require("path");

// Config for regular blocks
module.exports = {
    // entry points to the main js file for our build
    entry: "./assets/src/index.js",
    // and output lets us specify where it should be built to
    output: {
        path: path.resolve(__dirname, "./assets/dist"),
        filename: "build.js"
    },
    module: {
        rules: [
            {
                // If Webpack sees a .js file, use babel when it builds
                test: /\.js$/,
                exclude: /node_modules/,
                loader: "babel-loader"
            }
        ]
    },
    stats: {
        // Pretty colors in messages!
        colors: true
    },
    externals: {
        // We don't want webpack to bundle React in our files (that would make our
        // files huge!) so we say that whenever a file requires "React",
        // Webpack won't bundle it.  We're promising, though, that we're providing
        // React somewhere else (maybe a CDN or on the window)
        // so luckily we can through WordPress.  React is in core now, yay!
        react: "React"
    }
};

Cool.  And then we just need to tell Babel what presets to use.  So in that same folder, let’s make a ‘.babelrc’ file:

{
    "presets": ["@babel/preset-env", "@babel/preset-react"]
}

Now that wasn’t too bad.

Fourth: We have to make a js file that gets built.  So to make sure our build process works, got to ‘assets/src’ and create ‘index.js’:

console.log('Hello from guty-paint')

And in our package.json, let’s add the following scripts so that we can run this thing:

...,
  "scripts": {
    "build": "webpack --config webpack.config.js --mode=development", //new
    "watch": "webpack --config webpack.config.js --mode=development --watch", //new
    "test": "echo \"Error: no test specified\" && exit 1"
  },
...

Now the moment of truth.  Run ‘npm run build’.  You should be rewarded with a nice build message:

Successful build… life is good

In summary, we have a build process running, and our folder should look like this:

Setting up/installing the block as a WordPress plugin

Okay, so we have to get this thing to work in WordPress.  Let’s create a plugin file that will enqueue our scripts in the editor and make this folder recognizable as a plugin.  I’ve created ‘guty-paint.php’ with the following in it:

<?php
/**
 * Plugin Name: Guty Paint
 * Description: A gutenberg block paint project
 * Version: 1.0.0
 * Author: Jim Schofield
 * Text Domain: guty-paint
 * Domain Path: /languages
 *
 * @package guty-paint
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}


function guty_paint_editor_scripts() {
    // Enqueue block editor JS
    wp_enqueue_script(
        // the php name for this
        'guty-paint/editor-scripts',
        // Where the file is
        plugins_url( '/assets/dist/build.js', __FILE__ ),
        // various dependencies js needs loaded with the script
        [ 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n' ],
        filemtime( plugin_dir_path( __FILE__ ) . '/assets/dist/build.js' ) 
    );
}

// Hook the enqueue functions into the editor
add_action( 'enqueue_block_editor_assets', 'guty_paint_editor_scripts' );

If you go into the plugins page in WP admin you should be able to activate “Guty Paint”!  Once activated, create a new post and open your browser console.  We will then be greeted by our new block if our scripts are being loaded correctly

‘Hello from Javascript land!’

But we don’t see a block we can select yet!

Let’s change our index.js file to this:

const { registerBlockType } = wp.blocks;

registerBlockType("guty-paint/block", {
    title: "Guty Paint",
    icon: "admin-customizer",
    category: "common",

    attributes: {},

    edit(props) {
        return (
            <p>Future home of guty-paint</p>
        );
    },

    save(props) {
        return (
            <p>Future home of guty-paint</p>
        );
    }
});

This is where javascript says “hey, WordPress, we have a block we want you to offer in the editor, and here’s the code for it.”

It’s not that interesting right now.  If you run ‘npm run build’ and reload the editor, you should have the option to add ‘guty-paint’

Admittedly, it’s not that interesting yet…

But we have what we need to start creating the interactive part of the canvas.

A note on styles and CSS…

I’ve been dealing a lot with how to extract stylesheets from webpack and enqueue them in WordPress.  It. Is. A. Pain.  For this tutorial, I’m going to simply use inline styles, because I can :).  You won’t find information about css enqueuing for blocks here, but you can check out this.

Lastly, creating the canvas component

Let’s create another js file called ‘MyCanvas.js’ in the ‘assets/src’ folder and put the following in it:

import React from 'react';

export default class MyCanvas extends React.Component {
    render() {
        return (
            <p style={{ background : 'orange' }}>Hello from MyCanvas</p>
        )
    }
}

Our goal is to have a single React component be the canvas for the editor and for when people are viewing the content.  We need to now import this component and use it in index.js:

import MyCanvas from './MyCanvas'; // new!

const { registerBlockType } = wp.blocks;

registerBlockType("guty-paint/block", {
    title: "Guty Paint",
    icon: "admin-customizer",
    category: "common",

    attributes: {},

    edit(props) {
        return (
            <MyCanvas /> // instead of rewriting, we just use our component
        );
    },

    save(props) {
        return (
            <MyCanvas />
        );
    }
});

If you load our editor once more and add guty-paint you should see a beautiful, halloweenish, block.

Continued in part 2

Here’s the repo for everything we did today: https://github.com/JimSchofield/guty-paint-tutorial/tree/master

Part 2 coming soon!

This is the second part of an article in a series on creating WordPress Gutenberg blocks. Here is part 1.  If you want to pick up where we are, here is the repository.

We left off last time with a Gutenberg hero image block that had centered text over an image.  We added the ability to edit that text and change the text color.  It started to get a little long, so the homework I left off with was to set up the overlay so that it would also be able to be changed with a color palette option in InspectorControls.  We still have yet to use the wonderful MediaUpload component.

The repo for this in all its finished glory is here if you would desire it.

Let’s do this.

How I hooked up the ColorPalette to our transparent overlay

There are many ways to skin a cat. Here is the route I chose: I will apply an opacity to our to our overlay through the css, but since the color of the background will be determined by the content editor I’ll apply that color inline.

.wp-block-firstgutyblocks-hero-image .overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    /* background: rgba(150,0,0,.3); <- old*/
    opacity: .3;
}

In our index.js, I will be adding an overlayColor attribute and add that as an inline style on my overlay div:

...
attributes: {
    textString: {
        type: 'array',
        source: 'children',
        selector: 'h2',
    },
    fontColor: {
        type: 'string',
        default: 'black'
    },
    overlayColor: { // new!
        type: 'string',
        default: 'orange'
    }
},

...

edit(props) {

    const { 
        setAttributes, 
        attributes,
        className,
        focus
    } = props;
    const { fontColor, overlayColor } = props.attributes;

...
<div 
    className={className}
    style={{
        backgroundImage: `url('http://placehold.it/1440x700')`,
        backgroundSize: 'cover',
        backgroundPosition: 'center'
    }}>
    <div 
        className="overlay"
        style={{ background: overlayColor }} {/*NEW!*/}
        ></div>
    <RichText
        tagName="h2"
        className="content"
        value={attributes.textString}
        onChange={onTextChange}
        placeholder="Enter your text here!"
        style={{color: fontColor}}
        />
</div>

Orange is a temporary choice just to make sure it’s working.  And tada!  Here we have it if we reload the editor!

Hero image block now displaying a semi-transparent orange overlay.

Okay, so now let’s add another ColorPalette component in the InspectorControls and hook it up so the user could change the color:

...
// Adding an attribute handler
function onOverlayColorChange(changes) {
    setAttributes({
        overlayColor: changes
    })
}

...

//Adding another control to the InspectorControls
<InspectorControls>
    <div>
        <strong>Select a font color:</strong>
        <ColorPalette
            value={fontColor}
            onChange={onTextColorChange}
            />
    </div>
    <div>
        <strong>Select an overlay color:</strong>
        <ColorPalette
            value={overlayColor}
            onChange={onOverlayColorChange}
            />
    </div>
</InspectorControls>,

An we now can edit text, edit the text color, AND change the overlay color.

Spiffy

Uploading a background image using MediaUpload

It’s super nice that the gutenberg editor gives us a bunch of components through wp.block that we can use.  Another component that is super nice is MediaUpload that does a lot of work for us.

MediaUpload is a component that uses what’s called render props.  You don’t have to go into detail about what those are, just realize they’re a thing and they’re very similar to Higher Order Components.  (And If you want to see a really great talk about why these things exist and how React has evolved with these sorts of things, here’s a really great talk on it.)

ANYWAY!  Let’s pull in the Media Upload, and create a new attribute that will hold our picture’s url.

const { 
    registerBlockType,
} = wp.blocks;

{
    RichText,
    InspectorControls,
    ColorPalette,
    MediaUpload  // Thanks wp.editor!
} = wp.editor;

...

attributes: {
    textString: {
        type: 'array',
        source: 'children',
        selector: 'h2',
    },
    fontColor: {
        type: 'string',
        default: 'black'
    },
    overlayColor: {
        type: 'string',
        default: null // let's get rid of the annoying orange
    },
    backgroundImage: {
        type: 'string',
        default: null, // no image by default!
    }
},

Now, let’s place our MediaUpload component:

<InspectorControls>
    <div>
        <strong>Select a font color:</strong>
        <ColorPalette
            value={fontColor}
            onChange={onTextColorChange}
        />
    </div>
    <div>
        <strong>Select an overlay color:</strong>
        <ColorPalette
            value={overlayColor}
            onChange={onOverlayColorChange}
        />
    </div>
    <div>
        <strong>Select a background image:</strong>
        <MediaUpload
            onSelect={(imageObject) => console.log(imageObject)}
            type="image"
            value={backgroundImage} // make sure you destructured backgroundImage from props.attributes!
            render={({ open }) => (
                <button onClick={open}>
                    Upload Image!
                </button>
            )}
        />
    </div>
</InspectorControls>,

A few things to note here:

  • The “onSelect” attribute is where we will eventually handle the new picture and save it’s url to “backgroundImage”.  For now, we will console.log this thing to see what MediaUpload gives us after we select an image
  • The most confusing part is the render attribute.  Basically whatever is returned inside the parentheses (in this case the <button> markup) will be shown in the editor.  MediaUpload doesn’t add anything more, but it does let you have that “open” function.  Whatever element is bound to that with onClick will be hooked up to open the MediaUpload library.

So if all goes well you can load the block, click the button, upload and select and image, and then check the console…

Editor showing our gutenberg block and a console showing the image object we console logged

MediaUpload wraps our button and allows us to make a button that opens up our media library.  Once we select an image, MediaUpload returns that image object.  For now, we set that to simply console.log the object.

MediaUpload and the object it returns

If you look closely, you notice that there’s a “sizes” property on this object?  inside of that we have lots of things that are served up by WordPress…

WordPress does it’s thing and resizes your image with different dimensions it would ordinarily use with srcset.  In this case, my picture was very large so I get a full, large, medium, and thumbnail.  In our case, I’m going to use “full” since these images are supposed to be large, detailed, full resolution things.  There’s room for optimizing and file size later!

So here’s the point, we need to create a function that will handle this object and setAttributes:

function onImageSelect(imageObject) {
    setAttributes({
        backgroundImage: imageObject.sizes.full.url
    })
}

...

<div>
    <strong>Select a background image:</strong>
    <MediaUpload
        onSelect={onImageSelect}
        type="image"
        value={backgroundImage}
        render={({ open }) => (
            <button onClick={open}>
                Upload Image!
            </button>
        )}
    />
</div>

...
//We need to make sure our div styles use this image!
<div
    className={className}
    style={{
        backgroundImage: `url(${backgroundImage})`,
        backgroundSize: 'cover',
        backgroundPosition: 'center'
    }}
>

Now, instead of logging the image when you select it, onImageSelect will set the attribute with the url of our image.  You should see the image automatically change when you select it.

Editor showing a hero image block with image showing in background of the block.

Edit: One thing I forgot to mention

I forgot to make this explicit when I first posted this.  We need to make sure that this background image shows up when we hit update! Here is what my save() method looks like now.

save(props) {

    const { attributes, className } = props;
    const { fontColor, backgroundImage } = props.attributes;

    return (
        <div
            className={className}
            style={{
                backgroundImage: `url(${backgroundImage})`,
                backgroundSize: 'cover',
                backgroundPosition: 'center'
            }}>
            <div className="overlay"></div>
            <h2 class="content" style={{ color: fontColor }}>{attributes.textString}</h2>
        </div>
    );
}

Summary

That was a a lot, but I hope that through this process this gives you ideas how to wire up your own custom block.  In these past two posts we

  • Structured the markup of a block to create an image hero
  • Learned how to use InspectorControl, ColorPalette, and MediaUpload
  • Used both css and inline styles

Thoughts? Comments?  Please post below or hit me up on twitter: @jschof

This is the 4th article in a series on creating WordPress Gutenberg blocks.  You may want to check out there other articles here, here, and here.  

Our goal here is to create a full width, customizable hero image Gutenberg block.  We aim to learn how to use the InspectorControls, ColorPalette, and MediaUpload components and use those to create a configurable block.

We’ll start off with the work from the previous article.  The completed repo for the last article is located here in the article3/styles branch.  You can pull that down to pick up right where we are now.  I have also pushed a finished hero block branch here in case you want to see it in all its glory- article4/hero-block.

Editing some of the details about our Gutenberg block first…

Let’s start by editing some of the meta information about our block.  This isn’t strictly necessary, but I’d like the title of the block to now be “Image Hero Block” so I know what it is.  I’m also changing the icon because I want it to look more like an image.

...

registerBlockType('firstgutyblocks/hero-image', {
    title: "Hero Image Block",
    icon: 'format-image',
    category: 'common',

...

Now keep in mind that since I’ve updated the string to ‘firstgutyblocks/hero-image’ I need to make sure that the php functions for our plugins are also updated.  They will be paying attention to that namespace/blockname that we have here.  So I’ve updated my firstgutyblocks.php to this:

function firstgutyblocks_hero_image_editor_assets() {
    wp_enqueue_script(
        'firstgutyblocks/hero-image',
        plugins_url( 'build/index.build.js', __FILE__ ),
        array( 'wp-blocks', 'wp-element' )
    );
    wp_enqueue_style(
		'firstgutyblocks/hero-image-editor-style',
        plugins_url( 'src/editor.css', __FILE__ ),
        array( 'wp-edit-blocks' )
	);
};

add_action( 'enqueue_block_editor_assets', 'firstgutyblocks_hero_image_editor_assets');

function firstgutyblocks_hero_image_assets() {
    wp_enqueue_style(
		'firstgutyblocks/hero-image',
        plugins_url( 'src/view.css', __FILE__ ),
        array( 'wp-blocks' )
	);
}
add_action( 'enqueue_block_assets', 'firstgutyblocks_hero_image_assets');

Now, if you have ‘npm run watch’ running in the background, you should be able to load up the editor and see a newly-named block with that image icon in the block options list.

WordPress Gutenberg editor menu open and displaying "Hero Image Block" as an option

Now let’s make markup for our hero image block

We are going to make a block in which the user can upload an image as a background.  We’ll also let them select colors for text and for the transparent overlay.  We’ll also let them select a padding height to determine the hero height.

First, let’s clear out our editor.css and view.css files so they are empty.

Second, we’re going to update the block by doing the following in the edit method:

edit(props) {

    const { 
        setAttributes, 
        attributes,
        className
    } = props;

    function onTextChange(changes) {
        setAttributes({
            textString: changes
        });
    }

    return (
        <div className={className}>
            {/* Adding an overlay element */}
            <div className="overlay"></div>
            <RichText
                tagName="h2"
                className="content" // adding a class we can target
                value={attributes.textString}
                onChange={onTextChange}
                placeholder="Enter your text here!"
                />
        </div>
    );
},

save(props) {

    const { attributes, className } = props;

    return (
        <div className={className}>
            <div className="overlay"></div>
            {/* the class also needs to be added to the h2 for RichText */}
            <h2 class="content">{attributes.textString}</h2>
        </div>
    );
}

We already had an editable rich text field.  Now we have a div that will act as our transparent overlay, and we’ve added classes to target with our css.  Also, remember to make these changes in your save method!

If you save and refresh your editor you can see that our markup is in place.  I went to the preview just to check the markup is the way I want it.

Markup for our gutenberg block is showing in the correct spots
Beautiful

Styling the hero image block

Let’s add a background image to our ‘wp-block-firstgutyblocks-hero-image’ div.  You have choices here- you can do styles through css, or you can place styles inline in the jsx.  There are many, many fights raging over whether we should ever place styles inline.  There are some really cool projects that allow you to basically keep all styles in our javascript.  I basically ask myself “Is this a style that the user will want to edit themselves?  Or is it static?”  If it is user-changeable then I go for inline.

Hence, we will make our background image inline.  To make sure it’s working, I’m going to place an inline style.  Here is the code for my edit method:

return (
    <div 
        className={className}
        style={{
            backgroundImage: `url('http://placehold.it/1440x700')`,
            backgroundSize: 'cover',
            backgroundPosition: 'center'
        }}>
        <div className="overlay"></div> {/* Adding an overlay element */}
        <RichText
            tagName="h2"
            className="content" // adding a class we can target
            value={attributes.textString}
            onChange={onTextChange}
            placeholder="Enter your text here!"
            />
    </div>
);

!!!Make sure you do this in your save method container div as well!!! I would load the block in the editor to make sure that the background image is showing up in both the editing block and the saved block.

If you aren’t familiar with how those inline styles are happening, here’s a pretty good overview.  Basically, you create an object that looks a bit like markup, but some of the property names have to be camel-cased.  We will be using this a lot in our blocks because many things the user wants control over are styles.

But the point here is that we now have a place that javascript determines the background image.

Let’s do a few more styles to make things look a little prettier…

/* view.css */
.wp-block-firstgutyblocks-hero-image {
    padding: 4em 0;
    text-align: center;
}

Now we’re starting to see a hero block emerge:

A Gutenberg block in the editor showing the hero block styling

Just one more thing: let’s create the styles we need to get that overlay div working.

/* view.css */
.wp-block-firstgutyblocks-hero-image {
    position: relative;
    padding: 4em 0;
    text-align: center;
}

.wp-block-firstgutyblocks-hero-image .overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(150,0,0,.3);
}

.wp-block-firstgutyblocks-hero-image .content {
    position: relative;
    z-index: 1;
}

So we made the container div the position relative element for our block.  We then position the overlay absolutely on the parent and make sure it covers the entire parent.  This will give that overlay look- and for now we’re adding a random rgba color to show it’s working.

We also need to position relative the content string so that we can z-index it and lift it above the overlay.

So far so good!

The hero image block has a transparent overlay and properly positioned text.

Adding a settings panel for our block using InspectorControls

Alright, so now we’re going to let the user choose two things: the overlay color and the text color.  First, let us import a block called InspectorControls.  We’re going to add this as part of our edit method only.  The idea is that when the block is selected, these options will show on the right side in the block settings.  So let’s add the following…

const { 
    registerBlockType,
} = wp.blocks;

const {
    RichText,
    InspectorControls // New component!
} = wp.editor;

...

edit(props) {
    const { 
        setAttributes, 
        attributes,
        className,
        focus // this is "true" when the user clicks on the block
    } = props;

...

return ([
    <InspectorControls>
        This text will show when the box is selected
    </InspectorControls>,
    <div 
        className={className}
        style={{
            backgroundImage: `url('http://placehold.it/1440x700')`,
            backgroundSize: 'cover',
            backgroundPosition: 'center'
        }}>
        <div className="overlay"></div> {/* Adding an overlay element */}
        <RichText
            tagName="h2"
            className="content" // adding a class we can target
            value={attributes.textString}
            onChange={onTextChange}
            placeholder="Enter your text here!"
            />
    </div>
]);

There’s a lot going on in the return part of the code above.

  1. React can render an array of things.  It’s easiest to return an array that has the InspectorControls separate from the actual block
  2. Anything inside of the InspectorControls elements will show in the sidebar in the editor.

If you refresh the block in the editor and click on the block, in the sidebar you should see your text.

Adding actual settings to the settings panel

Whew… this is getting long.

Anyway- we have our settings panel set up.  Let’s import another pre-made color tool so we don’t have to work too much.  Let’s import ColorPalette and use it just to show what it is.

const { 
    registerBlockType,
} = wp.blocks;

const {
    RichText,
    InspectorControls,
    ColorPalette
} = wp.editor;

...

<InspectorControls>
    <div>
        <strong>Select a font color:</strong>
        <ColorPalette />
    </div>
</InspectorControls>,

We should see when we reload the editor and click on the block:

So no we actually need to wire this color palette to the block and make it change the font color.  We will first: make a fontColor attribute, second: hook up an onChange function to the colorPalette to make it change the color, and third: create an inline style for the text that is based on the fontColor attribute.

attributes: {
    textString: {
        type: 'array',
        source: 'children',
        selector: 'h2',
    },
    fontColor: { // NEW attribute!
        type: 'string',
        default: 'black'
    }
},

...

// in the edit method, we are peeling off this attribute from props
const { 
    setAttributes, 
    attributes,
    className,
    focus
} = props;
const { fontColor } = props.attributes; {/* new! */}

...

//create a handler that will set the color when you click on the ColorPalette
function onTextColorChange(changes) {
    setAttributes({
        fontColor: changes
    })
}

...

return ([
    <InspectorControls>
        <div>
            <strong>Select a font color:</strong>
            <ColorPalette
                value={fontColor} {/* new! */}
                onChange={onTextColorChange} {/* new! */}
                />
        </div>
    </InspectorControls>,
    <div 
        className={className}
        style={{
            backgroundImage: `url('http://placehold.it/1440x700')`,
            backgroundSize: 'cover',
            backgroundPosition: 'center'
        }}>
        <div className="overlay"></div>
        <RichText
            tagName="h2"
            className="content"
            value={attributes.textString}
            onChange={onTextChange}
            placeholder="Enter your text here!"
            style={{color: fontColor}}  {/* new! */}
            />
    </div>
]);

...

//Make sure to update the save method text styles:
save(props) {

    const { attributes, className } = props;
    const { fontColor } = props.attributes;

    return (
        <div 
            className={className}
            style={{
                backgroundImage: `url('http://placehold.it/1440x700')`,
                backgroundSize: 'cover',
                backgroundPosition: 'center'
            }}>
            <div className="overlay"></div>
            <h2 class="content" style={{ color:fontColor }}>{attributes.textString}</h2>
        </div>
    );
}

To be continued…

This is getting too long to keep to one post.  I will be writing a part 2 where we allow editors to pick not only the text color, but also the gradient color.  We’ll also go over how to implement a MediaUpload and display custom images.

Here’s your homework: See if you can implement a second ColorPalette that changes the transparent overlay. (You will need to create another attribute.)  This will be a good test to see if you have absorbed all that went into the hero block so far.

Edit: Part 2 is now up