[Custom Gutenberg Blocks]
Resources:
Bill Erickson: Building a block with Advanced Custom Fields
ACF Documentation: Blocks
ACF Documentation: acf_register_block_type()
Bill Erickson: Block Styles in Gutenberg
File structure:
|-- radicati
|-- dist
|-- lib
|-- filters
|-- gutenberg_blacklist.js
|-- gutenberg_blacklist.php
|--gutenberg
|-- acf_gutenberg_blocks
| |-- simple-button
| | |-- simple-button.css
| | |-- simple-button.js
| | |-- simple-button.php
| | |-- simple-button.scss
| | |-- simple-button.svg
|-- block_custom-styles.js
|-- block_custom-styles.php
|-- block_registration.php
For each block that you create in the block_registration.php
file, you can enqueue styles and scripts that only load when the block is shown (front or backend). A gulp watch task compiles the scss files individually to css.
You can also set up a custom icon for your block. You can either use a dashicons string, or an svg stored as a string. For my Button block, I'm storing the contents of the svg file as a string in the 'icon' property using file_get_contents()
. (I found it easier to edit the svg when I could view the file in the browser, and then I decided to keep that functionality by importing the file contents.)
Blocks can have alternate styles (added and removed in block-custom-styles.js) that are applied as classes around the block. Stylesheets then use those classes to display alternate styles.
...
require_once("lib/gutenberg/acf_gutenberg_blocks/block_registration.php");
require_once("lib/gutenberg/block_custom-style-options.php");
// This is Jason's code that automatically sets the 'href' property of our button ACF group based on the other fields
require_once("lib/filters/update_button_href.php");
...
<?php
/**
* Register Blocks
* @link https://www.billerickson.net/building-gutenberg-block-acf/#register-block
*
* @reference: https://developer.wordpress.org/resource/dashicons/
*/
// TODO:
// Get Jason's help adding code to autogenerate the proper ACF fields so this is independent of the database!
// Currently:
// In ACF GUI, created field group: Gutenberg Block -- Button
// Set to appear when block type = Button
// Created group field: button, with subfields button.content, button.type, button.local_page, and button.external_url
// We've switched to a more modular approach.
// This loads the register_item functions for each
// custom block that we want to use.
require_once("accordion-item/block.php");
require_once("simple-button/simple-button.php");
add_action('acf/init', 'radicati_register_blocks');
// Then we have call each block's function
// Note that all the block settings are in the
// individual block file, rather than here
function radicati_register_blocks() {
if (function_exists('acf_register_block_type')) {
register_accordion_item();
register_simple_button_item();
}
}
<?php
/**
* Gutenberg scripts and styles
* @link https://www.billerickson.net/block-styles-in-gutenberg/
*/
function radicati_gutenberg_scripts()
{
wp_enqueue_script(
'radicati-editor',
get_stylesheet_directory_uri() . '/lib/acf_gutenberg_blocks/block_custom-styles.js',
array('wp-blocks', 'wp-dom'),
filemtime(get_stylesheet_directory() . '/lib/acf_gutenberg_blocks/block_custom-styles.js'),
true
);
}
add_action('enqueue_block_editor_assets', 'radicati_gutenberg_scripts');
wp.domReady(() => {
// always set a default style!
wp.blocks.registerBlockStyle("acf/radicati-simple-button", {
name: "default",
label: "Default",
isDefault: true
});
wp.blocks.registerBlockStyle("acf/radicati-simple-button", {
name: "button--white",
label: "White"
});
// add more blocks here
});
Add the following task to your gulpfile.js to individually compile each block's scss file back to its folder:
gulp.task('gutenberg-blocks-css', function() {
return (
gulp
.src("./lib/acf_gutenberg_blocks/**/*.scss")
// .pipe(sassGlob())
.pipe(
sass({
outputStyle: "compressed",
includePaths: [
"./patterns/_patterns"
]
}).on("error", sass.logError)
)
.pipe(autoprefixer())
// compile each css file to the same directory its scss file is in!
.pipe(gulp.dest("./lib/acf_gutenberg_blocks/"))
.pipe(browserSync.stream({ match: "**/*.css" }))
);
})
Note: the includePaths: ["./patterns/_patterns"]
bit is making sure that you can @import stylesheets from the pattern library (such as _variables.scss!)
To the watch task, add:
gulp.watch("./lib/acf_gutenberg_blocks/**/*.php", ["browserSyncReload"]);
gulp.watch("./lib/acf_gutenberg_blocks/**/*.scss", ["gutenberg-blocks-css"]);
The browserSyncReload task should already be defined, but I'll include it below just in case:
gulp.task("browserSyncReload", [], function(done) {
browserSync.reload();
done();
});
// Obviously, we wouldn't bother to enqueue a "hello world" script, but this is a demo :)
window.onload = function() {
console.log("hello world!");
};
<?php
/**
* Radicati Simple Button block
*
* Note that the $block variable is built-in to
* ACF Gutenberg blocks
**/
/**
* The built-in block alignment controls are saved
* in $block['align'], though we've elected not to
* use them
*
* Custom block styles are stored in
* $block['classname'], as are manually
* applied styles.
*/
function register_simple_button_item() {
$settings = [
'name' => 'radicati-simple-button',
'title' => __('Button', 'radicati'),
'render_callback' => 'render_simple_button_item_test',
'category' => 'layout',
'icon' => file_get_contents(get_stylesheet_directory_uri() . '/lib/gutenberg/acf_gutenberg_blocks/simple-button/simple-button.svg'),
'enqueue_style' => get_template_directory_uri() . '/lib/gutenberg/acf_gutenberg_blocks/simple-button/simple-button.css',
'enqueue_script' => get_template_directory_uri() . '/lib/acf_gutenberg_blocks/simple-button/simple-button.js',
'mode' => 'auto',
'keywords' => [
'link',
'local',
'url',
'button'
],
'supports' => [
'align' => false,
'mode' => false
]
];
acf_register_block_type($settings);
}
function render_simple_button_item_test($block, $content = '', $is_preview = false, $post_id = 0) {
// Set up baseClass for BEM :)
$baseClass = 'button';
// start with the base class :)
$blockClasses = $baseClass;
$blockMods = ['simple-block'];
$link = get_field('button_link');
$content = get_field('button_label');
$color = get_field('button_color');
$alignment = get_field('button_alignment');
// This is for supporting the default Gutenberg block alignment, which we've decided to abandon in favor of a field
// if (!empty($block['align'])) {
// $blockMods[] = 'align-' . $block['align'];
// }
// Add the button alignment to the modifier list
if(!empty($alignment)) {
$blockMods[] = 'align-' . $alignment;
$wrapperAlignment = 'align-' . $alignment;
}
// Add the color to the modifier list
if (!empty($color)) {
$blockMods[] = $color;
}
// add the modifiers to the button classes
foreach ($blockMods as $mod) {
$blockClasses .= ' ' . $baseClass . '--' . $mod;
}
// add in any extra classes from the gutenberg advanced tab
if (!empty($block['className'])) {
$blockClasses .= ' ' . $block['className'];
}
echo ('<div class="simple-button__wrapper simple-button__wrapper--' . $wrapperAlignment . '">');
echo ('<a href="' . $link . '" class="' . $blockClasses . '">' . $content . '</a>');
echo ('</div>');
}
// by using includePaths in our gulpfile, we can import our site-wide variables file, which is at the root of the directory I passed to includePaths
@import "variables";
.simple-button--wrapper {
a.simple-button--button {
@include rem('border-radius', 4px);
@include rem('letter-spacing', 1.94px);
display: inline-block;
@include rem('font-size', 16px);
font-weight: 900;
@include rem('margin', 21px 5px 21px 0);
@include rem('padding', 10px 26px 11px);
text-transform: uppercase;
@include button($color--terracotta);
&:hover,
&:focus {
border-color: $color--terracotta;
color: $color--terracotta;
text-decoration: none;
}
}
&.is-style-button--white {
.simple-button--button {
@include button($color--white, $color--black);
border-color: $color--black;
&:hover {
background-color: $color--black;
border-color: $color--black;
}
&.button--transparent:hover {
color: black;
}
}
}
}