Usando Opções para Armazenar Dados no Editor de Blocos do WordPress (Gutenberg)
Exploramos anteriormente o armazenamento de dados do editor de blocos do WordPress (Gutenberg) em atributos de bloco e em post meta, mas você sabia que pode armazenar e recuperar na tabela de opções do WordPress importando apide @wordpress/api.
Neste guia, vamos dar uma olhada no que você escreveria classicamente em PHP como update_optione get_option.
Para implementar isso, precisamos aproveitar o ciclo de vida do React, então veremos a criação de um React Component importando Componentdo @wordpress/element.
Pré-requisitos
- Familiarize-se com a criação de plugins para WordPress Gutenberg
- Familiarize-se com blocos dinâmicos e renderização do lado do servidor
- Entenda como você pode criar meta boxes em Gutenberg
Esse último requisito é útil para a interface do usuário que usaremos neste guia, no entanto, em aplicativos do mundo real, é provável que você use esse método em uma barra lateral ou página de opções.
Registrar as opções em PHP
Antes de podermos usar uma opção em JavaScript, temos que ter certeza de que a registramos em PHP usando register_settinge que o show_in_restargumento foi definido como true.
Seguindo o guia do Bloco Dinâmico, abra o arquivo PHP raiz do plug-in (neste caso wholesome-plugin.php) e adicione o seguinte código ao final desse arquivo após todas as outras funções:
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' );
Este código registra um metacampo chamado wholesomecode_wholesome_plugin_block_textpara o wholesomecode_wholesome_plugin_settingsgrupo de opções. Também garante que a API REST possa acessar esse metacampo com o show_in_restvalor definido como true.
Criar o componente
Abra o /src/edit.jsarquivo, vamos alterar um pouco a estrutura deste arquivo para que possamos gerar nosso arquivo Component.
Recorte e cole a totalidade deste bloco de código no /src/edit.jsarquivo, abordaremos o que ele faz em um momento:
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>
);
}
Você pode reconhecer que a interface do usuário que implementamos é exatamente a mesma do guia Gutenberg Meta Box, onde usamos meta atributos post. Você também pode notar que ainda não estamos obtendo ou configurando informações usando opções e, em vez disso, estamos apenas usando o componente state.
Usando Estado
A razão pela qual criamos um componente personalizado e o passamos para nossa Editfunção é para que possamos aproveitar o estado. Fizemos isso porque:
- Vamos criar uma função para carregar as opções da API, e precisamos armazenar isso no estado para que nossos componentes possam lê-lo
- Não queremos que a API atualize as opções assim que o texto mudar em nossa caixa de texto, então precisamos de uma função para salvar o estado nas opções por meio da API depois que a postagem for salva
Usar o estado é bastante simples. No construtor inicializamos o estado assim:
this.state = { exampleText: '' };
E no componente acessamos de forma semelhante a como acessamos atributos no guia anterior:
const { exampleText } = this.state;
A diferença é que, em nosso onChangemétodo, em vez de usar setAttributesusamos this.setState.
Obtendo opções da API
Na parte superior do documento, importe apide @wordpress/api:
import api from '@wordpress/api';
Adicione uma nova propriedade onde o estado é inicializado de isAPILoaded. Precisaremos disso para garantir que não tentamos acessar a API ou renderizar o componente antes que a API seja carregada.
this.state = {
exampleText: '',
isAPILoaded: false,
};
Agora dentro do Component que criamos, adicione um bloco de código sob o construtor chamado componentDidMount. Este é um método de ciclo de vida React, que é chamado depois que um componente foi adicionado ao DOM.
Nesse bloco de código, adicione o seguinte:
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,
} );
} );
}
} );
}
Aqui estamos acessando a opção que registramos anteriormente com a register_settingfunção.
Este bloco de código faz o seguinte:
- Obtém as configurações da API de configurações do WordPress Guttenberg.
- Obtém
isAPILoadeddo estado - Se a API não foi carregada, ela busca as configurações da API em um
response - Em seguida, definimos o estado para atualizar o estado com a opção que queremos acessar e definimos o
isAPILoadedestado como verdadeiro
Pare a renderização do bloco sem configurações
Não queremos que nosso bloco seja renderizado antes que as configurações sejam preenchidas. Para cuidar disso, podemos importar um PlaceHolder e um Spinner de $wordpress/components:
import {
Panel,
PanelBody,
Placeholder,
Spinner,
TextControl,
} from '@wordpress/components';
Em seguida, no método de renderização do componente, certifique-se de obter isAPILoadeddo estado e de saída Placeholdere Spinnerse não tiver:
const {
exampleText,
isAPILoaded,
} = this.state;
if (! isAPILoaded) {
return (<Placeholder>
<Spinner />
</Placeholder>
);
}
Dessa forma, se as opções não foram carregadas, obtemos um bom espaço reservado até que o componente seja carregado:
Espaço reservado e girador
Conectando-se ao Gutenberg em Salvar
Agora que estamos lendo as opções da tabela de opções, precisamos de uma maneira de salvar essas opções quando as alteramos. Para fazer isso, vamos ao subscribearmazenamento de dados do WordPress Gutenberg, que indicará quando algo mudou.
Usando isso, criaremos um ouvinte para quando a postagem for salva e salvaremos nossas configurações quando isso acontecer.
Para fazer essa importação subscribee selectde @wordpress/data.
import { select, subscribe } from '@wordpress/data';
Em seguida, no topo do componentDidMountbloco de código, escreva o seguinte:
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();
});
O código faz o seguinte:
- Verifica se a postagem está salvando
- Verifique se o salvamento é um salvamento automático
- Se a postagem estiver salvando e não for um salvamento automático, envie as novas configurações para a API de configurações
- Acione um salvamento da API de configurações.
Um pequeno hack
Poderíamos deixar nosso código assim, mas como estamos colocando nossas configurações em um bloco, e não em uma barra lateral ou outro componente do editor, se alterarmos uma das opções, e nada mais no editor, o botão ‘salvar’ não tornar-se ativo.
Isso porque não estamos usando setAttributesnem nada para alterar o código real do bloco.
Podemos contornar isso, apenas editando outra parte do post ou adicionando um pequeno hack no TextControlcódigo:
onChange={ (exampleText) => { this.setState( { exampleText } ); setAttributes( { exampleText }) } }
Lembrando de colocar essa linha de código no topo do método render para extrair setAttributesdo props(porque estamos usando um Component acessamos props um pouco diferente com this.
const { setAttributes } = this.props;
Agora, quando mudamos nosso atributo, um atributo ‘fake’ vai mudar, fazendo com que o editor pense que agora podemos salvar o post.
É um pouco hacky, mas para este caso de uso ele faz o que precisamos.
Todo o Editcódigo
Aqui está todo o código que você precisa para o Editmétodo:
i `importar {} de ‘ @wordpress /i18n’; importar api de ‘ @wordpress /api’; import { useBlockProps } de ‘ @wordpress /block-editor’; import { Panel, PanelBody, Placeholder, Spinner, TextControl, } from ‘ @wordpress /components’; import { selecione, assine } de ‘ @wordpress /data’; import { Componente } de ‘ @wordpress /element’;
import ‘./editor.scss’;
class OptionsExample extends Component { constructor() { super( …arguments );
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>) }
}
export função padrão 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-block-editor’, ‘render_callback’ = > function( $atributos, $conteúdo) { $example_text = get_option(‘wholesomecode_wholesome_plugin_example_text’); return "
$example_text
"; }, ‘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:
:
importar {} de ‘ @wordpress /i18n’; importar api de ‘ @wordpress /api’; import { useBlockProps } de ‘ @wordpress /block-editor’; import { Panel, PanelBody, PanelRow, Placeholder, SelectControl, Spinner, TextControl, ToggleControl, } from ‘ @wordpress /components’; import { selecione, assine } de ‘ @wordpress /data’; import { Componente } de ‘ @wordpress /element’;
import ‘./editor.scss’;
class OptionsExample extends Component { constructor() { super( …arguments );
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>) }
}
export função padrão Edit(props) { return (
); } `
Aqui está o resultado:
Opções extras
- Dê uma olhada na criação de blocos filho aninhados com o
InnerBlockscomponente - Dê uma olhada no uso de meta campos post em blocos Gutenberg
- Dê uma olhada na criação de Meta Boxes personalizadas em Gutenberg