Hi there,
I know this is not a Sage specific question, but more an overall Wordpress Gutenberg best practice question
We’re slowly migrating our wordpress theme development process over to the Gutenberg editor and I made quit a few custom Gutenberg blocks by now, either with the Wordpress Block API or with the ACF Blocks. I try to use the wp core blocks as much as possible for the best user experience when editing.
On a custom post type project
I want to have a leading paragraph, followed by a normal paragraph. Next to this I want to list some meta data as a static list, coming from a few ACF fields on the post type. This data is used elsewhere in my theme so I want to keep these as ACF fields.
I also would like this static list to be visible in the Gutenberg editor, so it matches the actual frontend.
So I’m creating a custom Gutenberg block for this using:
function registerBlocks() {
wp_register_script(
'domain/opening-paragraph-editor',
asset('scripts/opening-paragraph.editor.js')->uri(),
['sage/vendor.js']
);
wp_register_style(
'domain/opening-paragraph',
asset('styles/opening-paragraph.css')->uri()
);
register_block_type(
'domain/opening-paragraph', [
'api_version' => 2,
'editor_script' => 'domain/opening-paragraph-editor',
'editor_style' => 'domain/opening-paragraph',
'render_callback' => function($block_attributes, $content) {
wp_enqueue_style('domain/opening-paragraph');
return $content;
}
]
);
}
add_action('init', 'registerBlocks');
My scripts/opening-paragraph.editor.js
:
import React from 'react';
import {__} from '@wordpress/i18n';
import {registerBlockType} from '@wordpress/blocks';
import {RichText, InnerBlocks, useBlockProps} from '@wordpress/block-editor';
const ALLOWED_BLOCKS = ['core/paragraph'];
const TEMPLATE = [
['core/paragraph', {
className: 'lead'
}],
['core/paragraph']
];
registerBlockType('domain/lead-project', {
apiVersion: 2,
title: __('Lead Project'),
description: __('Opening lead paragraph with project meta block.'),
icon: 'editor-paragraph',
category: 'text',
attributes: {
projectClient: {
type: 'string'
},
projectDate: {
type: 'string'
},
projectLocation: {
type: 'string'
}
},
example: {
attributes: {
projectClient: 'Awakenings',
projectDate: '23 MAR 2014',
projectLocation: 'Gashouder, Amsterdam'
}
},
edit: ({attributes, setAttributes}) => {
const blockProps = useBlockProps();
const {projectClient, projectDate, projectLocation} = attributes;
return (
<div { ...blockProps }>
<div className="wp-block-domain-lead-project__wrap">
<div className="wp-block-domain-lead-project__inner">
<InnerBlocks
allowedBlocks={ALLOWED_BLOCKS}
template={TEMPLATE}
templateLock="all"
/>
</div>
<div className="wp-block-domain-lead-project__summary">
<RichText
tagName="p"
value={projectClient}
contentEditable={false}
onChange={text => setAttributes({projectClient: text})}
/>
<RichText
tagName="p"
value={projectDate}
contentEditable={false}
onChange={text => setAttributes({projectDate: text})}
/>
<RichText
tagName="p"
value={projectLocation}
contentEditable={false}
onChange={text => setAttributes({projectLocation: text})}
/>
</div>
</div>
</div>
);
},
save: ({attributes}) => {
const blockProps = useBlockProps.save();
const {projectClient, projectDate, projectLocation} = attributes;
return (
<div { ...blockProps }>
<div className="wp-block-domain-lead-project__wrap">
<div className="wp-block-domain-lead-project__inner">
<InnerBlocks.Content/>
</div>
<RichText.Content
tagName="p"
value={projectClient}
/>
<RichText.Content
tagName="p"
value={projectDate}
/>
<RichText.Content
tagName="p"
value={projectLocation}
/>
</div>
</div>
);
}
});
And for locking this block to the post type and passing the data as attributes, I’m creating a Block template:
function registerBlockTemplates() {
$post_id = isset($_GET['post']) ? $_GET['post'] : null;
if (get_post_type($post_id) == 'project') {
$project_object = get_post_type_object('project');
$projectClient = get_field('project_client', $post_id);
$projectDateStart = get_field('project_dates_start', $post_id);
$projectDateEnd = get_field('project_dates_end', $post_id);
$projectLocation = get_field('project_location', $post_id);
$project_object->template = [
['domain/lead-project', [
'projectClient' => $projectClient ?: '',
'projectDate' => $projectDateStart ? $projectDateStart . ($projectDateEnd ? ' – ' . $projectDateEnd : '') : '',
'projectLocation' => $projectLocation
]],
['domain/inner-blocks']
];
$project_object->template_lock = 'all';
}
}
add_action('init', 'registerBlockTemplates', 999, 0);
So far so good, this works great! When I start editing a project
post, the meta data is populated with the ACF fields from that project and I can add/save content to the core/paragraph blocks.
The content is saved in the database and displayed correctly in my frontend. However, whenever I change one of the ACF fields, these changes are not reflected in my Gutenberg block, not even on a page refresh.
Instead I get the message:
The content of your post doesn’t match the template assigned to your post type.
When I log the attribute values in javascript, these are the old values. Even on a page refresh. I can only see that the post content is still saved with the old attributes:
<!-- wp:domain/lead-project {"projectClient":"Testing","projectDate":"07 Jul 2013 – 04 Mar 2017","projectLocation":"Ibiza, Amsterdam"} -->
So my question basically is; what is the best practice to use/display static data that doesn’t have to be editable in the Gutenberg editor, but should update itself whenever it’s value changes?
I did found this Use Post Meta Data tutorial, but that just appends the data to the end of a block, without control over the markup plus it’s not visible in the editor.
Instead of using the RichText
block, I also tried using plain jsx
tags for displaying the data, but it has the same result.
Any thoughts, tips?
Thanks a lot!
EDIT: I found this article about Managing WordPress Metadata in Gutenberg, but I don’t think there’s an easy way yet to expose ACF fields in the post meta data?
select('core/editor').getEditedPostAttribute('meta')