- Magento 2: Introducing UI Components
- Magento 2: Simplest UI Component
- Magento 2: Simplest UI Knockout Component
- Magento 2: Simplest XSD Valid UI Component
- Magento 2: ES6 Template Literals
- Magento 2: uiClass Data Features
- Magento 2: UI Component Data Sources
- Magento 2: UI Component Retrospective
- Observables, uiElement Objects, and Variable Tracking
- Magento 2: uiElement Features and Checkout Application
- Magento 2: Remaining uiElement Defaults
- Magento 2: Knockout.js Template Primer
- Magento 2 UI Component Code Generation
Today we’re going to talk about a new feature in Magento 2 — UI Components. This may end up being a stand alone article, or it may be the start of a longer in depth series. I’m still figuring out the best way to cover this large, complex topic.
UI Components are an ambitious new approach to building user interface elements in Magento 2, and much of the new admin console is built on this functionality. Today we’ll take a high level tour of what the UI Component System’s goals are, cover implementation details at as high a level as possible, and then wrap up by using pestle
to generate a grid/listing UI Component.
The Positive Spin
The easiest way to understand the goals of UI Components is to talk about Magento 1’s backend user interface generating code. Here’s an example of some Magento 1 layout update XML code
<!-- #File: app/design/adminhtml/default/default/layout/catalog.xml -->
<adminhtml_catalog_product_new>
<update handle="editor"/>
<reference name="content">
<block type="adminhtml/catalog_product_edit" name="product_edit"></block>
</reference>
<reference name="left">
<block type="adminhtml/catalog_product_edit_tabs" name="product_tabs"></block>
</reference>
<reference name="js">
<block type="adminhtml/catalog_product_edit_js" template="catalog/product/js.phtml" name="catalog_product_js"></block>
<block type="core/template" template="catalog/wysiwyg/js.phtml"/>
</reference>
</adminhtml_catalog_product_new>
That’s 4 separate layout update XML nodes to add a single product editing form. If you consider the layout update XML behind the reusable <update handle="editor"/>
node
<!-- #File: app/design/adminhtml/default/default/layout/main.xml -->
<editor>
<reference name="head">
<action method="setCanLoadExtJs"><flag>1</flag></action>
<action method="addJs"><script>mage/adminhtml/variables.js</script></action>
<action method="addJs"><script>mage/adminhtml/wysiwyg/widget.js</script></action>
<action method="addJs"><script>lib/flex.js</script></action>
<action method="addJs"><script>lib/FABridge.js</script></action>
<action method="addJs"><script>mage/adminhtml/flexuploader.js</script></action>
<action method="addJs"><script>mage/adminhtml/browser.js</script></action>
<action method="addJs"><script>prototype/window.js</script></action>
<action method="addItem"><type>js_css</type><name>prototype/windows/themes/default.css</name></action>
<action method="addCss"><name>lib/prototype/windows/themes/magento.css</name></action>
</reference>
</editor>
You see that adding the product editing form to a page is even more complex.
The intention behind UI Components is to do away with and/or hide this complexity. Magento 2 introduces a new <uiComponent/>
tag for its layout handle XML files (Magento 2 handle XML files are analogous to Magento 1’s layout update XML files). In Magento 2, you can add a product editing form to your page with the following configuration
<uiComponent name="product_form"/>
By introducing the concept of a <uiComponent/>
, Magento 2 makes it easier for developers to reuse different components in different locations. While It was possible to drop different Magento 1 UI forms and grids in different areas, you needed to know which blocks and javascript files made up a particular component. Magento 1’s approach made it easy to accidentally setup a grid listing or a form so the component almost worked.
Magento 2’s UI Components set out to solve this problem, and simplify everyone’s layout handle XML files considerably.
The Actual Spin
While everything we just said it true enough, the reality of the UI Component system is a little murkier than that rosy picture. That’s because the UI Component system has a number of other goals, and those goals introduce a significant amount of complexity.
As near as I can tell, the UI Component System
- Simplifies Layout Handle XML files
- Moves admin user interface elements from HTML+Javascript to a “pure javascript” custom widget system
- Is a system for building more complex UI components out of smaller components
- Pre-renders data for UI components as JSON, binding closely to Magento backend data objects
- Uses ajax to update component data
- Introduce a new DSL for creating all of the above
The UI Component system is an ambitious one, and like a lot of things in Magento 2, it’s not quite fully baked. While you might want to stay away from a system that’s not quite done, most core grids and many forms use the UI Component system to render their interface elements, and others use a mix of a traditional block rendering and javascript files. If you want to build a fully featured module, you’ll need to work with the UI Component system.
The rest of this article represents my best understanding of UI Components at this time, (the Magento 2.1 era). Many of the specifics are likely to change in the future, but hopefully the core concepts will stay the same. There’s no great standard course of action for developers who want (or need) to develop backend UI interface elements — as per usual its best to look at what the core team is doing with their own components, mimic that, and keep a close eye on your module/extension code whenever there’s a Magento version update.
Unless you’re interested in complex implementation details, you may want to skip to the end where we use pestle to create a UI Component.
Pure Javascript
If you navigate to Content -> Block
in Magento 2’s backend, you’ll see a grid listing of all the CMS Blocks in your Magento system. If you’re not familiar with Blocks, they’re a way to create usable chunks of HTML for your store. Block information is stored in Magento’s backend using CRUD Models.
The listing you see is a UI Component, configured with the following layout handle XML
<!-- File: vendor/magento/module-cms/view/adminhtml/layout/cms_block_index.xml -->
<!-- ... -->
<referenceContainer name="content">
<uiComponent name="cms_block_listing"/>
</referenceContainer>
<!-- ... -->
If you’re new to Magento’s layout XML, a plain english reading of the above is
Get a reference to the already created container named content, and add the
cms_block_listing
UI Component to it
If you view the raw source of the HTML page, the <uiComponent/>
tag is responsible for rendering the following HTML
<div class="admin__data-grid-outer-wrap" data-bind="scope: 'cms_block_listing.cms_block_listing'">
<div data-role="spinner" data-component="cms_block_listing.cms_block_listing.cms_block_columns" class="admin__data-grid-loading-mask">
<div class="spinner">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</div>
<!-- ko template: getTemplate() --><!-- /ko -->
<script type="text/x-magento-init">
{"*": {"Magento_Ui/js/core/app": {...very large js object...}}}
</script>
</div>
If you’ve worked through the Magento 2 Advanced Javascript series, particularly the Javascript Init Scripts tutorial, you know the x-magento-init
script tag will invoke the Magento_Ui/js/core/app
RequireJS module as a program, passing in the very large js object as a parameter.
Without getting too deep into the implementation details (some of which you can read about in these Stack Exchange answers), this javascript code ends up creating a series of javascript constructor objects that Magento will use as KnockoutJS view models.
The actual rendering of the interface element in the browser is handled by KnockoutJS. The outer div of the skeleton HTML uses Magento’s custom KnockoutJS scope
binding to bind a view model that was created by the text/x-magento-init
javascript.
<div ... data-bind="scope: 'cms_block_listing.cms_block_listing'">
</div>
and then rendering of the UI Component happens via the KnockoutJS “tag-less” template binding
<!-- ko template: getTemplate() --><!-- /ko -->
The call to getTemplate
actually kicks off a number of KnockoutJS nested template renderings — starting with a file named collection.html
. You can find all these templates by looking for .html
files in your browser’s XHR debugging window. If you’re not familiar with Magento’s extension of KnockoutJS templates to XHR, or any of the other KnockoutJS code, try reading through the KnockoutJS Integration article that’s part of the Magento 2: Advanced Javascript series. Also, keep in mind that Magento’s core team has enhanced KnockoutJS templates with some custom tags and attributes that can be a little disorienting.
In summary, Magento 1 rendered a listing in HTML, and then used javascript to provide the enhanced user interface functionality. Magento 2, while it still uses some skeleton HTML, has shifted most of the rendering of these interface elements to RequireJS modules and KnockoutJS templates.
Sub Components
If you take a closer look at the x-magento-init
JSON object, you’ll see there’s a number of nested child javascript objects.
{
"*": {
"Magento_Ui/js/core/app": {
"types": /*...*/
"components": {
"cms_block_listing": {
"children": {
"cms_block_listing": {
/*...*/
"children": {
"listing_top": {
"type": "container",
"name": "listing_top",
"children": {
"bookmarks": {/*...*/},
"columns_controls": {/*...*/},
"fulltext": {/*...*/},
"listing_filters": {/*...*/},
"listing_massaction": {/*...*/},
"listing_paging": {/*...*/}
},
Older developers will be bemused to note the return of nodes named children
— a practice we thought was left behind in Magento 1. These child element are each, themselves, fully featured UI Components. The cms_block_listing
component is made up of components named listing_top
, bookmarks
, etc.
As we mentioned earlier, that initial getTemplate
call ends up rendering many sub-components. The first KnockoutJS template, collection.html
, is so named because its a collection of many different UI Components. Covering this rendering process in full is, unfortunately, not something we have time for today.
One thing we can cover today is how a PHP developer controls what’s rendered in that javascript tree. If we jump back to our <uiComponent/>
tag
<!-- #File: vendor/magento/module-cms/view/adminhtml/layout/cms_block_index.xml -->
<uiComponent name="cms_block_listing"/>
Magento uses the uiComponent
‘s name to look for a new XML file named cms_block_listing.xml
.
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<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">cms_block_listing.cms_block_listing_data_source</item>
<item name="deps" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">cms_block_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 Block</item>
<item name="class" xsi:type="string">primary</item>
<item name="url" xsi:type="string">*/*/new</item>
</item>
</item>
</argument>
<!-- ... we'll get to this in a second ... -->
</listing>
These UI Component XML files are a new domain specific language (DSL). The above instructions tell Magento to
- Look up a PHP class name and default arguments for the root level
listing
node - Instantiate that class, using the
argument
node as constructor arguments.
Magento will look up the PHP class name and default arguments in the following file
#File: vendor/magento/module-ui/view/base/ui_component/etc/definition.xml
<components xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_definition.xsd">
<!-- ... -->
<listing sorting="true" class="Magento\Ui\Component\Listing">
<argument name="data" xsi:type="array">
<item name="template" xsi:type="string">templates/listing/default</item>
<item name="save_parameters_in_session" xsi:type="string">1</item>
<item name="client_root" xsi:type="string">mui/index/render</item>
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">uiComponent</item>
</item>
</argument>
</listing>
<!-- ... -->
</components>
So, when Magento renders <uiComponent name="cms_block_listing"/>
as JSON, it starts by running code that (oversimplified) looks like this
$uiComponent = new Magento\Ui\Component\Listing(
$context, $components, [
'template'=>'templates/listing/default',
'save_parameters_in_session'=>'1',
'client_root'=>'mui/index/render',
'config'=>[
'component'=>'uiComponent'
],
'js_config'=>[
'provider'=>'',
'deps'=>''
],
'spinner'=>'cms_block_columns',
'buttons'=>[
'add'=>[
'name'=>'add',
'label'=>'Add New Block',
'class'=>'primary',
'url'=>'*/*/new'
]
],
]
)
The data for the arguments above comes from merging the <argument/>
nodes together. Each of these parameters has a different effect — but the one we’re interested in is the templates/listing/default
parameter. This specifies the XHTML template to render for this UI Component. The templates/listing/default
string corresponds to the following template.
#File: vendor/magento//module-ui/view/base/ui_component/templates/listing/default.xhtml
<div
class="admin__data-grid-outer-wrap"
data-bind="scope: '{{getName()}}.{{getName()}}'"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../../Ui/etc/ui_template.xsd">
<div data-role="spinner" data-component="{{getName()}}.{{getName()}}.{{spinner}}" class="admin__data-grid-loading-mask">
<div class="spinner">
<span/><span/><span/><span/><span/><span/><span/><span/>
</div>
</div>
<!-- ko template: getTemplate() --><!-- /ko -->
</div>
This XHTML template is rendered by a completely different rendering engine than a standard Magento phtml
template.
Magento replaces the {{...}}
text by calling the PHP method on the UI Component object (getName()
), or directly accessing a data property of the same object ({{spinner}}
).
The more astute among you may have noticed there’s no x-magento-init
listed in the template. Rendering the x-magento-init
portion of the UI Component is still handled by the XHTML rendering engine — specifically in the appendLayoutConfiguration
method called here
#File: vendor/magento/module-ui/TemplateEngine/Xhtml/Result.php
public function __toString()
{
try {
//...
$this->appendLayoutConfiguration();
$result = $this->compiler->postprocessing($this->template->__toString());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
$result = $e->getMessage();
}
return $result;
}
//...
public function appendLayoutConfiguration()
{
$layoutConfiguration = $this->wrapContent(
json_encode(
$this->structure->generate($this->component)
)
);
$this->template->append($layoutConfiguration);
}
//...
protected function wrapContent($content)
{
return '<script type="text/x-magento-init"><![CDATA['
. '{"*": {"Magento_Ui/js/core/app": ' . str_replace(['<![CDATA[', ']]>'], '', $content) . '}}'
. ']]></script>';
}
Magento will render the structure of the UI Component object as the JSON string, and then append that string to the template.
What is the structure of a UI Component you ask? Remember the we’ll get to the rest in a second hand waving we did here?
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<?xml version="1.0" encoding="UTF-8"?>
<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">cms_block_listing.cms_block_listing_data_source</item>
<item name="deps" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">cms_block_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 Block</item>
<item name="class" xsi:type="string">primary</item>
<item name="url" xsi:type="string">*/*/new</item>
</item>
</item>
</argument>
<!-- ... we'll get to this in a second ... -->
</listing>
If we look at the actual contents of those nodes
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<listingToolbar name="listing_top">
<argument name="data" xsi:type="array">
<!-- ... -->
</argument>
</listingToolbar>
<columns name="cms_block_columns">
<argument name="data" xsi:type="array">
<!-- ... -->
</argument>
</columns>
we see more configured UI Components. Any sub-node of a UI Component that’s not named argument
is considered a child node of the parent object. i.e. When Magento renders the listing
component, it also looks up classes and arguments for listingToolbar
, columns
, etc. in definitions.xml
#File: vendor/magento/module-ui/view/base/ui_component/etc/definition.xml
<components xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_definition.xsd">
<listingToolbar class="Magento\Ui\Component\Container"><!--...--></listingToolbar>
<columns class="Magento\Ui\Component\Listing\Columns"><!--...--></columns>
</components>
and the pseudo code we used earlier actually looks more like this
$uiComponent = new Magento\Ui\Component\Listing(...);
$listingToolbar = new Magento\Ui\Component\Container(...);
$columns = new Magento\Ui\Component\Listing\Columns(...);
$uiComponent->addComponent($listingToolbar);
$uiComponent->addComponent($columns);
As a (potentially overwhelming) side note, these child components are the ones configured with the RequireJS module names
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<columns class="Magento\Ui\Component\Listing\Columns">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/listing</item>
<!-- ... -->
</item>
</argument>
</columns>
These are the RequireJS modules that Magento turns into KnockoutJS view models. If you lookup the source to these KnockoutJS view models — you’ll usually find the KnockoutJS template configured on the view model constructor.
#File: vendor/magento//module-ui/view/base/web/js/grid/listing.js
define([
'ko',
'underscore',
'Magento_Ui/js/lib/spinner',
'uiLayout',
'uiCollection'
], function (ko, _, loader, layout, Collection) {
'use strict';
return Collection.extend({
defaults: {
template: 'ui/grid/listing',
}
//...
});
});
Data Source Nodes
Finally, there’s one more special UI Component sub-node — the <dataSource/>
node.
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<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">cms_block_listing.cms_block_listing_data_source</item>
<item name="deps" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">cms_block_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 Block</item>
<item name="class" xsi:type="string">primary</item>
<item name="url" xsi:type="string">*/*/new</item>
</item>
</item>
</argument>
<!-- ... -->
<dataSource name="cms_block_listing_data_source">
<!-- ... -->
</dataSource>
</listing>
The nodes named dataSource
are still UI Components, but they get special treatment. When Magento renders the JSON for the UI component, dataSource
nodes get pulled out of the children
structure, and Magento renders them right along side the main, top level component (using the name of the component appended with the string _data_source
as an object key).
{
"*": {
"Magento_Ui/js/core/app": {
"types": {/*...*/},
"components": {
"cms_block_listing": {
"children": {
"cms_block_listing": {/*...*/},
"cms_block_listing_data_source": {
"type": "dataSource",
"name": "cms_block_listing_data_source",
"dataScope": "cms_block_listing",
"config": {
"data": {
"items": [],
"totalRecords": 0
},
"component": "Magento_Ui\/js\/grid\/provider",
"update_url": "http:\/\/magento-2-1-0.dev\/admin\/mui\/index\/render\/key\/e628fdf18db9219474935e85ab3f25b445287503a00a230704b4168c566f8059\/",
"storageConfig": {
"indexField": "block_id"
},
"params": {
"namespace": "cms_block_listing"
}
}
}
}
}
}
}
}
}
The dataSource
component is where Magento will look for the actual data that populates your UI Component (i.e. the rendered collection data for a model)
Summary of the UI Component Rendering DSL
OK — that was a bananas-pants amount of information. I just finished writing it and I’m not sure even I followed all of it, so don’t worry if your head is spinning. Here’s a very high level summary.
- UI Components render an
x-magento-init
script that populates a global registry of KnockoutJS view models - UI Components also render skeleton HTML that uses KnockoutJS and the custom
scope
binding to rendering the DOM nodes that make up a component - The
ui_component
XML files are a domain specific language for instantiating a nested hierarchy of UI Component objects, which Magento will ultimately use to render the JSON for thex-magento-init
script - A
ui_component
‘s XML node name is used to lookup PHP classes to instantiate - Magento uses any sub-
<agument/>
nodes as constructor arguments for that class - Magento uses any sub-node named
<dataSource/>
to render the actual data used in a UI component (i.e. grid listing information) - Any other sub-node will be used to render a child UI Component — those child UI Components follow the same rules as their parent
- The top level UI node configures an XHTML template, which Magento renders via PHP
- UI Component nodes configure the RequireJS module(s) that Magento uses as KnockoutJS view model constructors
As you can see, while the uiComponent
tags greatly simplifies Magento 2’s layout handle XML files, they also hide a much more complex UI rendering system that includes both front end and backend Magento systems code, and requires developers to understand Magento’s customizations to RequireJS and KnockoutJS as well.
Creating a Grid Listing with Pestle
As you can see from the above (whether you read it or not), the UI Component system rivals Magento 1’s layout update XML system in both its complexity and the lack of clear guidance on usage. In other words, it’s exactly the sort of place a code generation tool like pestle can make things better for working Magento 2 developers. The most recent versions of pestle include a magento2:generate:ui:grid
command for creating UI listings, with more commands to come soon.
We’re going to run through using pestle to create a UI grid. We’re going to assume you’ve worked your way through the CRUD Models for Database Access tutorial and have a working Pulsestorm_ToDoCrud
module. We’ll also assume you’ve been able to create an admin endpoint with a layout handle XML file, and have a backend page you can navigate to.
In order to create a grid listing, invoke pestle’s magento2:generate:ui:grid
command with the following arguments
$ pestle.phar magento2:generate:ui:grid
Which Module? (Pulsestorm_Gridexample)] Pulsestorm_ToDoCrud
Create a unique ID for your Listing/Grid! (pulsestorm_gridexample_log)] pulsestorm_todo_listing
What Resource Collection Model should your listing use? (Magento\Cms\Model\ResourceModel\Page\Collection)] Pulsestorm\ToDoCrud\Model\ResourceModel\TodoItem\Collection
What's the ID field for you model? (pulsestorm_gridexample_log_id)] pulsestorm_todocrud_todoitem_id
The Which Module? argument tells pestle the Magento module you want to create your grid listing in. This is, generally speaking, the same module as the collection file, but there’s nothing enforcing this convention. We’ve specified the Pulsestorm_ToDoCrud
from the previous tutorials
The Create a unique ID for your Listing/Grid! argument is the name we want for our UI Component. This will be the name=""
attribute we use in the <uiComponent/>
tag, as well as the base file name on disk for the UI Component XML file.
The What Resource Collection Model should your listing use? argument is the class name of the collection model to use. We want our grid listing to display Pulsestorm_ToDoCrud
models, so we use the Pulsestorm\ToDoCrud\Model\ResourceModel\TodoItem\Collection
collection.
The What’s the ID field for you model? argument is the primary key database column for a model’s database table.
After running the above command, add the following <uiComponent/>
to your admin module’s layout handle XML file.
<!-- File: app/code/Pulsestorm/ToDoCrud/view/adminhtml/layout/pulsestorm_admin_todocrud_index_index.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="content">
<uiComponent name="pulsestorm_todo_listing"/>
</referenceBlock>
</body>
</page>
With the above in place, clear your cache, and you should have a simple UI Grid that lists the model ID for each individual Pulsestorm\ToDoCrud\Model\ToDoItem
model. If you want to add a column for the model’s title
attribute, just add the following <column/>
node to the generated UI Component XML file
<!-- File: app/code/Pulsestorm/ToDoCrud/view/adminhtml/ui_component/pulsestorm_todo_listing.xml -->
<!-- ... -->
<columns>
<!-- ... --->
<column name="title">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Item Title</item>
<item name="sortOrder" xsi:type="number">20</item>
</item>
</argument>
</column>
<!-- ... --->
</columns>
<!-- ... -->
In addition to generating the UI Component pulsestorm_todo_listing.xml
file, pestle also generates a “data provider” class, and a “page action” class.
The data provider class wraps the collection resource model
#File: app/code/Pulsestorm/ToDoCrud/Ui/Component/Listing/DataProviders/Pulsestorm/Todo/Listing.php
<?php
namespace Pulsestorm\ToDoCrud\Ui\Component\Listing\DataProviders\Pulsestorm\Todo;
class Listing extends \Magento\Ui\DataProvider\AbstractDataProvider
{
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
\Pulsestorm\ToDoCrud\Model\ResourceModel\TodoItem\CollectionFactory $collectionFactory,
array $meta = [],
array $data = []
) {
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
$this->collection = $collectionFactory->create();
}
}
and the page action class is responsible for rendering the edit link in the final column.
#File: app/code/Pulsestorm/ToDoCrud/Ui/Component/Listing/Column/Pulsestormtodolisting/PageActions.php
<?php
namespace Pulsestorm\ToDoCrud\Ui\Component\Listing\Column\Pulsestormtodolisting;
class PageActions extends \Magento\Ui\Component\Listing\Columns\Column
{
public function prepareDataSource(array $dataSource)
{
if (isset($dataSource["data"]["items"])) {
foreach ($dataSource["data"]["items"] as & $item) {
$name = $this->getData("name");
$id = "X";
if(isset($item["pulsestorm_todocrud_todoitem_id"]))
{
$id = $item["pulsestorm_todocrud_todoitem_id"];
}
$item[$name]["view"] = [
"href"=>$this->getContext()->getUrl(
"adminhtml/pulsestorm_todo_listing/viewlog",["id"=>$id]),
"label"=>__("Edit")
];
}
}
return $dataSource;
}
}
While not yet fully featured, the magento2:generate:ui:grid
command will get you started with a base grid listing configuration. From there, you should be able to examine Magento’s core grid classes and replicate any functionality you see in a core module.