Introducing UI Components
Jan 9, 2017 · 11 minute readCategory: magento2
Contents
- Introduction
- What is a UI Component
- Technologies You Need to Understand
- Javascript, KnockoutJS + Magento and UI Components
- UI Component Process Flow
- Magento Core UI Component Examples
Introduction
Over the course of this article I will give you a whistle stop tour of UI Components.
What is a UI Component?
In a nutshell a UI Component is an easily reusable collection of PHP, Javascript and HTML code that provides one user interface functionality. A UI component can easily be used anywhere within your layout files using only this small snippet.
<uiŠ”omponent name="some_ui_component_instance_name"/>
This will make more sense later, but it also looks like UI Components add the M (model) to KnockoutJS’s MVVM (or really VVM) architecture. This architecture is used to build all of Magento 2’s front end.
Structure
UI Components are made up of ‘Basic Components’ and ‘Secondary components’. The Basic Components are:
- Listing Component - Visualising and filtering data
- Form Component - CRUD
The Secondary Components can be used to extend the functionality of the Basic Components. Each Basic
Component has it’s own set of Secondary Components. The latest devdocs (2.1) have a partial list of
all components here.
You can also find all the available UI Components listed in
this definition.xml
(more on that later).
Important to Note
You Can’t Create Custom UI Components
According to the official devdocs, extension developers cannot create custom UI Components. You can only customise existing ones. However, Alan Storm does have a solution (hack) for getting around this.
UI Components Are Only Really for the Admin
The devdocs state that UI Components can be used on the storefront as well as within the admin. However, you will need to provide all your own styling and the Magento core team have not themselves used any UI Components on the storefront.
Technologies You Need to Understand
Before I dive into any UI Component details there are a number of technologies you must understand first.
XML, XHTML and XSD
What Are These?
If you’ve already worked with Magento 1 then you’ll already be (very) familiar with XML. If not, then here’s a short description from Wikipedia:
Extensible Markup Language (XML) is a markup language that defines a set of rules for encoding documents in a format that is both human-readable and machine-readable.
XML is used for configuration files as well as the XHTML templates used by UI Components.
So what’s XHTML? From Wikipedia:
Extensible Hypertext Markup Language (XHTML) is part of the family of XML markup languages. It mirrors or extends versions of the widely used Hypertext Markup Language (HTML) … XHTML documents are well-formed and may therefore be parsed using standard XML parsers, unlike HTML, which requires a lenient HTML-specific parser.
The TL;DR here is that unlike standard HTML, XHTML is strict. You can’t use anything that isn’t defined within it’s XSD schema. UI Component templates are all defined using XHTML.
So what’s an XSD schema? From Wikipedia:
XSD (XML Schema Definition), a recommendation of the World Wide Web Consortium (W3C), specifies how to formally describe the elements in an Extensible Markup Language (XML) document. It can be used by programmers to verify each piece of item content in a document.
So the XSD file defines the valid XML nodes and their structure within a given document. This is used to validate the XHTML templates and XML configuration files within Magento 2 as well as create a DSL (Domain Specific Language) for defining UI Components.
Further reading
KnockoutJS
From the KnockoutJS site:
Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model.
What is KnockoutJS?
KnockoutJS uses an MVVM (model–view–view-Model) architecture to create interfaces that update when an underlying data model is changed.
Knockout’s MVVM looks like this:
The Javascript view-model is bound to the HTML view using Knockouts custom data-bind
attribute.
An example of this can be seen here.
Now, when the Javascript
data model within the view-model changes any HTML elements which are observing this view-model will
be updated. The view-model itself is triggered to update by the same HTML elements. The view-model will
update it’s temporary data model and pull data from / trigger updates within the Magento Model as required.
What’s It Used For?
KnockoutJS is used to power the UI Component interfaces (and all other Magento 2 interfaces). It handles templating as well as user interface updates.
KnockoutJS Customisations made for Magento 2
Custom Templating Engine
The standard KnockoutJS templating engine expects the HTML templates to be present within the current page like this:
<script type="text/html" id="template_id">
...
</script>
The Magento core team have customised the KnockoutJS templating engine so that it can now load templates from a remote source.
This feature makes it easier to reuse templates as they are no longer embedded within .phtml
files.
Scope
The Magento core team have also added a custom scope
binding. This binding allows you to easily
bind a view-model to a template (HTML) element on a per template basis. For example:
<div data-bind="scope: 'some_view_model.some_view_model'">
...
</div>
This knockout view-model (listed under the "components"
key within the JSON configuration) is
instantiated using the Magento_Ui/js/core/app
Javascript component:
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"components": {
"some_view_model": {
"component": "<Namespace>_<Module>/js/view/someViewModel"
}
}
}
}
}
</script>
Further reading
- Alan Storm - KnockoutJS Primer for Magento Developers
- Alan Storm - KnockoutJS Integration
- KnockoutJS - benefits
- KnockoutJS - MVVM
- Wikipedia - MVVM
RequireJS
From the RequireJS site:
RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code.
What is RequireJS?
RequireJS uses the AMD (Asynchronous Module Definition) standard to provide dependency management and modules. RequireJS uses lazy loading to only load a module when it’s required, this can speed up page load times. By implementing the AMD standard it can also load dependencies asynchronously and provides no guarantees about the order in which a modules dependencies will be fetched.
The asynchronous nature of RequireJS can be an issue for non AMD compliant modules. For example, JQuery plugins that depend on JQuery itself. In these cases RequireJS can be configured to load one (or more) of a modules dependencies after a subset of the other dependencies have loaded using the shim configuration value.
What’s It Used For?
All Magento 2 Javascript code (modules) make use of RequireJS to load their dependencies.
Further reading
- AMD specification
- RequireJS - AMD
- RequireJS - Shim
- Alan Storm - Magento 2 and RequireJS
- Magento 2 devdocs - RequireJS
Pestle
If you don’t want to spend your time creating module boiler plate code / configuration then you’ll want to check out Pestle. Pestle is a static code generator which provides a number of templates / helpful functions for Magento 2.
$ php pestle list
...
magento2:generate:acl
magento2:generate:command
magento2:generate:config_helper
magento2:generate:crud_model
magento2:generate:di
magento2:generate:install
magento2:generate:menu
magento2:generate:module
magento2:generate:observer
magento2:generate:plugin_xml
magento2:generate:preference
magento2:generate:psr_log_level
magento2:generate:registration
magento2:generate:route
magento2:generate:theme
magento2:generate:ui:add-column-actions
magento2:generate:ui:add-column-sections
magento2:generate:ui:grid
magento2:generate:view
...
Further Reading
Javascript, KnockoutJS + Magento and UI Components
Did you know there are three types of component used within the Magento 2 front end?
What is a Javascript (JS) Component?
A JS Component is a subtype of RequireJS module. It’s a RequireJS module that is loaded using a
text/x-magento-init
script tag and that always returns a function.
This function is then called by core Magento code which passes any configuration values you set
within the text/x-magento-init
script tag as arguments. Magento_UI/js/core/app
that we saw above is a good example of this.
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": { <---- Module to be instantiate
"components": { ^------ Object with all values to be passed as function arguments
"some_view_model": {
"component": "<Namespace>_<Module>/js/view/someViewModel"
}
}
}
}
}
</script>
Further Reading
What is a KnockoutJS + Magento component?
A plain KnockoutJS component is the grouping of a KnockoutJS template (HTML) and view-model (JS). This component is then registered with KnockoutJS and provided a unique key. This key can then be used within your standard HTML markup to instantiate the component.
<div data-bind="component:'some-component-key'"></div>
Due to the KnockoutJS customisations listed above this process is not quite the same in Magento. Within Magento the template is loaded via a URL and the view-model is pulled from the registered view-models using the scope binding. These are then linked together and registered as a component.
What’s the difference between the three?
The naming is unfortunate here but to keep this straight in your head it’s probably best to remember:
- JS Components - provide additional behavioural functionality.
- KnockoutJS + Magento Components - provide additional user interface functionality.
- UI Components - provide building blocks for creating Admin interfaces (and utilise the above two component types).
Further Reading
UI Component Process Flow
The Players
Layout
This is your standard <handle>.xml
layout file. You add the UI Component to the layout using:
<uiŠ”omponent name="some_ui_component_instance_name"/>
Module Agnostic Configuration
The definition.xml
file contains all default configuration for all the UI Components available in Magento 2. It
also defines the PHP classes used to generate any JSON configuration which eventually gets passed
to KnockoutJS. Any configuration set within the definition.xml
are global and effect all modules.
...
<form class="Magento\Ui\Component\Form">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/form/form</item>
</item>
<item name="template" xsi:type="string">templates/form/default</item>
</argument>
</form>
...
The XML above basically says; instantiate the Magento\Ui\Component\Form
class by passing
the array of arguments contained within <argument>
to it’s constructor.
The first argument:
...
<item name="component" xsi:type="string">Magento_Ui/js/form/form</item>
...
Says use magento/module-ui/view/base/web/js/form/form.js
as the KnockoutJS view-model for this
component.
The second argument:
<item name="template" xsi:type="string">templates/form/default</item>
Means use magento/module-ui/view/base/templates/form/default.phtml
as the default template for
this UI Component.
Top Level Configuration
The <component_name>.xml
file contains any configuration you wish to set on a UI Component at the module level.
This will usually include the arguments to be passed to the UI Components PHP constructor
<arguments>
, the data source to be used by the UI Component <dataSource>
and any
Secondary Components to be included.
<form xmlns:xsi="..." xsi:noNamespaceSchemaLocation="...">
<argument name="data" xsi:type="array">
...
</argument>
<dataSource name="customer_form_data_source">
<!-- Covered in more detail below -->
</dataSource>
<fieldset name="customer">
<!-- Secondary Component -->
</fieldset>
<fieldset name="address">
...
</fieldset>
</form>
Any nodes added within the <form>
node here which are not either
<argument>
or <dataSource>
are considered to be a new Secondary Component instance.
These can be recursively nested so a Secondary Component can contain another etc.
Any arguments set here will override the default values set within definition.xml
.
AbstractComponent
Each UI Component is backed by a
PHP class
extending from
Magento\Ui\Component\AbstractComponent
.
This class processes both the top level and module agnostic <argument>
values and generates the
JSON configuration later used to instantiate a KnockoutJS view-model and assign a template.
View-Model Constructor
In the module agnostic configuration above we configured a view-model. This view-model is a RequireJS module that extends from uiElement. This extended uiElement is then returned from the module to be used as a view-model constructor.
Default values can be set within this constructor; top level and module agnostic configuration values take precedence over these.
If you require KnockoutJS you can also start adding state for your templates to observe and functions to update / manage that state.
define([
'uiElement',
'ko'
], function(Element, ko){
return Element.extend({
"defaults": {
template: '<Namespace>_<ModuleName>/path/to/template'
},
"some_value": ko.observable("value")
});
});
Template
UI Component templates like collapsible.xhtml
are set within either the view-model constructor or the module agnostic or top level configurations.
For the template to be rendered correctly it also needs to include the following comment:
<!-- ko template: getTemplate() --><!-- /ko -->
This calls the getTemplate() function on our view-model. This is inherited from uiElement.
uiRegistry
The uiRegistry is a Magento 2 RequireJS module that’s used (as global state) to store all the currently instantiated view-models. These models are stored as key value pairs. The key here maps to a KnockoutJS scope binding value.
engine.js (Magento_UI/js/lib/knockout/template/engine)
This module implements Magento 2’s custom KnockoutJS templating engine.
bootstrap.js (Magento_UI/js/lib/knockout/bootstrap)
This module requires Magento 2’s custom templating engine and sets it as KnockoutJS’s current templating engine.
layout.js (Magento_UI/js/core/renderer/layout)
This module is required by the the app module (see below) and handles the instantiation of view-model objects. It adds these view-models to the uiRegistry.
app.js (Magento_UI/js/core/app)
This module requires the bootstrap module as well as the layout module. It calls the function returned by the layout module starting the instantiation of view-models.
scope.js (Magento_UI/js/lib/knockout/bindings/scope)
This final module implements Magento 2’s custom scope binding. This module handles the linking of view-models and views using it’s applyComponents() function.
Process Flow Chart
Video
Here’s a video of me stepping through the above flow in PHPStorm and Chrome.
I’ve extracted the PHPStorm breakpoints and added them to this GitHub repo (just in case you want to have a go yourself).
Further Reading
- See all Alan Storm links above
- Magento 2 devdocs - UI Component Configuration Flow
- Magento 2 devdocs - Overview of UI Components
Magento Core UI Component Examples
Here are some links to the Magento 2 GitHub repo for you to investigate further.
Form
This is a <form>
used in the Magento_Theme
module.
- Layout XML
- Module Agnostic Configuration
- Top Level Configuration
- AbstractComponent
- View-Model
- Template
Listing
This is a <listing>
used in the Magento_Customer
module.