Wcześniej badaliśmy przechowywanie danych edytora bloków WordPress (Gutenberg) w atrybutach bloku i w meta postu, ale czy wiesz, że możesz przechowywać i pobierać z tabeli opcji WordPress, importując apiz @wordpress/api.
W tym przewodniku przyjrzymy się, co klasycznie pisałbyś w PHP jako update_optioni get_option.
Aby to zaimplementować, musimy skorzystać z cyklu życia Reacta, więc przyjrzymy się tworzeniu komponentu React poprzez import Componentz @wordpress/element.
Warunki wstępne
- Zapoznaj się z tworzeniem wtyczek do WordPress Gutenberg
- Poznaj dynamiczne bloki i renderowanie po stronie serwera
- Zrozum, jak tworzyć metaboxy w Gutenberg
To ostatnie wymaganie jest przydatne dla interfejsu użytkownika, którego będziemy używać w tym przewodniku, jednak w rzeczywistych aplikacjach prawdopodobnie użyjesz tej metody na pasku bocznym lub stronie opcji.
Zarejestruj opcje w PHP
Zanim będziemy mogli użyć opcji w JavaScript, musimy upewnić się, że zarejestrowaliśmy ją w PHP za pomocą register_settingi że show_in_restargument został ustawiony na true.
Kontynuując przewodnik po blokach dynamicznych, otwórz główny plik PHP wtyczki (w tym przypadku wholesome-plugin.php) i dodaj następujący kod na końcu tego pliku po wszystkich innych funkcjach:
function wholesomecode_wholesome_plugin_register_settings() {
register_setting(
'wholesomecode_wholesome_plugin_settings',
'wholesomecode_wholesome_plugin_example_text',
[
'default' => '',
'show_in_rest' => true,
'type' => 'string',
]
);
}
add_action( 'init', 'wholesomecode_wholesome_plugin_register_settings' );
Ten kod rejestruje pole meta wywoływane wholesomecode_wholesome_plugin_block_textdla wholesomecode_wholesome_plugin_settingsgrupy opcji. Zapewnia również, że interfejs API REST może uzyskać dostęp do tego pola meta z show_in_restwartością ustawioną na true.
Utwórz komponent
Otwórz /src/edit.jsplik, zmienimy nieco strukturę tego pliku, abyśmy mogli wypisać nasze Component.
Wytnij i wklej cały ten blok kodu do /src/edit.jspliku, omówimy to za chwilę:
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import {
Panel,
PanelBody,
TextControl,
} from '@wordpress/components';
import { Component } from '@wordpress/element';
import './editor.scss';
class OptionsExample extends Component {
constructor() {
super( ...arguments );
this.state = { exampleText: '' };
}
render() {
const { exampleText } = this.state;
return (<Panel>
<PanelBody
title={ __( 'Example Meta Box', 'wholesome-plugin') }
icon="admin-plugins"
>
<TextControl
help={ __( 'This is an example text field.', 'wholesome-plugin') }
label={ __( 'Example Text', 'wholesome-plugin') }
onChange={ (exampleText) => this.setState( { exampleText }) }
value={ exampleText }
/>
</PanelBody>
</Panel>) }
}
export default function Edit( props) {
return (<div { ...useBlockProps() }>
<OptionsExample { ...props }/>
</div>
);
}
Możesz rozpoznać, że interfejs użytkownika, który wprowadziliśmy, jest dokładnie taki sam, jak w przewodniku Gutenberg Meta Box, w którym użyliśmy atrybutów post meta. Możesz również zauważyć, że jeszcze nie pobieramy ani nie ustawiamy informacji za pomocą opcji, a zamiast tego używamy tylko komponentu state.
Korzystanie ze stanu
Powodem, dla którego stworzyliśmy niestandardowy komponent, a następnie przekazaliśmy go do naszej Editfunkcji, jest możliwość wykorzystania stanu. Zrobiliśmy to, ponieważ:
- Stworzymy funkcję do ładowania opcji z API i musimy to przechowywać w stanie, aby nasze komponenty mogły to odczytać
- Nie chcemy, aby API aktualizowało opcje, gdy tylko tekst zmieni się w naszym polu tekstowym, więc potrzebujemy funkcji, aby zapisać stan w opcjach przez API po zapisaniu postu
Korzystanie ze stanu jest dość proste. W konstruktorze inicjujemy stan tak:
this.state = { exampleText: '' };
A w komponencie uzyskujemy do niego dostęp podobny do tego, w jaki uzyskaliśmy dostęp do atrybutów w poprzednim przewodniku:
const { exampleText } = this.state;
Różnica polega na tym, że w naszej onChangemetodzie zamiast setAttributesużywać this.setState.
Pobieranie opcji z API
W górnej części importu dokumentu apiz @wordpress/api:
import api from '@wordpress/api';
Dodaj nową właściwość do miejsca, w którym zainicjowano stan isAPILoaded. Będzie nam to potrzebne, aby upewnić się, że nie spróbujemy uzyskać dostępu do interfejsu API ani nie wyrenderować komponentu przed załadowaniem interfejsu API.
this.state = {
exampleText: '',
isAPILoaded: false,
};
Teraz wewnątrz utworzonego przez nas komponentu dodaj blok kodu pod konstruktorem o nazwie componentDidMount. Jest to metoda cyklu życia Reacta, która jest wywoływana po dodaniu komponentu do DOM.
W tym bloku kodu dodaj następujące elementy:
componentDidMount() {
api.loadPromise.then(() => {
this.settings = new api.models.Settings();
const { isAPILoaded } = this.state;
if (isAPILoaded === false) {
this.settings.fetch().then( (response) => {
this.setState( {
exampleText: response[ 'wholesomecode_wholesome_plugin_example_text' ],
isAPILoaded: true,
} );
} );
}
} );
}
Tutaj mamy dostęp do opcji, którą zarejestrowaliśmy wcześniej z register_settingfunkcją.
Ten blok kodu wykonuje następujące czynności:
- Pobiera ustawienia z interfejsu API ustawień WordPress Guttenberg.
- Dostaje
isAPILoadedod sate - Jeśli API nie zostało załadowane, pobiera ustawienia z API w a
response - Następnie ustawiamy stan, aby zaktualizować stan z opcją, do której chcemy uzyskać dostęp i ustawiamy
isAPILoadedstan na true
Zatrzymaj renderowanie bloków bez ustawień
Nie chcemy, aby nasz blok był renderowany przed wypełnieniem ustawień. Aby się tym zająć, możemy zaimportować PlaceHolder i Spinner z $wordpress/components:
import {
Panel,
PanelBody,
Placeholder,
Spinner,
TextControl,
} from '@wordpress/components';
Następnie w metodzie renderowania komponentu upewnij się, że otrzymujesz isAPILoadedze stanu i wypisujesz Placeholderi Spinnerjeśli nie:
const {
exampleText,
isAPILoaded,
} = this.state;
if (! isAPILoaded) {
return (<Placeholder>
<Spinner />
</Placeholder>
);
}
W ten sposób, jeśli opcje nie zostały załadowane, otrzymamy ładny symbol zastępczy do momentu załadowania komponentu:
Symbol zastępczy i Spinner
Podłączanie do Gutenberga na Save
Teraz, gdy czytamy opcje z tabeli opcji, potrzebujemy sposobu na zapisanie tych opcji, gdy je zmienimy. Aby to zrobić, udajemy się subscribedo magazynu danych WordPress Gutenberg, który wskaże, kiedy coś się zmieniło.
Korzystając z tego, utworzymy słuchacza, kiedy post zostanie zapisany i zapiszemy nasze ustawienia, gdy to się stanie.
Aby to zrobić, importuj subscribei selectz @wordpress/data.
import { select, subscribe } from '@wordpress/data';
Następnie u góry componentDidMountbloku kodu napisz:
subscribe(() => {
const { exampleText } = this.state;
const isSavingPost = select('core/editor').isSavingPost();
const isAutosavingPost = select('core/editor').isAutosavingPost();
if (isAutosavingPost) {
return;
}
if (! isSavingPost) {
return;
}
const settings = new api.models.Settings( {
[ 'wholesomecode_wholesome_plugin_example_text' ]: exampleText,
} );
settings.save();
});
Kod wykonuje następujące czynności:
- Sprawdza, czy wpis się zapisuje
- Sprawdź, czy zapis jest zapisem automatycznym
- Jeśli wpis jest zapisywany i nie jest to autozapis, wepchnij nowe ustawienia do interfejsu Settings API
- Uruchom zapisywanie interfejsu Settings API.
Mały hack
Moglibyśmy tak zostawić nasz kod, ale ponieważ umieszczamy nasze ustawienia w bloku, a nie w pasku bocznym lub innym składniku edytora, jeśli zmienimy jedną z opcji i nic innego w edytorze, przycisk „zapisz" nie uaktywniać się.
Dzieje się tak, ponieważ nie używamy setAttributesani niczego do zmiany rzeczywistego kodu bloku.
Możemy to obejść, po prostu edytując inną część posta lub dodając mały hack do TextControlkodu:
onChange={ (exampleText) => { this.setState( { exampleText } ); setAttributes( { exampleText }) } }
Pamiętając o umieszczeniu tego wiersza kodu na początku metody render w celu wyodrębnienia setAttributesz props(ponieważ używamy Component, uzyskujemy dostęp do właściwości nieco innych z this.
const { setAttributes } = this.props;
Teraz, gdy zmienimy nasz atrybut, zmieni się atrybut „fałszywy”, co sprawi, że redaktor pomyśli, że możemy teraz zapisać post.
To trochę dziwaczne, ale w tym przypadku użycia robi to, czego potrzebujemy.
Cały Editkod
Oto cały kod potrzebny do Editmetody:
importuj {} z ’ @wordpress /i18n’; importuj api z ’ @wordpress /api’; importuj { useBlockProps } z ’ @wordpress /block-editor’; importuj { Panel, PanelBody, Placeholder, Spinner, TextControl, } z ’ @wordpress /components’; importuj { wybierz, zasubskrybuj } z ’ @wordpress /data’; importuj { Komponent } z „ @wordpress /element”;
import ’./editor.scss’;
class OptionsExample extends Składnik { konstruktor() { super( …argumenty );
this.state = {
exampleText: '',
isAPILoaded: false,
};
}
componentDidMount() {
subscribe( () => {
const { exampleText } = this.state;
const isSavingPost = select('core/editor').isSavingPost();
const isAutosavingPost = select('core/editor').isAutosavingPost();
if (isAutosavingPost) {
return;
}
if (! isSavingPost) {
return;
}
const settings = new api.models.Settings( {
[ 'wholesomecode_wholesome_plugin_example_text' ]: exampleText,
} );
settings.save();
});
api.loadPromise.then( () => {
this.settings = new api.models.Settings();
const { isAPILoaded } = this.state;
if (isAPILoaded === false) {
this.settings.fetch().then( (response) => {
this.setState( {
exampleText: response[ 'wholesomecode_wholesome_plugin_example_text' ],
isAPILoaded: true,
} );
} );
}
} );
}
render() {
const {
exampleText,
isAPILoaded,
} = this.state;
const { setAttributes } = this.props;
if (! isAPILoaded) {
return (<Placeholder>
<Spinner />
</Placeholder>
);
}
return (<Panel>
<PanelBody
title={ __( 'Example Meta Box', 'wholesomecode') }
icon="admin-plugins"
>
<TextControl
help={ __( 'This is an example text field.', 'wholesome-plugin') }
label={ __( 'Example Text', 'wholesome-plugin') }
onChange={ (exampleText) => { this.setState( { exampleText } ); setAttributes( { exampleText }) } }
value={ exampleText }
/>
</PanelBody>
</Panel>) }
}
eksportuj funkcję domyślną Edit( props) { return (
); }
### Remove the Attributes
Option up `src/index.js` and remove the attributes block that we placed there in the previous guides. We are not storing any attributes, the data will be pushed into and retrieved from the options table.
Render the Output
Because we have saved our attribute as settings in the WordPress options table, we could output this anywhere in WordPress using `get_option`:
get_option( 'wholesomecode_wholesome_plugin_block_text’, ” );
Continuing from the [Dynamic Block guide](https://wholesomecode.ltd/guides/php-render-block-wordpress-gutenberg/), let's see how we can access this attribute on the server side in PHP.
With this in mind, let’s update our `register_block_type` to output the option:
register_block_type( 'wholesomecode/wholesome-plugin’, array( 'editor_script’ => 'wholesomecode-wholesome-plugin-block-editor’, 'editor_style’ => 'wholesomecode-wholesome-plugin-editor’, 'render_callback’ = > function( $attributes, $content) { $example_text = get_option( 'wholesomecode_wholesome_plugin_example_text’ ); return "
$przykład_tekst
"; }, 'style’ => 'wholesomecode-wholesome-plugin-block’,) );
Note that we no longer need to register the `attributes` here, because we are only accessing the post meta field via the `get_post_meta` function.
5.
Using the Block
--------------------
Putting it all together, let’s see the block in action:
:
import {} z ’ @wordpress /i18n’; importuj api z ’ @wordpress /api’; importuj { useBlockProps } z ’ @wordpress /block-editor’; importuj { Panel, PanelBody, PanelRow, Placeholder, SelectControl, Spinner, TextControl, ToggleControl, } z ’ @wordpress /components’; importuj { wybierz, zasubskrybuj } z ’ @wordpress /data’; importuj { Komponent } z „ @wordpress /element”;
import ’./editor.scss’;
class OptionsExample extends Składnik { konstruktor() { super( …argumenty );
this.state = {
exampleSelect: '',
exampleText: '',
exampleText2: '',
exampleText3: '',
exampleToggle: false,
isAPILoaded: false,
};
}
componentDidMount() {
subscribe( () => {
const {
exampleSelect,
exampleText,
exampleText2,
exampleText3,
exampleToggle,
} = this.state;
const isSavingPost = select('core/editor').isSavingPost();
const isAutosavingPost = select('core/editor').isAutosavingPost();
if (isAutosavingPost) {
return;
}
if (! isSavingPost) {
return;
}
const settings = new api.models.Settings( {
[ 'wholesomecode_wholesome_plugin_example_select' ]: exampleSelect,
[ 'wholesomecode_wholesome_plugin_example_text' ]: exampleText,
[ 'wholesomecode_wholesome_plugin_example_text_2' ]: exampleText2,
[ 'wholesomecode_wholesome_plugin_example_text_3' ]: exampleText3,
[ 'wholesomecode_wholesome_plugin_example_toggle' ]: exampleToggle,
} );
settings.save();
});
api.loadPromise.then( () => {
this.settings = new api.models.Settings();
const { isAPILoaded } = this.state;
if (isAPILoaded === false) {
this.settings.fetch().then( (response) => {
this.setState( {
exampleSelect: response[ 'wholesomecode_wholesome_plugin_example_select' ],
exampleText: response[ 'wholesomecode_wholesome_plugin_example_text' ],
exampleText2: response[ 'wholesomecode_wholesome_plugin_example_text_2' ],
exampleText3: response[ 'wholesomecode_wholesome_plugin_example_text_3' ],
exampleToggle: Boolean( response[ 'wholesomecode_wholesome_plugin_example_toggle' ] ),
isAPILoaded: true,
} );
} );
}
} );
}
render() {
const {
exampleSelect,
exampleText,
exampleText2,
exampleText3,
exampleToggle,
isAPILoaded,
} = this.state;
if (! isAPILoaded) {
return (<Placeholder>
<Spinner />
</Placeholder>
);
}
return (<Panel>
<PanelBody
title={ __( 'Example Meta Box', 'wholesome-plugin') }
icon="admin-plugins"
>
<SelectControl
help={ __( 'An example dropdown field.', 'wholesome-plugin') }
label={ __( 'Example Select', 'wholesome-plugin') }
onChange={ (exampleSelect) => this.setState( { exampleSelect }) }
options={ [
{
label: __( 'Please Select...', 'wholesome-plugin' ),
value: '',
},
{
label: __( 'Option 1', 'wholesome-plugin' ),
value: 'option-1',
},
{
label: __( 'Option 2', 'wholesome-plugin' ),
value: 'option-2',
},
] }
value={ exampleSelect }
/>
<TextControl
help={ __( 'This is an example text field.', 'wholesome-plugin') }
label={ __( 'Example Text', 'wholesome-plugin') }
onChange={ (exampleText) => this.setState( { exampleText }) }
value={ exampleText }
/>
<PanelRow>
<TextControl
help={ __( 'Use PanelRow to place controls inline.', 'wholesome-plugin') }
label={ __( 'Example Text 2', 'wholesome-plugin') }
onChange={ (exampleText2) => this.setState( { exampleText2 }) }
value={ exampleText2 }
/>
<TextControl
help={ __( 'This control is inline.', 'wholesome-plugin') }
label={ __( 'Example Text 3', 'wholesome-plugin') }
onChange={ (exampleText3) => this.setState( { exampleText3 }) }
value={ exampleText3 }
/>
</PanelRow>
<ToggleControl
checked={ exampleToggle }
help={ __( 'An example toggle.', 'wholesome-plugin') }
label={ __( 'Example Toggle', 'wholesome-plugin') }
onChange={ (exampleToggle) => this.setState( { exampleToggle }) }
/>
</PanelBody>
</Panel>) }
}
eksportuj funkcję domyślną Edit( props) { return (
); } `
Oto wynik:
Dodatkowe opcje
- Spójrz na tworzenie zagnieżdżonych bloków podrzędnych za pomocą
InnerBlockskomponentu - Zobacz, jak używać meta pól postu w blokach Gutenberga
- Spójrz na tworzenie niestandardowych skrzynek Meta w Gutenberg