Build a Block Series - Part 7: Placeholder
In this seventh part, I do away with screencasting and work on improving the editor interaction with the QRCode block. Specifically, showing in the editor how the QRCode will look when published.
Previous screencasts:
Intro
One of the primary tenets of Gutenberg is as a WYSIWYG editor, what you see in the editor, should be as close to what you get when published. Keep this in mind when building blocks.
A meta-comment, I'm not going to continue screencasting as part of this series. I don't think there is enough value in hearing my voice and watching me type to offset the time and effort it takes. The explanations and code are where the true value lies, and it is easier for me to continue with just posts.
So on with the show.
Plan
The QRCode block, as we left it after Part 6, only shows a URL TextField in the editor. You don't see the actual QRCode until the block is published. In this part, our goal is to show the URL field if none is entered, and the QRCode after the URL is entered.
There is a special property of a block called isSelected
this is a boolean and is set to true when the block is selected, or being edited, and set to false when the author moves to another block in the editor.
We can use this property to control if the block is selected then show the URL field, otherwise show the QRCode, assuming a URL is set.
Code
If you have been following the series and look at the code, you will see I broke up the edit and save functions into their own file. I did this to make development a little easier isolating each, see the Import section of Part 5 for an explanation.
Placeholder
The state when a block has been inserted, but no data has been entered yet, is called a placeholder. There is a Placeholder
component built that gives us a standard look. You can see example placeholders in use with the image and embed blocks.
To use the Placeholder, wrap the <TextControl>
component so it becomes a child element of the <Placeholder>
component. Try it out in your code. After updating, you might have something like:
<Placeholder label="QRCode Block" instructions="Add URL for QRCode">
<TextControl value={attributes.url} onChange={(val) => setAttributes({ url: val })} />
</Placeholder>
Ternary Function
A ternary function is an inline if-else statement, using the syntax:
( clause ) ? ( doIfTrue ) : ( doIfFalse )
This can be used inside a block to control what shows when a parameter is set or not. A simple case that checks if the URL is set might look like:
return (
<div>
{ url ?
<div>URL: { attributes.url }</div> :
<div> No URL <TextField/> </div>
}
);
We need to add another check for isSelected
since we want to show the QRCode field if the URL is set and !isSelected
, meaning we are not editing the block, the focus is elsewhere.
The logic case is: attributes.url && ! isSelected
Combined together: we want to show the same div
from the save function, this holds our QRCode; otherwise, if no URL or the block has the focus, show the Placeholder. Go ahead and attempt an implementation.
Here's what my edit function looks like this:
const edit = ({ attributes, isSelected, setAttributes }) => {
return (
<div>
{attributes.url && !isSelected ? (
<div id="qrcode" data-url={attributes.url}></div>
) : (
<Placeholder label="QRCode Block" instructions="Add URL for QRCode">
<TextControl value={attributes.url} onChange={(val) => setAttributes({ url: val })} />
</Placeholder>
)}
</div>
);
};
Show QRCode in Editor
The above function will work, but the QRCode will not show. It only inserts the div
which as-is renders nothing visible. This is because we only load the library and run the trigger on the front-end.
We need to update qrcode-block.php
and load the library on the back-end, in WordPress these are called the admin screens. Use admin_enqueue_scripts
hook to enqueue a script there.
add_action( 'admin_enqueue_scripts', function() {
wp_enqueue_script( 'mkaz-qrcode-qrcodejs' );
} );
Trigger
The next part is to add the trigger to run each time the URL is updated and the <div>
is rendered. There is a mechanism already in place to do this, it is the React Hook useEffect. To use this hook, you define a function that React will call after each render.
In our case, the function we want to call is the same trigger code used on the front-end. I just duplicate the code, for now, it probably could be refactored later so both use the same function.
Putting it all together, here is the complete edit function. When editing or no URL is set it renders a Placeholder, otherwise, it shows the QRCode when the URL is set.
import {
Placeholder,
TextControl,
} from '@wordpress/components';
import { useEffect } from '@wordpress/element';
const edit = ( { attributes, isSelected, setAttributes } ) => {
useEffect( () => {
if ( ! isSelected ) {
const elem: document.getElementById( 'qrcode' );
if ( elem ) {
const url: elem.dataset.url;
new QRCode( elem, {
text: url,
width: 256,
height: 256,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H,
} );
}
}
} );
return (
<div>
{ attributes.url && ! isSelected ?
<div
id="qrcode"
data-url={ attributes.url }
></div> :
<Placeholder
label="QRCode Block"
instructions="Add URL for QRCode"
>
<TextControl
value={ attributes.url }
onChange={ ( val ) => setAttributes( { url: val } ) }
/>
</Placeholder>
}
</div>
);
};
export default edit;
The updated code is available at github.com/mkaz/qrcode-block
If you want to see the changes I made just for this part, see PR #1