Kategorien
Allgemein Gutenberg JavaScript

Wieso eigene Gutenberg Blöcke nicht immer die Lösung sind.

Bei der Entwicklung und Erweiterung des neuen Gutenberg Editors in WordPress dreht sich aktuell alles darum, wie man eigene Blöcke erstellen kann. Es scheint als würde jeder seinen eigenen mehr oder weniger sinnvollen Block erstellen.

Block Libraries

Ganze Block Libraries wie z.B. „Atomic Blocks“ werden so aktuell entwickelt. Diese beinhalten eine Reihe Vielzahl an Blöcken, von denen man meist nur wenige benötigt.
Oft beinhalten diese Libraries sogar Blöcke, die in ähnlicher Form bereits im Gutenberg Standard vorhanden sind. Statt diese standard Blöcke zu verändern, werden immer mehr neue Blöcke erstellt. Beispielsweise bietet Atomic Blocks Blöcke wie den „Container Block“ (entspricht dem Group Block) oder den Spacer & Divider (ähnlich dem „Abstandshalter Block)

Das Problem

Mir stellt sich daher die Frage wie nachhaltig dieses vorgehen ist. Was passiert, wenn diese Block Libraries in ein paar Jahren nicht mehr weiterentwickelt werden? Gutenberg erlebt gerade eine so schnelle Entwicklung, Funktionen werden geändert und die Plugin Entwickler müssen bei diesen Änderungen mithalten.

Was aber, wenn der Entwickler die Lust verliert?

Wenn diese Block Libraries nicht mehr kompatibel zu neusten Gutenberg Version sind, kann Gutenberg den HTML Coder nicht mehr in Blöcke umwandeln und es kommt zu Validierungsfehlern ähnlich diesem hier:

Der in diesen Blöcken vorhandene Inhalt ist also verloren.

Wenn wir eine Website erstellen möchten, die auch in ein paar Jahren noch existieren und editierbar sind soll, sollten wir diese Libraries und sonstigen Plugins, die eigene Blöcke mit sich bringen, nur Vorsicht einsetzten.

Gibt es Alternativen?

Natürlich gibt es viele Anwendungfälle in denen eigene Blöcke sinnvoll und vermutlich auch auch der einzige Weg sind, um zum gewünschten Ergebnis zu kommen. Ich habe in einem meiner neusten Projekte selbst 8 verschiedene Custom-Blöcke erstellt.

Bevor wir aber unseren eigenen Block entwickeln, nur um z.B. eine andere Galerie-Ansicht zu realisieren, sollten wir uns überlegen welche Alternativen wir haben.

Gutenberg bietet eine Reihe von Erweiterungmöglichkeiten. Aber auch hierbei bei diesen müssen wir immer darauf achten, dass wir keine Änderungen vornehmen, die wir nicht rückgängig machen können.

Welche Möglichkeiten haben wir also?

CSS Anpassungen

Für einfache Änderungen, reichen Anpassungen der Stylesheets oft aus. Schau dir das HTML Markup der Blöcke an, dieses bietet mehr Darstellungsmöglichkeiten, als die Standardansicht.

Beispiel Bildergalerie:

Du möchtest, dass die Caption nicht auf, sondern unter dem Bild liegt?

Dafür reichen ein paar Zeilen CSS:

.wp-block-gallery .blocks-gallery-item figure {
    display: flex;
    flex-direction: column;
}

.wp-block-gallery .blocks-gallery-item figcaption {
    position: relative;
    color: black;
    background: transparent;
}

Auch aufwändiger Galerielayouts, wie ein horizontales Masonry Grid sind über reine CSS Anpassungen möglich:

Block Rendering im Frontend manipulieren

Es gibt natürlich Fälle, in denen die Anpassungen via CSS nicht ausreichen. Dies ist der Fall, wenn wir zwar eine Galeriefunktion haben, im Frontend aber einen ander HTML Struktur benötigen, z.B. um einen Bilderslider darzustellen.

Für diesen Anwendungsfall bietet uns Gutenberg den render_block Filter. Diesen Filter durchlaufen alle Blöcke, bevor sie im Frontend ausgegeben werden.

Der Filter besitzt bietet Parameter, $block_content und $block.

Der erste Parameter ist das unveränderte HTML Markup, das im Frontend ausgegeben wird. Der zweite Parameter enthält alle Attribute des Blocks strukturiert in einem Array.

 add_filter( 'render_block', 'derweili_modify_gallery_block', 10, 3);
 function derweili_modify_gallery_block( $block_content, $block ) {

     // only modify the Gallery Block
     if( "core/gallery" !== $block['blockName'] ) {
         return $block_content;
     }

     return $block_content;

}

Bevor wir einen Block manipulieren, müssen wir prüfen um welchen Block es sich handelt, da alle Blöcke diesen Filter durchlaufen. Dies tun wir, indem wir $block['blockName'] auslesen.

Anschließend können wir wir den Block nach belieben verändern. Z.B. können wir den Block mit einer Galerie umschließen:

 add_filter( 'render_block', 'derweili_modify_gallery_block', 10, 3);
 function derweili_modify_gallery_block( $block_content, $block ) {

     // only modify core code block
     if( "core/gallery" !== $block['blockName'] ) {
         return $block_content;
     }

     $output = '<div class="gallery-wrapper">';
     $output .= $block_content;
     $output .= '</div>';

     return $output;
 }

Wir können aber auch das komplette Markup neu schreiben. Dafür finden wir im $block['attrs']['ids'] Array die IDs der in der Galerie enthaltenen Bilder. Diese können wir dann z.B. so verwenden um einen Slider zu erzeugen:

 add_filter( 'render_block', 'derweili_modify_gallery_block', 10, 3);
 function derweili_modify_gallery_block( $block_content, $block ) {

     // only modify core code block
     if( "core/gallery" !== $block['blockName'] ) {
         return $block_content;
     }

     $output = '<div class="slider">';
     foreach ($block['attrs']['ids'] as $img_id) {
       $output .= '<div class="slide">';
       $output .= wp_get_attachment_image( $img_id, 'large' );
       $output .= '</div>';
     }
     $output .= '</div>';

     return $output;
 }

Der Vorteil des „render_block“ Filter ist, dass dieser nur die Ausgabe im Frontend modifiziert. Es werden keine an der Datenstruktur des Blocks vorgenommen. Block-Validierungsfehler sind damit ausgeschlossen.

Die Block Edit Funktion bearbeiten

Es gibt aber auch Fälle in denen wir den Block Editor bearbeiten möchten. Z.B. um dem Block weitere Einstellungen (Inspector Controls) hinzuzufügen.

Dafür gibt es React Filter, die ähnlich den Filtern in PHP funktionieren. Wir benötigen zwei davon. Zu erst müssen wir die vom Block unterstützten Attribute erweitern.

Anschließend müssen wir die entsprechenden UI Elemente hinzufügen.

Block Attribute erweitern

Über die Funktion addFilter können wir in React auf Filter zugreifen. Diese Funktion benötigt drei Paremeter.

  1. Den Filter, den wir nutzen wollen
  2. Einen Namespace für unsere Anpassung
  3. Die Callback-Funktion mit der wir Anpassungen vornehmen

Die Callback Funktion nimmt ebenfalls zwei Parameter entgegen.

  1. Das Settings Array (entspricht dem Settings Array der registerBlockType Funktion
  2. Den Block Name
addFilter(
    "blocks.registerBlockType",
    "gutenbergcodehighlighter/code-attributes",
    addCodeAttributes
)

function addCodeAttributes( settings, name ){

    // only filter code block
    if("core/code" !== name ) return settings;

    settings.supports = lodash.merge({}, settings.supports, {
        align: ["full", "wide"]
    });

    // do something with the settings
    settings.attributes.align = {
        type: "string",
        default: "wide"
    }

    return settings;

}

Zuerst überprüfen wir wieder, um welchen Block es sich handelt, danach modifizieren wir die Settings und geben diese zurück.

In diesem Beispiel erweitern wir den Code Block um ein full/wide Alignment und stellen dieses im Standard auf „wide“.

Block Edit erweitern

Im Grunde könnten wir im oben genannten Code auch die Edit Funktion erweitern/ersetzen. Das bringt aber einige Probleme mit sich, weshalb es einen zweiten Filter gibt, den editor.BlockEdit Filter.

Hier ein Auszug aus meinem Code Highlighter Plugin:

function addCodeInspectorControls( BlockEdit ) {

    const withInspectorControls =  createHigherOrderComponent( BlockEdit => {

        return props => {
            if( "core/code" !== props.name ) return <BlockEdit {...props} />;

            return (
                <Fragment>
                    <InspectorControls>
                        <PanelBody title={__("Language", 'gutenberg-code-highlighter')}>
                            <PanelRow>
                            <RadioControl
                                label="Language"
                                help="Language of your Code"
                                selected={ props.attributes.language }
                                options={ [
                                    { label: 'Default', value: '' },
                                    { label: 'PHP', value: 'php' },
                                    { label: 'JavaScript', value: 'javascript' },
                                    { label: 'JavaScript ES6', value: 'es6' },
                                    { label: 'HTML', value: 'html' },
                                    { label: 'CSS', value: 'css' },
                                    { label: 'SASS', value: 'sass' },
                                    { label: 'SCSS', value: 'scss' }
                                ] }
                                onChange={ ( language ) => { props.setAttributes( {language} ) } }
                            />

                            </PanelRow>
                        </PanelBody>
                    </InspectorControls>
                    <div className={ `gutenberg-code-highlighter ${props.attributes.language} ` }>
                        <BlockEdit {...props} />
                    </div>
                </Fragment>
            );

        }

    });

    return withInspectorControls(BlockEdit);

}

Original: https://github.com/derweili/gutenberg-code-highlighter/blob/master/filters/code-block/index.js

Die Callback Funktion des Filters nimmt hier nur einen Paramter engegen, den BlockEdit Parameter. Bei diesem handelt es sich um den Edit Component des ursprünglichen Blocks.

Wir können den Block Edit Component nicht direkt verändern. Stattdessen umschließen wir diesen mit einem Fragment. In diesem Fragment erstellen wir Inspector Controls die in der Block Sidebar des Blocks angezeigt werden.

Wir erstellen also einen High Order Component, in dem wir den den Block Edit Component mit einem ein <Fragment> umschließen.
Achtung: wir dürfen nicht vergessen, die props an den Component weiterzugeben.
In das Fragment Element können wir außerdem InpectorControl einfügen und so die Block Sidebar des Code Blocks um z.B. Radio Controls erweitern.

Diese Radio Controls bilden das UI für die zuvor registrierten Blockattribute.

Über props.attributes können wir auf die Blockattribute zugreifen und diese auch über props.setAttribute() verändern.

Fazit

Es kann Sinnvoll sein neue Blöcke zu erstellen, wir sollten aber immer überlegen, ob es nicht nachhaltiger ist, bestehende Blöcke zu erweitern. Gutenberg bietet uns dafür einige Möglichkeiten die wir uns auf jeden Fall anschauen sollten.