2016-09-08

This entry is part 2 of 7 in the series Magento 2 UI Components. Earlier posts include Magento 2: Introducing UI Components. Later posts include Magento 2: Simplest UI Knockout Component, Magento 2: Simplest XSD Valid UI Component, Magento 2: ES6 Template Literals, Magento 2: uiClass Data Features, and Magento 2: UI Component Data Sources.

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.

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.

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

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.

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.

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.

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)

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).

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

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

and add the following class file to your module

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.

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

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

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

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.

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!

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

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

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.

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.

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

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

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

The following two nodes are also required

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

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.

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

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

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)

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

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

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

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

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

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

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

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

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.

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.

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.

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

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

Show more