How to build a dynamic Gutenberg block (tutorial)

This blog post will help you create a Gutenberg block to display dynamic content on the front and backend.

Detailed description

On the backend, the admin will be able to select the number of users per page on the right sidebar. Default, it will have 10 users per page. Besides that, it will also have a preview of the users’ table, which will have the same look and feel from the frontend.

On the frontend, the visitor will be able to see a list of the users.

Frontend view

Backend view

How to start

How do you begin a project like this? We will have to use Reactjs for Gutenberg block, so we will need to have a compiler to do that. Besides that, we will have to use PHP for the dynamic content.

Luckily there is someone in the WordPress community who made an easy start setup. His name is Ahmad Awais and you can access the repository by clicking on his name.

When you follow the instructions, you will have a plugin with a clean Gutenberg block.

Modifying the block

We will need to use Javascript and PHP to modify that block to achieve our goal.

The Javascript part (block.js)

The first part I want to explain is the place where we want to import all the classes we are going to use to build our block.

// to translate strings in javascript
const { __ } = wp.i18n;
// to register the block
const {
    registerBlockType,
} = wp.blocks;
// for the right sidebar settings on the backend
const {
    InspectorControls,
    RichText
} = wp.editor;
// previewing the list in the content
const {
    ServerSideRender,
    PanelBody,
} = wp.components;
// combines multiple elements
const {
    Fragment
} = wp.element;

Then we will need to register the block:

registerBlockType( 'cgb/block-users-table-block', {
    title: __( 'Block users table', 'block-users-table' ), 
    icon: 'admin-users',
    category: 'common', 
    keywords: [
        __( 'block-users-table — CGB Block', 'block-users-table' )
    ],

As you can notice in the previous image, we’ve used the WordPress translation function (__()). This does also work in JavaScript thanks to the wp.i18n package. For the translation though, you will have to convert the translation file into a JSON file. I’ll explain this in another blog post.

After registering the block, we will need to define the attributes we will need to have. In our case, we will need to adjust the users per page.

attributes: {
        usersPerPage: {
            type: 'number',
            default: 10
        }
    },

On the page editor, we will need to be able to change the number of the users per page and also display a preview of the table. To do both of that, we are going to use the Fragment. This will create 2 sections on the page editor, InspectorControls and ServerSideRender.

InspectorControls are used to add a settings block on the right side of the page editor. There you can manage the number of users per-page setting.

ServerSideRender will process the PHP part so you have the same look and feel from the frontend on the page editor.

edit: function ( props ) {


        function updateUsersPerPage( e ) {
            props.setAttributes( {
                usersPerPage: parseInt( e.target.value ),
            } )
        }

        return (
            <Fragment>
                <InspectorControls>
                    <PanelBody
                        title={ __( 'Table settings', 'block-users-table' ) }
                        initialOpen={ true }
                    >
                        <label className="blocks-base-control__label">
                            { __( 'Users per page', 'block-users-table' ) }
                        </label>
                        <input
                            type="number"             
                            className="gbs-block-inspected-inspector-control-field"
                            onChange={ updateUsersPerPage }
                            value={ props.attributes.usersPerPage }
                        />
                    </PanelBody>
                </InspectorControls>
                <ServerSideRender
                    block="cgb/block-users-table-block"
                    attributes={ props.attributes }
                />
            </Fragment>


        );
    },

On the save part, we don’t need to do anything. Because it will be dynamically created with PHP.

save: function ( props ) {
        return null;
    },

The PHP part

Registration of the block needs to happen on the “init” hook.

add_action( 'init', 'users_table_block_cgb_block_assets' );

/**
 * Enqueue Gutenberg block assets for both frontend + backend.
 *
 * Assets enqueued:
 * 1. blocks.style.build.css - Frontend + Backend.
 * 2. blocks.build.js - Backend.
 * 3. blocks.editor.build.css - Backend.
 *
 * @uses {wp-blocks} for block type registration &amp; related functions.
 * @uses {wp-element} for WP Element abstraction — structure of blocks.
 * @uses {wp-i18n} to internationalize the block's text.
 * @uses {wp-editor} for WP editor styles.
 * @uses {wp-components} for WP editor
 * @since 1.0.0
 */
function users_table_block_cgb_block_assets() { // phpcs:ignore
	// Register block styles for both frontend + backend.
	wp_register_style(
		'users_table_block-cgb-style-css', // Handle.
		plugins_url( 'dist/blocks.style.build.css', dirname( __FILE__ ) ), // Block style CSS.
		array( 'wp-editor' ), // Dependency to include the CSS after it.
		null // filemtime( plugin_dir_path( __DIR__ ) . 'dist/blocks.style.build.css' ) // Version: File modification time.
	);

	// Register block editor script for backend.
	wp_register_script(
		'users_table_block-cgb-block-js', // Handle.
		plugins_url( '/dist/blocks.build.js', dirname( __FILE__ ) ),
		// Block.build.js: We register the block here. Built with Webpack.
		array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor', 'wp-components' ), // Dependencies, defined above.
		null,
		// filemtime( plugin_dir_path( __DIR__ ) . 'dist/blocks.build.js' ), // Version: filemtime — Gets file modification time.
		true // Enqueue the script in the footer.
	);

	// Register block editor script.
	wp_register_script(
		'users_table_block-script-js', // Handle.
		plugins_url( '/dist/block-users-table-block.js', dirname( __FILE__ ) ),
		// Block.build.js: We register the block here. Built with Webpack.
		array( 'jquery' ),
		null,
		true // Enqueue the script in the footer.
	);

	// localize block editor script
	wp_localize_script( 'users_table_block-script-js', 'ajax',
		array( 'url' => admin_url( 'admin-ajax.php' ) ) );

	// Register block editor styles for backend.
	wp_register_style(
		'users_table_block-cgb-block-editor-css', // Handle.
		plugins_url( 'dist/blocks.editor.build.css', dirname( __FILE__ ) ), // Block editor CSS.
		array( 'wp-edit-blocks' ), // Dependency to include the CSS after it.
		null // filemtime( plugin_dir_path( __DIR__ ) . 'dist/blocks.editor.build.css' ) // Version: File modification time.
	);

	/**
	 * Register Gutenberg block on server-side.
	 *
	 * Register the block on server-side to ensure that the block
	 * scripts and styles for both frontend and backend are
	 * enqueued when the editor loads.
	 *
	 * @link https://wordpress.org/gutenberg/handbook/blocks/writing-your-first-block-type#enqueuing-block-scripts
	 * @since 1.0.0
	 */
	register_block_type(
		'cgb/block-users-table-block', array(
			// Enqueue blocks.style.build.css on both frontend &amp; backend.
			'style'         => 'users_table_block-cgb-style-css',
			// enqueue blocks.users-table-block in the frontend
			'script'        => 'users_table_block-script-js',
			// Enqueue blocks.build.js in the editor only.
			'editor_script' => 'users_table_block-cgb-block-js',
			// Enqueue blocks.editor.build.css in the editor only.
			'editor_style'  => 'users_table_block-cgb-block-editor-css',

			'attributes'      => array(
				'usersPerPage' => array(
					'type'    => 'integer',
					'default' => 10,
				),
			),
			'render_callback' => 'render_users_block',
		)
	);

}

For the dynamic content, we will have to use the render_callback from the register_block_type. The callback function will have the number of users per-page as an argument, so we can use that to build the HTML.

function render_users_block( $args ) {
	if ( ! current_user_can( 'administrator' ) ) {
		return __( 'you are not authorized to view this block.', 'block-users-table' );
	}

	/**
	 * Use attribute from the block
	 */
	if ( isset( $args['usersPerPage'] ) ) {
		$args['number'] = $args['usersPerPage'];
	}

	$defaults = array(
		'order'       => 'asc',
		'orderby'     => 'display_name',
		'role'        => '',
		'number'      => 10,
		'paged'       => 1,
		'count_total' => true,
	);

	// Parse incoming $args into an array and merge it with $defaults
	$params = wp_parse_args( $args, $defaults );

	// get the users with the given parameters
	$users = new WP_User_Query(
		$params
	);
        
        // generate the output html
	ob_start();

        // build the html

        // return the html
        return ob_get_clean();
}

I have left the “build HTML” part empty, so you can create your own frontend custom code.

Conclusion

In the past, I would have created this with a shortcode. The frontend would have been the same, but the backend would be a simple row with no visible data. And that is the exact thing that makes Gutenberg great. You have the same look and feel from the frontend on the page editor. On top of that, it does have user-friendly settings on the right side of the page editor.