Como criar formulários do Admin do Magento 2

Lucas Teixeira dos Santos Santana - May 11 '22 - - Dev Community

Contextualizando

O que é UI Component?

Os Ui Components são usados para representar elementos distintos da interface do usuário, como tabelas, botões, formulários e outros. São componentes simples e flexíveis projetados para renderização da interface do usuário, estes componentes são responsáveis por renderizar fragmentos de página e fornecer/suportar interações adicionais de componentes JavaScript.

Os UI Components são implementados no módulo Magento_UI. Para utilizar os Ui Components em um módulo personalizado é necessário adicionar uma dependência para o módulo Magento_UI no arquivo composer.json do módulo.

No Magento 2 os Ui Components básicos são os formulários e as grades de listagens, todos os outros são secundários. Os componentes básicos são declarados nos arquivos de layout da página, já os componentes secundários são declarados nos arquivos de configuração das instâncias dos componentes de nível superior.

Saiba como inserir ui_components em uma página no post "Como criar arquivos de layout Magento 2".

Formulário

Os formulários são utilizados para disponibilizar os campos para serem cadastrados e/ou editados de uma entidade, sendo que esses campos podem ser divididos por conjuntos de campos (tabulações laterais) para especificar a quais campos pertencem a área.


Código para criar um formulário

Provedor de Dados

É a classe PHP responsável por providenciar os dados do banco e deixar disponível através do atributo name. A interface \Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface deve ser implementada pela classe \{Vendor}\{Module}\Ui\Component\{EntityName}\FormDataProvider para criar e retornar os dados para o formulário da página.

<?php

namespace {Vendor}\{Module}\Ui\Component\{EntityName};

use Magento\Ui\DataProvider\AbstractDataProvider;
use {Vendor}\{Module}\Model\ResourceModel\{EntityName}\CollectionFactory;
use {Vendor}\{Module}\Model\ResourceModel\{EntityName}\Collection;
use {Vendor}\{Module}\Model\{EntityName};

class FormDataProvider extends AbstractDataProvider
{
    private Collection $collection;
    private array $loadedData;

    public function __construct(
        string $name,
        string $primaryFieldName,
        string $requestFieldName,
        CollectionFactory $collectionFactory,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
        parent::__construct(
            $name,
            $primaryFieldName,
            $requestFieldName,
            $meta,
            $data
        );
    }

    public function getData(): array
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }

        if (!$this->collection->getSize()) {
            return $this->loadedData = [];
        }

        if ($this->collection->getSize()) {
            /** @var {EntityName} $item */
            foreach ($this->collection->getItems() as $item) {
                $this->loadedData[$item->getId()] = $item->getData();
            }
        }

        return $this->loadedData;
    }
}
Enter fullscreen mode Exit fullscreen mode

ui_component.xml

Arquivos ui_components devem seguir a estrutura de pastas \{Vendor}\{Module}\view\{area}\ui_components\{ui_component_name}.xml.

O caminho da área pode ser frontend ou adminhtml que define onde o templates será aplicado. Para inserir um template no painel administrativo do Magento utilizá-se a área adminhtml, e para inserir um template na parte visual da loja do site utilizá-se a área frontend (não é muito usual serem utilizados nessa área).

<?xml version="1.0"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">{ui_component}.{ui_component}_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">{Label}</item>
    </argument>

    <settings>
        <buttons>
            <button name="{button_name}" class="{Vendor}\{Module}\Block\Adminhtml\{Directory}\{ButtonClassName}"/>
        </buttons>
        <namespace>{ui_component}</namespace>
        <layout>
            <navContainerName>left</navContainerName>
            <type>tabs</type>
        </layout>
        <deps>
            <dep>{ui_component}.{ui_component}_data_source</dep>
        </deps>
    </settings>

    <dataSource name="{ui_component}_data_source">
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
        <settings>
            <validateUrl path="{route_id}/{controller_directory}/{controller_name}"/>
            <submitUrl path="{route_id}/{controller_directory}/{controller_name}"/>
        </settings>
        <aclResource>{Vendor}_{Module}::{parentResourceId}</aclResource>
        <dataProvider class="{Vendor}\{Module}\Ui\Component\{EntityName}\FormDataProvider" name="{ui_component}_data_source">
            <settings>
                <requestFieldName>{id}</requestFieldName>
                <primaryFieldName>{entity_id}</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>

    <fieldset name="{fieldset_name}">
        <settings>
            <disabled>{true/false}</disabled>
            <label>{Fieldset Label}</label>
            <visible>{true/false}</visible>
            <additionalClasses>
                <class name="{class-name}">{true/false}</class>
            </additionalClasses>
            <level>{integer}</level>
            <collapsible>{true/false}</collapsible>
            <opened>{true/false}</opened>
        </settings>
    </fieldset>
</form>
Enter fullscreen mode Exit fullscreen mode

O nó <form> representa o componente de formulário que é uma coleção de campos que podem ser agrupados em guias e fieldsets.

Argument

O nó <argument> é uma estrutura arbitrária que declara a configuração do componente de formulário que inclui dados que são utilizados para inicializar um objeto. O nó filho <item name="provider"> é responsável por transferir os dados para o nó <dataSource>. O valor deste item é dividido em duas partes (que é separado por um ponto final), o primeiro valor é responsável por indicar o nome do UI component, já a segunda parte é responsável pelo nome do <dataSource>, que é destinado a processar os dados no servidor e fazer o UI component processar esses dados.

Settings

O nó <settings> é responsável por configurar o nó pai <form>.

  • O nó <buttons>: é responsável por adicionar botões no menu superior do formulário. Cada botão que queira adicionar basta criar um nó filho chamado <button> e preencher um atributo chamado name para este nó, que será o id deste botão. Para configurar o botão, basta criar nós filhos (<label>, <url> e <class>).
  • O nó <namespace>: é o nó responsável por identificar o formulário e passar ao back-end para realizar ações (validar, endiar, etc).
  • O nó <layout>: é responsável por definir qual o layout que o UI Component irá assumir. O nó filho <type> indica onde os fieldsets ficarão reparados. O nó <navContainerName> indica qual a posição da tabulação ficará de acordo com o atributo layout do arquivo de layout da página.
  • O nó <deps>: deve possuir um nó filho <dep> e possuir o mesmo valor do <item name="provider">.

Data Source

o nó <dataSource> referencia a classe que é responsável por obter os dados solicitados e interagir com o UI component (m*uitos *UI components necessitam deste nó para o seu funcionamento).

O atributo component="Magento_Ui/js/form/provider" é utilizado para um provedor de dados para o formulário em formato específico que é compartilhado entre todos os UI components no escopo do componente do formulário. É recomendado que o atributo seja nomeado como name=”{ui_component_name}_data_source”.

  • O nó <settings>: é responsável por configurar o nó pai <dataSource> e neste é possível definir qual será o controlador responsável por validar os dados do formulário através do nó <validateUrl>, e o nó <submitUrl> é responsável por definir para qual o controlador será enviado os dados do formulário.
  • O nó <aclResource>: define a qual recurso ACL está vinculado e se o usuário logado poderá ter acesso com base no seu papel de usuário, cada usuário ficará limitado aos recursos selecionados do seu papel.
  • O nó <dataProvider>: é a classe responsável por tratar os dados retornado do Provedor de Dados, esta classe deve ser referenciada através do atributo class e é possível definir a classe responsável por essa execução que deve estender a classe \Magento\Ui\DataProvider\AbstractDataProvider. O nó <dataProvider> tem o nó filho <settings>, onde é possível determinar o campo para usar como id para a grade no banco de dados (através do nó filho <primaryFieldName>) e para a solicitação (através do nó filho <requestFieldName>).

Fieldsets

O componente fieldset implementa um contêiner para elementos de formulário agrupados visualmente, como botões e campos de formulário. O nó <settings> é responsável por configurar o conjunto de campos, e possui os seguintes nós filhos:

Descrição Tipo
disabled Estado do componente inicial. Quando true, os usuários não podem realizar ações no elemento. bool
label Legenda para um item no contexto. string
visible Visibilidade do componente inicial. Quando false, o componente ficará oculto. bool
additionalClasses Classes personalizadas adicionadas ao bloco no componente do DOM. Objeto (nó filho XML: {true/false})
level Indica explicitamente o nível de aninhamento. int
collapsible Ativa/desativa a funcionalidade de retrair para ocultar os campos. bool
opened Estado inicial expandido . Aplicado quando a funcionalidade de está habilitada. bool

Campos

Atributos Descrição Tipo
class Caminho para a classe PHP responsável pela implementação de backend do componente. string
component O caminho para o arquivo .js do componente em termos de RequireJS. string
displayArea Renderiza o componente no local declarado no layout. string
elementTmpl O caminho para o modelo .html do tipo de campo específico. string
extends Estende a configuração do componente especificado. string
formElement Elemento do formulário. type
name O índice do elemento no escopo da coleção atual que será usado para construir seu identificador exclusivo. string
provider Referência o provedor de dados do componente. string
sortOrder Posição do elemento. integer
template O caminho para o modelo .html do componente. string
disabled Estado do componente inicial. Quando definido como verdadeiro, os usuários não podem realizar ações no elemento. bool
label Label do campo. string
visible Visibilidade do componente inicial. bool
notices A matriz de avisos que devem ser associados por chave com o valor da opção selecionada. array
Input
<field name="{input_name}" formElement="input" sortOrder="{integer}">
    <settings>
        <visible>{true/false}</visible>
        <label translate="true">{Label}</label>
    </settings>
</field>
Enter fullscreen mode Exit fullscreen mode

Input Field

Text
<field name="{text_name}" formElement="input" sortOrder="{integer}">
    <settings>
        <elementTmpl>ui/form/element/text</elementTmpl>
        <label translate="true">{Label Text}</label>
    </settings>
</field>
Enter fullscreen mode Exit fullscreen mode

Text Field

Textarea
<field name="{textarea_name}" formElement="textarea" sortOrder="{integer}">
    <settings>
        <label translate="true">{Label Textarea}</label>
    </settings>
</field>
Enter fullscreen mode Exit fullscreen mode

Textarea Field

Select
<field name="{select_name}" formElement="select" sortOrder="40">
    <settings>
        <label translate="true">{Label Select}</label>
    </settings>
    <formElements>
        <select>
            <settings>
                <options>
                    <option name="{option_name}" xsi:type="array">
                        <item name="value" xsi:type="string">{option_value}</item>
                        <item name="label" xsi:type="string">{Option Label}</item>
                    </option>
                </options>
                <caption translate="true">{-- Please Select --}</caption>
            </settings>
        </select>
    </formElements>
</field>
Enter fullscreen mode Exit fullscreen mode

Select Field

Multiselect
<field name="{multiselect_name}" formElement="multiselect" sortOrder="{integer}">
    <settings>
        <dataType>text</dataType>
        <label translate="true">{Label Multiselect}</label>
        <dataScope>multiselect_name</dataScope>
    </settings>
    <formElements>
        <multiselect>
            <settings>
                <options>
                    <option name="{option_name}" xsi:type="array">
                        <item name="value" xsi:type="string">{option_value}</item>
                        <item name="label" xsi:type="string">{Option Label}</item>
                    </option>
                </options>
            </settings>
        </multiselect>
    </formElements>
</field>
Enter fullscreen mode Exit fullscreen mode

Multiselect Field

Checkbox
<field name="{checkbox_name}" formElement="checkbox" sortOrder="{integer}">
    <settings>
        <label translate="true">{Label Checkbox}</label>
        <tooltip>
            <description translate="true">{Description}</description>
        </tooltip>
    </settings>
    <formElements>
        <checkbox>
            <settings>
                <prefer>checkbox</prefer>
                <valueMap>
                    <map name="false" xsi:type="boolean">false</map>
                    <map name="true" xsi:type="boolean">true</map>
                </valueMap>
            </settings>
        </checkbox>
    </formElements>
</field>
Enter fullscreen mode Exit fullscreen mode

Checkbox Field

<field name="{checkbox_toggle_name}" formElement="checkbox" sortOrder="{integer}">
    <settings>
        <label translate="true">{Label Checkbox}</label>
        <tooltip>
            <description translate="true">{Description}</description>
        </tooltip>
    </settings>
    <formElements>
        <checkbox>
            <settings>
                <prefer>toggle</prefer>
                <valueMap>
                    <map name="false" xsi:type="boolean">false</map>
                    <map name="true" xsi:type="boolean">true</map>
                </valueMap>
            </settings>
        </checkbox>
    </formElements>
</field>
Enter fullscreen mode Exit fullscreen mode

Toggle Field

Radio
<field name="{radio_name}" formElement="checkbox" sortOrder="{integer}">
    <settings>
        <label translate="true">{Label Radio}</label>
        <tooltip>
            <description translate="true">{Description}</description>
        </tooltip>
    </settings>
    <formElements>
        <checkbox>
            <settings>
                <prefer>radio</prefer>
                <valueMap>
                    <map name="false" xsi:type="boolean">false</map>
                    <map name="true" xsi:type="boolean">true</map>
                </valueMap>
            </settings>
        </checkbox>
    </formElements>
</field>
Enter fullscreen mode Exit fullscreen mode

Radio Field

<radioset name="{radioset_name}" sortOrder="{integer}">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="additionalInfo" xsi:type="string">{Additional information}</item>
        </item>
    </argument>
    <settings>
        <label translate="true">{Label Radioset}</label>
        <options>
            <option name="{option_name}" xsi:type="array">
                <item name="value" xsi:type="string">{option_value}</item>
                <item name="label" xsi:type="string">{Option Label}</item>
            </option>
        </options>
    </settings>
</radioset>
Enter fullscreen mode Exit fullscreen mode

Radioset Field

Date Time
<field name="{date_name}" formElement="date" sortOrder="{integer}">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="source" xsi:type="string">{source_name}</item>
        </item>
    </argument>
    <settings>
        <validation>
            <rule name="validate-date" xsi:type="boolean">{true/false}</rule>
        </validation>
        <dataType>{type}</dataType>
        <label translate="true">{Label Date}</label>
        <dataScope>{scope_name}</dataScope>
    </settings>
</field>
Enter fullscreen mode Exit fullscreen mode

Date Time Field

ColorPicker
<colorPicker name="{colorspicker_name}" class="Magento\Ui\Component\Form\Element\ColorPicker" component="Magento_Ui/js/form/element/color-picker" sortOrder="{integer}">
    <settings>
        <label translate="true">{Label Color}</label>
        <elementTmpl>ui/form/element/color-picker</elementTmpl>
        <colorFormat>rgb</colorFormat>
        <colorPickerMode>full</colorPickerMode>
        <dataScope>colors_filter</dataScope>
    </settings>
</colorPicker>
Enter fullscreen mode Exit fullscreen mode

ColorPicker Field

File
<file name="file_name" sortOrder="{integer}">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string">{Label File}</item>
            <item name="visible" xsi:type="boolean">{true/false}</item>
            <item name="formElement" xsi:type="string">fileUploader</item>
            <item name="uploaderConfig" xsi:type="array">
                <item name="url" xsi:type="url" path="{router_id}/{controller_folder}/{controller_name}"/>
            </item>
        </item>
    </argument>
</file>
Enter fullscreen mode Exit fullscreen mode

File Field

<field name="file_uploader_name" formElement="fileUploader" sortOrder="{integer}">
    <settings>
        <label translate="true">{Label File Uploader}</label>
    </settings>
    <formElements>
        <fileUploader>
            <settings>
                <uploaderConfig>
                    <param xsi:type="string" name="url">{router_id}/{controller_folder}/{controller_name}</param>
                </uploaderConfig>
            </settings>
        </fileUploader>
    </formElements>
</field>
Enter fullscreen mode Exit fullscreen mode

File Uploader Field

HtmlContent

O componente HtmlContent fornece a capacidade de processar e renderizar uma estrutura de layout ou um bloco diretamente dentro de uma configuração de componente do UI. O processamento e a renderização são executados no lado do servidor.

A estrutura de layout dentro de HtmlContent deve conter apenas um bloco de nível superior. O bloco de nível superior pode conter quantos blocos filhos ou contêineres forem necessários. Todos os blocos dentro de HtmlContent são integrados ao layout, então os blocos externos podem se referir a eles e vice-versa.

<htmlContent name="{html_component_name}">
    <block class="{Vendor}\{Module}\Block\Adminhtml\{Directory}\{ClassName}" name="{block_name}" template="{Vendor_Module}::{directory}/{file}.phtml">
        <arguments>
            <argument name="{argument_name}" xsi:type="{type}">{value}</argument>
        </arguments>
        <block class="{Vendor}\{Module}\Block\Adminhtml\{Directory}\{ClassName}" name="{block_name}" template="{Vendor_Module}::{directory}/{file}.phtml" />
    </block>
</htmlContent>
Enter fullscreen mode Exit fullscreen mode

Botão do Menu

Esta classe é responsável por configurar o botão que ficará no menu, para isso está classe deve implementar a interface \Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface e implementar o método getButtonData(): array, retornando array com as configurações do botão.

<?php

namespace {Vendor}\{Module}\Block\Adminhtml\{Directory};

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
use Magento\Framework\UrlInterface;

class {ButtonClassName} implements ButtonProviderInterface
{
    public function __construct(
        private UrlInterface $urlBuilder
    ) {
    }

    public function getUrl(string $route = '', array $params = []): string
    {
        return $this->urlBuilder->getUrl($route, $params);
    }

    public function getButtonData(): array
    {
        return [
            'label' => __('{Label Button}'),
            'on_click' => sprintf("location.href = '%s';", $this->getUrl('{router_id}/{controller_directory}/{controller_name}')),
            'class' => '{class_name}',
            'sort_order' => {integer}
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

Finalizando

Valores entre chaves ({test}) devem ser alterados na implementação do código.

Habilitando as alterações

Apague os arquivos que são gerados na compilação do Magento e execute o comando PHP para gerar a configuração das injeções de dependência e todas as classes ausentes que precisam ser geradas (proxys, interceptors, etc) e para limpar todos os caches de armazenamento em cache do processos.

rm -rf var/generation/
rm -rf generated/
php bin/magento setup:di:compile
php bin/magento cache:clean
php bin/magento flush
Enter fullscreen mode Exit fullscreen mode

Diretórios e Arquivos

Segue a a lista de diretórios e arquivos que devem ser criados.

- app/
  - code/
    - {Vendor}/
        - {Module}/
          - Block/
            - Adminhtml/
              - {ClassName}.php
          - etc/
            - di.xml
            - module.xml
          - Ui/
            - Component/
              - {EntityName}/
                - FormDataProvider.php
          - view/
            - adminhtml/
              - layout/
                - {router_id}_{controller_directory}_{controller_name}.xml
              - ui_component/
                - {ui_component}.xml
          - registration.php
          - composer.json
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .