Como criar uma Listagem/Grade do Admin do Magento 2 (Admin Grid/Listing)

Lucas Teixeira dos Santos Santana - May 9 '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, 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_component em uma página no post "Como criar arquivos de layout Magento 2".

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_component devem seguir a estrutura de pastas \{Vendor}\{Module}\view\{area}\ui_component\{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"?>
<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>
Enter fullscreen mode Exit fullscreen mode

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

<column name="{column_name}" sortOrder="{integer}">
    <settings>
        <filter>text</filter>
        <dataType>text</dataType>
        <label translate="true">{Label}</label>
    </settings>
</column>
Enter fullscreen mode Exit fullscreen mode
Text Range

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

Filter Text Range

<column name="{column_name}" sortOrder="{integer}">
    <settings>
        <filter>textRange</filter>
        <dataType>text</dataType>
        <label translate="true">{Label}</label>
    </settings>
</column>
Enter fullscreen mode Exit fullscreen mode
Select

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

Filter Select

<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>
Enter fullscreen mode Exit fullscreen mode
Date Range

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

Filter Data Range

<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>
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode
Type
<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>
Enter fullscreen mode Exit fullscreen mode
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

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
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Finalizando

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

Habilitando as alterações

Execute o comando PHP 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}/
          - 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
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .