22.1. Einen Block programmieren

Wie eine Seite vorher bereits erwähnt, wollen wir uns nun damit beschäftigen, einen Gutenberg-Block zu programmieren. Dabei baue ich auf das Beispiel aus dem Kapitel Dependency Injection auf und benutze die dort erstellten REST-API Endpunkte zum Bewerten von Artikeln.

Entstehen soll also ein Bewertungs-Block. Bevor wir starten, müssen wir uns anschauen, welche Voraussetzungen gegeben sein müssen:

Voraussetzungen

  1. Ich möchte mit ESNext programmieren. Da nicht jeder Browser den neuen Syntax unterstützt, benötigen wir ein Übersetzungstool. Der Compiler der Wahl ist BabelJS.
  2. Damit die Kompilierung immer automatisch läuft, brauchen wir einen FileWatcher der horcht, ob eine Datei sich verändert hat und die Kompilierung automatisch ausführt. Das Tool der Wahl ist hier WebpackJS.
  3. Um das alles zu konfigurieren, brauchen wir den Paketmanager npmJS. Hier steht, wie Sie NPM zusammen mit NodeJS auf Ihrem System installieren können.
  4. Ich befinde mich einen MacOS-System. Für Linux-Systeme dürften die nachfolgenden Befehle gleich sein. Bei Windows sieht es etwas anders aus.

Vorbereitungen

Wir erstellen ein neues Plugin mit folgender Struktur im wp-content/plugins-Verzeichnis:

/plugins
   /mm-rating-block
       mm-rating-block.php

Davon ausgehend, dass NodeJS und npm installiert sind, öffnen wir ein Terminal-Fenster und bewegen uns zum gerade erstellten Ordner:

cd /pfad/zu/wp/wp-content/pllugins/mm-rating-block/

Danach geben wir ein:

npm init

und folgen dem Anweisungen auf dem Bildschirm. Die Ausgabe sieht ungefähr so aus:

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (mm-rating-block) 
version: (1.0.0) 0.1.0
description: An easy Gutenberg Block.
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: Florian Simeth
license: (ISC) GPL-3.0-or-later
About to write to /pfad/zu/wp/wp-content/plugins/mm-block/package.json:

{
  "name": "mm-rating-block",
  "version": "0.1.0",
  "description": "An easy Gutenberg Rating Block.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Florian Simeth",
  "license": "GPL-3.0-or-later"
}

Is this OK? (yes) yes

Das Script hat uns dann eine neue Datei mit der Bezeichnung package.json erstellt. Nun beginnen wir damit, die nötigen Pakete zu installieren. Das geht in einem Befehl:

npm install webpack webpack-cli @babel/core @babel/preset-env babel-loader @babel/plugin-transform-react-jsx --save-dev
  • webpack und webpack-cli installiert Webpack.
  • @babel/core ist der Kern-Mechanismus von Babel, wie weiter oben angemerkt.
  • @babel/preset-env ermöglicht uns, neuestes JavaScript zu verwenden, ohne dass wir uns um die von der Zielumgebung kümmern zu müssen.
  • babel-loader ist ein Helfer der Babel über Webpack lädt.
  • @babel/plugin-transform-react-jsx kompiliert React JSX-Syntax zu reinem HTML.
  • Mit --save-dev schließlich teilen wir NPM mit, dass die Abhängigkeiten nur zur Entwicklung, nicht aber in der Produktion nötig sind.

Die package.json-Datei sieht nun wie folgt aus:

{
  "name": "mm-rating-block",
  "version": "0.1.0",
  "description": "An easy Gutenberg Rating Block.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Florian Simeth",
  "license": "GPL-3.0-or-later",
  "devDependencies": {
    "@babel/core": "^7.5.5",
    "@babel/plugin-transform-react-jsx": "^7.3.0",
    "@babel/preset-env": "^7.5.5",
    "babel-loader": "^8.0.6",
    "webpack": "^4.36.1",
    "webpack-cli": "^3.3.6"
  }
}

Webpack konfigurieren

Als nächsten konfigurieren wir Webpack. Dazu verändern wir die package.json-Datei und entfernen die Zeile:

"main": "index.js",

Und fügen die folgende Zeile neu ein:

"private": true,

Das ist nötig, weil wir den Einstiegspunkt gleich in der webpack.config.js definieren:

const path = require( 'path' );

module.exports = {
	entry  : {
		'./js/rating'     : './blocks/rating.js'
	},
	output : {
		path    : path.resolve( __dirname ),
		filename: '[name].js'
	},
	watch  : true
};

Nun weiß Webpack, dass es die Datei blocks/rating.js kompilieren und in als Datei js/rating.js speichern soll. Das alles geschieht immer dann automatisch, wenn sich eine Datei verändert (watch: true).

Was noch fehlt ist, dass Babel den Code transformiert. Wir ergänzen also:

const path = require( 'path' );

module.exports = {
	entry  : {
		'./js/rating': './blocks/rating.js'
	},
	output : {
		path    : path.resolve( __dirname ),
		filename: '[name].js'
	},
	watch  : true,
	devtool: 'cheap-eval-source-map',
	module : {
		rules: [
			{
				test   : /\.m?js$/,
				exclude: /(node_modules|bower_components)/,
				use    : {
					loader : 'babel-loader',
					options: {
						presets: [ '@babel/preset-env' ],
					}
				}
			}
		]
	},
	plugins: []
};

NPM konfigurieren

Zum Schluss teilen wir NPM mit, mit welchen Befehlen das ganze nun gestartet wird, indem wir den scripts Parameter verändern:

{
  "name": "mm-rating-block",
  "version": "0.1.0",
  "description": "An easy Gutenberg Rating Block.",
  "main": "index.js",
  "private": true,
  "author": "Florian Simeth",
  "license": "GPL-3.0-or-later",
  "devDependencies": {
    "@babel/core": "^7.5.5",
    "@babel/plugin-transform-react-jsx": "^7.3.0",
    "@babel/preset-env": "^7.5.5",
    "babel-loader": "^8.0.6",
    "webpack": "^4.36.1",
    "webpack-cli": "^3.3.6"
  },
  "scripts": {
    "dev": "webpack --watch --mode=development",
    "build": "webpack --mode=production"
  },
  "dependencies": {}
}

Babel konfigurieren

Als letztes erstellen wir noch eine .babelrc-Datei die schließlich dafür sorgt, die Plugins zu laden, die wir zuvor installiert haben:

{
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "pragma": "wp.element.createElement"
      }
    ]
  ]
}

Wichtig ist die Zeile 6 im Code oben. Normalerweise würde man ein React-Element mit:

React.createElement('ul', /* ... ul children ... */)

erstellen. WordPress bzw. Gutenberg verwendet aber seine eigene createElement-Funktion. Und genau dies wird hier eingestellt.

In der Datei package.json wird der Parameter browserlist angepasst um die Unterstützung der aktuellsten Browser anzugeben:

{
  "name": "mm-rating-block",
  "version": "0.1.0",
  "description": "An easy Gutenberg Rating Block.",
  "main": "index.js",
  "private": true,
  "author": "Florian Simeth",
  "license": "GPL-3.0-or-later",
  "devDependencies": {
    "@babel/core": "^7.5.5",
    "@babel/plugin-transform-react-jsx": "^7.3.0",
    "@babel/preset-env": "^7.5.5",
    "babel-loader": "^8.0.6",
    "webpack": "^4.36.1",
    "webpack-cli": "^3.3.6"
  },
  "scripts": {
    "dev": "webpack --watch --mode=development",
    "build": "webpack --mode=production"
  },
  "dependencies": {},
  "browserslist": "> 0.25%, not dead"
}

Siehe dazu auch die Beschreibung bei Browserlist auf Github.

NPM Development starten

Nun können wir wie folgt den FileWatcher starten:

npm run dev

Nun horcht Webpack nach Änderungen in den Dateien und macht daraus die benutzbare Datei, die wir später in WordPress integrieren werden.

Gutenberg-Block registrieren

In der Datei blocks/rating.js definieren wir nun initial einen Block:

You’re not allowed to see this content. Please log in first.

Block in Gutenberg einbinden

Damit die neue JavaScript-Datei geladen wird, brauchen wir noch ein bisschen PHP-Code.

Das Registrieren von JavaScripten ist Ihnen ja sicherlich bereits bekannt. Die Funktion register_block_type() ist hingegen neu:

<?php
/**
 * Plugin Name: mm Rating Block
 * Plugin URI:  https://wp-plugin-erstellen.de/ebook/block-apis/block-registration/
 * Description: An easy Gutenberg Rating Block.
 * Version: 0.1.0
 * Author: Florian Simeth
 */

add_action( 'init', 'mmbr_init' );

function mmbr_init() {

	wp_register_script(
		'mmbr-rating-block',
		plugin_dir_url( __FILE__ ) . 'js/rating.js',
		[ 'wp-blocks' ],
		filemtime( plugin_dir_path( __FILE__ ) . '/js/rating.js' )
	);

	register_block_type(
		'mm/rating',
		[
			'editor_script' => 'mmbr-rating-block'
		]
	);
}

Für register_block_type() gilt:

  • $name (string)
    Der Name des Blocks inklusive des Namespaces (wie wir ihn oben in der Datei blocks/rating.js angegeben haben).
  • $args (array)
    Ein Array mit zusätzlichen Optionen. Dabei ist (derzeit) folgendes möglich:
    • $render_callback (callable)
      Eine Funktion oder Methode, die den HTML-Code für den Block im Frontend zurückgeben muss. Als Parameter wird ein Array mit Argumenten übergeben.
    • $attributes (array)
      Ein Array mit Attributen, die an die Callback-Funktion übergeben werden soll. Dabei wird ein Array mit Arrays erwartet wobei der Schlüssel der Name des Attributes ist und weitere Optionen übergeben werden (siehe Beispiel weiter unten). Möglich sind folgende Werte:
      • $default (mixed)
        Ein Standardwert.
      • $type (string)
        Art des Attributes. Möglich sind: string|array|number|bool|object
      • $items (array)
        Falls type = array|object kann hier ein weiteres Array mit Typen angegeben werden:
        • $type (string)
          Wie oben: string|array|number|bool|object
    • $script (string)
      Der Handle der JavaScript-Datei (wie mittels wp_register_script() registriert), die für den Block geladen werden soll.
  • $editor_script (string)
    Der Handle der JavaScript-Datei (wie mittels wp_register_script() registriert), die für das Backend (im Editor) genutzt wird.
  • $style (string)
    Der Handle der CSS-Datei (wie mittels wp_register_style() registriert), die für den Block genutzt werden soll (Frontend und Backend)
  • $editor_style (string)
    Der Handle der CSS-Datei (wie mittels wp_register_style() registriert), die für den Block genutzt werden soll (nur Backend).

Zurückgegeben wird false im Fehlerfall. Ansonsten das WP_Block_Type Objekt.

Wenn wir das Plugin nun aktivieren und den Gutenberg-Editor im Backend aufrufen, sehen wir bereits den Block:

Rating-Block im Block-Inserter

Gutenberg-Block im Backend

So, nun wollen wir den Block ein bisschen aufhübschen. Dazu öffnen wir wieder die JavaScript-Datei und modifzieren sie wie folgt:

const {registerBlockType} = wp.blocks;

import Rater from './components/Rater';

registerBlockType(
    'mm/rating',
    {
        title: 'MM Rating',
        category: 'widgets',
        icon: 'heart',
        keywords: [
            'MM',
            'Rating'
        ],
        supports: {
            multiple: true,
            align: true
        },
        attributes: {},
        edit: props => {
            return <Rater />;
        },
        save: props => {
            return <div>Rating</div>;
        }
    }
);

Dazu benötigen wir noch eine weitere Datei, die blocks/components/Rater.js:

You’re not allowed to see this content. Please log in first.

Wenn man den Block integriert, sieht man zumindest schon einmal die Sterne:

Animation die zeigt, wie der Rating-Block eingefügt wird und dann die fünf Sterne angezeigt werden.

Aktuelle Bewertung abrufen

Schön wäre natürlich, wenn der Block sofort die aktuelle Bewertung anzeigen würde. Dazu müssen wir das Plugin aus dem vorherigen Kapitel ergänzen, damit die aktuellen Bewertungen über die REST-API ausgegeben werden.

Wir ergänzen in der Datei src/RestController.php aus dem Plugin im vorherigen Kapitel wie folgt:

You’re not allowed to see this content. Please log in first.

  • In Zeile 2 wird withSelect eingebunden.
  • Bevor die edit-Methode ausgeführt wird, wird der Code in withSelect ausgeführt, was dazu führt, dass die Daten aus dem Data-Store gelesen werden können (Zeilen 21 bis 35).
  • Die Daten befinden sich danach in der Variable props. Sie können dann an die Klasse Rater übergeben werden (Zeile 36).

Was schließlich noch fehlt sind die Ergänzungen in der Rater-Klasse. Hier im besonderen die Übergabe der Properties an this.state (Zeile 15f):

You’re not allowed to see this content. Please log in first.

Ausgabe im Frontend

Da wir mit dynamischen Inhalten arbeiten, brauchen wir keine HTML-Daten zu speichern. Wir verändern also die Datei blocks/rating.js wie folgt ab:

You’re not allowed to see this content. Please log in first.

Fazit

Fassen wir nochmal zusammen:

  • Ich habe gezeigt, wie sie einen Build-Prozess mit Webpack und BabelJS einrichten.
  • Sie haben die JavaScript-Funktionen registerBlockType() und withSelect kennen gelernt.
  • Bekannt ist Ihnen nun, wie Sie einen Block mittels PHP registrieren und eine Callback-Funktion übergeben. Nämlich mit der PHP-Funktion register_block_type().

Im nächsten Kapitel sehen wir uns dann an, wie man eigene Einstellungsfelder in die Sidebar bekommt.