Möchtest Du einen Core Gutenberg Block um Steuerelemente erweitern? In diesem Tutorial erfährst Du wie das mithilfe eines Plugins geht. Dieses Plugin programmieren wir selbst.
Ein Core Gutenberg Block ist ein Block, der bereits mit WordPress kommt. Die verwendete Methode lässt sich aber auch bei Blöcken von Drittanbietern einsetzen.
Unser Ziel ist einfach: Wir wollen ein zusätzliches Radiobutton bei jedem Block. Abhängig von der Einstellung des Radiobuttons ändert sich der Abstand nach oben (zum vorherigen Element).
Unter der Haube setzt dieser Radio-Button dem aktuellen Block eine CSS Klasse. Den Abstand erreichen wir also über die CSS-Eigenschaft margin‑top
, die je nach CSS-Klasse unterschiedliche Werte erhält.
Disclaimer: Der Artikel ist nicht für absolute Anfänger. Man sollte das WordPress-Ökosystem etwas kennen, PHP & JavaScript lesen können. Ein Hauch von React kann auch nicht schaden. 🤓
🙏 Großes Danke geht an dieser Stelle an Wholesome Code (externer Link), die großartige englische Anleitungen rund um den Gutenberg Block‑Editor veröffentlichen. Inspiriert hat mich ein Artikel [1] einer Serie, in der Steuerelemente eines (selbstgebauten) Blocks auf alle Gutenberg Blöcke erweitert werden.
1. Voraussetzungen
Da wir ein WordPress-Plugin programmieren, müssen bestimmte Voraussetzungen erfüllt sein:
- NodeJS [2] muss installiert sein.
npm
sollte auch bekannt sein.- Natürlich eine lokale WordPress‑Installation für die Entwicklung.
- Ein Code-Editor Deiner Wahl.
2. Plugin erstellen
Wir bauen unsere Erweiterung von Gutenberg Blöcken in Form eines WordPress-Plugins. Genauso, wie man auch eigene Gutenberg Blöcke baut. Nichts Außergewöhnliches.
Sind alle Voraussetzungen erfüllt?
Wenn ja, geht’s los.
Öffne ein Kommandozeilen-Fenster und navigiere zu der WordPress-Installation, in das Plugin-Verzeichnis: \wp‑content\plugins
.
2.1. Plugin installieren
Gib npm init @wordpress/block
, bestätige mit <Enter>
und folge den Instruktionen der Installation.
In folgendem Screenshot sieht man meine Konfiguration. Das Plugin, das wir bauen, heißt „Crafted Style Helper“.
Den Rest der Konfiguration siehst Du hier:
Folgende Optionen solltest Du Dir vorab überlegen:
Block-Slug | Der Block-Slug wird zur Identifikation genutzt (auch für das Plugin und den Ordner). Ich nenne mein Plugin „Crafted Style Helpers“, also wähle ich: crafted-style-helpers |
---|---|
Namespace | Der interne Namespace für den Block (etwas Eindeutiges für Deine Produkte). Ich wähle crafted . Das ist der Name meines Unternehmens. |
Titel des Blocks | Wie bereits erwähnt: Crafted Style Helpers |
Beschreibung (optional) | Die Plugin-Beschreibung sieht man im WordPress-Dashboard. Da das Plugin Standard-Blöcke um Steuerelemente erweitert, die wiederum CSS-Klassen setzen, habe ich das auch geschrieben 🙂 |
Icon (optional) | Die Wahl fiel auf das cover-image . Aber Icons gibt es reichlich im Dashicons-Handbuch. |
Kategorie | Du kannst aus einigen vordefinierten Kategorien wählen. Die Kategorie erleichtert Nutzern/Nutzerinnen das Durchsuchen und Entdecken Deines Blocks. |
Autor (optional) | Der Autor Deines Plugin bist Du, aber in meinem Fall Ich. 😉 |
Lizenz – Kurzfassung (optional) | Gewählt habe die Standardoption (die Lizenz unter der auch WordPress veröffentlicht wurde). |
Lizenz – Langfassung (optional) | Ich habe ebenso die vorgeschlagene Option gewählt, wie bei der Kurzfassung. |
Version des Plugins | Wir starten mit 0.1.0 . |
Nachdem Du alle Optionen eingestellt hast, läuft die Installation 1-2 Minuten.
„Code is Poetry“ in Deinem Kommandozeilen-Fenster kündigt die erfolgreiche Installation an:
Angezeigt werden nun eine Reihe von Befehlen, die bei der Entwicklung helfen. Bei unserem kleinen, lokalen Projekt kommt nur npm start
zum Einsatz, mehr brauchen wir nicht.
Erwähnenswert sind aber auch npm run lint:css
und npm run lint:js
. Diese Befehle prüfen Deinen JS bzw. CSS Code auf WordPress Code Standards. Sehr hilfreich, um konsistenten Code über Deine WordPress-Projekte hinweg zu schreiben.
Das Plugin ist nun installiert.
Bevor es noch aktiviert wird gehen wir grob die wichtigsten Dateien und Ordner durch:
block.json
– Enthält Meta-Informationen zum Block und ermöglicht, dass der Block von Gutenberg erkannt wird.crafted-style-helpers.php
– Die Hauptdatei um das Plugin zu laden.- Order
\src\
enthält alle Quelldateien inklusive JavaScript und SCSS Dateien. - Ordner
\build\
– Der sogenannte Build-Ordner, mit allen Dateien, die aus/src/
erzeugt werden.
2.2. Plugin aktivieren
Navigiere im WordPress Dashboard zu Plugins. Such das Plugin in der Liste und aktiviere es wie jedes andere Plugin auch.
🤓 Nerd-Fact: Keine Sorge, das Plugin kann auch „aktiviert“ weiterentwickelt werden.
3. Gutenberg Blöcke um Steuerelemente erweitern
Was wir nun also vorhaben:
- Wir bauen keinen neuen Gutenberg Block, sondern erweitern alle bestehenden Blöcke.
- Jeder Block soll einen Radio-Button im Editor erhalten.
- Der Radio-Button setzt CSS-Klassen, je nach Auswahl der Option.
- Wir definieren mehrere CSS-Klassen, die unser Element verändern.
- In unserem Beispiel verändern wir der Abstand nach oben per
margin‑top
.
- In unserem Beispiel verändern wir der Abstand nach oben per
Für Gutenberg gibt viele UI-Elemente/Steuerelemente. Eine Übersicht findest Du in dem Gutenberg UI Storybook [3].
Los geht’s.
Wir navigieren im Kommandozeilen-Fenster in das Plugin-Verzeichnis (in unserem Fall: wp‑content\plugins\crafted-style-helpers
).
Mit npm start
starten wir den Entwicklungs-Prozess.
🤓 Nerd-Tipp: Zwar wird bei jeder Änderung das Plugin neu kompiliert, aber den Browser, in dem man testet, muss man dennoch neu laden.
3.1. Dateien entfernen, die wir nicht brauchen
Da wir keinen eigenen Block entwickeln, sondern bestehende erweitern, lösche ich folgende 2 Dateien:
\src\edit.js
❌\src\save.js
❌
edit.js
beinhaltet die Bearbeitungs-Funktionalität und bestimmt, wie die Steuerelemente im Editier-Modus aussehen.
save.js
beinhaltet die Speicher-Funktionalität für den Block und das Markup für das Frontend.
Wir erledigen sowohl das Speichern, als auch das HTML-Markup an einer anderen Stelle.
Da diese Dateien gelöscht werden, benötigt man auch die Verweise nicht mehr.
Öffne hierzu \src\index.js
und lösche die zwei imports
:
/* LÖSCHE DIESEN TEIL */
import Edit from './edit';
import save from './save';
Und lösche den gesamten Aufruf von registerBlockType
:
/* LÖSCHE DIESEN TEIL */
registerBlockType('crafted/crafted-style-helpers', {
/**
* @see ./edit.js
*/
edit: Edit,
/**
* @see ./save.js
*/
save,
});
Keine Sorge, den gesamten Code gibt’s weiter unten im Artikel und/oder als Download (Abschnitt: 4. Gesamter Quellcode).
3.2. Attribut für alle Blöcke registrieren
Um Eigenschaften für einen Gutenberg Block setzen zu können, muss diese zuerst „bekannt“ sein; sie muss registriert werden.
Also registrieren wir eine Eigenschaft (topMargin
) für alle Blöcke. Den Namen habe ich einfachheitshalber passend zur CSS-Eigenschaft gewählt.
Füge (auch in der \src\index.js
) folgenden Code hinzu:
import { addFilter } from '@wordpress/hooks';
/*
* Registiere die topMargin-Eigenschaft für jeden Block
*/
addFilter(
'blocks.registerBlockType',
// Dein Namespace heisst wahrscheinlich anders:
'crafted/crafted-style-helpers-attributes',
(settings) => {
const { attributes } = settings;
return {
...settings,
attributes: {
...attributes,
topMargin: {
type: 'string',
default: '',
},
},
};
}
);
Das Fundament ist gelegt: Jeder Block hat nun ein Attribut namens topMargin
. Es ist ein String und standardmäßig leer.
Wenn man dieses Attribut nur für bestimmte Blöcke setzen will, dann ist hier die richtige Stelle.
Wenn z.B. nur der „Absatz“-Block (engl. paragraph) das Attribut erhalten soll, sieht der Code-Block innerhalb der Arrow-Function (settings) => { }
etwas anders aus:
/* […] */
const { attributes, name } = settings;
if ( 'core/paragraph' === name ) {
return {
...settings,
attributes: {
...attributes,
topMargin: {
type: 'string',
default: '',
},
},
};
}
// Default behavior
return settings;
/* […] */
Was ist nun eigentlich mit diesem Radio-Button?
Das folgt jetzt …
3.3. Steuerelemente als Komponente höherer Ordnung (HoC) erstellen
Ich bin kein React-Profi, aber meines Verständnisses sind Higher Order Components (HoC) ein zentrales Konzept.
Sie werden dazu genutzt, um Logik einer Komponente wiederzuverwenden. Oder eine Komponente in einer HoC einzukapseln und zu erweitern.
Das klingt nach genau dem, was wir suchen.
Das Ziel ist nämlich, die Leiste mit den Einstellungen durch Radio-Buttons zu erweitern. Diese Leiste beinhaltet Einstellungen der aktuellen Seite & des markierten Blocks.
In der Abbildung sieht man die Einstellungen für den Gutenberg-Block „Absatz“ (engl. „Paragraph“)
Im ersten Schritt erstellen wir eine neue JS‑Datei, die unsere Higher Order Component beinhalten wird. Anschließend importieren wir diese in unsere index.js
und „hängen“ diese HoC an alle Blöcke.
Die neue Datei kommt ins \src\components\
Verzeichnis und heißt topMarginInspectorControls.js
.
Es werden alle notwendigen Komponenten importiert, z.B. unser Radiobutton [3] 🤘, aber auch createHigherOrderComponent
aus unserer @wordpress
Node Bibliothek.
/**
* Imports
*/
import { InspectorControls } from '@wordpress/block-editor';
import {
Panel,
PanelBody,
__experimentalRadio as Radio,
__experimentalRadioGroup as RadioGroup,
} from '@wordpress/components';
import { createHigherOrderComponent } from '@wordpress/compose';
import { Fragment } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
🤓⚠ Nerd-Warning: Das Radiobutton-Element ist derzeit noch experimentell, daher kann es fehleranfällig sein und/oder es könnte sich noch die Verwendungsweise ändern.
Zur Erinnerung, wir wollen ein Radiobuttton-Element mit 4 Optionen:
- Standard („Default“)
- Doppelter Abstand („2x“)
- Halber Abstand („½“)
- Kein Abstand („0“)
Passend dazu werden etwas später (3.4. Fehlende CSS-Klassen erzeugen) folgende 3 CSS-Klassen erstellt:
- Doppelter Abstand:
crafted-top-margin--doubled
- Halber Abstand:
crafted-top-margin--halved
- Kein Abstand:
crafted-top-margin--zero
So wird die Option aussehen:
Im anschließenden Code-Abschnitt wird unsere HoC generiert und exportiert. Diese erweitert die Seitenleiste um ein RadioGroup
Element mit den gewünschten Optionen.
/**
* Export HoC
*/
export default createHigherOrderComponent((BlockEdit) => {
return (props) => {
/**
* Extrahiere properties
*/
const { attributes, setAttributes } = props;
/**
* Extrahiere das topMargin Attribut, das neu registiert wurde.
*/
const { topMargin } = attributes;
return (
<Fragment>
<BlockEdit {...props} />
<InspectorControls key="InspectorControls">
<Panel>
<PanelBody
title={__('Top Margin', 'crafted-style-helpers')}
icon="table-row-before"
>
<RadioGroup
label="Top Margin"
checked={topMargin}
onChange={(topMargin) =>
setAttributes({ topMargin })
}
>
<Radio value="">
{__('Default', 'crafted-style-helpers')}
</Radio>
<Radio value="crafted-top-margin--doubled">
2x
</Radio>
<Radio value="crafted-top-margin--halved">
½
</Radio>
<Radio value="crafted-top-margin--zero">
0
</Radio>
</RadioGroup>
</PanelBody>
</Panel>
</InspectorControls>
</Fragment>
);
};
}, 'topMarginInspectorControls');
Wenn man diese Radiobuttons nur für bestimmte Blöcke braucht, dann verändert man den Rückgabewert auch nur für diese Blöcke.
Vergleichbar mit der bereits vorgestellten Methode, die beim Attribut topMargin
zum Einsatz kam, kann z.B. nur der „Paragraph“-Block die Radiobuttons erhalten.
So sieht der Code-Block innerhalb der Arrow-Function return (props) => { }
etwas anders aus:
/* […] */
/**
* Extract Props
*/
const { attributes, setAttributes } = props;
const { topMargin } = attributes;
if ( 'core/paragraph' === props.name ) {
return (
<Fragment>
<BlockEdit {...props} />
<InspectorControls key="InspectorControls">
/* […] */
</InspectorControls>
</Fragment>
);
}
// Default behavior
return <BlockEdit { ...props } />
/* […] */
Zurück in unserer index.js
importieren wir – wie oben erwähnt – die topMarginInspectorControls
und „hängen“ diese an alle Blöcke.
/*
* Importiere die Higher Order Component
*/
import topMarginInspectorControls from './components/topMarginInspectorControls';
/*
* Füge Steuerelemente zu jedem Block hinzu.
*/
addFilter(
'editor.BlockEdit', // hookName
'crafted/crafted-style-helpers-inspector', // namespace
topMarginInspectorControls
);
Nachdem Du die Änderungen gespeichert hast, solltest Du den Admin-Bereich neu laden.
Unter den Block-Optionen taucht nun beim Editieren ein neues Radiobutton-Element auf:
3.4. Fehlende CSS-Klassen erzeugen
Die einfachste Übung für jemanden, der fürs Web entwickelt.
Öffne /src/style.scss
, erstelle die gewünschten CSS-Klassen:
.crafted-top-margin--doubled {
margin-top: 3rem !important;
}
.crafted-top-margin--halved {
margin-top: 1.5rem !important;
}
.crafted-top-margin--zero {
margin-top: 0 !important;
}
3.5. CSS-Klassen im Frontend setzen
Bis auf einen Baustein, haben wir nun alles um das Plugin zu verwenden.
Noch muss das HTML-Markup für unsere Besucher:innen angepasst werden.
Wenn ein Block das Attribut topMargin
hat, muss die entsprechende CSS-Klasse gesetzt werden. Hierfür nutzen wir den Filter render_block
[5].
Wenn ein Block nun ein topMargin
Attribut hat, gibt es im Wesentlichen 2 Möglichkeiten:
- Das Element hat schon andere CSS-Klassen
- Das Element hat keine CSS-Klassen
Dementsprechend müssen wir den HTML-Code anders modifizieren und nutzen hierfür Regex (preg_replace
):
function crafted_crafted_style_helpers_block_render_block_filter( $block_content, $block ) {
// Falls wir im Admin-Bereich sind, Notbremse ziehen.
if ( is_admin() ) {
return $block_content;
}
if (
isset( $block['attrs'] ) &&
isset( $block['attrs']['topMargin'] )
) {
// Block hat bereits eine CSS Klass
if ( isset( $block['attrs']['className'] ) ) {
$block_class_pattern = 'class="' . $block['attrs']['className'];
$block_class_replacement = '$1' . $block_class_pattern . ' ' . $block['attrs']['topMargin'] . '$3';
$block_content = preg_replace(
'/(.*)' . $block_class_pattern . '(.*)/',
$block_class_replacement,
$block_content,
);
} else {
$block_class_replacement = '$0 class="' . $block['attrs']['topMargin'] . '" $1';
$block['attrs']['className'] = $block['attrs']['topMargin'];
$block_content = preg_replace(
'/<\w+\s?/',
$block_class_replacement,
$block_content,
1,
);
} }
return $block_content;
}
add_filter( 'render_block', 'crafted_crafted_style_helpers_block_render_block_filter', 0, 2 );
🤓 Nerd-Fact: Ich habe (leider erfolglos) versucht nur den Wert von $block['attrs']['className']
neu zu setzen und direkt aus dem $block
‑Array das „neue“ HTML-Markup zu generieren. Das erschien mir am cleversten. Bedauerlicherweise ohne Erfolg, man muss derzeit scheinbar den HTML-Code ($block_content
) direkt verändern.
4. Gesamter Quellcode
Der gesamte Code um eine Erweiterung aller Gutenberg Blöcke zu erstellen, kannst Du Dir als ZIP-Datei herunterladen.
ACHTUNG: Du musst die ZIP-Datei in deinem Plugin-Verzeichnis entpacken. Anschließend per Kommandozeile mit npm install
installieren.
Falls Du alle wichtigsten Quellcodes stöbern willst, hier nochmal als pro Datei:
Datei: crafted-style-helpers.php
<?php
/**
* Plugin Name: Crafted Style Helpers
* Description: A plugin to extend Core Blocks with controls. These controls add CSS helper classes which are unique to the Crafted Theme.
* Requires at least: 5.8
* Requires PHP: 7.0
* Version: 0.1.0
* Author: Robert Chwistek
* Author URI: https://www.crafted.at
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: crafted-style-helpers
*
* @package Crafted
*/
/**
* Registers the block using the metadata loaded from the `block.json` file.
* Behind the scenes, it registers also all assets so they can be enqueued
* through the block editor in the corresponding context.
*
* @see https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/writing-your-first-block-type/
*/
function crafted_crafted_style_helpers_block_init() {
register_block_type( __DIR__ );
}
add_action( 'init', 'crafted_crafted_style_helpers_block_init' );
/**
* Filters the content of a single block.
*
* @param string $block_content The block content about to be appended.
* @param array $block The full block, including name and attributes.
*
* @since 0.1.0
*
* @return string The block content about to be appended.
*/
function crafted_crafted_style_helpers_block_render_block_filter( $block_content, $block ) {
// If we are in the admin interface, bail.
if ( is_admin() ) {
return $block_content;
}
if (
isset( $block['attrs'] ) &&
isset( $block['attrs']['topMargin'] )
) {
if ( isset( $block['attrs']['className'] ) ) {
$block_class_pattern = 'class="' . $block['attrs']['className'];
$block_class_replacement = '$1' . $block_class_pattern . ' ' . $block['attrs']['topMargin'] . '$3';
$block_content = preg_replace(
'/(.*)' . $block_class_pattern . '(.*)/',
$block_class_replacement,
$block_content,
);
} else {
$block_class_replacement = '$0 class="' . $block['attrs']['topMargin'] . '" $1';
$block['attrs']['className'] = $block['attrs']['topMargin'];
$block_content = preg_replace(
'/<\w+\s?/',
$block_class_replacement,
$block_content,
1,
);
}
}
return $block_content;
}
add_filter( 'render_block', 'crafted_crafted_style_helpers_block_render_block_filter', 0, 2 );
Datei: index.js
/**
* Imports
*/
import { addFilter } from '@wordpress/hooks';
import topMarginInspectorControls from './components/topMarginInspectorControls';
import './style.scss';
/*
* Register attributes to every block
*/
addFilter(
'blocks.registerBlockType',
'crafted/crafted-style-helpers-attributes',
(settings) => {
const { attributes } = settings;
return {
...settings,
attributes: {
...attributes,
topMargin: {
type: 'string',
default: '',
},
},
};
}
);
/*
* Add inspector controls to every block
*/
addFilter(
'editor.BlockEdit',
'crafted/crafted-style-helpers-inspector',
topMarginInspectorControls
);
Datei: topMarginInspectorControls.js
/**
* WordPress Imports.
*/
import { InspectorControls } from '@wordpress/block-editor';
import {
Panel,
PanelBody,
__experimentalRadio as Radio,
__experimentalRadioGroup as RadioGroup,
} from '@wordpress/components';
import { createHigherOrderComponent } from '@wordpress/compose';
import { Fragment } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
export default createHigherOrderComponent((BlockEdit) => {
return (props) => {
/**
* Extract Props
*/
const { attributes, setAttributes } = props;
const { topMargin } = attributes;
return (
<Fragment>
<BlockEdit {...props} />
<InspectorControls key="InspectorControls">
<Panel>
<PanelBody
title={__('Top Margin', 'crafted-style-helpers')}
icon="table-row-before"
>
<RadioGroup
label="Top Margin"
checked={topMargin}
onChange={(topMargin) =>
setAttributes({ topMargin })
}
>
<Radio value="">
{__('Default', 'crafted-style-helpers')}
</Radio>
<Radio value="crafted-top-margin--doubled">
2x
</Radio>
<Radio value="crafted-top-margin--halved">
½
</Radio>
<Radio value="crafted-top-margin--zero">
0
</Radio>
</RadioGroup>
</PanelBody>
</Panel>
</InspectorControls>
</Fragment>
);
};
}, 'topMarginInspectorControls');
Datei: style.scss
.crafted-top-margin--doubled {
margin-top: 3rem !important;
}
.crafted-top-margin--halved {
margin-top: 1.5rem !important;
}
.crafted-top-margin--zero {
margin-top: 0 !important;
}
5. Fazit
Manchmal möchte man vorhandene Blöcke um ein kleines Feature erweitern, wie in unserem Fall. Ein Radiobutton setzt eine CSS-Klasse, die den Block geringfügig anpasst. Das kommt immer wieder vor und kann – wie wir gesehen haben – mit überschaubarem Aufwand
Ein weiterer, interessanter Anwendungsfall ist beispielsweise einen Block als „Entwurf“ zu markieren und dann für die Besucher nicht anzuzeigen. Wie das geht, findest Du auf dem erwähnten Blog Wholesome Code [1].
Ist das nicht eine tolle Möglichkeit Gutenberg Blöcke zu erweitern?
Hat Dir dieser Beitrag geholfen?
Dann bitte teile ihn – das hilft uns besonders 👍.
Wenn Du weitere Fragen hast, haben sie andere womöglich auch – hinterlasse uns ein Kommentar! 😉
Links
- Wholesome Code: Add Controls to the Core and Third Party Block Sidebar with Filters and Higher Order Components
- NodeJS (Offizielle Website)
- Gutenberg UI: Storybook Dokumention
- Gutenberg UI Steuerelement: Radio Group Default (Achtung, derzeit experimentell)
- WordPress Code Reference: render_block Filter
Hallo
Kann man den Spaltenblock auf um folgende Funktion erweitern:
Die Spalten innerhalb des Spaltenblocks unabhängig voneinander scrollbar machen.
Wenn man auf der Rechtenspalte scrollt, sollte der Linkespalt nicht mitscrollen.
Hallo Faton,
diese Methode kann man für verschiedenste Dinge einsetzen.
Um den beschriebenen Effekt zu erzielen, solltest du dir das CSS-Attribut
position: sticky;
genauer ansehen.Oder wenn du eine Plugin-Lösung suchst, googel etwas wie „gutenberg columns sticky content“.