Magento 2 UI Grid Component
There is a great explanation of Magento 2 UI Component Grid on StackExchange (the link to the original answer is provided below). Below, we provide its major aspects. The official devdoc is here:
Table of contents
1)
layout file from Company/Module/view/adminhtml/layout/module_controller_action.xml defines grid as uiComponent via:
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <update handle="styles"/> <body> <referenceContainer name="content"> <uiComponent name="listing_name"/> </referenceContainer> </body> </page> |
2)
uiComponent is defined in the Company/Module/view/adminhtml/ui_component/listing_name.xml file. Note that the file name should be the same as uiComponent name from an appropriate layout file. The structure of the file always have some repeating nodes, so it is not as complex as you may think after the first review.
Main node of the component file:
<listing xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=”urn:magento:module:Magento_Ui:etc/ui_configuration.xsd”>
Next, there are 4 nodes inside the <listing /> node:
<argument />, <dataSource />, <container /> and <columns />
Note that it isn’t a strict setup since the <argument /> node might be duplicated for providing additional configuration or <container /> as in CMS page listing (adding “sticky” container).
The <argument /> node:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<argument name="data" xsi:type="array"> <item name="js_config" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name_data_source</item> <item name="deps" xsi:type="string">listing_name.listing_name_data_source</item> </item> <item name="spinner" xsi:type="string">listing_columns</item> <item name="buttons" xsi:type="array"> <item name="add" xsi:type="array"> <item name="name" xsi:type="string">add</item> <item name="label" xsi:type="string" translate="true">Add New Item</item> <item name="class" xsi:type="string">primary</item> <item name="url" xsi:type="string">*/*/new</item> </item> </item> </argument> |
- The <argument /> node requires attribute name. In this case data specifies basic information about the component. It contains multiple <item /> nodes for each specific part of the configuration. Thus, js_config tells the component where the provider of the data and dependencies in the listing xml configuration are.
- provider value consist of listing name used in layout file and uniqure data source name that will be used later. provider and deps are the same.
- spinner takes name of the node where columns of the grid are defined.
- buttons allows to add buttons to the top of the grid. They have few elements: name used as element id, label is what the button says, class is the button class, and url is the link to which it points. Asterisk is replaced by the portion of current url. Other possible <item /> nodes for button: id, title, type (reset, submit or button), onclick(instead of url, it has precedence), style, value, disabled. Button element is rendered by Magento\Ui\Component\Control\Button class.
The <dataSource /> node:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<dataSource name="listing_name_data_source"> <argument name="dataProvider" xsi:type="configurableObject"> <argument name="class" xsi:type="string">UniqueNameGridDataProvider</argument> <argument name="name" xsi:type="string">listing_name_data_source</argument> <argument name="primaryFieldName" xsi:type="string">database_id</argument> <argument name="requestFieldName" xsi:type="string">request_id</argument> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="update_url" xsi:type="url" path="mui/index/render"/> </item> </argument> </argument> <argument name="data" xsi:type="array"> <item name="js_config" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item> </item> </argument> </dataSource> |
name used in <dataSource /> node must match the one used in argument/js_config/providerand argument/js_config/deps. Next node defines which class is responsible for preparing data for the grid. class argument requires unique name that will be matched in di.xml. primaryFieldName relates to the database primary column and requestFieldName to the variable in http requests. They may be equal but don’t have to, CMS page listing uses page_id as primaryFieldName and id as requestFieldName. update_url refers to the entry point where ajax calls for filtering and sorting are send. Second argument in <dataSource /> refers to javascript file that handles js part of sending and processing ajax calls for the grid. The default file is Magento/Ui/view/base/web/js/grid/provider.js.
The <container /> node
1 |
<container name="listing_top"> ... </container> |
As it contains a lot of data let me divide it as well. Its children are the parts of the entire page. First child <argument />:
1 2 3 4 5 |
<argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">ui/grid/toolbar</item> </item> </argument> |
It defines knockout template responsible for handling the layout and all actions and by default points to Magento/Ui/view/base/web/templates/grid/toolbar.html
The <bookmark /> node
1 2 3 4 5 6 7 8 9 |
<bookmark name="bookmarks"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="storageConfig" xsi:type="array"> <item name="namespace" xsi:type="string">listing_name</item> </item> </item> </argument> </bookmark> |
This node adds bookmark feature to the grid. It allows admin to set up different “profiles” of the grid which displays different columns. Thanks to that you can add all columns from the table to the grid and let the user decide which information are relevant to him. namespace must match name used in layout file.
The <component /> node
1 2 3 4 5 6 7 8 9 10 11 |
<component name="columns_controls"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="columnsData" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name.listing_columns</item> </item> <item name="component" xsi:type="string">Magento_Ui/js/grid/controls/columns</item> <item name="displayArea" xsi:type="string">dataGridActions</item> </item> </argument> </component> |
We have 3 values to configure here:
- First is provider which follows the pattern [listing_name_from_layout].[listing_name_from_layout].[listing_columns_node_name] (like in node listing/argument/spinner).
- component refers to js file that displays grid and by default points to Magento/Ui/view/base/web/js/grid/controls/columns.js which uses template Magento/Ui/view/base/web/templates/grid/controls/columns.html.
- The last item is displayAreawhich defines where column controls need to be displayed. It refers to getRegion(‘dataGridActions’) in file defined in container/argument/config/template .
The <filterSearch /> Node
1 2 3 4 5 6 7 8 9 10 11 12 |
<filterSearch name="fulltext"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name_data_source</item> <item name="chipsProvider" xsi:type="string">listing_name.listing_name.listing_top.listing_filters_chips</item> <item name="storageConfig" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name.listing_top.bookmarks</item> <item name="namespace" xsi:type="string">current.search</item> </item> </item> </argument> </filterSearch> |
This node adds full text search into the page. It is located above the grid as single text input field with “Search by keyword” as a placeholder. I am not sure what options are possible here as I didn’t play with this much but listing_filters_chips refers to Magento/Ui/view/base/web/js/grid/filters/chips.js file.
The <filters /> node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<filters name="listing_filters"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="columnsProvider" xsi:type="string">listing_name.listing_name.listing_columns</item> <item name="storageConfig" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name.listing_top.bookmarks</item> <item name="namespace" xsi:type="string">current.filters</item> </item> <item name="templates" xsi:type="array"> <item name="filters" xsi:type="array"> <item name="select" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item> <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item> </item> </item> </item> <item name="childDefaults" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name.listing_top.listing_filters</item> <item name="imports" xsi:type="array"> <item name="visible" xsi:type="string">listing_name.listing_name.listing_columns.${ $.index }:visible</item> </item> </item> </item> <item name="observers" xsi:type="array"> <item name="column" xsi:type="string">column</item> </item> </argument> </filters> |
This node defines configuration for column filtering that is visible after clicking “Filters” button at the top right above the grid.
- columnsProvider follows similar structure as previous nodes, so [listing_name_from_layout].[listing_name_from_layout].[listing_columns_node_name].
- storegeConfig goes like [listing_name_from_layout].[listing_name_from_layout].[container_node_name][bookmark_node_name].
- In templates item node you can define which files are used to render specific filter options. In this case only select is defined and it uses Magento/Ui/view/base/web/js/form/element/ui-select.js as component and Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html as knockout template. Look into Magento/Ui/view/base/web/js/form/element to see other possibilities. Only select is defined here to override default values: Magento/Ui/view/base/web/js/form/element/select.js and Magento/Ui/view/base/web/templates/grid/filters/elements/select.html. Default values for filters and other nodes are defined in Magento/Ui/view/base/ui_component/etc/definition.xml.
The <massAction /> node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<massaction name="listing_massaction"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="selectProvider" xsi:type="string">listing_name.listing_name.listing_columns.ids</item> <item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item> <item name="indexField" xsi:type="string">database_id</item> </item> </argument> <action name="delete"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="type" xsi:type="string">delete</item> <item name="label" xsi:type="string" translate="true">Delete</item> <item name="url" xsi:type="url" path="*/*/massDelete"/> <item name="confirm" xsi:type="array"> <item name="title" xsi:type="string" translate="true">Delete items</item> <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected items?</item> </item> </item> </argument> </action> <action name="change_status"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="type" xsi:type="string">change_status</item> <item name="label" xsi:type="string" translate="true">Change Status</item> </item> </argument> <argument name="actions" xsi:type="configurableObject"> <argument name="class" xsi:type="string">Company\Module\Ui\Component\MassAction\Status\Options</argument> <argument name="data" xsi:type="array"> <item name="confirm" xsi:type="array"> <item name="title" xsi:type="string" translate="true">Change Status</item> <item name="message" xsi:type="string" translate="true">Are you sure to change status for selected feed(s)?</item> </item> </argument> </argument> </action> </massaction> |
name argument should be unique.
- First child node <argument /> defines basic data.
- provider follows the same structure as other nodes and refers to columns node name and its ids column. This column will hold checkboxes with selected items for the mass action to process.
- componentdefines what file is used to render and handle the mass action.
- Default value is Magento_Ui/js/grid/massactions (points to Magento/Ui/view/base/web/js/grid/massactions.js). You can use Magento_Ui/js/grid/tree-massactions to add tree like structure. In the above case I use it to add “Change status” action which shows “enable” and “disable” options. After <argument /> node you can add as many <action /> nodes as many actions you want to have. Each <action /> node follows similar scheme. In the first case (delete action) node requires unique name. Then argument contains configuration where label is what is visible in select option, url endpoint to send data and confirm adds confirmation modal before send. In case of “Change status” action url in first argument node is omitted as urls are provided per status by class defined in second argument node. The class should implement Zend\Stdlib\JsonSerializable interface. Check Magento\Customer\Ui\Component\MassAction\Group\Options as a reference.
The <paging /> node
1 2 3 4 5 6 7 8 9 10 11 |
<paging name="listing_paging"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="storageConfig" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name.listing_top.bookmarks</item> <item name="namespace" xsi:type="string">current.paging</item> </item> <item name="selectProvider" xsi:type="string">listing_name.listing_name.listing_columns.ids</item> </item> </argument> </paging> |
Structure for provider and selectProvider should be clear now.
The <columns /> node
1 |
<columns name="listing_columns"> ... </columns> |
and the name attribute is used in other nodes when refers to it. First child is
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="storageConfig" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name.listing_top.bookmarks</item> <item name="namespace" xsi:type="string">current</item> </item> <item name="childDefaults" xsi:type="array"> <item name="storageConfig" xsi:type="array"> <item name="provider" xsi:type="string">listing_name.listing_name.listing_top.bookmarks</item> <item name="root" xsi:type="string">columns.${ $.index }</item> <item name="namespace" xsi:type="string">current.${ $.storageConfig.root}</item> </item> <item name="fieldAction" xsi:type="array"> <item name="provider" xsi:type="string">name_listing.name_listing.listing_columns.actions</item> <item name="target" xsi:type="string">applyAction</item> <item name="params" xsi:type="array"> <item name="0" xsi:type="string">edit</item> <item name="1" xsi:type="string">${ $.$data.rowIndex }</item> </item> </item> </item> </item> </argument> |
supply correct provider values following the scheme used in the listing. The fieldAction node allows to define action that is fired when clicked on the cell. Default settings call edit action.
<selectionColumns />
1 2 3 4 5 6 7 8 9 |
<selectionsColumn name="ids"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="resizeEnabled" xsi:type="boolean">false</item> <item name="resizeDefaultWidth" xsi:type="string">55</item> <item name="indexField" xsi:type="string">id</item> </item> </argument> </selectionsColumn> |
This node defines column with checkboxes for mass action to use. It names is referred to after dot in several nodes described above.
After that you can add any number of columns in the same format:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<column name="name"> <argument name="data" xsi:type="array"> <item name="options" xsi:type="object">Company\Module\Model\Source\Type</item> <item name="config" xsi:type="array"> <item name="filter" xsi:type="string">text</item> <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item> <item name="dataType" xsi:type="string">select</item> <item name="bodyTmpl" xsi:type="string">ui/grid/cells/html</item> <item name="label" xsi:type="string" translate="true">Name</item> <item name="sortOrder" xsi:type="number">80</item> <item name="visible" xsi:type="boolean">false</item> </item> </argument> </column> |
Not all most inner items node are necessary. They are defining:
- filter – filter type of the column.
- component – defines js files which is used to render column.
- dataType – provides information of data type used for the column value.
- label – this will be displayed in the column header and filter block.
- sortOrder – ordering.
- visible – whether or not display the column.
At the end you can add actions column witch actions available to do for the single item
1 2 3 4 5 6 7 8 9 |
<actionsColumn name="actions"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="resizeEnabled" xsi:type="boolean">false</item> <item name="resizeDefaultWidth" xsi:type="string">107</item> <item name="indexField" xsi:type="string">database_id</item> </item> </argument> </actionsColumn> |
indexField refers to column name in the database. Actions class should extends Magento\Ui\Component\Listing\Columns\Column and define prepareDataSource method. See Magento/Cms/Ui/Component/Listing/Column/PageActions.php as a reference
3)
to finish the grid we need to define some elements in Company/Module/etc/di.xml
define provider class used in node dataSource/class
1 2 3 4 5 6 |
<virtualType name="UniqueNameGridDataProvider" type="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider"> <arguments> <argument name="collection" xsi:type="object" shared="false">Company\Module\Model\Resource\Item\Collection</argument> <argument name="filterPool" xsi:type="object" shared="false">UniqueNameItemIdFilterPool</argument> </arguments> </virtualType> |
collection resolves to standard collection class and filerPool defines new element:
1 2 3 4 5 6 7 8 |
<virtualType name="UniqueNameItemIdFilterPool" type="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool"> <arguments> <argument name="appliers" xsi:type="array"> <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item> <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item> </argument> </arguments> </virtualType> |
register our data source:
1 2 3 4 5 6 7 |
<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory"> <arguments> <argument name="collections" xsi:type="array"> <item name="listing_name_data_source" xsi:type="string">Company\Module\Model\Resource\Item\Grid\Collection</item> </argument> </arguments> </type> |
node name must match the one used in <dataSource /> node in listing xml and it resolves not to collection but to GridCollection class. It should extend regular collection class and additionally implement Magento\Framework\Api\Search\SearchResultInterface.
configure grid collection (argument names are rather obvious)
1 2 3 4 5 6 7 8 |
<type name="Company\Module\Model\Resource\Item\Grid\Collection"> <arguments> <argument name="mainTable" xsi:type="string">database_table_name</argument> <argument name="eventPrefix" xsi:type="string">name_for_events</argument> <argument name="eventObject" xsi:type="string">event_object_name</argument> <argument name="resourceModel" xsi:type="string">Company\Module\Model\Resource\Item</argument> </arguments> </type> |
More tips from Magento 2 Cookbook