Crie uma página de configurações usando os componentes do WordPress Block Editor (Gutenberg)
Anteriormente, analisamos as opções e configurações de armazenamento usando o WordPress Block Editor (Gutenberg) e expandimos o Create Block Script para permitir pontos finais extras. Neste guia, vamos colocá-los todos juntos para criar uma página de configurações usando componentes do Gutenberg.
A página de configurações que estamos prestes a construir
Mas primeiro, crédito onde o crédito é devido, a inspiração para este guia vai para o artigo Code in WP de Hardeep Asrani: Making a “Plugin Options Page" With Gutenberg Components.
Pré-requisitos
- Ter seguido o guia Criando um Plugin para o Editor de Blocos do WordPress (Gutenberg)
- Seguiu o guia Using Options to Store Data in the WordPress Block Editor (Gutenberg)
- Seguiu o guia Adicionar pontos de entrada ao WordPress Create Block Script
Crie a página de configurações em PHP
Seguindo os guias nos pré-requisitos, abra o arquivo PHP raiz do plugin (neste caso wholesome-plugin.php) e adicione o seguinte:
Registre as configurações
Como nas opções de uso para armazenar dados, adicione as seguintes configurações ao arquivo:
function wholesomecode_wholesome_plugin_register_settings() {
register_setting(
'wholesomecode_wholesome_plugin_settings',
'wholesomecode_wholesome_plugin_example_select',
[
'default' => '',
'show_in_rest' => true,
'type' => 'string',
]
);
register_setting(
'wholesomecode_wholesome_plugin_settings',
'wholesomecode_wholesome_plugin_example_text',
[
'default' => '',
'show_in_rest' => true,
'type' => 'string',
]
);
register_setting(
'wholesomecode_wholesome_plugin_settings',
'wholesomecode_wholesome_plugin_example_text_2',
[
'default' => '',
'show_in_rest' => true,
'type' => 'string',
]
);
register_setting(
'wholesomecode_wholesome_plugin_settings',
'wholesomecode_wholesome_plugin_example_text_3',
[
'default' => '',
'show_in_rest' => true,
'type' => 'string',
]
);
register_setting(
'wholesomecode_wholesome_plugin_settings',
'wholesomecode_wholesome_plugin_example_toggle',
[
'default' => '',
'show_in_rest' => true,
'type' => 'string',
]
);
}
add_action( 'init', 'wholesomecode_wholesome_plugin_register_settings', 10 );
Registre as configurações que acessaremos na página de configurações. Certifique-se de definir show_in_restpara truecada um, para que possam ser acessados via Gutenberg.
Registre a página de configurações
Adicione o bloco de código para registrar a página de configurações:
function wholesomecode_wholesome_plugin_settings_page() {
add_options_page(
__( 'Wholesome Plugin Settings', 'wholesome-plugin' ),
__( 'Wholesome Plugin Settings', 'wholesome-plugin' ),
'manage_options',
'wholesome_plugin_settings',
function() {
?>
<div id="wholesome-plugin-settings"></div>
<?php
},
);
}
add_action( 'admin_menu', 'wholesomecode_wholesome_plugin_settings_page', 10 );
O código acima adiciona uma nova página ao menu de configurações. Observe que tudo o que ele faz é produzir um <div>, é isso que usaremos para renderizar os componentes Gutenberg baseados em React.
Item de menu de configurações
Enfileirar recursos de administrador
Para o próximo bloco de código, precisamos ter seguido todas as etapas do guia Adicionar pontos de entrada ao script de criação de bloco . Certifique-se de seguir todas as etapas desse guia antes desta etapa e volte e siga o restante deste guia.
function wholesomecode_wholesome_plugin_admin_scripts() {
$dir = __DIR__;
$script_asset_path = "$dir/build/admin.asset.php";
if (! file_exists( $script_asset_path)) {
throw new Error(
'You need to run `npm start` or `npm run build` for the "wholesomecode/wholesome-plugin" block first.'
);
}
$admin_js = 'build/admin.js';
$script_asset = require( $script_asset_path );
wp_enqueue_script(
'wholesomecode-wholesome-plugin-admin-editor',
plugins_url( $admin_js, __FILE__ ),
$script_asset['dependencies'],
$script_asset['version']
);
wp_set_script_translations( 'wholesomecode-wholesome-plugin-block-editor', 'wholesome-plugin' );
$admin_css = 'build/admin.css';
wp_enqueue_style(
'wholesomecode-wholesome-plugin-admin',
plugins_url( $admin_css, __FILE__ ),
['wp-components'],
filemtime( "$dir/$admin_css") );
}
add_action( 'admin_enqueue_scripts', 'wholesomecode_wholesome_plugin_admin_scripts', 10 );
Construir a página de administração em JavaScript
Se você seguiu todas as etapas no guia Adicionar pontos de entrada ao guia Criar script de bloco, você deve ter um /src/admin.jsarquivo. Abra esse arquivo e exclua seu conteúdo.
Renderize o componente
Lembre-se de executar npm startem seu terminal de acordo com o guia de criação de plug -ins e adicione o abaixo ao seu /src/admin.jsarquivo.
import './admin.scss';
import { Icon } from '@wordpress/components';
import {
Fragment,
render,
Component,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
class App extends Component {
constructor() {
super( ...arguments );
}
render() {
return (<Fragment>
<div className="wholesome-plugin__header">
<div className="wholesome-plugin__container">
<div className="wholesome-plugin__title">
<h1>{ __( 'Wholesome Plugin Settings', 'wholesome-plugin') } <Icon icon="admin-plugins" /></h1>
</div>
</div>
</div>
<div className="wholesome-plugin__main"></div>
</Fragment>) }
}
document.addEventListener( 'DOMContentLoaded',() => {
const htmlOutput = document.getElementById( 'wholesome-plugin-settings' );
if (htmlOutput) {
render(
<App />,
htmlOutput
);
}
});
Se você for para a página de configurações no navegador, agora deverá ver o seguinte:
Renderizando componentes na tela de configurações
Adicione os campos de configuração
Lembre-se das etapas extras no guia ‘Usando opções para armazenar dados’? Bem, vamos colar tudo literalmente neste componente, então você deve acabar com algum código que se parece um pouco com isso:
import './admin.scss';
import api from '@wordpress/api';
import {
Button,
Icon,
Panel,
PanelBody,
PanelRow,
Placeholder,
SelectControl,
Spinner,
TextControl,
ToggleControl,
} from '@wordpress/components';
import {
Fragment,
render,
Component,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
class App extends Component {
constructor() {
super( ...arguments );
this.state = {
exampleSelect: '',
exampleText: '',
exampleText2: '',
exampleText3: '',
exampleToggle: false,
isAPILoaded: false,
};
}
componentDidMount() {
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 (<Fragment>
<div className="wholesome-plugin__header">
<div className="wholesome-plugin__container">
<div className="wholesome-plugin__title">
<h1>{ __( 'Wholesome Plugin Settings', 'wholesome-plugin') } <Icon icon="admin-plugins" /></h1>
</div>
</div>
</div>
<div className="wholesome-plugin__main">
<Panel>
<PanelBody
title={ __( 'Panel Body One', '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 }
/>
</PanelBody>
<PanelBody
title={ __( 'Panel Body Two', '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>
<PanelBody
title={ __( 'Panel Body Three', 'wholesome-plugin') }
icon="admin-plugins"
>
<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>
</PanelBody>
<PanelBody
title={ __( 'Panel Body Four', 'wholesome-plugin') }
icon="admin-plugins"
>
<ToggleControl
checked={ exampleToggle }
help={ __( 'An example toggle.', 'wholesome-plugin') }
label={ __( 'Example Toggle', 'wholesome-plugin') }
onChange={ (exampleToggle) => this.setState( { exampleToggle }) }
/>
</PanelBody>
<Button
isPrimary
isLarge
onClick={ () => {}}
>
{ __( 'Save', 'wholesome-plugin') }
</Button>
</Panel>
</div>
</Fragment>) }
}
document.addEventListener( 'DOMContentLoaded', () => {
const htmlOutput = document.getElementById( 'wholesome-plugin-settings' );
if (htmlOutput) {
render(
<App />,
htmlOutput
);
}
});
Além da remoção do subscribein componentDidMountque usamos anteriormente para salvar nesse guia e da adição do botão, o código é praticamente copiar e colar.
Se tudo estiver bem, nossa página de configurações agora deve se parecer um pouco com isso:
Renderizando os campos de configuração
Não se preocupe, vamos limpar os estilos na seção 4 deste guia.
Lidar com o Salvar
No onClickmanipulador do <button>componente adicione o seguinte código:
<Button
isPrimary
isLarge
onClick={ () => {
const {
exampleSelect,
exampleText,
exampleText2,
exampleText3,
exampleToggle,
} = this.state;
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? 'true': '',
} );
settings.save();
}}
>
{ __( 'Save', 'wholesome-plugin') }
</Button>
Isso salvará nossas opções e configurações quando o botão for clicado. No entanto, não há indicação de que as opções foram salvas por padrão.
Criar a notificação
Para dar ao nosso usuário algum feedback de que as opções e configurações foram salvas, vamos implementar uma notificação de ‘snackbar’. Esta é a mesma notificação usada na tela principal do Editor de Post que o editor de blocos usa quando o post foi salvo.
Para adicionar isso, precisamos portar um componente principal do Gutenberg em nossa compilação, pois a lista de notificações não está disponível usando as instruções de importação usuais.
Precisamos adicionar o seguinte código no arquivo:
import { SnackbarList } from '@wordpress/components';
import {
dispatch,
useDispatch,
useSelect,
} from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
const Notices = () => {
const notices = useSelect( (select) =>
select( noticesStore) .getNotices()
.filter( (notice) => notice.type === 'snackbar' ),
[]
);
const { removeNotice } = useDispatch( noticesStore );
return (<SnackbarList
className="edit-site-notices"
notices={ notices }
onRemove={ removeNotice }
/>
);
};
Em seguida, na renderização principal do <App>componente, adicione o seguinte antes do fechamento </Fragment>:
<div className="wholesome-plugin__notices">
<Notices/>
</div>
Por fim, adicione o seguinte ao onClickmanipulador do botão:
dispatch('core/notices').createNotice(
'success',
__( 'Settings Saved', 'wholesome-plugin' ),
{
type: 'snackbar',
isDismissible: true,
}
);
Isso criará um pequeno pop-up ‘snackbar’ sempre que as configurações forem salvas.
Notificação da lanchonete em ação
Eu sei, eu sei, ainda precisamos corrigir esses estilos.
O /src/admin.jsarquivo completo
Para referência, aqui está o /src/admin.jscódigo completo do arquivo:
import './admin.scss';
import api from '@wordpress/api';
import {
Button,
Icon,
Panel,
PanelBody,
PanelRow,
Placeholder,
SelectControl,
SnackbarList,
Spinner,
TextControl,
ToggleControl,
} from '@wordpress/components';
import {
dispatch,
useDispatch,
useSelect,
} from '@wordpress/data';
import {
Fragment,
render,
Component,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
const Notices = () => {
const notices = useSelect( (select) =>
select( noticesStore) .getNotices()
.filter( (notice) => notice.type === 'snackbar' ),
[]
);
const { removeNotice } = useDispatch( noticesStore );
return (<SnackbarList
className="edit-site-notices"
notices={ notices }
onRemove={ removeNotice }
/>
);
};
class App extends Component {
constructor() {
super( ...arguments );
this.state = {
exampleSelect: '',
exampleText: '',
exampleText2: '',
exampleText3: '',
exampleToggle: false,
isAPILoaded: false,
};
}
componentDidMount() {
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 (<Fragment>
<div className="wholesome-plugin__header">
<div className="wholesome-plugin__container">
<div className="wholesome-plugin__title">
<h1>{ __( 'Wholesome Plugin Settings', 'wholesome-plugin') } <Icon icon="admin-plugins" /></h1>
</div>
</div>
</div>
<div className="wholesome-plugin__main">
<Panel>
<PanelBody
title={ __( 'Panel Body One', '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 }
/>
</PanelBody>
<PanelBody
title={ __( 'Panel Body Two', '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>
<PanelBody
title={ __( 'Panel Body Three', 'wholesome-plugin') }
icon="admin-plugins"
>
<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>
</PanelBody>
<PanelBody
title={ __( 'Panel Body Four', 'wholesome-plugin') }
icon="admin-plugins"
>
<ToggleControl
checked={ exampleToggle }
help={ __( 'An example toggle.', 'wholesome-plugin') }
label={ __( 'Example Toggle', 'wholesome-plugin') }
onChange={ (exampleToggle) => this.setState( { exampleToggle }) }
/>
</PanelBody>
<Button
isPrimary
isLarge
onClick={ () => {
const {
exampleSelect,
exampleText,
exampleText2,
exampleText3,
exampleToggle,
} = this.state;
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? 'true': '',
} );
settings.save();
dispatch('core/notices').createNotice(
'success',
__( 'Settings Saved', 'wholesome-plugin' ),
{
type: 'snackbar',
isDismissible: true,
}
);
}}
>
{ __( 'Save', 'wholesome-plugin') }
</Button>
</Panel>
</div>
<div className="wholesome-plugin__notices">
<Notices/>
</div>
</Fragment>) }
}
document.addEventListener( 'DOMContentLoaded', () => {
const htmlOutput = document.getElementById( 'wholesome-plugin-settings' );
if (htmlOutput) {
render(
<App />,
htmlOutput
);
}
});
Adicione o SCSS
Hora de corrigir esses estilos. Basta adicionar o seguinte no /src/admin.scssarquivo (que você teria criado no guia Adicionar Pontos de Entrada ao Criar Script de Bloco .
#wholesome-plugin-settings {
.components-placeholder {
background: #f1f1f1;
}
.wholesome-plugin__header {
background-color: #ffffff;
box-shadow: 0 1px 0 rgba(213, 213, 213, .5), 0 1px 2px #eeeeee;
margin-left: -2em;
padding: 20px 10px;
.wholesome-plugin__container {
margin: 0 auto;
max-width: 750px;
.wholesome-plugin__title {
align-items: center;
display: flex;
justify-content: center;
.dashicon {
color: #757575;
}
}
}
}
.wholesome-plugin__main {
margin-left: auto;
margin-right: auto;
max-width: 750px;
.components-panel {
background: none;
border: none;
}
.components-panel__body {
background: #ffffff;
border: 1px solid #e2e4e7;
margin: 1rem 0;
}
}
.components-base-control__help {
margin-top: .5rem;
}
.components-panel__row {
> div {
flex-grow: 1;
margin-right: 1rem;
&:last-of-type {
margin-right: 0;
}
}
}
.wholesome-plugin__notices {
.components-snackbar {
bottom: .5rem;
position: fixed;
}
}
}
Visualizando a página de configurações
Aqui está o resultado final:
A página de configurações
Alguns plugins têm um link de ‘configurações’ na página de plugins assim:
Link de configurações no painel da página de configurações do plug-in
Para conseguir isso, adicione o seguinte bloco de código à raiz do seu arquivo de plug-in (neste caso wholesome-plugin.php):
function wholesomecode_wholesome_plugin_settings_link( $links ): array {
$label = esc_html__( 'Settings', 'wholesome-plugin' );
$slug = 'wholesome_plugin_settings';
array_unshift( $links, "<a href='options-general.php?page=$slug'>$label</a>" );
return $links;
}
add_action( 'plugin_action_links_'. plugin_basename( __FILE__ ), 'wholesomecode_wholesome_plugin_settings_link', 10 );
- 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 um painel personalizador com componentes Gutenberg