Create Your Own Dynamic Gutenberg Block for Wordpress, Part 2
This is “part 2” of the Create Your Own Dynamic Gutenberg Block for Wordpress tutorial that was published last year. But instead of a narrative step-by-step it’s a collection of useful code snippets you can use to build feature rich custom Gutenberg blocks for WordPress (see the 1/14/20 update at the beginning of part 1 for a little more context for this portion).
The first tutorial covers the infrastructure required to house custom blocks in WordPress, so go there first if you don’t know how to set up your own Gutenberg blocks. Then once that is complete you can add the code snippets below to further develop your custom Gutenberg blocks. The companion GitHub repository tracks the lastest code snippets added here on the master
branch. The base code developed in part 1 is maintained in the tutorial/part1
branch.
Important!
Note that the code snippets below should be inserted into the appropriate locations in the file in which they go, never just copy and then replace the full file code, this will break the code. You can always refer to the GitHub repository to see the full context.
Image upload
Steps:
- Use
MediaUpload
andMediaUploadCheck
fromwp.blockEditor
. - Access
Button
fromwp.components
for a UI helper.
Helpful documentation:
index.js
In blocks/book-details/index.js
, add the following code:
js…
const { MediaUpload, MediaUploadCheck, RichText } = wp.blockEditor
const { Button } = wp.components
…
registerBlockType('davidyeiser-detailer/book-details', {
…
// Set up data model for custom block
attributes: {
image: {
type: 'object',
selector: 'js-book-details-image'
},
…
},
…
// The UI for the WordPress editor
edit: props => {
// Pull out the props we'll use
const { attributes, className, setAttributes } = props
// Pull out specific attributes for clarity below
const { image } = attributes
return (
<div className={className}>
<MediaUploadCheck>
<MediaUpload
className="js-book-details-image wp-admin-book-details-image"
allowedTypes={['image']}
multiple={false}
value={image ? image.id : ''}
onSelect={image => setAttributes({ image: image })}
render={({ open }) => (
image ?
<div>
<p>
<img src={image.url} width={image.width / 2} />
</p>
<p>
<Button onClick={() => setAttributes({ image: '' })} className="button is-small">Remove</Button>
</p>
</div> :
<Button onClick={open} className="button">Upload Image</Button>
)}
/>
</MediaUploadCheck>
…
</div>
)
},
…
index.php
In blocks/book-details/index.php
, add the following code:
php…
function render_dynamic_block($attributes) {
// Parse attributes
$book_details_imageObj = $attributes['image'];
$book_details_image_url = $book_details_imageObj['sizes']['full']['url'];
$book_details_image_alt_text = $book_details_imageObj['alt'];
$book_details_image_width = $book_details_imageObj['sizes']['full']['width'] / 2;
…
/* BEGIN HTML OUTPUT */
?>
<div class="block-book-details">
<?php if ($book_details_image_url) : ?>
<img class="book-details-image" src="<?php echo $book_details_image_url; ?>" alt="<?php echo $book_details_image_alt_text; ?>" width="<?php echo $book_details_image_width; ?>" />
<?php endif; ?>
…
</div>
…
Toggle switch
Along with the toggle switch, we also access the block’s sidebar so we can place meta details and controls there instead of inline with the content.
Steps:
- Access
InspectorControls
fromwp.blockEditor
to add the toggle in the sidebar. - Use
PanelBody
,PanelRow
,ToggleControl
fromwp.components
to build the toggle and toggle area.
index.js
In blocks/book-details/index.js
, add the following code:
js…
const { InspectorControls } = wp.blockEditor
const { PanelBody, PanelRow, ToggleControl } = wp.components
…
registerBlockType('davidyeiser-detailer/book-details', {
…
// Set up data model for custom block
attributes: {
haveRead: {
type: 'boolean',
selector: 'js-book-details-read'
},
…
},
…
// The UI for the WordPress editor
edit: props => {
// Pull out the props we'll use
const { attributes, className, setAttributes } = props
// Pull out specific attributes for clarity below
const { haveRead } = attributes
return (
<div className={className}>
{/* Sidebar Controls */}
<InspectorControls>
<PanelBody title={__('Book Status')}>
<PanelRow>
<ToggleControl
className="js-book-details-read"
label="Read"
checked={haveRead}
help={haveRead ? "This book has been read." : "Currently unread."}
onChange={checked => setAttributes({ haveRead: checked })}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
…
</div>
)
},
…
index.php
In blocks/book-details/index.php
, add the following code:
php…
function render_dynamic_block($attributes) {
// Parse attributes
$book_details_have_read = $attributes['haveRead'];
…
/* BEGIN HTML OUTPUT */
?>
<div class="block-book-details">
<?php if ($book_details_have_read) : ?>
<p><em>This book has been read.</em></p>
<?php endif; ?>
…
</div>
…
Using state to manage variable number of items
Note that this one requires a structural change to the edit
property of the registerBlockType()
function from a stateless component function to a WordPress (React) Component
class. I also added the nanoid npm package to generate ids for React keys.
Also, there was a blog post I referenced for a lot of this, but I can no longer find it! If I do I’ll link it here.
Steps:
- Add nanoid package to create ids (
yarn add nanoid
). - Access WordPress’s
Component
class. - Change stateless component to extend the WordPress (React) Component class so we can use
state
to manage data.
index.js
In blocks/book-details/index.js
, add the following code:
jsconst { Component } = wp.element
…
registerBlockType('davidyeiser-detailer/book-details', {
…
// Set up data model for custom block
attributes: {
quotes: {
type: 'array',
selector: 'js-book-details-quotes'
},
…
},
…
// The UI for the WordPress editor
edit: class BookDetails extends Component {
constructor() {
super(...arguments)
// Match current state to saved quotes (if they exist)
this.state = {
quotes: this.props.attributes.quotes || []
}
this.addQuote = this.addQuote.bind(this)
this.removeQuote = this.removeQuote.bind(this)
this.editQuote = this.editQuote.bind(this)
}
// adds empty placeholder for quote
addQuote(e) {
e.preventDefault()
// get quotes from state
const { quotes } = this.state
// set up empty quote
const emptyQuote = {
id: nanoid(),
content: '',
pageRef: ''
}
// append new emptyQuote object to quotes
const newQuotes = [...quotes, emptyQuote]
// save new placeholder to WordPress
this.props.setAttributes({ quotes: newQuotes })
// and update state
return this.setState({ quotes: newQuotes })
}
// remove item
removeQuote(e, index) {
e.preventDefault()
// make a true copy of quotes
// const { quotes } = this.state does not work
const quotes = JSON.parse(JSON.stringify(this.state.quotes))
// remove specified item
quotes.splice(index, 1)
// save updated quotes and update state (in callback)
return (
this.props.setAttributes(
{ quotes: quotes },
this.setState({ quotes: quotes })
)
)
}
// handler function to update quote
editQuote(key, index, value) {
// make a true copy of quotes
const quotes = JSON.parse(JSON.stringify(this.state.quotes))
if (quotes.length === 0) return
// update value
quotes[index][key] = value
// save values in WordPress and update state (in callback)
return (
this.props.setAttributes(
{ quotes: quotes },
this.setState({ quotes: quotes })
)
)
}
render() {
// Pull out the props we'll use
const { attributes, className, setAttributes } = this.props
// Pull out specific attributes for clarity below
const { haveRead, image, quotes } = attributes
return (
<div className={className}>
…
{!!quotes && quotes.map((quote, index) =>
<div key={quote.id || index} className="wp-admin-book-details-quote">
<RichText
className="wp-admin-book-details-quote-content"
value={quote.content}
onChange={value => this.editQuote('content', index, value)}
tagName="div"
multiline="p"
placeholder="Quote"
/>
<RichText
className="wp-admin-book-details-quote-page-ref"
value={quote.pageRef}
onChange={value => this.editQuote('pageRef', index, value)}
tagName="p"
placeholder="Page number"
/>
<p>
<input
className="button-secondary button"
type="submit"
value="Remove Quote"
onClick={(e) => this.removeQuote(e, index)}
/>
</p>
</div>
)}
<p class="wp-admin-book-details-quote">
<input
className="button-primary button"
type="submit"
value="Add Quote"
onClick={(e) => this.addQuote(e)}
/>
</p>
…
</div>
)
}
},
…
index.php
In blocks/book-details/index.php
, add the following code:
php…
function render_dynamic_block($attributes) {
// Parse attributes
$book_details_quotes = $attributes['quotes'];
…
/* BEGIN HTML OUTPUT */
?>
<div class="block-book-details">
…
<div class="book-details-quotes">
<?php
if ($book_details_quotes) :
foreach($book_details_quotes as $quote) :
?>
<blockquote class="book-details-quote-<?php echo $quote['id']; ?> book-details-quote">
<?php echo $quote['content']; ?>
<cite><?php echo $quote['pageRef']; ?></cite>
</blockquote>
<?php
endforeach;
endif;
?>
</div>
…
</div>
…
Other Tutorials
- Docker + WordPress Setup
- Simple Storybook React Setup with Light and Dark Mode
- Create Your Own Dynamic Gutenberg Block for Wordpress, Part 1
- → Create Your Own Dynamic Gutenberg Block for Wordpress, Part 2
- Installing Composer
- How to Create a Blog with the Airtable API & Next.js