Magento 2: Simplest XSD Valid UI Component

Like this article? 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.

In our previous two articles we ran through creating a new UI Component from scratch. While we were successful, we needed to add a class <preference/> (i.e. a rewrite) that disabled Magento 2’s XSD validation. While this was useful as a learning exercise, it’s not that helpful in a real world.

It’s probably OK for a developer managing a single Magento system to use a class <preference/>, but those sorts of developers probably aren’t creating UI Components. For an extension developer, disabling XSD validation with a class preference seriously compromises the stability of the systems an extension will be deployed to.

This problem with the XSD files sheds light on an untrue thing the software industry likes to tell itself over and over. Namely, that it’s possible, purely through use of advanced design patterns, to crete a system that will be both flexible and stable for developers who didn’t create the system.

It’s clear that Magento 2 set out to create a flexible system for developers — plugins, dependency injection, RequireJS map features, etc. However, the XSD schema files (a tool meant to reign in the complexity of the XML files) ended up limiting the flexibility of the UI Component system.

Whatever role systems design, and gang-of-four style design patterns play, time and time again we rediscover that the only way to build flexible and stable systems for your end-user-programmers is to listen to them and adjust the system over time toward real world usage patterns. Even if (or especially if) this means abandoning whatever high minded ideals your project’s trying to bring to the table.

All that, however, is a topic for another time (and possibly for all-time). Today, carrying with us the lessons learned so far, we’re going to revisit our simple UI Component. This time though we’re going to do it with XSD validation on, and come up with a pattern we can reuse in real world projects.

The specifics here were tested against Magento 2.1.1, but the concepts should apply across all Magento 2 versions.

Setup

The first thing we’ll want to do is disable our Pulsestorm_SimpleUiComponent module. We’re doing this to reenable the XSD validation for XML files. Run the following command, and you should be all set. If you never created a Pulsestorm_SimpleUiComponent module, this step is not necessary.

$ php bin/magento module:disable Pulsestorm_SimpleUiComponent
The following modules have been disabled:
- Pulsestorm_SimpleUiComponent

Cache cleared successfully.
Generated classes cleared successfully. Please run the 'setup:di:compile' command to generate classes.
Info: Some modules might require static view files to be cleared. To do this, run 'module:disable' with the --clear-static-content option to clear them.

Once we’ve done that, we’ll want to create a new admin module for our UI Component. As usual, we’ll use pestle for this.

pestle.phar generate_module Pulsestorm SimpleValidUiComponent 0.0.1

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

pestle.phar generate_menu Pulsestorm_SimpleValidUiComponent Magento_Backend::system_other_settings Pulsestorm_SimpleValidUiComponent::a_menu_item Pulsestorm_SimpleValidUiComponent::menu_1 "Hello Simple Valid Ui Component" pulsestorm_simplevaliduicomponent/index/index 1

pestle.phar generate_route Pulsestorm_SimpleValidUiComponent adminhtml pulsestorm_simplevaliduicomponent

pestle.phar generate_view Pulsestorm_SimpleValidUiComponent adminhtml pulsestorm_simplevaliduicomponent_index_index Main content.phtml 1column

php bin/magento module:enable Pulsestorm_SimpleValidUiComponent

php bin/magento setup:upgrade

With the above in place, clear your cache, and you should be able to navigate to a System -> Other Settings -> Hello Simple Valid Ui Component menu in your Magento backend.

With our boilerplate generated, lets get started!

A New UI Components

As we did earlier in this series, we’ll add a new <uiComponent/> to our layout handle XML file.

#File: app/code/Pulsestorm/SimpleValidUiComponent/view/adminhtml/layout/pulsestorm_simplevaliduicomponent_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\SimpleValidUiComponent\Block\Adminhtml\Main" name="pulsestorm_simplevaliduicomponent_block_main" />

        <uiComponent name="pulsestorm_simple_valid"/>

    </referenceBlock>
</page>

and then we’ll create an XML file for this named UI Component.

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

If we clear our cache and reload the page, we’ll end up with an error something like this.

Exception #0 (Magento\Framework\Exception\LocalizedException): Element
'pulsestorm_simple_valid': No matching global declaration 
available for thevalidation root.

As previously mentioned, this error happens because there’s no pulsestorm_simple_valid in Magento’s vendor/magento/module-ui/view/base/ui_component/etc/definition.xml file, and we can’t add one because Magento’s XSD schema validation file for UI Component files doesn’t allow root nodes named <pulsestorm_simple_valid/>.

Unfortunately, there’s no way we can work around this. The schema is the schema. Even if we removed the xsi:noNamespaceSchemaLocation from our XML files, Magento’s merging our nodes into XML trees that use this XSD file. As of Magento 2.1, there’s no way for third party developers to distribute UI Components without some half-baked module that disables or modifies the XSD validation routines. This is disappointing.

However, we can still take advantage of the <uiCompnent/> tag if we change our root node to the following.

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

The <container/> node is a valid root level node. If we reload with the above in place, we’ll end up with the following error instead.

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

In other words, our schema validation errors are gone, and Magento’s just complaining about a missing dataSource node. We can fix that with a new dataSource node

#File: app/code/Pulsestorm/SimpleValidUiComponent/view/adminhtml/ui_component/pulsestorm_simple_valid.xml
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <dataSource name="pulsestorm_simple_valid_data_source">                        
        <argument name="dataProvider" xsi:type="configurableObject">
            <!-- the PHP class that implements a data provider -->
            <argument name="class" xsi:type="string">Pulsestorm\SimpleValidUiComponent\Model\DataProvider</argument>    

            <!-- redundant with the `dataSource` name -->
            <argument name="name" xsi:type="string">pulsestorm_simple_valid_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>
</container>

and a new data provider class

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

Reload your page with the above in place, and you should see your page rendered again, sans errors.

What Did We Render?

If we take a look at our raw page source (i.e. not from a DOM Inspector, but the pre-javascript “View Source” source), we’ll see we’ve rendered the following

<div>
    <div data-bind="scope: 'pulsestorm_simple_valid.areas'" class="entry-edit form-inline">
        <!-- ko template: getTemplate() --><!-- /ko -->
    </div>
    <script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "types": {
                    "dataSource": [],
                    "container": {
                        "extends": "pulsestorm_simple_valid"
                    },
                    "html_content": {
                        "component": "Magento_Ui\/js\/form\/components\/html",
                        "extends": "pulsestorm_simple_valid"
                    }
                },
                "components": {
                    "pulsestorm_simple_valid": {
                        "children": {
                            "pulsestorm_simple_valid": {
                                "type": "pulsestorm_simple_valid",
                                "name": "pulsestorm_simple_valid",
                                "config": {
                                    "component": "uiComponent"
                                }
                            },
                            "pulsestorm_simple_valid_data_source": {
                                "type": "dataSource",
                                "name": "pulsestorm_simple_valid_data_source",
                                "dataScope": "pulsestorm_simple_valid",
                                "config": {
                                    "params": {
                                        "namespace": "pulsestorm_simple_valid"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }    
    </script>
</div>

If we take a look at the <container/> node’s definition.xml configuration.

<!-- File: vendor/magento//module-ui/view/base/ui_component/etc/definition.xml -->

<!-- ... -->
<container class="Magento\Ui\Component\Container">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="component" xsi:type="string">uiComponent</item>
        </item>
        <item name="template" xsi:type="string">templates/container/default</item>
    </argument>
</container>
<!-- ... -->

We see its XHTML template is templates/container/default, and its component is the RequireJS map alias uiComponent (Magento_Ui/js/lib/core/collection).

The reason we chose the <container/> component is two fold. First, it’s one of the few generic components we can use at the root of our ui_compnent file without tripping a Magento XSD validation error. Second though, the uiComponent javascript component is exactly what we want. You’ll remember from last time a uiComponent’s Knockout.js template (ui/collection) will run through all its child elements and render their templates — similar to a layout update XML <container/> node, or a Magento 1 core/text_list block.

The templates/container/default XHTML template, however, does not suit our needs.

#File: vendor/magento/module-ui/view/base/ui_component/templates/container/default.xhtml
<div xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../Ui/etc/ui_template.xsd">
    <div data-bind="scope: '{{getName()}}.areas'" class="entry-edit form-inline">
        <!-- ko template: getTemplate() --><!-- /ko -->
    </div>
</div>

It’s not 100% clear what this template is for. Magento’s core ui_component files use <container/> as a sub-node, which means the XHTML template is never rendered. It’s likely this is some legacy cruft left over from earlier days when container was used by Magento as a root level node. Or maybe it’s something forward looking. Whatever the reason, this is probably why we can use <container/> as a root level node in the first place. It’s hard to say if this “feature” will stick around, but for now it’s the best we have.

Changing the Template

So if this XHTML template is no good for us, are we stuck? Of course not — we can just configure a new template in our pulsestorm_simple_valid.xml file

#File: app/code/Pulsestorm/SimpleValidUiComponent/view/adminhtml/ui_component/pulsestorm_simple_valid.xml
<container 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/pulsestorm_simple_valid/default</item>
    </argument>    
    <!-- ... -->

</container>

Remember, the definition.xml file is where the default sub-nodes for a particular node are set, but each individual file in the ui_component folder can override these values.

We’ll also want to create the xhtml file for our templates/pulsestorm_simple_valid/default template.

#File: app/code/Pulsestorm/SimpleValidUiComponent/view/adminhtml/ui_component/templates/pulsestorm_simple_valid/default.xhtml
<div    
    data-bind="scope: '{{getName()}}.{{getName()}}'"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../../../../../../Ui/etc/ui_template.xsd">

    <!-- ko template: getTemplate() --><!-- /ko -->
</div>

The code above is based on the default XHTML for a listing grid (vendor/magento/module-ui/view/base/ui_component/templates/listing/default.xhtml). While it performs the same Knockout.js scope kickoff as the template from our previous articles, there’s a few more things going on here that are worth mentioning.

First, let’s start with what we know. We have our standard Knockout tag-less template binding.

<!-- ko template: getTemplate() --><!-- /ko -->

We also know that Knockout’s view model for this section is set by the outer scope binding. However, if we look at that scope binding.

<div data-bind="scope: '{{getName()}}.{{getName()}}'" ...>

We see the first unfamiliar bit. Instead of a hard coded scope, we have {{getName()}}.{{getName()}}. The text inside the {{...}} brackets are part of the XHTML template language, and will call through to the underlying UI Component object’s getName() method. This name will be the name we used in the layout handle XML file — <uiComponent name="pulsestorm_simple_valid"/>. Meaning the above will render as

<div data-bind="scope: 'pulsestorm_simple_valid.pulsestorm_simple_valid'" ...>

We talked more about these template tags a few articles ago.

The next confusing bit is this

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../../Ui/etc/ui_template.xsd"

We have an XML namespace declaration in the root level <div/>, as well as a schema validation file (xsi:noNamespaceSchemaLocation). Remember — these are XHTML, not HTML templates. They behave like XML files. This also means you’re only allowed one top level node in an XHTML template.

While these attributes aren’t strictly necessary, they’re used in the Magento core XHTML templates so it’s best to use them here for consistency.

If you’re curious why these attributes aren’t rendered in the final HTML, Magento removes them before rendering here

#File: vendor/magento/module-ui/TemplateEngine/Xhtml/Result.php
    public function __toString()
    {
        //...
        foreach ($templateRootElement->attributes as $name => $attribute) {
            if ('noNamespaceSchemaLocation' === $name) {
                $this->getDocumentElement()->removeAttributeNode($attribute);
                break;
            }
        }
        $templateRootElement->removeAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi');
        //...
    }

Finally, it’s worth taking a peek at the ui_template.xsd file

#File: vendor/magento/module-ui/etc/ui_template.xsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:element name="form">
        <xs:complexType >
            <xs:sequence>
                <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="div" >
        <xs:complexType >
            <xs:sequence>
                <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax" />
            </xs:sequence>
            <xs:anyAttribute processContents="lax" />
        </xs:complexType>
    </xs:element>
</xs:schema>

Covering the entire xs:schema language is a task beyond the scope of this article, but the above says that our xhtml files must have a root node of div, or form. Also, leaving the xsi:noNamespaceSchemaLocation out of our file won’t skip validation, as these .xhtml files are merged into an XML tree that includes this schema.

Adding to the Collection

Alright, if we clear our cache and reload the page with the above in place, we won’t see anything changed on the page. However, using the Sources tab of Chrome’s debugger, we can see Magento has included collection.js (from Magento_Ui/js/lib/core/collection, which is the RequireJS library uiCollection points at).

We can also (via XHR debugging) see that Magento has downloaded the collection.html Knockout.js template.

We can also pop into the javascript console, and we’ll see a registered view model constructor named pulsestorm_simple_valid.pulsestorm_simple_valid.

> reg = requirejs('uiRegistry');
> reg.get(function(item){
    console.log(item.name);
    console.log(item);
});

pulsestorm_simple_valid.pulsestorm_simple_valid
UiClass {_super: undefined, ignoreTmpls: Object, _requesetd: Object, containers: Array[0], exports: Object…}

If you’re unsure of the significance here, you probably want to review our last article. Our next step is adding some child components to render.

We’re going to use an htmlContent component/node. Add the following to your pulsestorm_simple_valid.xml file

#File: app/code/Pulsestorm/SimpleValidUiComponent/view/adminhtml/ui_component/pulsestorm_simple_valid.xml
<container>
    <!-- ... -->
    <htmlContent name="our_first_content">
        <argument name="block" xsi:type="object">Pulsestorm\SimpleValidUiComponent\Block\Example</argument>
    </htmlContent>           
    <!-- ... -->
</container>

The htmlContent node allows you to render the contents of a Magento block object into our x-magento-init script, and then have those contents rendered onto the page via Knockout.js. The above example will render the block named Pulsestorm\SimpleValidUiComponent\Block\Example. If we look at the definition.xml file for <htmlContent/>

<htmlContent class="Magento\Ui\Component\HtmlContent">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Ui/js/form/components/html</item>
        </item>
    </argument>
</htmlContent>

We see this rendering happens via the RequireJS Magento_Ui/js/form/components/html module. Or, said more completely, the Magento_Ui/js/form/components/html module returns a Knockout.js view model constructor with a Knockout.js “Magento remote” template of ui/content/content.

//File: vendor/magento/module-ui/view/base/web/js/form/components/html.js
//...
return Component.extend({
    defaults: {
        content:        '',
        showSpinner:    false,
        loading:        false,
        visible:        true,
        template:       'ui/content/content',
        additionalClasses: {}
    },  
//...

We’ll leave the specifics of this rendering as an advanced exercise for the user.

If we clear our cache and reload with the above in place, we’ll get the following error.

Exception #0 (ReflectionException): Class Pulsestorm\SimpleValidUiComponent\Block\Example does not exist

Whoops! We forgot to create our Pulsestorm\SimpleValidUiComponent\Block\Example class. Lets do that now.

#File: app/code/Pulsestorm/SimpleValidUiComponent/Block/Example.php     
<?php
namespace Pulsestorm\SimpleValidUiComponent\Block;

use Magento\Framework\View\Element\BlockInterface;

class Example extends \Magento\Framework\View\Element\AbstractBlock
{
    public function toHtml()
    {
        return '<h1>Hello PHP Block Rendered in JS</h1>';
    }
}    

These are standard block classes rendered via the current area’s layout, and need to extend the Magento\Framework\View\Element\AbstractBlock class. Normally these are phtml template blocks, but we’re using a block with a hard coded toHtml method for simplicity’s sake.

If we reload with the above in place, we should see our block rendered.

Hijacking htmlContent

While the htmlContent nodes are interesting, if only for their amusing “render some server side code that renders some front-end code that renders some more server side code” pattern, we’re not interested in them today for their core functionality. We chose htmlContent nodes because

  1. They’re “XSD allowed” as children of <container/> elements
  2. Their base functionality is relatively simple/uncomplicated
  3. They’re generic, and not likely to imply a specific piece of functionality (vs., say, <listingToolbar/>)

This makes them ideal blocks to hijack. By hijack, we mean we’re going to take advantage of the UI Component’s XML merging to make our htmlContent blocks

  1. Use a different component class
  2. Use a different RequireJS view model constructor factory
  3. Have that RequireJS view model constructor factory point to a new Knockout.js template

Regarding Use a different component class, all we need to do is add a new class attribute to our htmlContent XML node

#File: app/code/Pulsestorm/SimpleValidUiComponent/view/adminhtml/ui_component/pulsestorm_simple_valid.xml
<htmlContent class="Pulsestorm\SimpleValidUiComponent\Component\Simple" name="our_first_content">
</htmlContent>   

We’ve also removed the block argument, as this was required by the Magento\Ui\Component\HtmlContent class our Pulsestorm\SimpleValidUiComponent\Component\Simple replaces. We’ll also (of course) want to create our Pulsestorm\SimpleValidUiComponent\Component\Simple class (confused by this? Checkout our previous article for the server side functionality of UI Components).

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

Regarding Use a different RequireJS view model constructor factory, all we need to do is add a new @name="data"/@name="config"/@name="component" argument

<htmlContent class="Pulsestorm\SimpleValidUiComponent\Component\Simple"  name="our_first_content">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="component" xsi:type="string">Pulsestorm_SimpleValidUiComponent/js/pulsestorm_simple_component</item>
        </item>
    </argument>
</htmlContent>

and then, (conveniently covering our third Have that RequireJS view model constructor factory point to a new Knockout.js template point), have that RequireJS module return a view model constructor with a new Knockout.js remote template.

#File: app/code/Pulsestorm/SimpleValidUiComponent/view/adminhtml/web/js/pulsestorm_simple_component.js
define(['uiElement','ko'], function(Element, ko){
    viewModelConstructor = Element.extend({
        defaults: {
            template: 'Pulsestorm_SimpleValidUiComponent/pulsestorm_simple_template'
        }
    });

    return viewModelConstructor;
});

and then create the Pulsestorm_SimpleValidUiComponent/pulsestorm_simple_template template.

<!-- File: app/code/Pulsestorm/SimpleValidUiComponent//view/adminhtml/web/template/pulsestorm_simple_template.html -->
<h1>Our Remote Knockout Template!</h1>

With the above in place, clear your cache and reload the page

Congratulations! You’ve just successfully created a UI Component, fully under our programmatic control, without violating Magento’s XSD schema validations.

Wrap Up

Whether or not this is a good idea or not remains to be seen. While we’ve taken every step possible to keep our htmlContent node under our control (custom PHP component class, custom RequireJS component, custom Knockout.js template), it’s still theoretically possible that a future change by the core Magento engineering team might break what we’ve done here. Right now, a fundamental problem with UI Components is all the programmatic and political evidence points to them being for the core team only, and only time will tell if third party developers are meant to, or will be able to, incorporate them stably into their extensions.

Originally published September 21, 2016

Magento 2: Simplest UI Knockout Component

Like this article? 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.

Last time we created (with the help of some <preference/> hackery) the simplest possible Magento 2 UI Component. If you made it all the way through, I bet you were a little disappointed that we left out the javascript. Today we’ll try to sooth that disappointment.

There’s two big reasons we didn’t discuss javascript last time.

The first was complexity — between the XML, the fact UI Component files are a new domain specific language (DSL), the need to side step XSD validation, and the strangeness of an XHTML template system, adding the new javascript systems on top of that felt like too much.

The second reason was — it’s not 100% clear where Magento’s new RequireJS and Knockout.js systems start, and the UI Component systems begin.

As always, make sure your system is running in developer mode, and keep in mind the specifics here refer to Magento 2.1.1, but the concepts should apply across versions. You really should work your way through the previous article before continuing, but if you’re the reckless type we’ve put up our starting module on GitHub (you want the first-pass-unstable module).

An App for Knockout.js View Models

Articles two through six in our Magento 2 for PHP MVC developers series covered the basics of serving and using frontend files in Magento 2. In our short Advanced Javascript series, we covered Magento’s x-magento-init scripts, and the ground-up basics of Magento’s Knockout.js implementation. Back in July we also covered some of the strange tags you’ll find in Magento’s Knockout.js templates. You can probably get something out of this article without having read those previous articles, but they’ll help tremendously if you lose your footing here.

When we finished up our last article, we had managed to create a Pulsestorm_SimpleUiComponent module with a simple UI Component configuration. The rendered UI Component included an x-magento-init section that looked something like this.

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "types": {
                    "dataSource": [],
                    "html_content": {
                        "extends": "pulsestorm_simple",
                        "component": "Magento_Ui\/js\/form\/components\/html"
                    },
                    "pulsestorm_simple": {
                        "extends": "pulsestorm_simple"
                    }
                },
                "components": {
                    "pulsestorm_simple": {
                        "children": {
                            "pulsestorm_simple": {
                                "type": "pulsestorm_simple",
                                "name": "pulsestorm_simple",
                                "children": {
                                    "example_content": {
                                        "type": "html_content",
                                        "name": "example_content",
                                        "config": {
                                            "component": "Pulsestorm_SimpleUiComponent\/js\/pulsestorm_simple_component_child",
                                            "content": null
                                        }
                                    }
                                },
                                "config": {
                                    "component": "uiComponent"
                                }
                            },
                            "hello_world_data_source": {
                                "type": "dataSource",
                                "name": "hello_world_data_source",
                                "dataScope": "pulsestorm_simple",
                                "config": {
                                    "data": {
                                        "foo": "bar"
                                    },
                                    "params": {
                                        "namespace": "pulsestorm_simple"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }    
</script>

This x-magento-init script will pass the JSON object into the RequireJS program located in the Magento_Ui/js/core/app module. If we take a look at that module’s source file (info on deriving the file name is over here).

//File: vendor/magento//module-ui/view/base/web/js/core/app.js
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
define([
    './renderer/types',
    './renderer/layout',
    '../lib/knockout/bootstrap'
], function (types, layout) {
    'use strict';

    return function (data, merge) {
        types.set(data.types);
        layout(data.components, undefined, true, merge);
    };
});

This seemingly simple program is actually one of the most important javascript files in Magento’s UI Component system. This code (or more specifically, the code in the Magento_Ui/js/core/renderer/types and Magento_Ui/js/core/renderer/layout modules) is responsible for creating and registering any and all Knockout.js view model constructor objects.

A view model is the javascript object that Knockout.js’s inline javascript uses to fetch data and/or perform complex actions. If you’ve worked your way through the official Knockout.js tutorials and our Advanced Javascript Tutorial series you should have a solid understanding of view model basics.

Less familiar though will be the registration of these view model constructor objects. We’re not going to cover this registration in full — just know that after Magento_Ui/js/core/app runs Magento will have added a number of view model constructor objects to a global registry.

The quickest way to understand this is to take a look at the registry on a core grid page. Navigate to the product grid listing at Products -> Catalog and open up your browser’s javascript debugger (View -> Developer -> Javascript Console in Google Chrome).

The uiRegistry

Magento registers each Knockout.js view model constructor into the object returned by the uiRegistry RequireJS module. I wasn’t deeply involved in the javascript world when the AMD specification came to life, so I’m not sure if this storing of global state in a module is considered a good practice or not, but it’s what Magento does so it’s best to accept it and move on.

The uiRegistry is a key in a RequireJS map.

// File: vendor/magento/module-ui/view/base/requirejs-config.js
var config = {
    paths: {
        'ui/template': 'Magento_Ui/templates'
    },
    map: {
        '*': {
            uiElement:      'Magento_Ui/js/lib/core/element/element',
            uiCollection:   'Magento_Ui/js/lib/core/collection',
            uiComponent:    'Magento_Ui/js/lib/core/collection',
            uiClass:        'Magento_Ui/js/lib/core/class',
            uiEvents:       'Magento_Ui/js/lib/core/events',
            uiRegistry:     'Magento_Ui/js/lib/registry/registry',
            uiLayout:       'Magento_Ui/js/core/renderer/layout',
            buttonAdapter:  'Magento_Ui/js/form/button-adapter'
        }
    }
};

This key points to the actual module — Magento_Ui/js/lib/registry/registry, defined in vendor/magento/module-ui/view/base/web/js/lib/registry/registry.js. The registry object functions similarly to a dictionary or hash map — you can use the registry set method to set a value, and use the registry’s get method to fetch a value. Let’s give that a try. First, load the uiRegistry module/object in your debugger.

> reg = requirejs('uiRegistry');
Registry {}

You won’t be able to see the items in the debugger. Magento’s core team programmed the registry in such a way that the data properties are private — the get method is how you’ll want to fetch a registered value. Give the following a try

> reg.get('product_listing.product_listing');
UiClass {_super: undefined, ignoreTmpls: Object, _requesetd: Object, 
    containers: Array[0], exports: Object…}

Here we’ve fetched the Knockout.js view model registered with the name product_listing.product_listing.

Where the uiRegistry differs from your average dictionary or hash map is its get method supports a query syntax for fetching items. You can find a brief description of this query language in the vendor/magento/module-ui/view/base/web/js/lib/registry/registry.js definition file. We’ll skip to the chase though and let you know there’s support for a callback method that will fetch every object in the registry. Give the following a try

> reg.get(function(item){
    console.log(item.name);
    console.log(item);
});
//long list of view model constructor and names snipped

This callback query lets us work around the private data member problem, and peek at all the registered view models.

Configuring a View Model Constructor

The product listing grid contains a wealth of view models, but let’s return to our simpler model. Navigate back to our page at System -> Other Settings -> Hello Simple UI Component. If we try the javascript debugging method here

> reg = requirejs('uiRegistry');
reg.get(function(item){
    console.log(item.name);
    console.log(item);
});    
undefined      

We’ll get no results. The UI Component system does not automatically create view models. We need to configure our UI Component with a RequireJS module, and then program that module to return a view model constructor.

To start, we’ll need to add the following configuration node 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="config" xsi:type="array">
                <item name="component" xsi:type="string">Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component</item>
            </item>            
        </argument>        
    </pulsestorm_simple>
</components> 

Here we’ve added an item node named config and given it a sub-node named component. The value of this node, Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component, is the name of our RequireJS module. If you clear your cache, reload with the above in place, and view the rendered source of the UI Component, you’ll see the following in that blob of JSON.

"components": {
    "pulsestorm_simple": {
        "children": {
            "pulsestorm_simple": {
                "type": "pulsestorm_simple",
                "name": "pulsestorm_simple",
                "config": {
                    "component": "Pulsestorm_SimpleUiComponent\/js\/pulsestorm_simple_component"
                }
            },
//...

However, you’ll also see the following error in your javascript console.

error message screen shot

The Magento_Ui/js/core/app tried to load our Pulsestorm_SimpleUiComponent\/js\/pulsestorm_simple_component, but failed. Let’s fix that. Add the following file, clear the cache, and reload the page

//File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/web/js/pulsestorm_simple_component.js
define([], function(){
    console.log("Called");
});         

With the above in place, you’ll see a new error in the console

Called    
Uncaught TypeError: Constr is not a constructor    

This is progress. We know Magento loaded our RequireJS module — the Called text makes this clear. However, our module failed to return a view model constructor. Let’s fix that — make your javascript file match the following

//File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/web/js/pulsestorm_simple_component.js    
define(['uiElement'], function(Element){
    viewModelConstructor = Element.extend({
        defaults: {
            template: 'Pulsestorm_SimpleUiComponent/pulsestorm_simple_template'
        }
    });

    return viewModelConstructor;
});  

Here we’re importing the uiElement RequireJS module, using that module’s extend method to create a new object with some data, and then we return that new object. This object is our view model constructor.

The uiElement module (a RequireJS map key to Magento_Ui/js/lib/core/element/element) is part of Magento’s custom class based javascript object system, built for the UI Component system. It’s beyond the scope of this article to cover this object system in full, but it’s based on underscore JS, and this quickie is a good place to start if you’re the curious type.

The template property of the defaults object above defines the Magento 2 Knockout.js remote template our view model should use.

Hooking up the View Model

With the above in place, if we clear our Magento cache, reload the page, and enter the following in the javascript debugger

reg = requirejs('uiRegistry');
//hold your questions on pulsestorm_simple.pulsestorm_simple
//we'll get there in a second
viewModelConstructor = reg.get('pulsestorm_simple.pulsestorm_simple')

we’ll see a single returned view model.

UiClass {_super: undefined, ignoreTmpls: Object, _requesetd: Object, containers: Array[0], exports: Object…}

Our next step is linking this view model constructor with a DOM node in our HTML page. This is where Magento’s special Knockout.js scope binding comes into play. Edit your UI Component’s XHTML template so it matches the following.

<!-- 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 data-bind="scope: 'pulsestorm_simple.pulsestorm_simple'" class="entry-edit form-inline">
        <!-- ko template: getTemplate() --><!-- /ko -->
    </div>    
</div>       

Here we’ve done two things. First, we’ve added the following attribute: data-bind="scope: 'pulsestorm_simple.pulsestorm_simple'" This attribute invokes Magento’s Knockout.js scope binding. The scope binding takes a single argument (pulsestorm_simple.pulsestorm_simple above). Magento will use this argument to lookup a view model in the uiRegistry, and make this view model the current Knockout.js view model for every inner node. The scope data binding allows you to have different Knockout.js view models used on different parts of the page.

The second thing we’ve done is include the following “tag-less” Knockout.js binding: <!-- ko template: getTemplate() --><!-- /ko -->. This will render the current view model’s template. The getTemplate method is one of the methods we get “for free” by basing our view model on the uiElement class above.

With the above in place, if we clear our cache and reload the page, we’ll see the following error.

Unable to resolve the source file for ‘adminhtml/Magento/backend/enUS/PulsestormSimpleUiComponent/template/pulsestormsimpletemplate.html’ #0 /path/to/magento/vendor/magento/framework/App/StaticResource.php(97): Magento\Framework\View\Asset\File->getSourceFile() #1 /path/to/magento/vendor/magento/framework/App/Bootstrap.php(258): Magento\Framework\App\StaticResource->launch() #2 /path/to/magento/pub/static.php(13): Magento\Framework\App\Bootstrap->run(Object(Magento\Framework\App\StaticResource)) #3 {main}

Whoops! Back when we configured a template

//File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/web/js/pulsestorm_simple_component.js    
defaults: {
    template: 'Pulsestorm_SimpleUiComponent/pulsestorm_simple_template'
}

we forgot to create the template file. Let’s do that now. If you create a file for the Pulsestorm_SimpleUiComponent/pulsestorm_simple_template template URN

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/web/template/pulsestorm_simple_template.html -->
<h1>Rendered with Knockout.js</h1>

and then clear your cache and reload the page, you should see a rendered template.

Congratulations, you just created your first Knockout.js based Magento UI Component.

Using Knockout

Of course, all of this seems like a lot of work to render a static HTML template. If you want to really take advantage of Knockout.js, you’ll need to import Knockout into your RequireJS module.

For example, to get knockout-observable data into the following data-bind="text: message" binding

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/web/template/pulsestorm_simple_template.html -->
<h1>Rendered with Knockout.js</h1>
<strong data-bind="text: message"></strong>

You’ll create your view model constructor like this

//File: vendor/magento//module-ui/view/base/web/js/core/app.js
define(['uiElement','ko'], function(Element, ko){
    viewModelConstructor = Element.extend({
        defaults: {
            template: 'Pulsestorm_SimpleUiComponent/pulsestorm_simple_template'
        },
        message: ko.observable("Hello Knockout.js!")    
    });

    return viewModelConstructor;
});

Above, we’ve imported the ko module into our module. This ko module is a replacement for the global ko normally available in Knockout.js. We’ve also added a message property to our view model constructor, and set this property to a ko.observable object. This is nuts and bolts Knockout.js coding. If you reload the page, you should see the Hello Knockout.js text rendered in the strong tag.

Since the message is a Knockout.js observable, we can change it with the following (try it out via the javascript debugger)

> reg = requirejs('uiRegistry');
> reg.get('pulsestorm_simple.pulsestorm_simple').message("Change Me");

The above code snippet uses the uiRegistry to fetch our view model (with the name pulsestorm_simple.pulsestorm_simple), and then call the observable message property.

Modern Javascript and the Browser Debugger

One of the challenges in working with Magento 2’s (and a lot of other modern) javascript is keeping track of what is and isn’t loaded. It’s no longer as simple as viewing your page source and looking for a <script/> tag.

For Google Chrome’s debugger, if you’re looking for your RequireJS module(s), the Source tab is what you want

If you’re looking for your Knockout.js remote template, Network -> XHR is your friend

You’ll want to pay particular attention to the actual text these debugging tools report. Between Magento’s own cache and some aggressive headers set by Magento’s custom front-end file serving application, the files you’re working with on disk may not be the files your browser sees. In addition to clearing our your Magento cache, a full browser cache refresh is another good sanity check to use during development.

Why the Double Name

One thing that may be bothering you is the “double naming” of the view model constructor.

product_listing.product_listing
pulsestorm_simple.pulsestorm_simple

This name comes from the invoked name in our layout handle XML file/ui_component/*.xml filename

<!-- File: app/code/Pulsestorm/SimpleUiComponent/view/adminhtml/ui_component/pulsestorm_simple.xml -->        
<uiComponent name="pulsestorm_simple"/>    

However — based on the examples we’ve provided so far, it’s not 100% clear why we need to use the name twice, and in a fashion that implies a hierarchy of some kind. This is where the final features of the UI Component system come into play, and the features that will let us fully understand the listing and form components that ship with Magento.

First, let’s go back to our definition.xml file and change the configured component.

#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="config" xsi:type="array">
                <!-- <item name="component" xsi:type="string">Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component</item> -->
                <item name="component" xsi:type="string">uiComponent</item>
            </item>            
        </argument>        

    </pulsestorm_simple>
</components>  

Here we’ve replaced our Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component component/view model constructor with a uiComponent. This is another RequireJS map key that points to the Magento_Ui/js/lib/core/collection module.

If we clear our cache and reload with the above in place, we’ll see our template is no longer rendered. This makes sense — different view model, different template. If we take a look at the view model’s template URN in the javascript debugger.

> reg = requirejs('uiRegistry');
> viewModelConstructor = reg.get('pulsestorm_simple.pulsestorm_simple')
> viewModelConstructor.getTemplate()
ui/collection

We’ll see the template’s URN is a ui/collection. This corresponds to the following file.

<!-- File: vendor/magento//module-ui/view/base/web/templates/collection.html -->
<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<each args="data: elems, as: 'element'">
    <render if="hasTemplate()"/>
</each>

If you’re new to Magento 2’s frontend code, the tags probably confuse you. These tags are a Magento 2 extension to the Knockout.js rendering engine — Magento expands these into Knockout.js tag-less bindings. We wrote about this a bit over on Magento Quickies. In plain Knockout.js, the above looks like the following

<!-- ko foreach: {data: elems, as: 'element'} -->
    <!-- ko if: hasTemplate() --><!-- ko template: getTemplate() --><!-- /ko --><!-- /ko -->
<!-- /ko -->

This Knockout.js template foreachs over an elems property of our view model, and if the object inside elems has a template, it renders that template. If we look at elems

> reg = requirejs('uiRegistry');
> viewModelConstructor = reg.get('pulsestorm_simple.pulsestorm_simple')
> viewModelConstructor.elems()
[]

We see its an empty array. So how can we populate this array? Via UI Component configuration!

Let’s add the following to our UI Component.

<!-- 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">
    <!--  ... -->
    <htmlContent name="first_ever_child">
        <argument name="block" xsi:type="object">Magento\Framework\View\Element\Text</argument>
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="component" xsi:type="string">Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component</item>
            </item>            
        </argument>         
    </htmlContent>
</pulsestorm_simple>

Here we’ve added an <htmlContent/> sub-node to our pulsestorm_simple.xml file. This is a stock UI Component node Magento provides in definition.xml. The specific UI node isn’t important — what’s important is we’ve configured this node with our Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component component, and it’s a sub-node of pulsestorm_simple.

Clear your cache, and reload the page. You should see the rendered template from Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component again!

screenshot

More interesting to us is the uiRegistry.

> reg = requirejs('uiRegistry');
> reg.get(function(item){
    console.log(item.name);
})
undefined
pulsestorm_simple.pulsestorm_simple
pulsestorm_simple.pulsestorm_simple.first_ever_child
undefined

Here we see a hierarchy of components defined. If we go back to our UI Component XML and add another node.

<!-- 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">
    <!--  ... -->
    <htmlContent name="first_ever_child">
        <argument name="block" xsi:type="object">Magento\Framework\View\Element\Text</argument>
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="component" xsi:type="string">Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component</item>
            </item>            
        </argument>         
    </htmlContent>

    <htmlContent name="second_ever_child">
        <argument name="block" xsi:type="object">Magento\Framework\View\Element\Text</argument>
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="component" xsi:type="string">Pulsestorm_SimpleUiComponent/js/pulsestorm_simple_component</item>
            </item>            
        </argument>         
    </htmlContent>        
</pulsestorm_simple>

and clear cache/reload, we’ll see the template rendered twice

and another node added to the hierarchy.

> reg = requirejs('uiRegistry');
> reg.get(function(item){
    console.log(item.name);
})
pulsestorm_simple.pulsestorm_simple
pulsestorm_simple.pulsestorm_simple.first_ever_child
pulsestorm_simple.pulsestorm_simple.second_ever_child

While there are many ways you could use the UI Component system to compose your Magento frontend code, in the end this is how it’s primarily used in Magento 2. The uiComponent/Magento_Ui/js/lib/core/collection module collects and renders a series of Knockout.js view models.

The root level UI Component node is responsible for rendering an XHTML template, but if the configuration for this code includes a uiComponent component attribute, and the XHTML template invokes this component via a scope binding, the sub-nodes of the UI Component tree become named view models in the uiRegistry. Somewhat confusingly, the root node is also registered as a view model constructor, which is where the pulsestorm_simple.pulsestorm_simple comes from.

Wrap Up

Phew! It was quite a journey, but you should now have a better understanding of one of Magento 2’s more mysterious new systems. That said, there’s still plenty to explore in the UI Component system. In our next few articles, we’re going to dive even deeper, and discuss how UI Components access the data created by the <dataProvider/> node, and revisit our “simplest” UI Component to see if there’s a way to use the system without resorting to a class <preference/>.

Originally published September 16, 2016

Magento 2: Simplest UI Component

Like this article? 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.

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