Categories


Archives


Recent Posts


Categories


Magento 2: Introducing UI Components

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

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

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

  1. Look up a PHP class name and default arguments for the root level listing node
  2. 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.

  1. UI Components render an x-magento-init script that populates a global registry of KnockoutJS view models
  2. UI Components also render skeleton HTML that uses KnockoutJS and the custom scope binding to rendering the DOM nodes that make up a component
  3. 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 the x-magento-init script
  4. A ui_component‘s XML node name is used to lookup PHP classes to instantiate
  5. Magento uses any sub-<agument/> nodes as constructor arguments for that class
  6. Magento uses any sub-node named <dataSource/> to render the actual data used in a UI component (i.e. grid listing information)
  7. Any other sub-node will be used to render a child UI Component — those child UI Components follow the same rules as their parent
  8. The top level UI node configures an XHTML template, which Magento renders via PHP
  9. 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.

Originally published July 10, 2016
Series NavigationMagento 2: Simplest UI Component >>