Creating a Gutenberg block

Creating a Gutenberg block

WordPress Block Editor. Building blocks.

We are going to create a new block of dynamic content, implementing it in JavaScript. In this example, the block will be the featured image of a post and a link to that post, the user is being able to choose the post to be displayed by a search or the last 30 entries.

For this we will use the registerBlockType function. This function is responsible for specifying the model of a block, describing the behaviors necessary for the editor to understand how it appears, changes when it is edited and, ultimately, is saved in the content of the publication.

The blocks must be registered on the server to ensure that the script is queued when the editor is loaded. We will register the scripts and styles using wp_register_script and wp_register_style, then we will assign these as identifiers associated with the block using the script type block configuration, style, editor_script and editor_style. Identifiers with prefix editor_ will only be queued in the context of the editor, while the script and style will be placed both in the editor and in the viewing process.

<?php
/*
 *************************************************************
 * Plugin Name: Doowebs Gutenberg blocks
 * Description: Gutenberg blocks.
 * Author: Doowebs
 * Version: 0.0
 *************************************************************
 */
function dw-guten_block_register_block() {
  wp_register_script(
        'dwguten-block',
        plugins_url('block.js',__FILE__),
        array('wp-blocks','wp-editor','wp-element','wp-components','wp-data','wp-i18n'),
        filemtime(plugin_dir_path(__FILE__).'block.js')
  );
}
add_action('init','dwguten_block_register_block');

You have to consider the script dependencies, for example:

wp-blocks, includes block type registration and related functions.
wp-element, includes the abstraction of WordPress elements to describe the structure of the blocks.
wp-editor, includes the RichText component,

Register the block
With the script queued, let’s see the implementation of the block itself (block.js file):

( function( blocks, editor, element, components, data, i18n ) {
    var el = element.createElement;
    var InspectorControls = editor.InspectorControls;
    var PanelBody = components.PanelBody;
    var PanelRow = components.PanelRow;
    var SelectControl = components.SelectControl;
    var TextControl = components.TextControl;
    var withSelect = data.withSelect;
    var Button = components.Button;

    blocks.registerBlockType( 'dwguten-block/post', {
        title: i18n.__('Article Link'),
        icon: 'pressthis',
        category: 'doowebs-category',

        attributes: { 
            post_id: {
                type: 'number',
                default: 0,
            },
            post_title: {
                type: 'string',
                default: '',
            },
            search_text: {
                type: 'string',
                default: '',
            },
            search_text_sel: {
                type: 'string',
                default: '',
            },
        },

        edit: withSelect(function(select,props) {
            var postToShow=30;
            var searchTextSel=props.attributes.search_text_sel;
            var query = {
                per_page: postToShow,
                search: searchTextSel,
            };
            return {
                posts:select('core').getEntityRecords('postType','post',query)
            };
        })(function(props) {
            var postId=props.attributes.post_id;
            var postTitle=props.attributes.post_title;
            if (!props.posts) {
                return i18n.__('Cargando ...');
            }
            var className=props.className;
 

            let options = [{value:0,label:i18n.__('Selecciona una entrada')}];
            var i=0;
            while (i<props.posts.length) {
                options.push({value:props.posts[i].id,label:props.posts[i].title.raw});
                i++;
            }
            i=0;
            while (i<props.posts.length&&props.posts[i].id!=props.attributes.post_id) {i++;}
            if (i<props.posts.length) {
                props.setAttributes({post_title:props.posts[i].title.raw});
            }
            return [ 
                el(InspectorControls,{},
                    el(PanelBody,
                        {
                        title: i18n.__("Selección de entrada"),
                        initialOpen: true,
                        },
                        el(PanelRow,{},
                            el(TextControl,{
                                type: 'text',
                                label: i18n.__('Buscar'),
                                onChange: function (content) {
                                    props.setAttributes({search_text:content});
                                },
                                value: props.attributes.search_text,
                            }),
                            el(Button,{
                                className: 'components-button editor-post-preview is-button is-default is-large',
                                type: 'button',
                                onClick: function () {
                                    props.setAttributes({search_text_sel:props.attributes.search_text});
                               },
                            },i18n.__('Buscar')),
                        ),
                        el(PanelRow,{},
                            el(SelectControl,{
                                label: i18n.__('Entrada'),
                                options: options,
                                onChange: function (value) {
                                    props.setAttributes({post_id:parseInt(value)});
                                },
                                value: props.attributes.post_id,
                            }),
                        ),
                    ),
                ),
                el('p',{className:className+' head'},i18n.__('Article link')),
                el('p',{className:className+' title'},postTitle),
            ];
        }),

        save: function(props) {
            return null;
        },
    } );
}(
    window.wp.blocks,
    window.wp.editor,
    window.wp.element,
    window.wp.components,
    window.wp.data,
    window.wp.i18n,
) );

Once a block is registered, it is available as an option in the editor’s insert dialog box, using title, icon and category values ​​to organize its display. You can choose an icon from any of those included in the built-in Dashicons icon set, or provide a custom svg element.

The name of a block must be prefixed with a specific namespace for its complement. This helps avoid conflicts when more than one add-in registers a block with the same name. In this example, the namespace is dwguten.

The edit and save functions describe the structure of your block in the context of the editor and the content saved for the front-end, respectively. Being able to define the customization of the block in the part of the editor and in the part of the front-end.

Queue the block styles for the editor and the front-end.
Like scripts, block styles must be queued. The editor_style driver will be used for styles that are only relevant in the editor, and the style controller for common styles applied in both the editor and the front-end.
The code for the editor styles is passed to the editor.css file:

.wp-block-dwguten-block-post.title {
  color:navy;
  background: paleturquoise;
  border: 1px solid #c99;
  padding: 3px;
  margin: 0;
  width: 100%;
  text-align: center;
  display: flex;
  justify-content: center;
  flex-direction: column;
  min-height: 100px;
}

and the styles for the front-end in the style.css file, which in our case we will leave empty.

In the plugin file (guten-plugin.php) we register these files:

<?php
/*
 *************************************************************
 * Plugin Name: Doowebs dwguten blocks
 * Description: Gutenberg blocks.
 * Author: Doowebs
 * Version: 0.0
 *************************************************************
 */
function dwguten_block_register_block() {
  wp_register_style(
      'dwguten-block-editor',
      plugins_url( 'editor.css', __FILE__ ),
      array( 'wp-edit-blocks' ),
      filemtime( plugin_dir_path( __FILE__ ) . 'editor.css' )
  );
  wp_register_style(
      'dwguten-block-style',
      plugins_url( 'style.css', __FILE__ ),
      array( ),
      filemtime( plugin_dir_path( __FILE__ ) . 'style.css' )
  );
}
add_action( 'init', 'dwguten_block_register_block' );

Attributes
The edit and save functions are responsible for describing the structure of the block’s appearance in the editor and front-end, respectively. If the user changes a block, this structure may need to change it. To achieve this, the status of a block is maintained throughout the editing session as a simple JavaScript object, and when an update occurs, the editing function is invoked again. In other words: the output of a block is a function of its attributes.

A challenge of maintaining the representation of a block as a JavaScript object is that we must be able to extract this object again from the saved content of a publication. This is achieved with the property of block type attributes:

       attributes: { 
            post_id: {
                type: 'number',
                default: 0,
            },
            post_title: {
                type: 'string',
                default: '',
            },
            search_text: {
                type: 'string',
                default: '',
            },
            search_text_sel: {
                type: 'string',
                default: '',
            },
        },

When registering a new type of block, the attribute property describes the shape of the attribute object that you would like to receive in the edit and save functions. Each value is a source function to find the desired value of the block.

In the above code snippet, when loading the editor, the content value will be extracted from the HTML of the paragraph element in the edit function.

Block Controls: Sidebar

In the editor we can set the components in the block or in the sidebar (inspector).

The configuration sidebar is used to display less used configurations or configurations that require more screen space. The configuration sidebar should only be used for block level configuration.
If you have settings that affect only the selected content within a block. The configuration sidebar is displayed even when editing a block in HTML mode, so it should only contain block level settings.
The Block tab is displayed instead of the Document tab when a block is selected.
Similar to the representation of a toolbar, if you include an InspectorControls element in the return value of the editing function of your block type, those controls will be displayed in the region of the configuration sidebar.

Dynamic blocks

Dynamic blocks are blocks that build their structure and content on the fly when the block is represented in the front-end.
Dynamic blocks can be used to represent entries that can be updated or blocks in which code updating (HTML, CSS, JS) is necessary. This is the case of aggregation of classes or HTML elements that change the design of the front-end: this is achieved with the use of dynamic blocks ensuring that they are applied.
For many dynamic blocks, the save function must be returned as null, which tells the editor to save the attributes of the block in the database. These attributes are then passed to the server side representation, so you can decide how to display the block in the front-end of the site.
Block attributes can be used for any content or configuration that you want to save for that block. For example, attributes can be used for each content that you want to display in the front-end, such as header text, paragraph text, an image, a URL, etc.

In the previous code example, a dynamic block type was shown to display an entry (block.js).

Because it is a dynamic block, a server component is necessary. The content on the front end of your site depends on the function called by the render_callback property of register_block_type.

<?php
/*
 *************************************************************
 * Plugin Name: Doowebs dwguten blocks
 * Description: Gutenberg blocks
 * Author: Doowebs
 * Version: 0.0
 *************************************************************
 */
 
function dwguten_block_register_block() {

  register_block_type( 'dwguten-block/post', array(
    'style' => 'dwguten-block-style',
    'editor_style' => 'dwguten-block-editor',
    'editor_script' => 'dwguten-block',
    'attributes' => array(
      'post_id'     => array('type'=>'number',),
      'post_title'  => array('type'=>'string',),
      'search_text' => array('type'=>'string',),
      'search_text_sel' => array('type'=>'string',),
    ),
    'render_callback' => 'dwguten_block_dynamic_render_callback_post',
  ));
}
add_action( 'init', 'dwguten_block_register_block' );

function dwguten_block_dynamic_render_callback_post($attributes) {
    $post_id=$attributes['post_id'];
    $post=get_post($post_id);
    $category=get_the_category($post_id);
    $video=false;
    if (!empty($category)) {
      $i=0;
      while ($i<sizeof($category)) {
        if ($category[$i]->cat_name=='Videos') {$video=true;}
        $i++;
      }
    }
    $videost='';
    if ($video) {$videost='<img src="\wp-content\themes\tema\images\ic_playCircle.svg" class="video-logo" alt="Video-logo" />';}
    $bc=
        '<section class="article-link">'.
            '<a class="article-title-link" href="'.esc_url(get_permalink($post_id)).'">'.
                '<h2 class="article-title">'.get_the_title($post_id).'</h2>'.
            '</a>'.
            '<a class="thumbnail-link" href="'.esc_url(get_permalink($post_id)).'">'.
                '<span class="photo-overlay"></span>'. 
                get_the_post_thumbnail($post_id).
                $videost.
            '</a>'.
            '<div class="article-excerpt">'.the_excerpt_m($post_id).'</div>'.
            '<a class="article-link-btn" href="'.esc_url(get_permalink($post_id)).'">'.
                esc_html__('Ir al artículo').
            '</a>'.
        '</section>';
    return $bc;
}
 
?>

With the definition of the files guten-plugin.php, block.js, editor.css and style.css in a folder within our WordPress project plugins we will have defined a plugin, which once activated, we will have a new block in our Gutenberg editor, available for our posts. In this case, a block is generated with the featured image of a post, which in the case of being from the category Videos will be shown with the play icon (consider adding the image file ic_playCircle.svg in the path indicated in the code), and a link to the entry (Ir al artículo).

Finally, we would have to add the css code for our front-end in the style.css file of our WordPress theme, for example:

/* CSS Articulo link */
.article-link {
  width: 100%;
  margin: 24px 0 14px;
  background-color: #F9FBFC;
  padding: 10px 15px 50px;
}
.article-link .article-title {
  color: #25343D;
  font-family: Archia;
  font-size: 24px;
  font-weight: bold;
  line-height: 36px;
}
.article-link .article-title:hover {
  color: #71EB6C;
}
.article-link .thumbnail-link {
  display: block;
  width: 100%;
  position: relative;
}
.article-link .photo-overlay {
  background: rgba(0,0,0,0);
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  margin: 0;
  z-index: 5;
}
.article-link .photo-overlay:hover {
  background: rgba(0,0,0,0.4);
}
.article-excerpt {
  width: 100%;
  margin: 0;
  background-color: #F9FBFC;
  padding: 15px 0;
  color: #25343D; 
  font-size: 16px;  
  line-height: 24px;
}
.article-link-btn {
  color: #336BEF;
  font-size: 16px;
  font-weight: bold;
  line-height: 24px;
}