Gutenberg: Bloky editovatelné

V minulém článku jsem se ukázal základní principy tvorby bloků na příkladu bloků statických, které toho moc nedělají. Dnes se podíváme tvorbu bloků, které již mají nějaké to kontextové menu pro úpravu a umožní uživateli trochu více interakce.

Errata pro minulé díly: Zde bych také chtěl opravit chybu, která se dostala do předchozích dílů. V rámci Gutenbergu neexistují pouze bloky statické a dynamické. Rozlišujeme 3 typy bloků: statické, editovatelné, a dynamické.

Statické jsme si již ukázali, o editovatelných si povíme v tomto článku, a později se dostanu i k blokům dynamickým. Rozdíl mezi bloky editovatelnými a dynamickými je ten, že bloky editovatelné nepotřebují ke svému vykreslení žádnou komunikaci se serverem. Naopak bloky dynamické v editoru slouží jako “placeholer” pro data, která se později získají ze serveru. Kupříkladu výpis nejnovějších příspěvků.

Marquee

Pro tento tutoriál jsem připravil implementaci bloku reprezentující HTML element <marquee> s možností nastavení vlastního textu, směru a rychlosti pohybu.

Text budeme editovat pomocí textarea, směr pomocí vlastního panelu nástrojů, a rychlost pomocí textového vstupu v inspektoru bloku (panelu na levo od oblasti, kde komponujeme text).

Jak definovat vlastní Gutenberg Blok už jsme si ukázali. Pojďme se tedy zrovna vrhnout na ukázku kódu, který si dnes rozebereme. (Samozřejmě je možné jej nakopírovat a vložit do developer console ve vašem prohlížeči na obrazovce pro editaci příspěvku na webu, kde máte Gutenberg instalovaný a vyzkoušet si jeho funkci).

Velmi podobný kód naleznete také na GitHubu, pro někoho to může být pohodlnější způsob čtení.

( function() {
	var el = wp.element.createElement,
	registerBlockType = wp.blocks.registerBlockType,
	PlainText = wp.blocks.PlainText,
	BlockControls = wp.blocks.BlockControls,
	InspectorControls = wp.blocks.InspectorControls,
	TextControl = wp.components.TextControl;

	registerBlockType( 'david-binda/marquee', { // Název bloku - prefix / jméno.

		// Titulek, ikona a kategorie pro zobrazení v nabíce bloků.
		title: 'Marquee',
		icon: 'controls-repeat',
		category: 'layout',

		/*
		 * Všechny atributy, které chceme moci měnit, musí být zaregistrované.
		 */
		attributes: {
			content: { // Vlastnost "content" typu řetězec.
				type: 'string',
			},
			direction: { // Vlastnost "direction" typu řetězec.
				type: 'string',
				default: 'left', // Výchozí hodnota.
			},
			scrollAmount: {
				type: 'number', // Číselný typ.
				default: 6,
			}
		},

		/**
		 * Funkce, která se zavolá při úpravě bloku
		 */
		edit: function( props ) {

			/**
			 * Callback pro případ, kdy se změní hodnota obsahu.
			 */
			function onChangeContent( newContent ) {
				props.setAttributes( { content: newContent } );
			}

			/**
			 * Callback pro případ, kdy se změní hodnota směru posuvu.
			 * onClick očekává funkci, tudíž musíme vrátit funkci.
			 */
			function onChangeDirection( newDirection ) {
				return function() {
					// Pokud jsme na tlačítko s vybraným směrem klikli opakovaně, nastavení zrušíme na defaultní "left".
					props.setAttributes( { 'direction': props.attributes.direction === newDirection ? 'left' : newDirection } );
				};
			}

			function onChangeLoop( newAmount ) {
				props.setAttributes( { 'scrollAmount': newAmount } );
			}

			/*
			 * Jelikož chceme prvek zobrazit už v editoru, pokud není zrovna upravován,
			 * tak jako na frontendu, pomůžeme si defaultní vlastností isSelected.
			 */
			if ( true !== props.isSelected ) {
				// Zde vracíme prvek prakticky stejně, jako v případě funkce "save".
				return el(
					'marquee', // HTML element
					{
						className: props.className, // Třída generovaná Gutenbergem.
						'direction': props.attributes.direction, // vlastní attribute direction.
						'scrollamount': props.attributes.scrollAmount || 6, // vlastní attribute scrollamount.
					},
					props.attributes.content || props.attributes.placeholder || "Hello world!" // obsah prvku, placeholder, nebo defaultní placeholder.
				);
			} else {
				/*
				 * Pokud s prvkem manipulujeme, chceme zobrazit nějaké ovládací prvky
				 * a také změnit element marquee na textarea, aby uživatel mohl editovat text.
				 */

				// Vrátíme celé pole prvků
				return [
					// Nejprve panel nástrojů obsahující možnost změny směru posuvu.
					el(
						BlockControls, // BlockControls je prvek existující v rámci Gutenbergu.
						{ 'controls': [ { // Jednotlivá tlačítka lze snadno definovat jako pole objektů.
							icon: 'arrow-left', // Ikona tlačítka v panelu nástrojů.
							title: 'Direction Left', // Popisek.
							onClick: onChangeDirection( 'left' ), // Akce vykonávající se při kliknutí.
							isActive: ( 'left' === props.attributes.direction ) // zda-li je tlačítko aktivní či nikoli.
						}, {
							icon: 'arrow-right',
							title: 'Direction Right',
							onClick: onChangeDirection( 'right' ),
							isActive: ( 'right' === props.attributes.direction )
						}, {
							icon: 'arrow-up',
							title: 'Direction Up',
							onClick: onChangeDirection( 'up' ),
							isActive: ( 'up' === props.attributes.direction )
						}, {
							icon: 'arrow-down',
							title: 'Direction down',
							onClick: onChangeDirection( 'down' ),
							isActive: ( 'down' === props.attributes.direction )
						} ] }
					),
					// Inspektor (zobrazí se v záložce "Blok" v pravém sloupci).
					el(
						InspectorControls,
						{},
						el(
							TextControl,
							{
								label: 'Scroll speed (defaults to 6):',
							 	value: props.attributes.scrollAmount,
								onChange: onChangeLoop,
								type: 'number',
							}
						)
					),
					// Dále samotný prvek v editovatelné podobě.
					el(
						PlainText, // Textarea.
						{
							className: props.className,
							onChange: onChangeContent, // Callback.
							placeholder: props.attributes.placeholder || "Hello world!", // Placeholder.
							value: props.attributes.content, // Obsah textového pole.
							isSelected: props.isSelected,
							style: {'display':'inline-block'}, // Marquee je defaultně inline-block, tak toto chceme zachovat.
						}
					),
				];
			}
		},

		/**
		 * Funkce, která se zavolá při ukládání prvku.
		 * Například přechod mezi visual a code editorem.
		 */
		save: function( props ) {
			/*
			 * Všimněte si, že toto je stejné, jako v případě, kdy
			 * isSelected vrací false (ve funkci edit).
			 */
			return el(
				'marquee',
				{
					className: props.className,
					'direction': props.attributes.direction,
					'scrollamount': props.attributes.scrollAmount || 6,
				},
				props.attributes.content || props.attributes.placeholder || "Hello world!" // obsah prvku, placeholder, nebo defaultní placeholder.
			);
		},
	} );
})();

V kódu jsem se snažil okomentovat skoro všechny řádky tak, aby i ten, kdo s Gutenbergem doposud nepracoval, pochopil co se na nich děje. Nyní ještě trochu podrobněji.

Editovatelné atributy

Všechny atributy bloku, které chceme uživateli užmoňit upravovat či nastavovat, je nutné předem registrovat pomocí klíče “attributes”. V přípabě ukázkového kódu se jedná o “content”, “direction” a “scrollamount”. U prvních dvou očekáváme jednoduchý řetězec, u posledního poté číselný vstup.

Attributy “direction”, a “scrollamount” mají nastavenou defaultní hodnotu (“left”, respektive 6).

“Attributes” by měl být objekt (v PHP by to bylo asociativní pole), kde klíč slouží k identifikaci atributu, a jako hodnota je poté další pole obsahující hlavně typ atributu (v našem případě řetězec – string).

Typů atributů existuje ovšem více. Jsou velmi dobře popsány v dokumentaci. Téma je to trochu rozsáhlejší, a než abych se o něm rozepisoval zde, věnuji mu později celý článek.

Funkce edit a props.isSelected

U editovatelných bloků jako tvůrci chceme uživateli v rámci editoru nabídnout dva způsoby zobrazení. Jeden kdy se zobrazí prvek v “edit” módu a druhý kdy se prvek zobrazí tak, jako by byl na frontendu.

Mezi těmito módy nemusí být velký rozdíl. Obyčejně se bude jednat pouze o změnu z HTML elementu samotného na PlainText či RichText umožňující tvorbu obsahu. Jindy může jít pouze o přidání panelu nástrojů (toolbar), přidání polí do inspektoru, či kombinaci předchozích.

Pro to, abychom mohli mezi jednolivými módy rozlišovat, máme k dispozici props.isSelected, který vrací true v případě, že prvek uživatel chce editovat – tj. klikl na něj a je vybrán.

V případě, že prvek zrovna není editován, většinou budeme nejspíše vracet stejné elementy jako v případě funkce save. V načem příkladě bychom mohli kód zjednodušit na následující:

edit: function( props ) {

    // nějaké ty pomocné funkce etc.

    if ( true !== props.isSelected ) {
        return $this.save( props );
    }

BlockControls

BlockControls je prvek, pomocí kterého lze tvořit panel nástrojů (toolbar). Stačí jen nadefinovat vlastní tlačíka a přidat jim vlastní callbacks.

Přidat tlačítka lze hned několika způsoby. Asi nejjednodušší je využít druhého parametru ( props ) funkce wp.element.createElement, která vychází z Reactu

props by měl být objekt (a to platí u každého prvku tvořeného pomocí wp.element.createElement, nejen pro BlockControls) a v případě, že chceme zobrazit vlastní tlačíka pro BlockControls prvek, využijeme klíč “controls” a jako hodnotu mu předáme pole objektů:

{ // objekt
    'controls': // klíč controls
    [ // pole objektů
        { //objekt
            icon: 'arrow-left',
            title: 'Direction Left',
            onClick: onChangeDirection( 'left' ),
            isActive: ( 'left' === props.attributes.direction || undefined === props.attributes.direction )
        },
        { // objekt
            icon: 'arrow-right',
            title: 'Direction right',
            onClick: onChangeDirection( 'right' ),
            isActive: ( 'right' === props.attributes.direction )
        }
    ]
}

Pokud bychom chtěli tlačítka v liště nástrojů seskupovat, můžeme namísto “pole objeků” jako hodnotu pro klíč “controls” použít “pole polí objektů”:

{
    'controls': // klíč controls
    [ // pole polí objektů
        [ // pole objektů #1
            { //objekt
                icon: 'arrow-left',
                title: 'Direction Left',
                onClick: onChangeDirection( 'left' ),
                isActive: ( 'left' === props.attributes.direction || undefined === props.attributes.direction )
            },
            { // objekt
                icon: 'arrow-right',
                title: 'Direction Right',
                onClick: onChangeDirection( 'right' ),
                isActive: ( 'right' === props.attributes.direction )
            }
        ],
        [ // pole objektů #2
            { //objekt
                icon: 'arrow-up',
                title: 'Direction Up',
                onClick: onChangeDirection( 'up' ),
                isActive: ( 'up' === props.attributes.direction )
            },
            { // objekt
                icon: 'arrow-down',
                title: 'Direction Down',
                onClick: onChangeDirection( 'down' ),
                isActive: ( 'down' === props.attributes.direction )
            }
        ]
    ]
}

Gutenberg by nám pak měl tyto jednotlivé bloky oddělit separatorem:

Panelů nástrojů lze ovšem také vložít více za sebou. To může být výhodné zvláště v případě, kdy si buď vytvoříme vlastní prvek obsahující tlačíka, která se opakují, nebo budeme chtít využít nějaký, který se v Gutenbergu již nativně objevuje (například AlignmentToolbar), a doplnit jej o vlastní sadu nástrojů:

return [
            el( // První panel nástrojů:
                BlockControls,
                { 'controls': [ {
                    icon: 'arrow-left',
                    title: 'Direction Left',
                    onClick: onChangeDirection( 'left' ), // Akce vykonávající se při kliknutí.
                    isActive: ( props.attributes.direction === 'left' || props.attributes.direction === undefined )
                }, {
		    icon: 'arrow-right',
		    title: 'Direction Right',
                    onClick: onChangeDirection( 'right' ),
                    isActive: ( props.attributes.direction === 'right' )
	        } ] }
            ),
            el( // Druhý panel nástrojů.
                BlockControls,
                { key: 'controls' },
                el(
                    wp.blocks.AlignmentToolbar, // Defaultní panel pro zarovnání.
                    {
                        value: alignment,
                        onChange: onChangeAlignment // Je nutné definovat.
                    }
                )
            ),
            // Dále samotný prvek
];

No a v neposlední řadě lze tlačítka vygenerovat jako samostatné prvky, které se do prvku BlockControls předají pomocí třetího (a následujících) parametrů. Podobně jako jsme u statických bloků tvořili obsah HTML elementů.

Callback pro uložení nastavení

Při vytváření vlastních prvků jsme použili jako callback pro akci (kliknutní na tlačíko) následující zápis:

onClick: onChangeDirection( 'right' ),

Funkce onChangeDirection je poté definována následujícím způsobem:

        /**
         * Callback pro případ, kdy se změní hodnota směru posuvu.
         */
        function onChangeDirection( newDirection ) {
            return function() {
                props.setAttributes( { 'direction': props.attributes.direction === newDirection ? 'left' : newDirection } );
            };
        }

Tedy, funkce onChangeDirection vrací anonymní funkci. To proto, že Reactí onClick očekává funkci, jako svou hodnotu, a navíc se musíme vypořádat s různými hodnotami pro každé tlačítko.

Všimněte si toho, že pokud na to aktivní tlačíko klikneme opakovaně, znamená to, že chceme nastavení zrušit. Jelikož má ale Marquee defaultní hodnotu pro attribut direction, resetujeme jen v takovém případě na “left”.

V případě prvku wp.blocks.AlignmentToolbar naopak definujeme “pouze” onChange parametr. Abstraktní prvek Toolbar se poté postará o podbné řešení skrze onClick sám. Prohlédněte si zdrojový kód.

Inspector

Gutenberg vedle panelu nástrojů umožňuje ještě vložení formulářových polí a dalších ovládacích prvků do tzv. Inspektoru. To je pravý sloupec v editoru, kam by se mělo umisťovat to, co se do to panelu nástorojů nehodí.

Podobně jako přidáváme panel nástrojů, přidáme i inspector, nicméně tentokráte budeme čerpat z wp.components. V ukázce níže naleznete jakým způsobem lze přidat wp.components.TextControl. Další *Control componenty lze nalézt na GitHubu Gutenbergu:

// Inspektor (zobrazí se v záložce "Blok" v pravém sloupci).
el(
	InspectorControls,
	{},
	el(
		TextControl,
		{
			label: 'Scroll speed (defaults to 6):', // Popisek.
			value: props.attributes.scrollAmount, // Hodnota.
			onChange: onChangeSpeed, // save callback.
			type: 'number', // attribut prvku input - lze definovat i další.
		}
	)
),// Následující kód již znáte.

Funkci onChangeSpeed jsem v ukázce nedefinoval, nicméně bude hooodně podobná funkci onChangeContent, která je použita v úvodní úkázce.

Textové vstupy – PlainText a RichText

Vedle panelů nástrojů, je třeba také uživateli umožnit tvořit nějaký ten text. Gutenberg v základu nabízí dva způsoby. Textové pole (textarea) a RichText editor, který není nepopodbný TinyMCE známého ze současného WordPressu.

Jelikož ale RichText pracuje s attributy, kde zdrojem (source) je skupina prvků, dovolím si dnes rozebrat pouze PlainText a k RichText se vrátit v dalším díle. Pro zájemce však uvedu několik odkazů:

PlainText

Jednodušší na vysvětlení tedy, zvláště vzhledem k vazbě na “Attributes” vysvětlované o pár odstavců výše, je PlainText (konkrétně wp.blocks.PlainText). Atribut, který PlainText umožňuje vytvořit může být jednoduchý řetězec, jelikož jako vstupní pole je použita textarea, která se sama přizpůsobuje velikosti vstupu, a do níž, z pricipu, nelze vkládat nic jiného než text.

Tedy, abychom si připomněli kód:

                // Dále samotný prvek
                el(
                    PlainText, // Textarea.
                    {
                        className: props.className,
                        placeholder: props.attributes.placeholder || "Hello world!",
                        value: props.attributes.content, // Obsah textového pole.
                        onChange: onChangeContent, // Callback.
                        isSelected: props.isSelected,
                        style: {'display':'inline-block'}, // Marquee je defaultně inline-block, tak toto chceme zachovat

                    }
                ),

“className” je důležité hlavně proto, že Gutenberg generuje class name z názvu, a prefixu, prvku. Díky predikovatelnosti lze toto využít k tvorbě CSS stylů.

Dále máme “placeholder”. Zmiňoval jsem, že Gutenberg pracuje s placeholdery, aby tak umocnil intuitivnost ovládání. I náš marquee blok by tedy nějaký ten placeholder měl obsahovat.

“value” je hodnota, která je nastavena prvku, když je zobrazen. Připomeňme si, že textové pole se zobrazí pouze, pokud je prvek vybrán uživatelem s cílem s ním něco udělat – přesunout jej, nebo upravit obsah, či použít panel nástorů. Hodnota je tedy generována pomocí props.attributes.content, kam se pomocí callbacku definovaném v “onChange” ukládá.

Dále nalezneme “isSelected”, kterým předáváme hodnotu toho, zda-li je prvek vybrán a jako ukázku toho, že lze nadefinovat libovolný HTML atribut, který se poté vypíše, je použit “style”, kterým se v našem případě snažíme docílit toho, aby textarea měla podobné vlastnosti jako marquee – tedy aby byla zobrazena jako inline-block.

“onChange” je callback pro to, co se má stát, když se hodnota změní. Není volána pokaždé, když je připsáno písmeno, ale poté, co uživatel dokončí editaci. Nejedná se o nic komplikovaného:

        /**
         * Callback pro případ, kdy se změní hodnota obsahu.
         */
        function onChangeContent( newContent ) {
            props.setAttributes( { content: newContent } );
        }

Pomocí props.setAttributes() dostaneme hodnotu do props.attributes.content, kterou používáme i jinde v kódu.

Kam se tato data ukládají?

Všechny hodnoty, které lze v rámci marquee bloku upravovat, se ukládají přímo do post_content. V rámci parsovatelných HTML komentářů. Ty se na frontendu nezobrazují, ale Gutenbergu umožňují vygenerovat příslušné uživatelské rozhraní a skladovat dané hodnoty.

Pro zobrazení, pokud máte blok zaregistrovaný a vytvořený, staří přepnout do módu umožňující úpravu zdrojového kódu (stejné zobrazení existuje i u současného editoru obsahu). Zde je ukázka, jak takový uložený blok vypadá:

<!-- wp:david-binda/marquee {"direction":"right","scrollAmount":"10"} -->
<marquee class="wp-block-david-binda-marquee" direction="right" scrollamount="10">Hello world!</marquee>
<!-- /wp:david-binda/marquee -->

Závěrem

Udržet tento článek v rozumném rozsahu bylo opravdu těžké – některá témata budu muset později ještě jednou a tentokráte více do hloubky rozebrat. Ovšem věřím, že i bez hlubšího zastavení se, by měl kód posloužit jako dobrý základ pro další studium.

Určitě doporučuji si s kódem použitým v tomto článku trochu pohrát – stačí je vložit a upravit přímo v developer console vašeho prohlíže. Připomenu, že před opakovanou registrací prvku, lze použít následující kód pro jeho odstranění:

wp.blocks.unregisterBlockType( 'david-binda/marquee' );

Kód, který jsem v tomto článku použil jsem, více či méně, v nezměněné podobně nahrál také na GitHub. Velmi pravděpodobně se k němu v blízké budoucnosti ještě vrátím, rád bych jej přepsal do JSNext, nicméně v podobně v jaké je distukován zde bude navždy žít v tagu 0.1.0. Použitím ES5 pro tento tutoriál jsem chtěl zacílit i na ty z vás, kteří moderní JavaScript a další nástoje nemají ještě natolik zažité.

Pokud byste se rádi podívali na to, jak lze moderně precovat s Gutenbergem a nevíte jak přesně nastavit Webpack a další, vyzkoušjte projekt create-guten-block

Gutenberg: Bloky statické

Stejně tak jako WordPress těží z možnosti jednoduché rozšiřitelnosti, systém filtrů a ackí je takřka unikátní a snadno použitelný i pro programátora začátečníka, i Gutenberg je připravován tak, aby jej vývojáři mohli snadno doplnit o chybějící funkcionalitu – vlastní bloky.

V současném Gutenbergu lze identifikovat 3 druhy bloků. A sice bloky statické, dynamické, a bloky ukládající data do post meta – ne všechna data chceme nutně mít jen v `post_content`. Naopak, ne všechna data, která dnes ukládáme do post_meta musíme skladovat právě tam. Gutenberg se svým konceptem umožňuje často data, která by skončila jinde, skladovat přímo v post_content, kam patří.

V tomto článku se budeme věnovat pouze blokům statickým. Ačkoli toho moc neumí, poslouží nám jako velmi dobrý úvod do problematiky vytváření vlastních bloků.

Bloky statické

Statické bloky jsou nejjednodušším typem bloků. Pokud jsem vás v předchozím článku s úvodem do bloků a šablon navnadil contextovými panely nástrojů, tak vás statické bloky zklamou. Nicméně i ony mají své opodstatnění, a jsou velmi vhodné pro vysvětlení si anatomie bloku.

Předpokládám, že jste vývojář, a tak nemá cenu vás dlouho zdržovat povídáním. Tady je kód (originál lze nalézt v Gutenberg Handbook):

var el = wp.element.createElement;

wp.blocks.registerBlockType( 'mytheme/red-block', { // registrace bloku s názvem red-block a prefixem mytheme
    title: 'Červený Blok', // Titulek, zobrazuje se ve výpisu bloků
    icon: 'universal-access-alt', // Ikonka bloku, zobrazuje se ve výpisu bloků
    category: 'layout', // Kategorie bloku, ovlivňuje umístění ve výpise bloků
    edit: function() { // Funkce, která generuje HTML během editace příspěvku
        return el( 'div', { style: { backgroundColor: '#900', color: '#fff', padding: '20px' } }, 'Toto je červený blok.' );
    },
    save: function() { // Funkce, která generuje HTML zobrazené mimo editaci příspěvku
        return el( 'div', { style: { backgroundColor: '#900', color: '#fff', padding: '20px' } }, 'Toto je červený blok.' );
    }
} );

Název bloku

Jak jsem již zmiňoval minule, Gutenberg, mimo jiné, velmi dobře pracuje s best practices. Namísto toho, aby pouze doporučoval bloky prefixovat, užití prefixu přímo vyžaduje. Zde je ukázka toho, co se stane, pokud odstraním mytheme/ z názvu bloku v kódu zmíněném výše:

Takže, stejně jako je velmi vhodné si pro svůj plugin zvolit prefix, který se používá pro všechno (ano, pokud používáte v PHP namespaces, není třeba prefixovat názvy funkcí, ale určitě je vhodné prefixovat také názvy meta dat, názvy options, unikátní název by měly mít i lokalizační stringy, a tak dále, a tak dále), tak tento prefix by se měl objevit i v blocích, které vaše šablona či plugin nabízí.

Druhá část názvu bloku je jasná – unikátní název v rámci daného prefixu / namespace.

CSS třídy

Z názvu bloku Gutenberg také generuje třídu, která je vygenerovanému bloku přidělena. A tudíž je snadné připravit potřebné selectory. V případě našeho “Červeného Bloku” s názvem mytheme/red-block bude vygenerována třída s prefixem wp-block- a / bude nahrazeno pomlčkou -. Kaskádový styl pak může vypadat nějak takto:

.wp-block-mytheme-red-block {
    border: 2px solid #9c9;
    padding: 20px;
}

Titulek, Ikona, a Kategorie

Jak již bylo řečeno, Gutenberg počítá s tím, že bude snadno a často rozšiřován bloky třetích stran. Z TinyMCE víme, že toolbar není nafukovací. I když velmi často vídám instalované plugin, které přidávají další a další řady nástrojů jen proto, aby uživatel měl po ruce všechna myslitelná tlačítka. Nicméně Gutenberg se nesnaží všechny bloky zobrazit najednou. Nýbrž nabízí možnost bloky filtrovat pomocí našeptávače. Pokud blok stále uživatel nenachází, je možné navštívit záložku, která vypisuje bloky dle kategorií. V současné době jsou k dispozici následující kategorie:

  • Common Blocks (common) – bloky, které se v obsahu vyskytují často. Kategorie je první z vypsaných.
  • Formatting (formatting) – formátovací bloky, jako tabulka, zdrojový kód, citace
  • Layout Blocks (layout) – horizontální čára, more (aka zobrazit více), tlačítko, sloupce
  • Widgets (widgets) – shortcode, výpis nejnovějších příspěvků …
  • Embed (embed) – všechny embed služby – Twitter, Facebook, you name it!

Všechny bloky jsou ve výpisu a vyhledávání zobrazeny spolu se svým názvem a ikonkou. Ikonku si lze zvolit ze všech, které jsou k dispozici v rámci Dashicons – nejspíš znáte z vlastní položky menu, ikonky custom post type, či z vlastní konfigurační stránky.

Všechny tyto parametry, ačkoli se to může zdát malicherné, jsou důležité právě pro to, aby je uživatelé snadno nalezli. Rozhodně se podívejte do jakých kategorií jaké bloky patří, než plácnete svůj blok někam, kde by jej nikdo nehledal. Název by měl být intuitivní, jelikož si dokážu představit, že nikoli jen power-users, ale všichni uživatelé se pokusí blok nejdříve vyhledat pomocí klíčového slova.

No a ikonka? Vsadím se, že správná volba pomůže uživateli právě ve chvíli, kdy vyhledávání pomocí klíčového slova, tedy titulku, selže. Takže i zde věnujme chvíli času. Prohlédněte si galerii dostupných ikonek.

Funkce edit a save

V naší ukázce toho tyto funkce nedělají zrovna mnoho. Nicméně mají na svědomí HTML, které je blokem generováno a na frontendu nakonec vytištěno (to platí především pro funkci save).

HTML element vrácený z funkce edit je použito během editace příspěvku, naopak HTML element vrácený z funkce save je HTML, které je uloženo do post_content a nakonec tedy zobrazeno na frontendu (nebo při přepnutí do HTML, aka Code, editoru).

wp.element.createElement

Gutenberg nabízí jazykově neutrální API, abstrakci nad knihovnou, jenž je interně použita. Onou interní knihovnou je v současnoti React. Abstrakce na Reactem není pouze proto, aby bylo možné React v případě potřeby nahradit, ale také proto, aby bylo případně možné překlenout změny mezi jednotlivými verzemi Reactu bez toho, aniž by vývojáři šablon a pluginů museli přepisovat své implementace. Ano, i Gutenberg se řídí WordPress filozofií zpětné kompatiblity.

V našem příkladě:

var el = wp.element.createElement;
...
 el( 'div', { style: { backgroundColor: '#900', color: '#fff', padding: '20px' } }, 'Toto je červený blok.' );

Je vytvořen element div s atributy style nastavující červenou barvu pozadí, bílý text a padding. Poslední parametr funkce je poté innherHTML takového elementu. Záměrně uvádím innerHTML – nemusí totiž nutně obsahovat pouze text, ale také další element, či několik elementů:

return el(
    'div',
    { style: {
        backgroundColor: '#900',
        color: '#fff',
        padding: '20px'
    } },
    el( 'div', { style: { backgroundColor: '#900', color: '#fff', padding: '20px' } }, 'Toto je červený blok.' ),
    el( 'div', { style: { backgroundColor: '#900', color: '#fff', padding: '20px' } }, 'Toto je červený blok.' ) );

Použité ve funkcích edit a save vygeneruje následující HTML:

<!-- wp:mytheme/red-block -->
<div style="background-color:#900;color:#fff;padding:20px" class="wp-block-mytheme-red-block">
    <div style="background-color:#900;color:#fff;padding:20px">Toto je červený blok.</div>
    <div style="background-color:#900;color:#fff;padding:20px">Toto je červený blok.</div>
</div>
<!-- /wp:mytheme/red-block -->

Všimněte si v JS kódu konstrukce, kdy druhý vnořený blok div je použit jako 4. parametr – netřeba ze 3. parametru dělat pole, každý další vnořený element předáme jako další parametr.

Pro více informací o wp.element zavítejte do dokumentace přímo v GitHub repozitáři

Domácí úkol

Pokud jste si Gutenberg stále ještě nenainstalovali, učiňte tak nyní. Lze jej jednoduše nainstalovat přímo z plugin repozitáře. Po instalaci a aktivaci si budete moci vyzkoušet tvorbu obsahu, ale hlavně také vyzkoušet tvorbu bloků.

Navíc, tvorbu bloků zmíněných v tomto článku lze provést přímo z webového prohlíže tím, že kód vložíme přímo do konzole! Jednoduše nakopírujte kód zmíněný výše, otevřete konzoli, vložte kód a stiskněte enter. “Červený blok” se objeví v nabídce bloků a lze jej do stránky vložit.

Vyzkoušejte si změnu generovaných elementů a také změnu elementů mezi jednotlivými stavy (funkce edit a save). Kupříkladu změna textu či barvy pozadí.

Určitě si všimnete, že nelze jednoduše zaregistrovat jeden blok vícekrát. V tom případě se vám bude hodit následující funkce pro deregistraci existujícího blocku. Její zavolání vám umožní ukázkový kód zavolat znovu:

wp.blocks.unregisterBlockType( 'mytheme/red-block' );

Další zdroje ke studiu:

Gutenberg: Bloky a šablony obsahu

Projekt Gutenberg přichází s konceptem bloků, pomocí kterých uživatelé WordPressu budou moci nově skládat obsah jednotlivých příspěvků, namísto aby v rámci jednoho jediného WYSIWYG editoru vytvářeli obsah, ze kterého se již při tvorbě stává jakýsi blob.

Poznámka: pokud jste Gutenberg ještě netestovali, rozhodně si jej nainstalujte a vyzkoušejte.

Bloky a kontextové panely nástrojů

Bloky, které uživatel nově bude mít k dispozici, umožní pohodlný a jednoduchý způsob jakým lze s obsahem manipulovat. To uživatelé ocení pokaždé, když se budou muset k nějaké části příspěvku vrátit – ať již během úvodní tvorby příspěvku, nebo poté při jeho další úpravě.

To, že samotný obsah příspěvku, stránky, či vlastního typu obsahu (custom post type; CPT) bude rozdělen do jednotlivých bloků umožňuje zcela nový rozměr úprav. Tvůrci bloků (základní sada je součástí Gutenbergu, a tedy je/bude dostupna bez dalšího) mohou nadefinovat nástroje a nastavení, které se daného bloku týkají. To je zásadní rozdíl oproti TinyMCE, který poskytuje jeden globální panel nástrojů “pro všechno”.

Takový kontextový panel nástrojů může u odstavce vypadat následovně:

Pro odstavec opravdu většinou nepotřebujeme nic jiného než zarovnání, důraz (tučné písmo, kurzíva), přeškrtnutí, či možnost vytvořit odkaz. Naopak pro nadpis využijeme spíše možnost změnit úroveň nadpisu:

Takovýto kontextový panel nástrojů by měl uživatelům vyhovat více než jeden globální. Navíc Gutenberg jde dál, a v rámci jednoho bloku může existovat více oblastí, kde se panel nástrojů zobrazí, a tvůrci bloků tak mohou vytvářet i komplexnější struktury, přičemž je zachována přívětivost úprav a přímá zpětná vazba s tím, jak bude jejich obsah nakonec vypadat. Podívejme se na blok “cover image”:

Šablony příspěvků a vlastních typů obsahu

Gutenberg vedle bloků samotných, které uživatelé mohou přidávat, přináší také koncept šablon. Pro začátek se jedná “pouze” o šablony obsahu jednotlivých typů obsahu (příspěvek, stránka, CPT). Vývojář šablony či pluginu může ke svému, či některému z výchozích typů obsahu, nadefinovat šablonu v podobě bloků, které jsou při tvorbě nového příspěvku předvyplněny do obsahu.

Takové šablony mohou být zaregistrovány dokonce jako neměnné! To je, dle mého názoru, celkem silný nástroj pro tvůrce šablon (a nyní myslím vzledů/témat pro celý web, nikoli pro obsah příspěvku v rámci Gutenbergu), zvláště pak pokud tyto mají složitější design a předpokládají nějaký specifický obsah.

Placeholders

Na tomto místě si dovolím malou odbočku. Gutenberg se snaží tvůrce nových bloků trochu více dirigovat. A to nejen co se týče stylu programování, ale i kvality bloků z pohledu uživatelů. Osobně takovou snahu jen kvituji. Jeden z designových principů, které Gutenberg přináší jsou placeholders ( ale je toho vícero, a rozhodně doporučuji přečíst celé Gutenberg Design Principles & Vision ).

Placeholder je obsah, ať již textový či obrazový, kterým se předvyplní blok ve chvíli kdy je přidán do obsahu stránky. Tím dá okamžitě tvůrci obsahu jasnou představu o tom, jak který blok bude sám o sobě vypadat, a jak bude fungovat s okolím.

Navíc, takový placeholder může třeba obsahovat i krátký návod k tomu, co se po uživateli očekává, případně i vysvětlení jaký obsah má doplnit.

Registrace vlastních šablon

Šablony obsahu příspěvků jsou, po samotné instalaci pluginu, asi nejjednodušší způsob jakým lze začít s Gutenbergem jakožto vývojář. V zásadě se jedná jen o další parametr funkce register_post_type.

Zde si dovolím zkopírovat ukázku z Gutenberg Handbook, kde je zaregistrován nový CTP pro knihy, a je mu předpřipravena šablona obsahující blok pro obrázek, nadpis, a odstavec:

function register_post_type() {
    $args = array(
        'public' => true,
        'label'  => 'Books',
        'show_in_rest' => true,
        'template' => array(
            array( 'core/image', array(
                'align' => 'left',
            ) ),
            array( 'core/heading', array(
                'placeholder' => 'Add Author...',
            ) ),
            array( 'core/paragraph', array(
                'placeholder' => 'Add Description...',
            ) ),
        ),
    );
    register_post_type( 'book', $args );
}
add_action( 'init', 'register_post_type' );

Co se kódu týče, všimněte si, že názvy všech bloků (kupříkladu core/heading, či core/paragraph) obsahují prefix. Ono core/, který je vyhrazený pro defaultní sadu bloků. Gutenberg neumožňuje vývojáři zaregistrovat blok, který by prefix neměl. Je skvělé vidět, že jsou best practices jako “prefix everything” vynucovány.

Výše zmíněný kód by měl vyprodukovat něco podobného následujícímu rozhraní:

Všimněte si onoho “Add Author…” či “Add Description…”, což jsou přesně ony nápovědy toho, co se po uživateli očekává zmiňované v předchozím odstavci. Lze si třeba představit šablonu pro tiskové zprávy, kdy se uživatel z placeholderů jednotlivých bloků dozví, co kam patří, a šablona mu tak pomůže vytvořit kvalitní tiskovou zprávu:

Více ukázek, včetně toho jak takovou šablonu uzamknout pro editaci, či jak přidat šablonu již existujícím typům obsahu (které třeba nemůžete modifikovat), určitě si projděte příslušný handbook.

Závěrem

Jak již bylo zmíněno, šablony jsou asi nejjednodušší způsob jak začít s Gutenbergem něco dělat. Jejich tvorba je jednoduchá a defaultní nabídka bloků je dostatečně bohatá na to, aby bylo možné s jejich pomocí připravit nějaký funkční prototyp.

Tak jako je to v Gutenbergu se vším, je nutné hledět i do budoucna. Podobným způsobem jakým šablony definují obsah příspěvku či stránky, bude v dalších fázích projektu možné definovat celé layouty webu.

Osobně se mi nikdy nepodařilo, bez menších či větších kódovacích zásahů, nastavit šablonu na svém webu tak, jak jsem ji viděl v demu. Nicméně to, jakým způsobem Gutenberg umožňuje šablonovat a vést uživatele k tomu, aby jen “doplňoval prázdná místa” v layoutu, a přináší tak jiskřičku naděje, že jednou opravdu na svůj web dostanu šablonu přesně tak, jak jsem ji viděl v demu, jen si dosadit vlastní obsah.