Creating a Gutenberg Block for Voting Tally

In this chapter, I’ll cover how to add a few Gutenberg blocks to the Voting Tally plugin we’ve been creating over the last few chapters.

You’re welcome to follow along at wpajax.pro/gutenberg.

This chapter will take a nose-dive into Node, React, and command line. If you are unfamiliar with these terms, you can skip this chapter and work your way into the examples.

The first step is installing Node and NPM. From there, follow the installation instructions. When you’re all set, make sure you have Node and NPM installed by typing node -v and npm -v. You should receive version numbers if properly installed.

node -v
v11.15.0
npm -v
6.7.0

Initializing Your Setup

Let’s navigate to your plugin via command line. In my case, since I’m using MAMP, I’ll run the following:

cd /applications/mamp/htdocs/yourinstall/wp-content/plugins/votingtally_gutenberg

If you’re using a different local setup, your command will be different. For example, if using a popular tool like Local by Flywheel, you’d run the following:

cd ~/"Local Sites"/test-site/app/public/wp-content/plugins/votingtally_gutenberg

Inside the directory via command line, type npm init. It’ll allow you to customize your project. In our case, we’ll be using Voting Tally as the package name.

npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (votingtally_gutenberg) 

And you can fill out the details like I have below.

Press ^C at any time to quit.
package name: (votingtally_gutenberg) votingtally
version: (1.0.0) 
description: A WordPress plugin that allows voting on posts.
entry point: (index.js) 
test command: 
git repository: (https://github.com/wpajax/votingtally_gutenberg.git) 
keywords: vote, votingtally
author: Ronald Huereca
license: (ISC) GPL-3.0-or-later
About to write to /Applications/MAMP/htdocs/beaverbuilder/wp-content/plugins/votingtally_gutenberg/package.json:

{
  "name": "votingtally",
  "version": "1.0.0",
  "description": "A WordPress plugin that allows voting on posts.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/wpajax/votingtally_gutenberg.git"
  },
  "keywords": [
    "vote",
    "votingtally"
  ],
  "author": "Ronald Huereca",
  "license": "GPL-3.0-or-later",
  "bugs": {
    "url": "https://github.com/wpajax/votingtally_gutenberg/issues"
  },
  "homepage": "https://github.com/wpajax/votingtally_gutenberg#readme"
}

If all is well, type ‘yes’ and a file called package.json will be generated. I received a warning that my NPM is out of date, so I’ll go ahead and run npm install -g npm.

npm install -g npm

In my case, NPM didn’t have the right permissions for updating, so I ran:

sudo npm install -g npm

That got me to the latest version.

Installing Create Guten Block

Create Guten Block is an amazing utility for creating Gutenberg blocks. It does the hard parts for you. There’s no need to configure things like Webpack or Babel; the tool does the hard work. All you have to worry about is the JavaScript and PHP to register the blocks.

Let’s go ahead and install it by running this command in our plugin directory.

npm install cgb-scripts --save-dev

This will install Create Guten Block and create a node_modules folder in our plugin. Now, we have to create the files and folders it expects.

Create Guten Block expects the following directories and files:

  • /src
  • /src/block
  • /src/blocks.js
  • /src/common.scss
  • /src/block/editor.scss
  • /src/block/style.scss

Let’s go ahead and create those directories and files.

Finally, we need to modify our package.json file and modify the scripts portion. Here’s what our package.json file looks like right now without modifying the scripts.

{
  "name": "votingtally",
  "version": "1.0.0",
  "description": "A WordPress plugin that allows voting on posts.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/wpajax/votingtally_gutenberg.git"
  },
  "keywords": [
    "vote",
    "votingtally"
  ],
  "author": "Ronald Huereca",
  "license": "GPL-3.0-or-later",
  "bugs": {
    "url": "https://github.com/wpajax/votingtally_gutenberg/issues"
  },
  "homepage": "https://github.com/wpajax/votingtally_gutenberg#readme",
  "devDependencies": {
    "cgb-scripts": "^1.22.0"
  }
}

We’ll be stripping out the scripts portion and adding this instead.

"scripts": {
  "start": "cgb-scripts start",
  "build": "cgb-scripts build",
  "eject": "cgb-scripts eject"
},

And here’s what our final package.json file looks like:

{
  "name": "votingtally",
  "version": "1.0.0",
  "description": "A WordPress plugin that allows voting on posts.",
  "main": "index.js",
  "scripts": {
    "start": "cgb-scripts start",
    "build": "cgb-scripts build",
    "eject": "cgb-scripts eject"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/wpajax/votingtally_gutenberg.git"
  },
  "keywords": [
    "vote",
    "votingtally"
  ],
  "author": "Ronald Huereca",
  "license": "GPL-3.0-or-later",
  "bugs": {
    "url": "https://github.com/wpajax/votingtally_gutenberg/issues"
  },
  "homepage": "https://github.com/wpajax/votingtally_gutenberg#readme",
  "devDependencies": {
    "cgb-scripts": "^1.22.0"
  }
}

From there we can run npm run start and see the following:

Create Guten Block NPM Run Start
Create Guten Block NPM Run Start

This will create a dist folder in your plugin install. Right now the only thing in the dist folder is a file called blocks.build.js. It’s not picking up our stylesheets quite yet.

Inside src/blocks.js, let’s add the following:

import './block/style.scss';
import './block/editor.scss';

With npm run start still running, it should now create the following in the dist folder.

  • blocks.build.js
  • blocks.editor.build.css
  • blocks.style.build.css

Now that we have Create Guten Block working, let’s begin adding a block to replace (supplement?) one of our shortcodes.

Let’s begin by creating a popular-posts.js JavaScript file inside the src/block folder. Inside the src/blocks.js file, we’ll add an import statement to pull in our new JavaScript.

import './block/style.scss';
import './block/editor.scss';
import './block/popular-posts';

Registering the Block in JavaScript

Let’s create a popular-posts-edit.js file inside the src/block folder. We’ll be using popular-posts-edit.js to do the bulk of our block logic and use popular-posts.js to initialize the block. Let’s begin with initializing the block.

const { __ } = wp.i18n; // Import __() from wp.i18n
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks
import edit from './popular-posts-edit';

registerBlockType( 'votingtally/popular-posts', {
	title: __( 'Popular Posts', 'votingtally' ), // Block title.
	icon: <svg width="72" height="72" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="24" fill="none" rx="0" ry="0"></rect><path fill-rule="evenodd" clip-rule="evenodd" d="M9 2C7.89543 2 7 2.89543 7 4V5H6C4.89543 5 4 5.89543 4 7V20C4 21.1046 4.89543 22 6 22H15C16.1046 22 17 21.1046 17 20V19H18C19.1046 19 20 18.1046 20 17V4C20 2.89543 19.1046 2 18 2H9ZM17 7V17.8H17.8C18.3523 17.8 18.8 17.3523 18.8 16.8V4.2C18.8 3.64772 18.3523 3.2 17.8 3.2H9.2C8.64772 3.2 8.2 3.64771 8.2 4.2V5H15C16.1046 5 17 5.89543 17 7ZM6.2 6.2C5.64772 6.2 5.2 6.64771 5.2 7.2V19.8C5.2 20.3523 5.64771 20.8 6.2 20.8H14.8C15.3523 20.8 15.8 20.3523 15.8 19.8V7.2C15.8 6.64772 15.3523 6.2 14.8 6.2H6.2Z" fill="#ffffff"></path></svg>,
	category: 'widgets',
	keywords: [
		__( 'popular', 'votingtally' ),
		__( 'posts', 'votingtally' ),
		__( 'voting', 'votingtally' ),
	],
	supports: {
		align: true
	},
	edit: edit,
	save() { return null }
} );

Let’s start with what we import.

  • We import __ from wp.i18n in order to provide translatable strings.
  • registerBlockType is imported from wp.blocks. We use this to register our block.
  • edit is a variable that is assigned our file popular-posts-edit.js file. We’ll use this in place of putting code inside the edit option for cleaner separation.

And the contents of registerBlockType:

  • We give the block a namespace of votingtally and what the block will be called (in this case, popular-posts).
  • We give it a translatable title called Popular Posts.
  • We supply SVG code for the block icon. You can also use a dashicon for the icon.
  • We assign a category of widgets. Available categories are common, formatting, layout, widgets, and embed. You can also register your own category, but for one block, that is overkill (yes, I’ve made this mistake in the past).
  • We add a few keywords so people can search for our block.
  • We add support for alignment.
  • We assign the edit object our edit variable (the output of popular-posts-edit.js).
  • And finally, we assign save to null, which means we’ll be using PHP to output the contents of the block.

Now that our block is initialized via JavaScript, we also need to initialize it in PHP.

Registering the Block in PHP

Let’s create a blocks folder in our plugin. Inside this blocks folder, create a file called class-popular-posts.php.

<?php
/**
 * Initialize and output the Popular Posts block.
 *
 * @package votingtally
 */

namespace VotingTally\Blocks;

/**
 * Class Popular_Posts
 */
class Popular_Posts {

}

We’ll use the init action to point to our block register function, pass an attribute or two, and provide a render_callback callback function for our front-end output.

Here’s a barebones example of our class.

<?php
/**
 * Initialize and output the Popular Posts block.
 *
 * @package votingtally
 */

namespace VotingTally\Blocks;

/**
 * Class Popular_Posts
 */
class Popular_Posts {
	/**
	 * Class Constructor
	 */
	public function __construct() {
		add_action( 'init', array( $this, 'register_block' ) );
	}

	/**
	 * Register the Popular Posts Block in PHP.
	 */
	public function register_block() {
		if ( ! function_exists( 'register_block_type' ) ) {
			return;
		}
		register_block_type(
			'votingtally/popular-posts',
			array(
				'attributes'      => array(
					'align' => array(
						'type'    => 'string',
						'default' => 'center',
					),
				),
				'render_callback' => array( $this, 'frontend' ),
			)
		);
	}

	/**
	 * Front-end output for our block.
	 *
	 * @param array $attributes Attributes passed from Gutenberg.
	 */
	public function frontend( $attributes ) {

	}
}

We utilize a WordPress function called register_block_type, which takes the block name and an array of options. Attributes are initialized, which will be passed to the Gutenberg block. A render_callback argument is used to point to our frontend output.

What’s next is enqueuing our JavaScript and CSS files.

Enqueuing the Block JavaScript and CSS Files

Let’s create a new class inside our blocks folder. We’re going to use the unoriginal name of class-enqueue.php.

This will be used as our base code.

<?php
/**
 * Initialize and output the Popular Posts block.
 *
 * @package votingtally
 */

namespace VotingTally\Blocks;

/**
 * Class Popular_Posts
 */
class Enqueue {

	/**
	 * Class Constructor.
	 */
	public function __construct() {

	}
}

We’ll use the action enqueue_block_editor_assets to register our scripts and styles. Registering is necessary so we can tell WordPress which scripts and styles belong to our block.

class Enqueue {

	/**
	 * Class Constructor.
	 */
	public function __construct() {
		add_action( 'enqueue_block_editor_assets', array( $this, 'block_scripts_and_styles' ) );
	}

	/**
	 * Enqueue block assets.
	 */
	public function block_scripts_and_styles() {
		// Register block scripts and set translations.
		wp_register_script(
			'votingtally_block',
			VOTINGTALLY_URL . 'dist/blocks.build.js',
			array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' ),
			VOTINGTALLY_VERSION,
			true
		);
		wp_set_script_translations( 'votingtally_block', 'votingtally' );

		// Register our block stylesheet.
		wp_register_style(
			'votingtally_block_css',
			VOTINGTALLY_URL . 'dist/blocks.editor.build.css',
			array(),
			VOTINGTALLY_VERSION,
			'all'
		);
	}
}

The handles are important here: votingtally_block for the JavaScript file and votingtally_block_css for the CSS file.

Attaching our Assets to the Block

We’re now going to travel back to our block PHP file, which is the blocks folder. It’s named class-popular-posts.php in case you’ve already forgotten (I don’t blame you). Let’s use two arguments to attach the JavaScript and CSS files we registered in the previous section.

/**
 * Register the Popular Posts Block in PHP.
 */
public function register_block() {
	if ( ! function_exists( 'register_block_type' ) ) {
		return;
	}
	register_block_type(
		'votingtally/popular-posts',
		array(
			'attributes'      => array(
				'align' => array(
					'type'    => 'string',
					'default' => 'center',
				),
			),
			'editor_script'   => 'votingtally_block',
			'editor_style'    => 'votingtally_block_css',
			'render_callback' => array( $this, 'frontend' ),
		)
	);
}

Finally, we need to fill out our file popular posts edit file. It’s located in /src/block/popular-posts-edit.js.

WordPress does a lot of the heavy lifting when it comes to React. All we really need to do is import the components and helpers we need, and render our output.

const { Component, Fragment } = wp.element;
const { __, _x } = wp.i18n;

const {
	PanelBody,
	Placeholder,
	Toolbar,
	Spinner,
} = wp.components;

class Popular_Posts extends Component {
	constructor() {
		super( ...arguments );
	}
	render() {
		const { setAttributes } = this.props;
		const { align } = this.props.attributes;

		return (
			<Fragment>
				<Placeholder>
					<div>
						{__( 'Popular Posts', 'votingtally' )}
					</div>
				</Placeholder>
			</Fragment>
		)
	}
}
export default Popular_Posts;

Finally, we should be able to see our block in the block editor.

Voting Tally Selecting the Block
Voting Tally Selecting the Block
Voting Tally Block Output
Voting Tally Block Output

Right now, essentially, we have just a simple “Hello World” block. I know, this journey has been tedious just for one block that does almost nothing yet, but we’ll get to the Ajax portion soon.

Creating the Ajax Request in Our Block

We’ll need to journey back to the command line and import a library called axios. We’ll install it via NPM. It’ll help us make the Ajax call to retrieve the popular posts.

npm install axios --save-dev

Once that’s done, we’ll need to import it into our block.

import axios from "axios";
const { Component, Fragment } = wp.element;
const { __, _x } = wp.i18n;

Let’s set a state in our block for loading, which will display a spinner.

import axios from "axios";
const { Component, Fragment } = wp.element;
const { __, _x } = wp.i18n;

const {
	PanelBody,
	Placeholder,
	Toolbar,
	Spinner,
} = wp.components;

class Popular_Posts extends Component {
	constructor() {
		super( ...arguments );

		this.state = {
			loading: true,
		};
	}
	render() {
		const { setAttributes } = this.props;
		const { align } = this.props.attributes;

		return (
			<Fragment>
			{ this.state.loading &amp;&amp; 
				<Fragment>
					<Placeholder
						icon="admin-post"
						label={ __( 'Popular Posts',  'votingtally' ) }
					>
						<Spinner />
					</Placeholder>
				</Fragment>
			}
			</Fragment>
			
		)
	}
}
export default Popular_Posts;

Voting Tally Loading Screen
Voting Tally Loading Screen

Next, we’ll need something to load in the popular posts. This is where axios comes into play.

We’ll use the componentDidMount React lifecycle method to gather the popular posts via our REST API endpoint that we created in the previous chapter. It’ll use axios to make our Ajax request to the REST endpoint.

componentDidMount = () => {
	axios.get(
		votingtally_admin.rest_url + `votingtally/v1/get_posts/post/10/DESC`,
		{
			'headers': { 'X-WP-Nonce': votingtally_admin.nonce }
		}
	).then( ( response ) => {
		console.log( response );
	} );
}

We’re accessing two variables that do not exist. We’ll have to go back to our class-enqueue.php in the blocks directory and add the variables we need. We’ll be making use of wp_localize_script.

public function block_scripts_and_styles() {
	// Register block scripts and set translations.
	wp_register_script(
		'votingtally_block',
		VOTINGTALLY_URL . 'dist/blocks.build.js',
		array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' ),
		VOTINGTALLY_VERSION,
		true
	);
	wp_localize_script(
		'votingtally_block',
		'votingtally_admin',
		array(
			'rest_url' => rest_url(),
			'nonce'    => wp_create_nonce( 'wp_rest' ),
		)
	);
	wp_set_script_translations( 'votingtally_block', 'votingtally' );

	// Register our block stylesheet.
	wp_register_style(
		'votingtally_block_css',
		VOTINGTALLY_URL . 'dist/blocks.editor.build.css',
		array(),
		VOTINGTALLY_VERSION,
		'all'
	);
}

Let’s output the contents of the block using our componentDidMount React method.

componentDidMount = () => {
	axios.get(
		votingtally_admin.rest_url + `votingtally/v1/get_posts/post/10/DESC`,
		{
			'headers': { 'X-WP-Nonce': votingtally_admin.nonce }
		}
	).then( ( response ) => {
		this.setState(
			{
				loading: false,
				html: this.getListHtml( response.data ),
			}
		)
	} );
}

We set the state loading to false. And HTML is returned courtesy of a helper function called getListHtml.

getListHtml = (data) => {
	const listItems = data.map( ( item ) =>
		<li key={item.id}>
			{item.title}
		</li>
	);
	return (
		<ul>{listItems}</ul>
	);
}

We now need to tell our render method to output the HTML.

render() {
	const { setAttributes } = this.props;
	const { align } = this.props.attributes;

	return (
		<Fragment>
		{ this.state.loading &amp;&amp; 
			<Fragment>
				<Placeholder
					icon="admin-post"
					label={ __( 'Popular Posts',  'votingtally' ) }
				>
					<Spinner />
				</Placeholder>
			</Fragment>
		}
		{ ! this.state.loading &amp;&amp;
			<Fragment>
				{this.state.html}
			</Fragment>
		}
		</Fragment>
		
	)
}
Voting Tally Gutenberg Popular Posts Retrieval
Voting Tally Gutenberg Popular Posts Retrieval

Outputting the Contents of the Block

What’s left is outputting the contents of the block on the front-end. Let’s just simply echo out the shortcode content for the popular posts.

We’ll output the content in class-popular-posts located in the blocks directory.

/**
 * Front-end output for our block.
 *
 * @param array $attributes Attributes passed from Gutenberg.
 */
public function frontend( $attributes ) {
	return wp_kses_post( do_shortcode( '[votingtally]' ) );
}

That’s it! We’re done!

Further Refinements

Obviously we want our shortcode to run faster, so we’d modify it to just output the posts instead of requiring an HTTP request to do the hard work.

We’re also not taking into account other post types. Ideally, we’d modify our block to have a list of post types to display instead of just posts. Likewise, we’d need to add an option for the number of posts to retrieve and the order. Lastly, we’re not outputting any styles. Some basic styling can be added to our popular posts block both inside and outside the editor.

Feel free to fork and modify the code at wpajax.pro/gutenberg.

Conclusion

In this chapter, we painfully slogged through how to create a custom Gutenberg block to output our popular posts. There are a bunch of refinements we can make, but I’ll leave that as an exercise for the reader.

Up next, I will show through several examples of how to use Ajax to get the results you need and make for a positive user experience.