Categories


Archives


Recent Posts


Categories


Magento 2: Simplest UI Component

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 YOLO deep dive into Magento 2’s UI Components and attempt to create one from scratch. At this stage in Magento 2’s lifecycle this isn’t something third party developers can do without taking extra ordinary “not production safe” measures, but sometimes the only way to really understand a system is to inhabit it from the ground up.

Like most of these tutorials, you’ll want to make sure you’re running Magento 2 in developer mode (as opposed to production or default mode). Also, in case any of the following gets too crazy, we’ve put a completed module up on GitHub. Also also, these specifics here have been tested and developed against Magento 2.1.1 — but the concepts should apply across versions.

Baseline Admin Module with Pestle

To start, we’re going to use pestle to create a boilerplate module with a backend menu item. If you’ve not sure what the below commands are doing, you may want to work your way through the Magento 2 for PHP MVC Developers developers series.

pestle.phar generate_module Pulsestorm SimpleUiComponent 0.0.1

pestle.phar generate_acl Pulsestorm_SimpleUiComponent Pulsestorm_SimpleUiComponent::top,Pulsestorm_SimpleUiComponent::menu_1

pestle.phar generate_menu Pulsestorm_SimpleUiComponent Magento_Backend::system_other_settings Pulsestorm_SimpleUiComponent::a_menu_item Pulsestorm_SimpleUiComponent::menu_1 "Hello Simple Ui Component" pulsestorm_simpleuicomponent/index/index 1

pestle.phar generate_route Pulsestorm_SimpleUiComponent adminhtml pulsestorm_simpleuicomponent

pestle.phar generate_view Pulsestorm_SimpleUiComponent adminhtml pulsestorm_simpleuicomponent_index_index Main content.phtml 1column

php bin/magento module:enable Pulsestorm_SimpleUiComponent

php bin/magento setup:upgrade

After running the above commands, you should be able to navigate to Magento’s backend and click on the System -> Other Settings -> Hello Simple Ui Component menu to bring up our new backend section.

Configuring a UI Component

If you click on the System -> Other Settings -> Hello Simple Ui Component link, you should see the stock pestle auto-generated page.

The first thing we’ll want to do is add a <uiComponent> configuration to our layout handle XML file.

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/layout/pulsestorm_simpleuicomponent_index_index.xml -->
<?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">
    <referenceBlock name="content">
        <block template="content.phtml" class="Pulsestorm\SimpleUiComponent\Block\Adminhtml\Main" name="pulsestorm_simpleuicomponent_block_main" />

        <!-- START: our new ui component -->
        <uiComponent name="pulsestorm_simple"/>
        <!-- END:   our new ui component -->
    </referenceBlock>
</page>

In the code above, we’re telling Magento we want to add a pulsestorm_simple UI Component to the content block on our page. With the above in place, if we clear the Magento cache and reload the page, we’ll see the following error

1 exception(s):
Exception #0 (Magento\Framework\Exception\LocalizedException): Object 
DOMDocument should be created.

Exception #0 (Magento\Framework\Exception\LocalizedException): Object 
DOMDocument should be created.
#0 /path/to/magento/
vendor/magento/framework/View/Element/UiComponent/Config/Reader.php(95): 
Magento\Framework\View\Element\UiComponent\Config\DomMerger->getDom()

The reason we’re seeing this error is we’ve configured a UI Component named pulsestorm_simple, but Magento couldn’t find a configuration file for it. The error Object DOMDocument should be created comes from PHP code trying to read an XML object that wasn’t created.

Every named UI Component needs a defined ui_component/[...].xml configuration file. So, let’s get that configuration file in place so PHP stops complaining about the DOMDocument. Create the following file.

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/pulsestorm_simple.xml -->
<pulsestorm_simple xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
</pulsestorm_simple>

A UI Component’s configured name attribute will match its XML filename — pulsestorm_simple and pulsestorm_simple.xml in our examples above. All UI Component files are found in a ui_component sub-folder of a specific area’s view folder. Although there’s nothing stopping you from using a UI Component in the frontend area, it’s not 100% clear if this will work as you’d expect, as Magento’s core team have mostly (only?) released UI Components configured on backend layouts.

If we clear the Magento cache and reload with the above in place, we’ll be rewarded with a new error message.

1 exception(s):
Exception #0 (Magento\Framework\Exception\LocalizedException): Element
'pulsestorm_simple': No matching global declaration available for the
validation root.
Line: 1

Here Magento’s objecting to our top level node name — pulsestorm_simple. As you’ll recall from the first article in our UI Component series, a UI Component file is a domain specific language (DSL) that controls the instantiation of nested PHP object files. Each node in a ui_component file is linked up with a configuration node in in the following file.

vendor/magento/module-ui/view/base/ui_component/etc/definition.xml

So, the first problem is our pulsestorm_simple node does not exists in definition.xml, and Magento’s UI Component DSL wouldn’t know which PHP class to instantiate when it encountered this node. Now, thanks to Magento’s merged configuration file loading, we can add-to/change the final merged definition.xml by adding the following file (note: this must be in the base folder for this to work)

<!-- File: app/code/Pulsestorm/SimpleUiComponent/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">
    <pulsestorm_simple class="Pulsestorm\SimpleUiComponent\Component\Simple"/>
</components>

With the above configuration, we’re telling Magento

Whenever you encounter a pulsestorm_simple UI Component node, you should instantiate a Pulsestorm\SimpleUiComponent\Component\Simple object.

You’ll want to be careful naming your nodes here — this file will be merged with Magento’s core definition.xml file, and if you use a name that’s already in use, you may change core system behavior. Including your vendor namespace (pulsestorm_ above) is a good best practice here.

If we clear our cache with the latest file in place, we’ll manage to get a changed error message. (Clearly Sisyphus’ rock was made from XML).

1 exception(s):    
Exception #0 (Magento\Framework\Exception\LocalizedException): Element 
'pulsestorm_simple': This element is not expected. Expected is one of 
( range, tab, dataSource, paging, massaction, listing, form, fieldset, 
field, filters ).

Our problem here? Magento is merging our definition.xml file into an XML document with schema validation. Specifically, Magento demands that the final definition.xml match the structure rules setup in

vendor/magento/module-ui/etc/ui_definition.xsd

Unfortunately, there’s no supported way of adding to these rules in Magento 2. If you know where to look (Magento\Framework\Config\Dom::validateDomDocument) and aren’t above using the object manager’s preference system to inject some custom behavior, it’s possible to have Magento ignore these XSD validation rules. Unfortunately, there’s no way to do this that wouldn’t conflict with another extension trying to do the same thing, so it’s not really an option if you’re trying to redistribute code. Magento’s more stable plugin system isn’t an option, because the validateDomDocument, while public, is a static method, and Magento’s plugin system doesn’t work with static methods.

At this point, we’re out of luck if we want to create a new, top level ui_component node. This is the first sign that the UI Component system is either reserved for Magento’s core developers, was released before it was feature complete, or both.

Skipping Schema Validation

Of course, when we said “out of luck”, we meant “out of luck, unless we want to attack the problem with a possibly unstable class preference” (i.e. YOLO).

Class preferences are the system where Magento developers (core or third party) define concrete classes that the object manager can use to instantiate interfaces. i.e. They link a default class to a PHP interface, and then when a programmer asks the object manager to instantiate that interface, Magento returns an object of the linked class.

Class preferences can also be used to replace concrete class definitions, providing functionality that’s very similar to class rewrites in Magento 1 (with all the same downsides as the rewrite system).

To keep this tutorial going, we’re going to gin up a class preference that will skip XSD validation for XML files. This is not something you’ll want to do in a production system or distributable extension. We’re only doing it now because there’s no other way to proceed.

Create the following di.xml file

<!-- File: app/code/Pulsestorm/SimpleUiComponent/etc/di.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">                                                                -->
    <preference for="Magento\Framework\App\Arguments\ValidationState" type="Pulsestorm\SimpleUiComponent\Model\ValidationState"/>

</config>

and add the following class file to your module

#File: app/code/Pulsestorm/SimpleUiComponent/Model/ValidationState.php
<?php
namespace Pulsestorm\SimpleUiComponent\Model;
class ValidationState extends \Magento\Framework\App\Arguments\ValidationState
{
    public function isValidationRequired()
    {        
        return false;
    }
}

The specifics of why/how this works are left as an exercise for the reader, but this Magento quickie should give you a head start on debugging.

If you clear your cache with the above in place, you should now see a different error.

1 exception(s):
Exception #0 (ReflectionException): Class
Pulsestorm\SimpleUiComponent\Component\Simple does not exist

XSD validation left behind, we’re ready to continue our exploration.

UI Component Rendering Class

Before we went down that schema validation hole, we’d just added the following configuration to definition.xml

<!-- File: app/code/Pulsestorm/SimpleUiComponent/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">
    <pulsestorm_simple class="Pulsestorm\SimpleUiComponent\Component\Simple"/>
</components>

The etc/definition.xml configuration sets the default attributes and nodes that will be used whenever Magento encounters a particular parent node in a ui_component/[somefile].xml file. In the above example, we’ve configured Pulsestorm\SimpleUiComponent\Component\Simple as pulsestorm_simple‘s default class. This means when we use pulsestorm_simple here

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/pulsestorm_simple.xml -->
<pulsestorm_simple xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
</pulsestorm_simple>

Magento will attempt to instantiate a Pulsestorm\SimpleUiComponent\Component\Simple object, and use that object to render our UI Component. So, our error

1 exception(s):
Exception #0 (ReflectionException): Class Pulsestorm\SimpleUiComponent\Component\Simple does not exist

Is Magento complaining it can’t find the Pulsestorm\SimpleUiComponent\Component\Simple class it needs to instantiate. Let’s fix that! Create the following class file.

#File: app/code/Pulsestorm/SimpleUiComponent/Component/Simple.php
<?php
namespace Pulsestorm\SimpleUiComponent\Component;
class Simple extends \Magento\Ui\Component\AbstractComponent
{
    const NAME = 'pulsestorm_simple';
    public function getComponentName()
    {
        return static::NAME;
    }
}     

A Magento 2 UI Component class file should extend the base abstract Magento\Ui\Component\AbstractComponent class, and will need to define a getComponentName method. It’s not clear if a component name needs to be the same as the UI Component node name or ui_componont/[filename].xml (pulsestorm_simple above), but it’s best to follow the guidelines set by Magento’s core code here. For similar reasons, we’ve also given our component class a NAME constant.

With the above in place, let’s clear the Magento cache and reload our page to get the next error!

1 exception(s):
Exception #0 (Magento\Framework\Exception\LocalizedException): Object
DOMDocument should be created.

Exception #0 (Magento\Framework\Exception\LocalizedException): Object
DOMDocument should be created.

Once again Magento is complaining about a missing XML file. Here’s where we let you in on one of the biggest surprises of the UI Component system — a UI Component object, one that extends Magento\Ui\Component\AbstractComponent, is a system for rendering XHTML templates. XHTML — as in the series of specifications that attempted to replace the non-well-formed standard of HTML4 with an HTML that had XML’s draconian parsing rules.

We’ve told Magento we want it to render a Pulsestorm\SimpleUiComponent\Component\Simple object, we have not told Magento which template a Pulsestorm\SimpleUiComponent\Component\Simple object should use. Let’s change that! Add the following to our definition.xml file

<!-- File: app/code/Pulsestorm/SimpleUiComponent/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">
    <pulsestorm_simple class="Pulsestorm\SimpleUiComponent\Component\Simple">
        <argument name="data" xsi:type="array">
            <item name="template" xsi:type="string">templates/our-template</item>
        </argument>        
    </pulsestorm_simple>
</components>

The above tells Magento we want to render the templates/our-template XHTML template. Let’s add that template to our system.

#File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/templates/our-template.xhtml
<?xml version="1.0" encoding="UTF-8"?>
<div>
    <h1>Hello World</h1>
</div>    

The UI Component system will look for these templates in a module’s view/[area]/ui_component folder. The value from the definition.xml file is transformed into a template path by appending a .xhtml to the file name. Notice that, although these files look like HTML, they have an XML prolog. There are XHTML files, and will need to be well formed XML.

With the above in place, clear your cache and reload the page. You’ll see yet another error, but we promise you we’re almost there.

( ! ) Fatal error: Method Magento\Ui\TemplateEngine\Xhtml\Result::__toString() must not throw an
 exception, caught Error: Call to a member function getConfigData() on null
 in /path/to/magento/
 vendor/magento/module-ui/Component/Wrapper/UiComponent.php on line 0

Here Magento ran into an error while trying to render the XHTML template, and we’ve stumbled into another aspect of the UI Component system. In addition to being a system for rendering XHTML templates, UI Components are also a system that match up a data provider class with a specific XHTML template. The idea is a UI Component is meant to render server side data, and the data provider is the formal method for getting that information to the component.

This means our final (we swear) step for a bare bones UI Component object is configuring a data provider class. This happens in the pulsestorm_simple.xml file since each theoretical component instance renders a specific UI Component. Add the following dataSource node to our pulsestorm_simple.xml file.

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/pulsestorm_simple.xml -->
<pulsestorm_simple xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">

    <dataSource name="pulsestorm_simple_data_source">                        
        <argument name="dataProvider" xsi:type="configurableObject">
            <!-- the PHP class that implements a data provider -->
            <argument name="class" xsi:type="string">Pulsestorm\SimpleUiComponent\Model\DataProvider</argument>    

            <!-- redundant with the `dataSource` name -->
            <argument name="name" xsi:type="string">pulsestorm_simple_data_source</argument>

            <!-- required: means ui components are meant to work with models -->
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>

            <!-- required: means ui components are meant to work with URL passing -->
            <argument name="requestFieldName" xsi:type="string">id</argument>
        </argument>        

    </dataSource>

</pulsestorm_simple>

There’s some redundant boilerplate naming conventions to be aware of in the <dataSource/> tree. First, the name attribute

<dataSource name="pulsestorm_simple_data_source">...</dataSource>                       

Is a combination of the UI Component name (pulsestorm_simple), prepended to the string _data_source. Similarly, the following argument node

<argument name="name" xsi:type="string">pulsestorm_simple_data_source</argument>      

is required, even though it’s just a redundant naming of the node.

The following two nodes are also required

<argument name="primaryFieldName" xsi:type="string">entity_id</argument>
<argument name="requestFieldName" xsi:type="string">id</argument>

and hint at functionality that’s not present in our bare bones component, but PHP will raise an error if they’re not there.

Finally, the class argument

<argument name="class" xsi:type="string">Pulsestorm\SimpleUiComponent\Model\DataProvider</argument>    

tells our component which PHP data provider class to instantiate. We’d better create this class! Make sure the following class is a part of your module.

#File: app/code/Pulsestorm/SimpleUiComponent/Model/DataProvider.php
<?php
namespace Pulsestorm\SimpleUiComponent\Model;
class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{
}

DataProvider classes need to extend the base Magento\Ui\DataProvider\AbstractDataProvider class — although this class has zero abstract methods for us to define.

Alright. With the above in place, lets cross our fingers, clear the Magento cache, and reload the page.

Eureka! We’ve rendered an XHTML template!

What’s Happening Behind the Scenes

Before we get into some of the things we can do with this rendered XHTML template, let’s take a second to talk about what’s going on behind the scenes. When Magento’s layout rendering code encounters a UI Component tag, a bunch of code runs that’s equivalent to the following pseudo code

$data = functionThatLoadsArgumentNodesFromXmlFiles();
$ui_component = new Pulsestorm\SimpleUiComponent\Component\Simple(
    //...
    $data,    
);    
echo $ui_component->render();

The entire process of configuring a ui_component is to pick the class that’s instantiated, and to set data properties on that class. In our example the instantiation calls looks like this

$data = functionThatLoadsArgumentNodesFromXmlFiles();
$ui_component = new Pulsestorm\SimpleUiComponent\Component\Simple(
    //...
    [
        'template'=>'templates/our-template'
    ],    
);    
echo $ui_component->render();    

A good DSL usually lets you forget about implementation details like this — but if you’ve never encountered a DSL this sort of thing can seem strange and foreign. Whenever you’re stuck with a bit of UI Component configuration, try to remember you’re preparing values for Magento to convert into PHP code. These are not simple data attributes.

Raw Template Source

Let’s come back to our rendered template.

That’s how it looks in the browser — but what’s the actual rendered source look like. If we take a look (using the browser’s View Source menu and not the rendered DOM of a browser debugger), we’ll see the following (formatted for easier viewing below)

<div>
    <h1>
        Hello World
    </h1>
    <script type="text/x-magento-init">
        {
            "*": {
                "Magento_Ui/js/core/app": {
                    "types": {
                        "dataSource": [],
                        "pulsestorm_simple": {
                            "extends": "pulsestorm_simple"
                        },
                        "html_content": {
                            "component": "Magento_Ui\/js\/form\/components\/html",
                            "extends": "pulsestorm_simple"
                        }
                    },
                    "components": {
                        "pulsestorm_simple": {
                            "children": {
                                "pulsestorm_simple": {
                                    "type": "pulsestorm_simple",
                                    "name": "pulsestorm_simple"
                                },
                                "pulsestorm_simple_data_source": {
                                    "type": "dataSource",
                                    "name": "pulsestorm_simple_data_source",
                                    "dataScope": "pulsestorm_simple",
                                    "config": {
                                        "params": {
                                            "namespace": "pulsestorm_simple"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }        
    </script>
</div>

Not only has Magento rendered our <div> and <h1> tags from the XHTML template — they’ve also rendered a text/x-magento-init script. This is the final aspect of the UI Component system we’ll cover today. Not only does the UI Component render an XHTML template, not only does it bind that template to a data provider object: The UI Component system also renders a JSON object, and uses that JSON object to initialize an instance of the Magento_Ui/js/core/app RequireJS app/module via an x-magento-init script.

Now that we know the scope of a UI Component, let’s take a look at some features of this template/rendering engine.

XHTML Template Tags

Similar to phtml templates — you can “call through” to the underlying UI Component class in an XHTML template. There’s a special {{...}} template directive syntax you can use. For example, is we add the getEvenMoreData method to our component class

#File: app/code/Pulsestorm/SimpleUiComponent/Component/Simple.php
<?php
namespace Pulsestorm\SimpleUiComponent\Component;
class Simple extends \Magento\Ui\Component\AbstractComponent
{
    const NAME = 'pulsestorm_simple';
    public function getComponentName()
    {
        return static::NAME;
    }

    //added this method
    public function getEvenMoreData()
    {
        return 'Even More Data!';
    }
}    

We can use the following {{...}} calls in our xhtml template.

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/templates/our-template.xhtml -->
<?xml version="1.0" encoding="UTF-8"?>
<div>
    <h1>Hello World</h1>

    <p>
        {{getComponentName()}}
    </p>

    <p>
        {{getEvenMoreData()}}
    </p>
</div>

with the above in place, clear your cache and reload the page. You should see the data pass through from the class methods/properties to the template.

In addition to calling methods on the object, we should be able to fetch data properties by using a data configuration like this

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/base/ui_component/etc/definition.xml -->
<argument name="data" xsi:type="array">
    <item name="template" xsi:type="string">templates/our-template</item>

    <!-- NEW NODE HERE -->
    <item name="message" xsi:type="string">Hello World</item>                          
</argument>        

And then referencing the data property by name in a xhtml {{template}} variable.

<?xml version="1.0" encoding="UTF-8"?>
<div>
    <!-- ... -->
    {{message}}
</div>

However, there’s a bug in the XHTML rendering that will prevent this from working unless your data variable is in a tag’s attribute

<?xml version="1.0" encoding="UTF-8"?>
<div class="{{message}}">
    <!-- ... -->
    {{message}}
</div>

Super annoying, and another sign that the UI Component system isn’t fully baked.

Understanding UI Component Inheritance

When you place a top level node in definition.xml, you’re creating a reusable UI Component tag. This lets a UI Component programmer use your tag in a UI Component XML file loaded in through a <uiComponent/> layout tag.

The definition.xml file also lets you set defaults for your UI Component, but an end user programmer can override them.

For example, we set a default template with the following

<!-- File: app/code/Pulsestorm/SimpleUiComponent/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">
    <pulsestorm_simple class="Pulsestorm\SimpleUiComponent\Component\Simple">
        <argument name="data" xsi:type="array">
            <item name="template" xsi:type="string">templates/our-template</item>
        </argument>        
    </pulsestorm_simple>
</components>

If a theoretical UI Component programmer wanted a pulsestorm_simple UI Component, but wanted to change the template, all they’d need to do is create the same structure in their XML file. For example, if we wanted a new template

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/templates/different.xhtml -->
<?xml version="1.0" encoding="UTF-8"?>
<div>
    <h1>Hello Brave New World</h1>
</div>    

for pulsestorm_simple, all we need to do is add this to our XML file.

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/pulsestorm_simple.xml -->
<pulsestorm_simple 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="template" xsi:type="string">templates/different</item>
    </argument>
    <!-- ... -->         
</pulsestorm_simple>    

While this feature isn’t often used for templates — it is used for other UI Component configuration parameters, and is fundamental to using the system. While you won’t be adding information to definition.xml in the real world, you will be referencing definition.xml when you need to, say, debug a grid listing’s rendering parameters.

Adding Data

The last thing we’ll want to talk about today is a UI Component’s data. A UI Component’s data would be something like the rows of information for a grid listing, or the default values of a form. Behind the scenes, the UI Component system can render this backend data for you in the frontend as a javascript array/object.

All we need to do is define a getDataSourceData method on our component class.

#File: app/code/Pulsestorm/SimpleUiComponent/Component/Simple.php    
<?php
namespace Pulsestorm\SimpleUiComponent\Component;
class Simple extends \Magento\Ui\Component\AbstractComponent
{
    <!-- ... -->
    public function getDataSourceData()
    {
        return ['data' => ['foo'=>'bar']];
    }
}

If you clear your cache and reload the page with the above in place, you’ll see our little foo=>bar data structure rendered as JSON.

"pulsestorm_simple_data_source": {
    //...
    "config": {
        "data": {
            "foo": "bar"
        }
    //...
    }
}

Depending on how fried your brain is, this may be a little confusing, or a lot confusing, to you. If the data comes from the getDataSourceData method on our component class — why’d we need to configure a Pulsestorm\SimpleUiComponent\Model\DataProvider class?

Unfortunately, I don’t have a great answer for you there. Based on core code, it looks like the “correct” usage pattern is to have your component class fetch the data provider and call its getData method.

#File: app/code/Pulsestorm/SimpleUiComponent/Component/Simple.php 
public function getDataSourceData()
{
    return ['data' => $this->getContext()->getDataProvider()->getData()];
}

Then the data provider’s getData method is the one that returns the actual data.

#File: app/code/Pulsestorm/SimpleUiComponent/Model/DataProvider.php
<?php
namespace Pulsestorm\SimpleUiComponent\Model;
class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{
    public function getData()
    {
        return [['foo'=>'baz']];
    }
}    

While the UI Component system, at first glance, seems like a fully object oriented domain specific language for building user interface components, and may indeed have started out as that, looking deeper it seems like a system that was nowhere near complete as Magento 2’s launch date closed in and is filled with the sort of edge cases, bugs, and weird omissions that a poorly managed or directionless dev cycle brings to mind.

Next Steps

That, in a nutshell, is the PHP portion of the UI Component system. At the end of the day, all this complexity boils down to rendering an xhtml template and tying it to a data source.

In our next UI Component article, we’re going to dive a little bit deeper, and look at the ways Magento’s javascript systems (both RequireJS, and knockout.js) interact with this system. It’s here that the bulk of the work rendering Magento’s grid listings and backend forms happens, and understanding these systems will be vital for customizing Magento’s backend UI.

Originally published September 8, 2016
Series Navigation<< Magento 2: Introducing UI ComponentsMagento 2: Simplest UI Knockout Component >>

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 8th September 2016