Add an Admin grid
Admin grids are used to represent, filter, and sort various data in the Adobe Commerce and Magento Open Source Admin application. They are also used to perform mass actions such as updates and deletes. This tutorial will show you how to create a simple Admin grid.
1. Create a backbone module#
Everything starts with a module. Dev_Grid
will be used as the namespace:
Copied to your clipboardmkdir -p app/code/Dev/Grid/etc
Here are the required files to get started:
app/code/Dev/Grid/etc/module.xml
:
Copied to your clipboard1<?xml version="1.0"?>2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">3 <module name="Dev_Grid">4 <sequence>5 <module name="Magento_Backend"/>6 <module name="Magento_Ui"/>7 </sequence>8 </module>9</config>
app/code/Dev/Grid/registration.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67use Magento\Framework\Component\ComponentRegistrar;89ComponentRegistrar::register(10 ComponentRegistrar::MODULE,11 'Dev_Grid',12 __DIR__13);
app/code/Dev/Grid/etc/adminhtml/routes.xml
:
Copied to your clipboard1<?xml version="1.0"?>2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">3 <router id="admin">4 <route id="dev_grid" frontName="dev_grid">5 <module name="Dev_Grid" before="Magento_Backend" />6 </route>7 </router>8</config>
app/code/Dev/Grid/etc/adminhtml/menu.xml
:
Copied to your clipboard1<?xml version="1.0"?>2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">3 <menu>4 <add id="Dev_Grid::home" title="Category Listing" module="Dev_Grid" sortOrder="1000" parent="Magento_Catalog::catalog_categories" resource="Magento_Catalog::categories" action="dev_grid/index/index"/>5 </menu>6</config>
2. Define the Admin grid#
The grid displays a list of available categories that start with a letter b
or B
.
This grid has three columns: ID
, category path
and category name
. ID
and category path
are from the catalog_category_entity
table. For the name
values, joins are used.
The page layout file is app/code/Dev/Grid/view/adminhtml/layout/dev_grid_index_index.xml
:
Copied to your clipboard1<?xml version="1.0"?>2<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">3 <body>4 <referenceContainer name="content">5 <uiComponent name="dev_grid_category_listing"/>6 </referenceContainer>7 </body>8</page>
The UI component dev_grid_category_listing
must be defined separately in a file with the same name, ending with .xml
- app/code/Dev/Grid/view/adminhtml/ui_component/dev_grid_category_listing.xml
:
Copied to your clipboard1<?xml version="1.0" encoding="UTF-8"?>2<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">3 <argument name="data" xsi:type="array">4 <item name="js_config" xsi:type="array">5 <item name="provider" xsi:type="string">dev_grid_category_listing.dev_grid_category_listing_data_source</item>6 <item name="deps" xsi:type="string">dev_grid_category_listing.dev_grid_category_listing_data_source</item>7 </item>8 <item name="spinner" xsi:type="string">dev_grid_category_columns</item>9 <item name="buttons" xsi:type="array">10 <item name="add" xsi:type="array">11 <item name="name" xsi:type="string">add</item>12 <item name="label" xsi:type="string">View Category Tree</item>13 <item name="class" xsi:type="string">primary</item>14 <item name="url" xsi:type="string">catalog/category/index</item>15 </item>16 </item>17 </argument>18 <dataSource name="dev_grid_category_listing_data_source">19 <argument name="dataProvider" xsi:type="configurableObject">20 <argument name="class" xsi:type="string">Dev\Grid\Ui\DataProvider\Category\ListingDataProvider</argument>21 <argument name="name" xsi:type="string">dev_grid_category_listing_data_source</argument>22 <argument name="primaryFieldName" xsi:type="string">entity_id</argument>23 <argument name="requestFieldName" xsi:type="string">entity_id</argument>24 <argument name="data" xsi:type="array">25 <item name="config" xsi:type="array">26 <item name="update_url" xsi:type="url" path="mui/index/render"/>27 <item name="storageConfig" xsi:type="array">28 <item name="indexField" xsi:type="string">entity_id</item>29 </item>30 </item>31 </argument>32 </argument>33 <argument name="data" xsi:type="array">34 <item name="js_config" xsi:type="array">35 <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>36 </item>37 </argument>38 </dataSource>39 <listingToolbar name="listing_top">40 <bookmark name="bookmarks"/>41 <columnsControls name="columns_controls"/>42 <massaction name="listing_massaction">43 <argument name="data" xsi:type="array">44 <item name="data" xsi:type="array">45 <item name="selectProvider" xsi:type="string">dev_grid_category_listing.dev_grid_category_listing.dev_grid_category_columns.ids</item>46 <item name="displayArea" xsi:type="string">bottom</item>47 <item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>48 <item name="indexField" xsi:type="string">entity_id</item>49 </item>50 </argument>51 <action name="delete">52 <argument name="data" xsi:type="array">53 <item name="config" xsi:type="array">54 <item name="type" xsi:type="string">delete</item>55 <item name="label" xsi:type="string" translate="true">Delete</item>56 <item name="url" xsi:type="url" path="dev_grid/category/massDelete"/>57 <item name="confirm" xsi:type="array">58 <item name="title" xsi:type="string" translate="true">Delete items</item>59 <item name="message" xsi:type="string" translate="true">Are you sure you want to delete selected items?</item>60 </item>61 </item>62 </argument>63 </action>64 </massaction>65 <filters name="listing_filters">66 <argument name="data" xsi:type="array">67 <item name="config" xsi:type="array">68 <item name="templates" xsi:type="array">69 <item name="filters" xsi:type="array">70 <item name="select" xsi:type="array">71 <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item>72 <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item>73 </item>74 </item>75 </item>76 </item>77 </argument>78 </filters>79 <paging name="listing_paging"/>80 </listingToolbar>81 <columns name="dev_grid_category_columns">82 <selectionsColumn name="ids">83 <argument name="data" xsi:type="array">84 <item name="config" xsi:type="array">85 <item name="indexField" xsi:type="string">entity_id</item>86 </item>87 </argument>88 </selectionsColumn>89 <column name="entity_id">90 <settings>91 <filter>textRange</filter>92 <label translate="true">ID</label>93 <resizeDefaultWidth>25</resizeDefaultWidth>94 </settings>95 </column>96 <column name="path">97 <settings>98 <filter>text</filter>99 <bodyTmpl>ui/grid/cells/text</bodyTmpl>100 <label translate="true">Path</label>101 </settings>102 </column>103 <column name="name">104 <settings>105 <filter>text</filter>106 <bodyTmpl>ui/grid/cells/text</bodyTmpl>107 <label translate="true">Name</label>108 </settings>109 </column>110 <column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date">111 <settings>112 <filter>dateRange</filter>113 <dataType>date</dataType>114 <label translate="true">Created</label>115 </settings>116 </column>117 <actionsColumn name="actions" class="Dev\Grid\Ui\Component\Category\Listing\Column\Actions" sortOrder="200">118 <argument name="data" xsi:type="array">119 <item name="config" xsi:type="array">120 <item name="resizeEnabled" xsi:type="boolean">false</item>121 <item name="resizeDefaultWidth" xsi:type="string">107</item>122 <item name="indexField" xsi:type="string">entity_id</item>123 </item>124 </argument>125 <argument name="viewUrl" xsi:type="string">catalog/category/view</argument>126 </actionsColumn>127 </columns>128</listing>
This file consists of several sections:
dataSource
- references the class that is responsible for getting the requested data.listingToolbar
- where mass actions and filters are defined.columns
- lists the columns to be displayed.
3. Define the data source class#
The UI references Dev\Grid\Ui\DataProvider\Category\ListingDataProvider
as the data source class.
The corresponding file is app/code/Dev/Grid/Ui/DataProvider/Category/ListingDataProvider.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67namespace Dev\Grid\Ui\DataProvider\Category;89use Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider;1011class ListingDataProvider extends DataProvider12{13}
It has to extend \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider
.
The plugin then gets a name
attribute:
app/code/Dev/Grid/etc/di.xml
:
Copied to your clipboard1<?xml version="1.0"?>2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">3 <type name="Dev\Grid\Ui\DataProvider\Category\ListingDataProvider">4 <plugin name="dev_grid_attributes" type="Dev\Grid\Plugin\AddAttributesToUiDataProvider"/>5 </type>6 <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">7 <arguments>8 <argument name="collections" xsi:type="array">9 <item name="dev_grid_category_listing_data_source" xsi:type="string">DevGridCategoryCollection</item>10 </argument>11 </arguments>12 </type>13 <virtualType name="DevGridCategoryCollection" type="Dev\Grid\Ui\DataProvider\Category\Listing\Collection">14 <arguments>15 <argument name="mainTable" xsi:type="string">catalog_category_entity</argument>16 <argument name="resourceModel" xsi:type="string">Dev\Grid\Model\ResourceModel\Category</argument>17 </arguments>18 </virtualType>19</config>
app/code/Dev/Grid/Plugin/AddAttributesToUiDataProvider.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67namespace Dev\Grid\Plugin;89use Dev\Grid\Ui\DataProvider\Category\ListingDataProvider as CategoryDataProvider;10use Magento\Eav\Api\AttributeRepositoryInterface;11use Magento\Framework\App\ProductMetadataInterface;12use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;1314class AddAttributesToUiDataProvider15{16 /** @var AttributeRepositoryInterface */17 private $attributeRepository;1819 /** @var ProductMetadataInterface */20 private $productMetadata;2122 /**23 * Constructor24 *25 * @param AttributeRepositoryInterface $attributeRepository26 * @param ProductMetadataInterface $productMetadata27 */28 public function __construct(29 AttributeRepositoryInterface $attributeRepository,30 ProductMetadataInterface $productMetadata31 ) {32 $this->attributeRepository = $attributeRepository;33 $this->productMetadata = $productMetadata;34 }3536 /**37 * Get Search Result after plugin38 *39 * @param CategoryDataProvider $subject40 * @param SearchResult $result41 * @return SearchResult42 */43 public function afterGetSearchResult(CategoryDataProvider $subject, SearchResult $result)44 {45 if ($result->isLoaded()) {46 return $result;47 }4849 $edition = $this->productMetadata->getEdition();5051 $column = 'entity_id';5253 if ($edition == 'Enterprise') {54 $column = 'row_id';55 }5657 $attribute = $this->attributeRepository->get('catalog_category', 'name');5859 $result->getSelect()->joinLeft(60 ['devgridname' => $attribute->getBackendTable()],61 'devgridname.' . $column . ' = main_table.' . $column . ' AND devgridname.attribute_id = '62 . $attribute->getAttributeId(),63 ['name' => 'devgridname.value']64 );6566 $result->getSelect()->where('devgridname.value LIKE "B%"');6768 return $result;69 }70}
This works with both enterprise and community versions by linking on different fields.
In this case, LIKE
is case insensitive.
4. Data source collection#
The dataSource
name dev_grid_category_listing_data_source
links to Dev\Grid\Ui\DataProvider\Category\Listing\Collection
collection in app/code/Dev/Grid/etc/di.xml
.
di.xml
also sets the main table and resource model:
Copied to your clipboard1 <virtualType name="DevGridCategoryCollection" type="Dev\Grid\Ui\DataProvider\Category\Listing\Collection">2 <arguments>3 <argument name="mainTable" xsi:type="string">catalog_category_entity</argument>4 <argument name="resourceModel" xsi:type="string">Dev\Grid\Model\ResourceModel\Category</argument>5 </arguments>6 </virtualType>
The collection class translates to app/code/Dev/Grid/Ui/DataProvider/Category/Listing/Collection.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67namespace Dev\Grid\Ui\DataProvider\Category\Listing;89use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;1011class Collection extends SearchResult12{13 /**14 * Override _initSelect to add custom columns15 *16 * @return void17 */18 protected function _initSelect()19 {20 $this->addFilterToMap('entity_id', 'main_table.entity_id');21 $this->addFilterToMap('name', 'devgridname.value');22 parent::_initSelect();23 }24}
It uses a custom collection file to add custom filters to map, and makes the grid filters work with the ID and name fields. Without addFilterToMap
, you will not be able to search within the name
column.
The resource model class translates to app/code/Dev/Grid/Model/ResourceModel/Category.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67namespace Dev\Grid\Model\ResourceModel;89use Magento\Catalog\Model\ResourceModel\Category as BaseCategory;1011class Category extends BaseCategory12{13}
5. Column actions class#
The UI grid file defines a column actions class Dev\Grid\Ui\Component\Category\Listing\Column\Actions
.
app/code/Dev/Grid/Ui/Component/Category/Listing/Column/Actions.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67namespace Dev\Grid\Ui\Component\Category\Listing\Column;89use Magento\Framework\View\Element\UiComponentFactory;10use Magento\Framework\View\Element\UiComponent\ContextInterface;11use Magento\Framework\Url;12use Magento\Ui\Component\Listing\Columns\Column;1314class Actions extends Column15{16 /**17 * @var UrlInterface18 */19 protected $_urlBuilder;2021 /**22 * @var string23 */24 protected $_viewUrl;2526 /**27 * Constructor28 *29 * @param ContextInterface $context30 * @param UiComponentFactory $uiComponentFactory31 * @param Url $urlBuilder32 * @param string $viewUrl33 * @param array $components34 * @param array $data35 */36 public function __construct(37 ContextInterface $context,38 UiComponentFactory $uiComponentFactory,39 Url $urlBuilder,40 $viewUrl = '',41 array $components = [],42 array $data = []43 ) {44 $this->_urlBuilder = $urlBuilder;45 $this->_viewUrl = $viewUrl;46 parent::__construct($context, $uiComponentFactory, $components, $data);47 }4849 /**50 * Prepare Data Source51 *52 * @param array $dataSource53 * @return array54 */55 public function prepareDataSource(array $dataSource)56 {57 if (isset($dataSource['data']['items'])) {58 foreach ($dataSource['data']['items'] as &$item) {59 $name = $this->getData('name');60 if (isset($item['entity_id'])) {61 $item[$name]['view'] = [62 'href' => $this->_urlBuilder->getUrl($this->_viewUrl, ['id' => $item['entity_id']]),63 'target' => '_blank',64 'label' => __('View on Frontend')65 ];66 }67 }68 }69 return $dataSource;70 }71}
It gets a frontend URL for every category it lists.
6. Backend controllers#
The main route defined in app/code/Dev/Grid/etc/adminhtml/menu.xml
as dev_grid/index/index
translates to app/code/Dev/Grid/Controller/Adminhtml/Index/Index.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67namespace Dev\Grid\Controller\Adminhtml\Index;89use Magento\Backend\App\Action;10use Magento\Backend\App\Action\Context;11use Magento\Framework\View\Result\Page;12use Magento\Framework\View\Result\PageFactory;13use Magento\Framework\App\Action\HttpGetActionInterface;1415class Index extends Action implements HttpGetActionInterface16{17 /**18 * @var PageFactory19 */20 private $pageFactory;2122 /**23 * Constructor24 *25 * @param Context $context26 * @param PageFactory $rawFactory27 */28 public function __construct(29 Context $context,30 PageFactory $rawFactory31 ) {32 $this->pageFactory = $rawFactory;3334 parent::__construct($context);35 }3637 /**38 * Add the main Admin Grid page39 *40 * @return Page41 */42 public function execute(): Page43 {44 $resultPage = $this->pageFactory->create();45 $resultPage->setActiveMenu('Magento_Catalog::catalog_products');46 $resultPage->getConfig()->getTitle()->prepend(__('Admin Grid Tutorial Example'));4748 return $resultPage;49 }50}
The Ui grid file defines the custom route dev_grid/category/massDelete
(mass delete) and translates to app/code/Dev/Grid/Controller/Adminhtml/Category/MassDelete.php
:
Copied to your clipboard1<?php2/**3 * Copyright © Magento, Inc. All rights reserved.4 * See COPYING.txt for license details.5 */67namespace Dev\Grid\Controller\Adminhtml\Category;89use Magento\Backend\App\Action;10use Magento\Backend\App\Action\Context;11use Magento\Backend\Model\View\Result\Redirect;12use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;13use Magento\Catalog\Api\CategoryRepositoryInterface;14use Magento\Framework\App\Action\HttpPostActionInterface;15use Magento\Framework\Controller\ResultFactory;16use Magento\Framework\Exception\NotFoundException;17use Magento\Ui\Component\MassAction\Filter;1819class MassDelete extends Action implements HttpPostActionInterface20{21 /**22 * Authorization level23 */24 const ADMIN_RESOURCE = 'Magento_Catalog::categories';2526 /**27 * @var CollectionFactory28 */29 protected $collectionFactory;3031 /**32 * @var CategoryRepositoryInterface33 */34 private $categoryRepository;3536 /**37 * @var Filter38 */39 protected $filter;4041 /**42 * Constructor43 *44 * @param Context $context45 * @param Filter $filter46 * @param CollectionFactory $collectionFactory47 * @param CategoryRepositoryInterface $categoryRepository48 */49 public function __construct(50 Context $context,51 Filter $filter,52 CollectionFactory $collectionFactory,53 CategoryRepositoryInterface $categoryRepository54 ) {55 $this->filter = $filter;56 $this->collectionFactory = $collectionFactory;57 $this->categoryRepository = $categoryRepository;58 parent::__construct($context);59 }6061 /**62 * Category delete action63 *64 * @return Redirect65 */66 public function execute(): Redirect67 {68 if (!$this->getRequest()->isPost()) {69 throw new NotFoundException(__('Page not found'));70 }71 $collection = $this->filter->getCollection($this->collectionFactory->create());72 $categoryDeleted = 0;73 foreach ($collection->getItems() as $category) {74 $this->categoryRepository->delete($category);75 $categoryDeleted++;76 }7778 if ($categoryDeleted) {79 $this->messageManager->addSuccessMessage(80 __('A total of %1 record(s) have been deleted.', $categoryDeleted)81 );82 }83 return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('dev_grid/index/index');84 }85}
Completed extension#
The complete extension can be found on GitHub at Admin Grid Example Extension. Installation instructions:
Clone the repository
Copied to your clipboardgit clone https://github.com/goivvy/admin-grid-tutorial.gitCopy
app
folderCopied to your clipboardcp -r ~/admin-grid/tutorial/app /path/to/magento2/root/folderInstall and recompile
Copied to your clipboard1bin/magento setup:upgrade2bin/magento deploy:mode:set production
The grid can now be accessed at Catalog > Inventory > Category Listing.