David Yeiser🍔

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 and MediaUploadCheck from wp.blockEditor.
  • Access Button from wp.components for a UI helper.

Helpful documentation:

index.js

In blocks/book-details/index.js, add the following code:

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:

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 from wp.blockEditor to add the toggle in the sidebar.
  • Use PanelBody, PanelRow, ToggleControl from wp.components to build the toggle and toggle area.

index.js

In blocks/book-details/index.js, add the following code:

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:

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 shortid 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 shortid package to create ids (yarn add shortid).
  • 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:

const { 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: shortid.generate(),

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:

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

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;

?>

</div>

</div>

Other Tutorials