Como criar formulários do Admin do Magento 2
Tags: php, magento2, tutorial, braziliandevs
Published: 2022-05-11 22:37:02

## 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](https://dev.to/santanaluc94/como-criar-arquivos-de-layout-magento-2-1i69)".

### 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
<?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;
    }
}
```

### 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
<?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>
```

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:

| Nó | 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: <class name="{class-name}">{true/false}</class>) |
| 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 <opened> 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

```xml
<field name="{input_name}" formElement="input" sortOrder="{integer}">
    <settings>
        <visible>{true/false}</visible>
        <label translate="true">{Label}</label>
    </settings>
</field>
```

![Input Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tccq469ucmlpllby48d.png)

##### Text

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

![Text Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35wno7tinwxkuvhsa005.png)

##### Textarea

```xml
<field name="{textarea_name}" formElement="textarea" sortOrder="{integer}">
    <settings>
        <label translate="true">{Label Textarea}</label>
    </settings>
</field>
```

![Textarea Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3f0scvwuftlhy6vbsnvb.png)

##### Select

```xml
<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>
```

![Select Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h98faepw910wo70k0t3w.png)

##### Multiselect

```xml
<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>
```

![Multiselect Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cfhhch8gc5k3t1fwnuni.png)

##### Checkbox

```xml
<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>
```

![Checkbox Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rgwvamb51igqc20735ew.png)

```xml
<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>
```

![Toggle Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9z1m5ihyzv8fzvhp3tf1.png)

##### Radio

```xml
<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>
```

![Radio Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j8shzbrgk8sziuhtaf81.png)

```xml
<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>
```

![Radioset Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p12xclh3dvoojqa42550.png)

##### Date Time

```xml
<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>
```

![Date Time Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9d3lwa57hlfhrenet1a.png)

##### ColorPicker

```xml
<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>
```

![ColorPicker Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a34hninywrqk50kckyt.png)

##### File

```xml
<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>
```

![File Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stoxs5dmoe34415e4no6.png)

```xml
<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>
```

![File Uploader Field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iinui8uwvo3wzjxrwk16.png)

#### 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.

```xml
<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>
```

### 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
<?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}
        ];
    }
}
```

---

## 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.

```bash
rm -rf var/generation/
rm -rf generated/
php bin/magento setup:di:compile
php bin/magento cache:clean
php bin/magento flush
```

### 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
```