Dynamic Sources is a library that allows to set the values of Gutenberg blocks from standard and custom fields. Developers who create blocks for Gutenberg can use it to turn any block from static to dynamic.

With Dynamic blocks, users will be able to create templates, archives and Views using the blocks that you develop.

Download Dynamic Sources from Github   Demo plugin

How Dynamic Sources Work in the Block Editor

1. Users can enable dynamic sources for any attribute in the block.

2. Then, they choose the source.

Compatibility with Custom-Fields Plugins

Dynamic Sources is compatible with all the standard custom fields in WordPress, as well as custom fields coming from Toolset, ACF and Pods.

Additionally, it allows to hook and add support for any other content source.

Integrating Dynamic Sources with Your Gutenberg Blocks

To install the Toolset Dynamic Sources API library, Composer has to be used to include it as a dependency in the project’s composer.json file.

If the project is not using Composer, a composer.json file will not exist in the root folder of the project, so the following command needs to be run in order to first create the file:

composer init

Then, the Toolset Dynamic Sources API library Composer package will be installed by running the following command:

composer require toolset/dynamic-sources

Until the Toolset Dynamic Sources API library is published in Packagist, the process of adding the needed information inside the project’s composer.json file has to be done manually.

Add the needed information inside the project composer.json file
"repositories": [
 {
   "type": "vcs",
   "url": "https://github.com/OnTheGoSystems/toolset-dynamic-sources.git"
 }
],
"require": {
 "toolset/dynamic-sources": "dev-master"
}

After the relevant information has been added inside the project’s composer.json file, all the developer needs to do is to run the following command.

composer install

The Toolset Dynamic Sources integration has two parts:

  • The server-side integration
  • The client-side integration

Server-side integration

Only a few lines of PHP code are required to set up the the server-side part of the Toolset Dynamic Sources API integration. Usually, you would put these lines of code inside a file which is then required by the plugin’s main file.

The content of that file should be the following:

Decide instance of the Dynamic Sources
<?php
// The following two lines decide which instance of the Dynamic Sources API in the current WP installation is the most recent,
// in order to use that.
$ds_loader = require_once __DIR__ . '/vendor/toolset/dynamic-sources/server/ds-instance.php';
ts_dynamic_sources_adjust_ds_instance( __DIR__, $ds_loader['version'], $ds_loader['path'] );

For example, if this file is called ds.php and placed in the root folder of the plugin, then in the plugin’s main file you would add the following.
require_once trailingslashit( __DIR__ ) . 'ds.php';

Note that the code above needs to run before init, because the actual API is initialized on init, so either require this file on the plugins main file outside of any hook, or on a hook before init (e.g. plugins_loaded).

That’s it! This completes the server-side integration of the Toolset Dynamic Source library.

Client-side integration

As mentioned before, the Toolset Dynamic Sources API library is only useful for plugins that offer WordPress editor blocks. In these client-side integration instructions, we are assuming that the developer of the plugin uses JSX (along with a bundling tool that transpiles the code into a JS version that current browsers can understand). Of course, the Toolset Dynamic Sources API library can also be used by blocks built with ES5 JavaScript but naturally, you will need to adjust your code accordingly.

Nothing special needs to be done, codewise, in order for the plugin to be ready to allow integration of the Toolset Dynamic Sources API library into the blocks. Only the actual block code needs to be adjusted.

Block integration

In order for a block to be registered, the WordPress registerBlockType method should called with the block slug and a block configuration object. This block configuration object may have many properties but we are only interested in the following:

  • attributes – the set of structured data the block needs.
  • edit – the method/component that describes the structure of the block in the context of the editor. This represents what the editor will render when the block is used.
  • save – the method/component that defines the way in which different attributes should be combined into the final markup, which is then serialized into post_content.

Attributes

The only needed change in this part of the block’s configuration is that a new attribute needs to be added. This attribute will hold all the information related to Dynamic Sources. In the list of the block’s attributes, the following needs to be added:

List of the block attributes
dynamic: {
	type: 'object',
},

Edit

The majority of changes needed for a block to integrate the Toolset Dynamic Sources API library should happen in the edit part of the block’s configuration.

If it’s a function, it needs to be converted into a Component. The reason for this change is to make the Toolset Dynamic Sources API integration easier in terms of the needed code. To achieve this a HOC will be used that will wrap the edit component and will add all the needed Dynamic Sources methods and attributes.

This HOC is the withToolsetDynamicField provided by window.ToolsetDynamicSources. Let us assume that the edit component of the block is MyBlocksEditComponent. In order to wrap it using the above-mentioned HOC, instead of using something like the following:

export default MyBlocksEditComponent;

the developer needs to use:

export default withToolsetDynamicField( MyBlocksEditComponent );

To use withToolsetDynamicField this way, the developer needs to extract it like this

Use withToolsetDynamicField
const {
	withToolsetDynamicField,
} = window.ToolsetDynamicSources;

Then the developer will need to decide which of the block’s attributes will be eligible for content coming from Dynamic Sources. Once he has the chosen attributes’ slugs in hand (attr_a and attr_b), on the constructor of the edit component he needs to add the following:

Register the block attribute
// It registers the block attribute that will support content from Dynamic Sources.
this.props.dynamicFieldsRegister( {
	[ 'attr_a' ]: { // Attribute name
		category: 'text', // Supported attribute content type (eg. "text", "url", "image" etc.)
		label: __( 'Dynamic Attribute A' ), // Label for the Dynamic Sources toggle for this attribute.
	},
	[ 'attr_b' ]: { // Attribute name
		category: 'text', // Supported attribute content type (eg. "text", "url", "image" etc.)
		label: __( 'Dynamic Attribute B' ), // Label for the Dynamic Sources toggle for this attribute.
	},
} );

The next set of changes need to happen on the render method of the edit component. This method is the method that actually renders the component. Apart from what will be rendered in the editor, this method is also responsible for rendering the Inspector sidebar. The Inspector sidebar is the main part of the user interface where the Toolset Dynamic Sources API is visible. In this sidebar, the API needs to render one set of controls per chosen attribute (attr_a and attr_b). So, somewhere inside the tag, the developer needs to add the following:

Render one set of controls per chosen attribute
<PanelBody title={ __( 'Dynamic fields' ) }>
	{ this.props.dynamicFieldControlRender( 'attr_a' ) }
	{ this.props.dynamicFieldControlRender( 'attr_b' ) }
</PanelBody>

Finally, the developer needs to adjust the actual output of the edit component in the editor, in order for the block’s preview to reflect the changes that the user does on the Inspector sidebar.

First of all, the developer needs to wrap what is returned from the render method of the edit component with another Component, the EditWrapper, provided again from window.ToolsetDynamicSource. So, instead of the following:

Wrap what is returned
render() {
	return <div>
		...
	</div>;
}

the developer needs to write:

EditWrapper provided again window.ToolsetDynamicSource
render() {
	return <EditWrapper
					clientId={ this.props.clientId }
					isSelected={ this.props.isSelected }
					hasDynamicSource={ this.props.dynamicFieldGet( 'attr_a' ).isActive || this.props.dynamicFieldGet( 'attr_b' ).isActive }
	>
		<div>
			...
		</div>
	</EditWrapper>;
}

Short description of the attributes in the above code example:

  • clientId – the identifier for the block provided by the editor
  • isSelected – an attribute that defines if the block is currently being edited, again provided by the editor
  • hasDynamicSource – a boolean value that defines if the blocks has at least one attribute that uses content from Dynamic Sources.

To use EditWrapper this way, the developer needs to extract it like this:

Use EditWrapper this way
const {
	EditWrapper,
} = window.ToolsetDynamicSources;

The reason for this wrapping is that once the user selects a Dynamic Source for one of the block’s attributes, an orange border will be rendered around the block’s preview to signify that the block shows content from Dynamic Sources.

The final modification that will be needed is to “lock” a possible editable field on the editor side of the blocks. For example, if we look at a Heading block that allows Dynamic Content for the Heading text, as soon as the user has selected the content of the Heading to come from a Dynamic Source, we need to disable the editing of the Heading on the editor.

Disabling it is easily done by keeping RichText for cases where dynamic content is not used and adding RichText.Content with fewer attributes if the dynamic content is present. To do this, use the following:

Lock a possible editable field
const hasDynamicAttributeA = !! ( this.props.attributes.dynamic && this.props.attributes.dynamic[ 'attr_a' ] );

...

{
	hasDynamicAttributeA &&
	<RichText.Content
		tagName={ headingTag }
		value={ attr_a }
	/>
}
{
	! hasDynamicAttributeA &&
	<RichText
		tagName={ headingTag }
		placeholder={ __( "Write a Heading" ) }
		value={ attr_a }
		onChange={ ( value ) => {
			setAttributes( { attr_a: value } ) }
		}
	/>
}

Save

The changes needed on the save part of the block are simpler than the ones needed on the edit one.

As mentioned before, save is the method/component that defines the way in which the different attributes should be combined into the final markup, which is then serialized into post_content. So, practically the only thing you need to do is to tell the save part of the block how to use the previously declared dynamic attribute and what to save on the post_content in order to properly render the output of the blocks with always updated dynamic content on the front-end.

In order to offer always updated dynamic content, a shortcode is going to be used for the front-end rendering but the block needs to render properly even when it includes no dynamic content. For this, a method is offered from the Toolset Dynamic Sources API library that decides what content will be saved on the post_content – the getShortcodeOrStatic provided by window.ToolsetDynamicSources.

So back in code, the developer will need to add the following code before the save part of the block produces its output:

Add the following code before the save part
const dynamicAttributeA = props.attributes.dynamic && props.attributes.dynamic[ 'attr_a' ] ? props.attributes.dynamic[ 'attr_a' ] : {};
const dynamicAttributeAOrShortcode = ToolsetDynamicSources.getShortcodeOrStatic( dynamicAttributeA, attr_a, { repeatableFieldShowOnly: 'first' } );

Finally, you can use the dynamicAttributeAOrShortcode variable and offer that always-updated dynamic content or normal static content for the front-end, instead of the normal attr_a that you would normally use.

Integration for Server-Side rendered blocks

If your block is server-side rendered, use

import { ServerSideRender } from '@wordpress/editor';

instead of

import { ServerSideRender } from '@wordpress/components';

This way, you will make the post_id available for DS API in REST requests (e.g.: editor block refreshing). Or, in other words, this way everything will just work.