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.

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

In our previoustwo 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.

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.

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

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.

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

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

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

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.

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/>

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.

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

They’re “XSD allowed” as children of <container/> elements

Their base functionality is relatively simple/uncomplicated

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

Use a different component class

Use a different RequireJS view model constructor factory

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

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

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.

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.