Gutenberg: Napojení bloku na WordPress Rest API

JavaScript PHP WordPress

POZOR! Článek jsem napsal před více jak rokem, a tudíž už nemusí reflektovat můj nynější názor nebo může být zastaralý.

V minulém článku jsem ukázal, jak tvořit jednoduchý Gutenberg blok a v tomto příspěvku vytvořím nový blok, který napojím na data z WordPress Rest API. Ukážu to na jednoduchém příkladu, ve kterém si do mého Gutenberg bloku („Autoři článku“) budu natahovat data o uživatelích WordPressu.

  1. Vytvořím WordPress Rest API endpoint
  2. Vytvořím samotný Gutenberg blok s napojením na API
  3. Ukážu jak data využít při renderování bloku
  4. Ukážu jak blok následně reálně vypadá

Vytvoření WordPress Rest API endpointu

Vytvořím si jednoduchou třídu, která zaregistruje jediný API endpoint, který bude vracet uživatele:

/wp-content/plugins/katuscak-gutenberg/inc/API.class.php

<?php

namespace KatuscakGutenberg;

class API
{
    private $namespace;

    public function __construct()
    {
        $this->namespace = 'katuscak-gutenberg';
    }

    public function init()
    {
        add_action('rest_api_init', function () {
            register_rest_route(
                $this->namespace,
                '/users',
                [
                    'methods' => 'GET',
                    'callback' => [$this, 'getUsers'],
                ]
            );
        });
    }

    public function getUsers()
    {
        $users = [];
        $result = get_users(['fields' => ['display_name', "ID"]]);

        foreach ($result as $user) {
            $users[] = [
                'value' => $user->ID,
                'label' => $user->display_name,
            ];
        }

        return $users;
    }
}

Nesmím zapomenout jej také v pluginu inicializovat:

/wp-content/plugins/katuscak-gutenberg/katuscak-gutenberg.php

<?php
/*
Plugin Name: Katuscak Gutenberg Blocks
Description: Gutenberg plugin with awesome blocks
Author: Michal Katuščák
Version: 0.1
Author URI: https://www.katuscak.cz/
*/

include_once __DIR__ . "/inc/API.class.php";
$api = new \KatuscakGutenberg\API();
$api->init();

// ... další kód

Vytvoření Gutenberg bloku s napojení na API

Oproti předchozí ukázce bude blok o něco složitější. Zde už využiji npm balíček @wordpress/data, který je podobný React Reduxu (rozdíly jsou popsané v dokumentaci WordPressu, ale vesměs používá stejnou terminologii a principy), který se postará o management dat (v tomto případě o seznam uživatelů). Pro ty, kteří Redux a jeho logiku neznají, doporučuji pro pochopení trochu nastudovat, protože když jsem sám podobný kód viděl poprvé, tak se mi zavařil mozek a to jsem v JavaScriptu nebyl zrovna nováček.

Také je potřeba počítat s tím, že data se načítají asynchronně až při jejich potřebě, takže je dobré mít v bloku spinner, který uživateli ukazuje, že dochází k načítání dat.

/wp-content/plugins/katuscak-gutenberg/gutenberg/blocks/authors/editor.js

import Select from 'react-select';

const {apiFetch} = wp;
const {registerBlockType} = wp.blocks;
const {PanelBody, PanelRow, Spinner} = wp.components;
const {registerStore, withSelect} = wp.data;
const {InnerBlocks, InspectorControls} = wp.editor;


const actions = {
    setUsers(userAuthors) {
        return {
            type: 'SET_USERS',
            userAuthors,
        };
    },
    receiveUsers(path) {
        return {
            type: 'RECEIVE_USERS',
            path,
        };
    },
};

registerStore('katuscak-gutenberg/authors', {
    reducer(state = {userAuthors: {}}, action) {
        switch (action.type) {
            case 'SET_USERS':
                return {
                    ...state,
                    userAuthors: action.userAuthors,
                };
        }

        return state;
    },

    actions,

    selectors: {
        receiveUsers(state) {
            const {userAuthors} = state;
            return userAuthors;
        },
    },

    controls: {
        RECEIVE_USERS(action) {
            return apiFetch({path: action.path});
        },
    },

    resolvers: {
        * receiveUsers() {
            const userAuthors = yield actions.receiveUsers('/katuscak-gutenberg/users/');
            return actions.setUsers(userAuthors);
        },
    },
});

const block = registerBlockType('katuscak-gutenberg/authors', {
    title: 'Autoři článku',
    icon: 'admin-users',
    category: 'layout',

    attributes: {
        user: {
            type: 'string',
            default: null,
        },
    },

    edit: withSelect((select) => ({
        userAuthors: select('katuscak-gutenberg/authors').receiveUsers(),
    }))(props => {
        const {attributes: {user}, userAuthors, className, setAttributes} = props;
        const handleUsersChange = (user) => setAttributes({user: JSON.stringify(user)});

        let selectedAuthors = [];
        if (null !== user) {
            selectedAuthors = JSON.parse(user);
        }

        if (!userAuthors.length) {
            return (
                <p className={className}>
                    <Spinner/>
                    Načítání dat
                </p>
            );
        }

        return [
            <InspectorControls>
                <PanelBody title="Autoři"
                           className="secure-block-inspector">
                    <PanelRow>
                        <Select
                            className={props.className + '__select'}
                            name='secure-block-users'
                            value={selectedAuthors}
                            onChange={handleUsersChange}
                            options={userAuthors}
                            isMulti='true'
                        />
                    </PanelRow>
                </PanelBody>
            </InspectorControls>,

            <div className={props.className}>
                <span className={props.className + '__description'}>
                    <strong>Autoři článku: </strong>

                    {!selectedAuthors || selectedAuthors.length === 0 ?
                        <span> --- vyberte --- </span>
                        :
                        <span className={props.className + '__users'}>
                            {Object(selectedAuthors).map((value, key) =>
                                <span className={props.className + '__users_user'}>
                                    <span className={props.className + '__users_user_name'}>
                                        {value['label']}
                                    </span>
                                </span>
                            )}
                        </span>
                    }
                </span>
            </div>
        ];
    }),

    save: (props) => (
        <div className={props.className}>
            <InnerBlocks.Content/>
        </div>
    )
});

export {block}

Jak použít data při renderování bloku?

WordPress přímo v PHP umožňuje vyrenderovat si blok úplně jinak, než je vlastně celou dobu v databázi uložený. To znamená, že se zvolenými uživateli lze následně více pracovat až když dojde k vykreslení článku

/wp-content/plugins/katuscak-gutenberg/katuscak-gutenberg.php

// ... další kód
function katuscak_gutenber_register_blocks()
{
    // Pouze pokud je Gutenberg dostupný
    if (!function_exists('register_block_type')) {
        return;
    }

    register_block_type('katuscak-gutenberg/authors', [
        'render_callback' => 'katuscak_gutenberg_authors_render',
    ]);
}

katuscak_gutenber_register_blocks();


function katuscak_gutenberg_authors_render($attributes, $content)
{
    $users = json_decode($attributes["user"]);
    var_dump($users);
}

// ... další kód

Jak to potom vypadá při použití?

 

Kompletní kód (celý plugin do WP) najdete na: github.com/MichalKatuscak/gutenberg-blocks

Zdroje:

https://developer.wordpress.org/rest-api/
https://developer.wordpress.org/block-editor/packages/packages-data/
https://react-redux.js.org/

Znáte někoho, komu by článek mohl pomoct? Zasdílejte mu ho :)

Komentáře