A comprehensive database for everything WordPress related.

TOP
WPKlik Logo Newsletter

Sign up and receive a free copy of How to Create an online Store with WooCommerce (full guide)

Creating Blocks for Gutenberg Editor

Creating Blocks for Gutenberg Editor

In this module, we will show you how to create blocks for the Gutenberg editor the easy way. You can consider blocks as dynamic shortcodes that you can change live.

Specifically, in this article we will show you how to create Image With Text block as a plugin, so that you can create plugin module for blocks. Before we start, we’ll tell you a little about this editor. Gutenberg is the project name for the new WordPress block editor, which replaced the WordPress TinyMCE editor as the default WordPress editor in WordPress 5.0. The new WordPress editor introduces blocks and brings with it a completely different approach to content creation in the form of blocks (hence the name…). You can check main Gutenberg documentation for block registration here.

The first thing we’ll do here is create a PHP file with Class ImageWithTextBlock and inside class we will define a new block with corresponding styles. We used Class because of better readability of the code and because it’s generally our preferred way. You can also use functions instead of Class where you call that function on init hook.

<?php
/*
Plugin Name: My First Gutenberg Block
Description: Plugin that add blocks for Gutenberg editor
Author: Author Name
Version: 1.0
*/

if ( ! class_exists( 'ImageWithTextBlock' ) ) {
/**
* Main class for Gutenberg Block - Image With Text
*/
class ImageWithTextBlock {
private static $instance;

public function __construct() {

// Check is Gutenberg editor active.
if ( ! function_exists( 'register_block_type' ) ) {

// Init
add_action( 'init', array( $this, 'register_assets' ) );
add_action( 'init', array( $this, 'register_block' ) );
}
}

public static function get_instance() {
if ( self::$instance == null ) {
self::$instance = new self();
}

return self::$instance;
}

/**
* Register the block's assets for the editor.
*/
function register_assets() {

wp_register_script(
'image-with-text-block-script',
plugins_url( 'block.js', __FILE__ ),
array( 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-editor' ),
filemtime( plugin_dir_path( __FILE__ ) . 'block.js' ),
true
);

wp_register_style(
'image-with-text-block-editor-style',
plugins_url( 'editor.css', __FILE__ ),
array( 'wp-edit-blocks' ),
filemtime( plugin_dir_path( __FILE__ ) . 'editor.css' )
);

wp_register_style(
'image-with-text-block-frontend-style',
plugins_url( 'style.css', __FILE__ ),
array(),
filemtime( plugin_dir_path( __FILE__ ) . 'style.css' )
);
}

/**
* Register new block and set corresponding styles for it
*/
function register_block() {
register_block_type( 'image-with-text-block/block', array(
'editor_script' => 'image-with-text-block-script',
'editor_style'  => 'image-with-text-block-editor-style',
'style'         => 'image-with-text-block-frontend-style',
) );
}
}
ImageWithTextBlock::get_instance();
}

First we created two predefined methods __construct() and static method get_instance(), we used these methods to initialize class where get_instance() make the new instance of this Class and call the __construct() method. Inside __construct() method we define which methods we will call during initialization and in our case that will be register_assets and register_block.

Register assets method (function) registers CSS and JavaScript files that will be used in the Gutenberg editor and for the front-end page for this Block element. The first one block.js, is the most important file. Inside it we will create the entire block logic and functionality. You can use editor style and front-end style scripts to style that block inside the Gutenberg editor and for front-end page.

Now we will start from block.js file.

(function (blocks, editor, components, i18n, element) {
var el = element.createElement;
var registerBlockType = blocks.registerBlockType;
var RichText = editor.RichText;
var BlockControls = editor.BlockControls;
var AlignmentToolbar = editor.AlignmentToolbar;
var MediaUpload = editor.MediaUpload;
var InspectorControls = editor.InspectorControls;
var TextControl = components.TextControl;

registerBlockType('my-first-gutenberg-block/image-with-text-block', {
title: i18n.__('Image With Text', 'my-first-gutenberg-block'),
description: i18n.__('A custom block for displaying image with text section', 'my-first-gutenberg-block'),
icon: 'id',
category: 'common',
attributes: {
mediaID: {
type: 'number'
},
mediaURL: {
type: 'string',
source: 'attribute',
selector: 'img',
attribute: 'src'
},
title: {
type: 'text',
selector: 'h3'
},
text: {
type: 'text',
selector: 'p'
},
buttonText: {
type: 'text'
},
buttonURL: {
type: 'url'
},
alignment: {
type: 'string',
default: 'center'
}
},

edit: function (props) {
var attributes = props.attributes;

var onSelectImage = function (media) {
return props.setAttributes({
mediaURL: media.url,
mediaID: media.id
})
};

return [
el(BlockControls, {key: 'controls'},
el('div', {
className: 'components-toolbar'
},
el(MediaUpload, {
onSelect: onSelectImage,
type: 'image',
render: function (obj) {
return el(components.Button, {
className: 'components-icon-button components-toolbar__control',
onClick: obj.open
},
el('svg', {className: 'dashicon dashicons-edit', width: '20', height: '20'},
el('path', {d: 'M2.25 1h15.5c.69 0 1.25.56 1.25 1.25v15.5c0 .69-.56 1.25-1.25 1.25H2.25C1.56 19 1 18.44 1 17.75V2.25C1 1.56 1.56 1 2.25 1zM17 17V3H3v14h14zM10 6c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm3 5s0-6 3-6v10c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1V8c2 0 3 4 3 4s1-3 3-3 3 2 3 2z'})
));
}
})
),
el(AlignmentToolbar, {
value: attributes.alignment,
onChange: function(newAlignment) {
props.setAttributes({ alignment: newAlignment })
}
})
),
el(InspectorControls, {key: 'inspector'},
el(components.PanelBody, {
title: i18n.__('Block Content', 'my-first-gutenberg-block'),
className: 'block-content',
initialOpen: true
},
el('p', {}, i18n.__('Add custom meta options to your block', 'my-first-gutenberg-block')),
el(TextControl, {
type: 'text',
label: i18n.__('Button Text', 'my-first-gutenberg-block'),
value: attributes.buttonText,
onChange: function (newButtonText) {
props.setAttributes({ buttonText: newButtonText })
}
}),
el(TextControl, {
type: 'url',
label: i18n.__('Button URL', 'my-first-gutenberg-block'),
value: attributes.buttonURL,
onChange: function (newButtonUrl) {
props.setAttributes({ buttonURL: newButtonUrl })
}
})
)
),
el('div', {
className: props.className,
style: { textAlign: attributes.alignment }
},
el('div', {
className: 'my-block-image'
},
el(MediaUpload, {
onSelect: onSelectImage,
type: 'image',
value: attributes.mediaID,
render: function (obj) {
return el(components.Button, {
className: attributes.mediaID ? 'image-button' : 'button button-large',
onClick: obj.open
},
!attributes.mediaID ? i18n.__('Upload Image', 'my-first-gutenberg-block') : el('img', {src: attributes.mediaURL})
)
}
})
),
el('div', {
className: 'my-block-content'
},
el(RichText, {
key: 'editable',
tagName: 'h3',
className: 'my-block-title',
placeholder: i18n.__('Title Text', 'my-first-gutenberg-block'),
keepPlaceholderOnFocus: true,
value: attributes.title,
onChange: function (newTitle) {
props.setAttributes({title: newTitle})
}
}),
el(RichText, {
key: 'editable',
tagName: 'p',
className: 'my-block-text',
placeholder: i18n.__('Text', 'my-first-gutenberg-block'),
keepPlaceholderOnFocus: true,
value: attributes.text,
onChange: function (newText) {
props.setAttributes({text: newText})
}
}),
el('button', {
className: 'my-block-button',
href: attributes.buttonURL
}, attributes.buttonText)
)
)
];
},

save: function (props) {
var attributes = props.attributes;

return (
el('div', {
className: props.className,
style: {textAlign: attributes.alignment}
},
el('div', {
className: 'my-block-image'
},
el('img', {
src: attributes.mediaURL
})
),
el('div', {
className: 'my-block-content'
},
el(RichText.Content, {
tagName: 'h3',
className: 'my-block-title',
value: attributes.title
}),
el(RichText.Content, {
tagName: 'p',
className: 'my-block-text',
value: attributes.text
}),
el('button', {
className: 'my-block-button',
href: attributes.buttonURL
}, attributes.buttonText)
)
)
)
}
})
})(
window.wp.blocks,
window.wp.editor,
window.wp.components,
window.wp.i18n,
window.wp.element
);

As you can see inside code above we first import blocks, elements, components, editors and internationalization components. We can do that because WordPress extend default JavaScript window object with wp object and that object has additional objects that are useful for our blocks (window.wp.blocks, window.wp.editor, window.wp.components, window.wp.i18n, window.wp.element). We will use it to create our block structure and components.

MediaUpload and PlainText components serve for uploading the author image and to take in the different text inputs to save in the database. We will use InspectorControls and TextControl components to create custom section with corresponding block options. Main JavaScript function inside this file is a registerBlockType. This function registers the block and they accept two parameters.

First one is a block name. You have to prefix that name with a namespace specific to your plugin. This is to avoid any conflicts with another block with the same name. The second one is an array with the following properties and functions:

  • Title – The name of the block which will appear when you add a new block in the Gutenberg Editor.
  • Icon – The block’s icon which will be picked up from dashicons. You can also specify your own SVG icons if you want to.
  • Category – Set category slug of blocks will the block appear. Some of the categories are: common, formatting, layout widgets and embed.
  • Keywords – An array of strings that describe the block, similar to tags.
  • Attributes – A JavaScript object which contains a description of the data that the has saved.
  • Edit – The function that provides an interface for the block within the Gutenberg Editor.
  • Save – The function that describes how the block will be rendered in the frontend.

Gutenberg is built with React and the blocks that we build for Gutenberg use a similar syntax. The following things are useful to know before starting the block development:

  • The HTML class is replaced with className like in React.
  • The edit and save methods return JSX, which stands for JavaScript XML. If you are wondering, JSX is syntax exactly like HTML, except you can use HTML tags and other components like PlainText and RichText within it.
  • The setAttributes method works similar to React’s setState. What it does is, when you call setAttributes the data in the block is updated and the block within the editor is refreshed.
  • The block uses props in the edit and save functions, just like React. The props object contains the attributes object, the setAttributes function and a ton of other data.

Now that we understand different parts of the Gutenberg, we can proceed to create our block options, parameter for that is Attributes. In our case we created few options (mediaID, mediaURL, title, text, button text, button url and alignment) as you can see each option accepted additional parameters.

mediaURL: {
type: 'string',
source: 'attribute',
selector: 'img',
attribute: 'src'
},

This code tells you that editor will extract the src attribute from an image found in the block’s markup. In another sense, html tag will be img where attribute src will be filled with the string value. There are a lot of combinations and options you can use here. Also, you can always check the main documentation for that.

Now that we have defined all attributes we will proceed to create the edit function logic. The edit function describes the structure of your block in the context of the editor. This represents what the editor will render when the block is used. The function receives the following properties through an object argument. First we will store all attributes in local variable attributes. We will use that variable to change options value. Then we will create additional variable onSelectImage and that variable will be a function which will set media url and id attributes for the image when user uploads the image through Gutenberg editor. Inside return part we will define the whole html structure (markup), as you can see here:

el('div', {
className: props.className,
style: { textAlign: attributes.alignment }
},
el('div', {
className: 'my-block-image'
},
el(MediaUpload, {
onSelect: onSelectImage,
type: 'image',
value: attributes.mediaID,
render: function (obj) {
return el(components.Button, {
className: attributes.mediaID ? 'image-button' : 'button button-large',
onClick: obj.open
},
!attributes.mediaID ? i18n.__('Upload Image', 'my-first-gutenberg-block') : el('img', {src: attributes.mediaURL})
)
}
})
),
el('div', {
className: 'my-block-content'
},
el(RichText, {
key: 'editable',
tagName: 'h3',
className: 'my-block-title',
placeholder: i18n.__('Title Text', 'my-first-gutenberg-block'),
keepPlaceholderOnFocus: true,
value: attributes.title,
onChange: function (newTitle) {
props.setAttributes({title: newTitle})
}
}),
el(RichText, {
key: 'editable',
tagName: 'p',
className: 'my-block-text',
placeholder: i18n.__('Text', 'my-first-gutenberg-block'),
keepPlaceholderOnFocus: true,
value: attributes.text,
onChange: function (newText) {
props.setAttributes({text: newText})
}
}),
el('button', {
className: 'my-block-button',
href: attributes.buttonURL
}, attributes.buttonText)
)
)

We created div element with className which in this case is wp-block-image-with-text-block. Otherwise it’s wp-block- plus your block name. This is where we also set inline css style for alignment. Then we will add two additional div elements as child element. Here, the first div element has className my-block-image. Inside that element, we will print the image that user uploaded through editor.

Here we set that element to be MediaUpload. This element has several attributes (onSelect, type, value and render). The first one means that we will forward a media object to the onSelect variable function and that function will return new object with media ID and media URL. The second one defines that the element is an image and the third one collects media ID from the media object we have uploaded, and render function will print additional html tag where that tag will be upload button. Second child div element is for the content info where you can set Title text, Content Text and Button.

First and second elements are set as RichText and with attribute key editable. This means that you can change text live, the function in charge of it is onChange. This function grabs the new value of text on writing and changes it inside block element. Other attributes that can be useful are placeholder and keepPlaceholderOnFocus. These attributes are similar to a placeholder on input fields, and used to set a label for the user to know where to edit. The third element is button html tag where we set button attributes ( url and text ) through custom block options. Section that is charge of it is:

el(InspectorControls, {key: 'inspector'},
el(components.PanelBody, {
title: i18n.__('Block Content', 'my-first-gutenberg-block'),
className: 'block-content',
initialOpen: true
},
el('p', {}, i18n.__('Add custom meta options to your block', 'my-first-gutenberg-block')),
el(TextControl, {
type: 'text',
label: i18n.__('Button Text', 'my-first-gutenberg-block'),
value: attributes.buttonText,
onChange: function (newButtonText) {
props.setAttributes({ buttonText: newButtonText })
}
}),
el(TextControl, {
type: 'url',
label: i18n.__('Button URL', 'my-first-gutenberg-block'),
value: attributes.buttonURL,
onChange: function (newButtonUrl) {
props.setAttributes({ buttonURL: newButtonUrl })
}
})
)
)

We created the element InspectorControls, which you can consider as Meta Box section, but only for the Gutenberg editor. Inside it, we defined Section Title, Description and Options. If you want to create several InspectorControls sections, then this attribute will be useful for you: initialOpen. With it, you can define which section will be the default initial open. To create options inside the section, you need to create the element to be TextControl type, for example (you can find all available types of fields here). In our case, we created two options – Button Text and Button URL. We will use these options to render additional content, in this case button element below content text. The result inside Gutenberg editor will look like this:

advanced creating blocks

The save function defines the way in which the different attributes should be combined into the final markup, which is then serialized by Gutenberg into post_content. For most blocks, the return value of save should be an instance of WordPress Element representing how the block is to appear on the site. For dynamic blocks, the return value of save could represent a cached copy of the block’s content to be shown only in case the plugin implementing the block is ever disabled. Alternatively, return a null (empty) value to save no markup in post content for the dynamic block, instead of deferring this to always be calculated when the block is shown on the front of the site.

Front-end style

Inside this file we will set css style for this block element and that style will be applied for front-end and for Gutenberg editor page.

/* ==========================================================================
Block style - begin
========================================================================== */
.wp-block-image-with-text-block {
position: relative;
display: inline-block;
vertical-align: top;
width: 100%;
}
.wp-block-image-with-text-block .my-block-image {
position: relative;
display: inline-block;
vertical-align: top;
}
.wp-block-image-with-text-block .my-block-image img {
display: block;
}
.wp-block-image-with-text-block .my-block-content {
position: relative;
display: inline-block;
vertical-align: top;
width: 100%;
margin-top: 20px;
}
.wp-block-image-with-text-block .my-block-title {
margin: 0;
}
.wp-block-image-with-text-block .my-block-text {
margin: 0;
}

Editor style css

Inside this file you can set additional css style for your block in order to fix potential issues inside Gutenberg editor layouts or to add additional style for it. Also your block inside Gutenberg editor will have the same style as on the front-end, because this is where the front-end style also loads.

/* ==========================================================================
Block editor style - begin
========================================================================== */
.wp-block-image-with-text-block {
margin-top: 28px;
}
.wp-block-image-with-text-block .my-block-image button {
display: block;
padding: 0;
}

Now we created the new block Image With Text for the Gutenberg editor and you can see it live. The result will look like this:

advanced creating blocks gutenberg
Newsletter

WordPress perfection at your fingertips.

If you enjoyed this article, feel free to subscribe to our newsletter using the form below. You can also follow us on Facebook and Twitter and subscribe to our YouTube channel for WordPress video tutorials.

Leave a Reply