Como criar uma Listagem/Grade do Admin do Magento 2 (Admin Grid/Listing)
Tags: php, braziliandevs, magento2, tutorial
Published: 2022-05-09 12:20:48

## 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, entre outros. São componentes simples e flexíveis projetados para a renderização da interface do usuário, são componentes responsáveis por renderizar fragmentos da 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)".

### Listagens

As listagens são usadas para representar, filtrar e classificar vários dados no *back-end* do Magento, também são utilizados para executar ações em massa, como atualizações, exclusões, duplicações, entre outras ações.

---

## Código para criar uma Listagem

### 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"?>
<listing 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_name}.{ui_component_name}_data_source</item>
        </item>
    </argument>

    <settings>
        <buttons>
            <button name="{button_name}">
                <url path="{route_id}/{controller_folder_name}/{controller_name}"/>
                <class>{class name}</class>
                <label translate="true">{Button Label}</label>
            </button>
        </buttons>
        <spinner>{columns_name}</spinner>
        <deps>
            <dep>{ui_component_name}.{ui_component_name}_data_source</dep>
        </deps>
    </settings>

    <dataSource name="{ui_component_name}_data_source" component="Magento_Ui/js/grid/provider">
        <settings>
            <storageConfig>
                <param name="indexField" xsi:type="string">{entity_id}</param>
            </storageConfig>
            <updateUrl path="mui/index/render"/>
        </settings>
        <aclResource>{Vendor}_{Module}::{parentResourceId}</aclResource>
        <dataProvider class="{Vendor}\{Module}\Ui\DataProvider\{EntityName}\DataProvider" name="{ui_component_name}_data_source">
            <settings>
                <requestFieldName>{id}</requestFieldName>
                <primaryFieldName>{entity_id}</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>

    <listingToolbar name="listing_top">
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <filterSearch name="fulltext"/>
        <filters name="listing_filters" />
        <massaction name="listing_massaction" component="Magento_Ui/js/grid/tree-massactions">
            <action name="{action_name}">
                <settings>
                    <confirm>
                        <message translate="true">{Confirm message}</message>
                        <title translate="true">{Title message}</title>
                    </confirm>
                    <url path="{route_id}/{controller_folder_name}/{controller_name}"/>
                    <type>{type_name}</type>
                    <label translate="true">{Label}</label>
                </settings>
            </action>
            <action name="{action_name}">
                <settings>
                    <url path="{route_id}/{controller_folder_name}/{controller_name}"/>
                    <type>{type_name}</type>
                    <label translate="true">{Label}</label>
                </settings>
            </action>
        </massaction>
        <paging name="listing_paging"/>
        <exportButton name="export_button"/>
    </listingToolbar>

    <columns name="{columns_name}">
        <selectionsColumn name="ids" sortOrder="{integer}">
            <settings>
                <indexField>{entity_id}</indexField>
            </settings>
        </selectionsColumn>
        <column name="{column_name}" sortOrder="{integer}">
            <settings>
                <filter>{filter_type}</filter>
                <dataType>{data_type}</dataType>
                <visible>{true/false}</visible>
                <sorting>{asc/desc}</sorting>
                <label translate="true">{Label}</label>
            </settings>
        </column>
        <actionsColumn name="actions" class="{Vendor}\{Module}\Ui\Component\{ComponentName}\Listing\Column\{ActionName}" sortOrder="{integer}">
            <settings>
                <indexField>{entity_id}</indexField>
                <label translate="true">{Label}</label>
            </settings>
        </actionsColumn>
    </columns>
</listing>
```

O nó `<listing>` é um padrão dos *UI components* do Magento 2, onde o nome deste arquivo deve ser o mesmo nome do atributo `name` declarado no nó `<ui_component>` do arquivo de *layout*.

#### Argument

O nó `<argument>` é uma estrutura arbitrária que declara a configuração do componente de listagem 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 `<listing>`.

- O nó `<buttons>`: é responsável por adicionar botões no menu superior da listagem. Cada botao 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ó `<spinner>`: é responsável por esconder a imagem de carregamento da página, quando as colunas geradas na listagem são carregadas;
- O nó `<deps>`: deve possui 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/grid/provider"` é utilizado para um provedor de dados para a listagem em formato específico que é compartilhado entre todos os *UI components* no escopo do componente de listagem. É 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 indexador através do nó `<storageConfig>`, e o nó `<updateUrl>` é responsável por definir qual o controlador será responsável por atualizar a listagem;
- 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ó filho `<dataProvider>`: é a classe responsável por tratar os dados retornado do Provedor de Dados, está 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>`);

#### Listing Toolbar

O nó `<listingToolbar>` é responsável por definir as ações em massa, os filtros, editor de colunas, campo de busca, exportar, entre muitas outras funcionalidades que ficarão na parte superior da listagem.

- O nó `<bookmark>`: é responsável por disponbilizar uma funcionalidade que permite salvar o estado de diferentes configurações da listagem, vinculando com o nó `<columnsControls>`.
- O nó `<columnsControls>`: é responsável por adicionar uma funcionalidade com as colunas da listagem que permite ao usuário escolher quais colunas podem ser exibidas na listagem. Depois de alterar esta lista, o usuário pode salvar esse estado através do nó  `<bookmark>` que permite acessar esse estado rapidamente.
- O nó `<filterSearch>`: este nó é responsável por adicionar um campo de pesquisa, onde será possível pesquisar por todos os campos da tabela que estejam indexados como `fulltext`.
- O nó `<filters>`: é responsável por exibir um botão de filtro onde se definir uma caixa de filtro para cada coluna da listagem.
- O nó `<massaction>`: é responsável por adicionar a seleção de ações em massa à listagem, ou seja, com essa funcionalidade é possível realizar uma ação rapidamente os itens selecionados da lista.
- O nó `<paging>`: é responsável por adicionar a paginação para a listagem.
- O nó `<exportButton>`: é responsável por adicionar um botão de exportação, onde é possível exportar os dados da listagem.

#### Columns

O nó `<columns>` é responsável por exibir as colunas na listagem e por desativar o spinner quando carregado.

- O nó `<selectionsColumn>`: é o componente responsável por definir o filtro de seleção que permite aos usuários selecionar todos ou desmarcar todos os itens da listagem.
- O nó `<column>`: é responsável por definir a própria coluna
- O nó `<actionsColumn>`: é responsável por definir a classe a ser usada para os botões de ações do item selecionado individualmente.

#### Filtros

##### Text

Colunas com o valor `text` no nó `<filter>` aceitam *strings* para serem filtradas na coluna.

![Filter Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vn5ombbmvggkwzmit5i7.png)

```xml
<column name="{column_name}" sortOrder="{integer}">
    <settings>
        <filter>text</filter>
        <dataType>text</dataType>
        <label translate="true">{Label}</label>
    </settings>
</column>
```

##### Text Range

Colunas com o valor `textRange` no nó `<filter>` aceitam um alcance de valores para serem filtrados na coluna.

![Filter Text Range](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nbslh5l42vqjwvua7y4q.png)

```xml
<column name="{column_name}" sortOrder="{integer}">
    <settings>
        <filter>textRange</filter>
        <dataType>text</dataType>
        <label translate="true">{Label}</label>
    </settings>
</column>
```

##### Select

Colunas com o valor `select` no nó `<filter>` aceitam opções pré-definidas para serem filtradas na coluna.

![Filter Select](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86v0hsgr156jn66e401y.png)

```xml
<column name="{column_name}" sortOrder="{integer}" component="Magento_Ui/js/grid/columns/select">
    <settings>
        <dataType>select</dataType>
        <filter>select</filter>
        <options>
            <option name="{first_option_name}" xsi:type="array">
                <item name="value" xsi:type="string">{first value}</item>
                <item name="label" xsi:type="string" translate="true">{First Label}</item>
            </option>
            <option name="{second_option_name}" xsi:type="array">
                <item name="value" xsi:type="string">{second value}</item>
                <item name="label" xsi:type="string" translate="true">{Second Label}</item>
            </option>
        </options>
        <label translate="true">{Label}</label>
    </settings>
</column>
```

##### Date Range

Colunas com o valor `dateRange` no nó `<filter>` aceitam um alcance de datas para serem filtrados na coluna.

![Filter Data Range](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4i71z0cq7vtab8h1afrr.png)

```xml
<column name="{column_name}" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date" sortOrder="{integer}">
    <settings>
        <dataType>date</dataType>
        <filter>dateRange</filter>
        <dateFormat>{format}</dateFormat>
        <label translate="true">{Label}</label>
    </settings>
</column>
```

### Provedor de Dados

É a classe PHP responsável por providenciar os dados do banco e deixar disponível através do atributo `name`. A classe `\Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory` é responsável por criar e retornar uma coleção de dados para a listagem da página. Para criar está coleção definieramos os valores do construtor com a injeção de dependência através do nó `<type>`. Saiba como criar classes que utilizam argumentos injetáveis via o arquivo `di.xml`
 no post "[Como definir valores específicos dos argumentos do construtor no Magento 2 utilizando type](https://dev.to/santanaluc94/como-definir-valores-especificos-dos-argumentos-do-construtor-no-magento-2-utilizando-type-3dgm-temp-slug-6464167?preview=bd44791b203ab3a289262c31cf2fb0d9f3fb8edc8f5fcbbead1f8199ce944e63838e68b82eee8ee8b7d1399f1616683a011131d70c8a86a2d377186a)".

```xml
<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
    <arguments>
        <argument name="collections" xsi:type="array">
            <item name="{ui_component}_data_source" xsi:type="string">{Vendor}\{Module}\Model\ResourceModel\{EntityName}\Grid\{CollectionName}</item>
        </argument>
    </arguments>
</type>
```

#### Search Result

A lista de resultados inclui todos os itens que correspondem aos critérios de pesquisa da coleção. Os resultados podem ser formatados como uma listagem e classificados por uma seleção de atributos. 

É possível criar os resultados de busca da coleção da listagem de algumas formas (*virtual types*, *types* ou criando uma classe PHP). Em qualquer um dos casos, é necessário passar o nome da tabela e o *Resource Model* da entidade utilizada na listagem.

##### Virtual Type

Para saber como criar classes virtuais que utilizam argumentos injetáveis via o arquivo `di.xml` no post "[Como utilizar tipos virtuais (virtual type) no Magento 2](https://dev.to/santanaluc94/como-utilizar-tipos-virtuais-virtual-type-no-magento-2-35hf) ”.

```xml
<virtualType name="{Vendor}\{Module}\Model\ResourceModel\{EntityName}\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
    <arguments>
        <argument name="mainTable" xsi:type="string">{table_name}</argument>
        <argument name="resourceModel" xsi:type="string">{Vendor}\{Module}\Model\ResourceModel\{ResourceModelName}</argument>
    </arguments>
</virtualType>
```

##### Type

```xml
<type name="{Vendor}\{Module}\Model\ResourceModel\{EntityName}\Grid\{CollectionName}">
    <arguments>
        <argument name="mainTable" xsi:type="string">{table_name}</argument>
        <argument name="resourceModel" xsi:type="string">{Vendor}\{Module}\Model\ResourceModel\{ResourceModelName}</argument>
    </arguments>
</type>
```

##### Classe PHP

Caso queira criar a classe PHP, será necessário estender uma classe que possua a implementação da interface `\Magento\Framework\Api\Search\SearchResultInterface` e definir os valores no construtor.

```php
<?php

namespace {Vendor}\{Module}\Model\ResourceModel\{EntityName}\Grid;

use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Psr\Log\LoggerInterface as Logger;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Event\ManagerInterface as EventManager;
use {Vendor}\{Module}\Model\ResourceModel\{EntityName} as {ResourceModelName};

class {CollectionName} extends SearchResult
{
    protected array $_map = [
        'fields' => [
            'entity_id' => 'main_table.entity_id'
        ]
    ];

    public function __construct(
        EntityFactory $entityFactory,
        Logger $logger,
        FetchStrategy $fetchStrategy,
        EventManager $eventManager,
        string $mainTable = '{table_name}',
        string $resourceModel = {ResourceModelName}::class
    ) {
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $mainTable,
            $resourceModel
        );
    }
}
```

### Ações da Coluna

Está classe é responsável por retornar as ações que impactarão o item selecionado, para isso está classe deve estender a classe `\Magento\Ui\Component\Listing\Columns\Column` e implementar o método `prepareDataSource(array $dataSource): array`, retornando o a variável `$dataSource` editada como desejado.

```php
<?php

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

use Magento\Ui\Component\Listing\Columns\Column;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;

class Action extends Column
{
    protected const HREF_EDIT = '{route_id}/{controller_folder}/{controller_edit_name}';
    protected const HREF_DELETE = '{route_id}/{controller_folder}/{controller_delete_name}';

    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        array $components = [],
        array $data = []
    ) {
        parent::__construct(
            $context,
            $uiComponentFactory,
            $components,
            $data
        );
    }

    public function prepareDataSource(array $dataSource): array
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as &$item) {
                $item[$this->getData('name')]['edit'] = [
                    'href' => $this->context->getUrl(self::HREF_EDIT, ['id' => $item['entity_id']]),
                    'label' => __('Edit')
                ];
                $item[$this->getData('name')]['delete'] = [
                    'href' => $this->context->getUrl(self::HREF_DELETE, ['id' => $item['entity_id']]),
                    'label' => __('Delete')
                ];
            }
        }

        return $dataSource;
    }
}
```

---

## Finalizando

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

</aside>

### Habilitando as alterações

Execute o comando PHP 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}/
          - etc/
            - di.xml
            - module.xml
          - Ui/
            - Component/
              - {EntityName}/
                - Action.php
          - view/
            - adminhtml/
              - layout/
                - {router_id}_{controller_directory}_{controller_name}.xml
              - ui_component/
                - {ui_component}.xml
          - registration.php
          - composer.json
```