Magento 2: Introducing UI Components

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 talk about a new feature in Magento 2 — UI Components. This may end up being a stand alone article, or it may be the start of a longer in depth series. I’m still figuring out the best way to cover this large, complex topic.

UI Components are an ambitious new approach to building user interface elements in Magento 2, and much of the new admin console is built on this functionality. Today we’ll take a high level tour of what the UI Component System’s goals are, cover implementation details at as high a level as possible, and then wrap up by using pestle to generate a grid/listing UI Component.

The Positive Spin

The easiest way to understand the goals of UI Components is to talk about Magento 1’s backend user interface generating code. Here’s an example of some Magento 1 layout update XML code

<!-- #File: app/design/adminhtml/default/default/layout/catalog.xml -->
<adminhtml_catalog_product_new>
    <update handle="editor"/>
    <reference name="content">
        <block type="adminhtml/catalog_product_edit" name="product_edit"></block>
    </reference>
    <reference name="left">
        <block type="adminhtml/catalog_product_edit_tabs" name="product_tabs"></block>
    </reference>
    <reference name="js">
        <block type="adminhtml/catalog_product_edit_js" template="catalog/product/js.phtml" name="catalog_product_js"></block>
        <block type="core/template" template="catalog/wysiwyg/js.phtml"/>
    </reference>
</adminhtml_catalog_product_new>

That’s 4 separate layout update XML nodes to add a single product editing form. If you consider the layout update XML behind the reusable <update handle="editor"/> node

<!-- #File: app/design/adminhtml/default/default/layout/main.xml -->
<editor>
    <reference name="head">
        <action method="setCanLoadExtJs"><flag>1</flag></action>
        <action method="addJs"><script>mage/adminhtml/variables.js</script></action>
        <action method="addJs"><script>mage/adminhtml/wysiwyg/widget.js</script></action>
        <action method="addJs"><script>lib/flex.js</script></action>
        <action method="addJs"><script>lib/FABridge.js</script></action>
        <action method="addJs"><script>mage/adminhtml/flexuploader.js</script></action>
        <action method="addJs"><script>mage/adminhtml/browser.js</script></action>
        <action method="addJs"><script>prototype/window.js</script></action>
        <action method="addItem"><type>js_css</type><name>prototype/windows/themes/default.css</name></action>
        <action method="addCss"><name>lib/prototype/windows/themes/magento.css</name></action>
    </reference>
</editor>

You see that adding the product editing form to a page is even more complex.

The intention behind UI Components is to do away with and/or hide this complexity. Magento 2 introduces a new <uiComponent/> tag for its layout handle XML files (Magento 2 handle XML files are analogous to Magento 1’s layout update XML files). In Magento 2, you can add a product editing form to your page with the following configuration

<uiComponent name="product_form"/>

By introducing the concept of a <uiComponent/>, Magento 2 makes it easier for developers to reuse different components in different locations. While It was possible to drop different Magento 1 UI forms and grids in different areas, you needed to know which blocks and javascript files made up a particular component. Magento 1’s approach made it easy to accidentally setup a grid listing or a form so the component almost worked.

Magento 2’s UI Components set out to solve this problem, and simplify everyone’s layout handle XML files considerably.

The Actual Spin

While everything we just said it true enough, the reality of the UI Component system is a little murkier than that rosy picture. That’s because the UI Component system has a number of other goals, and those goals introduce a significant amount of complexity.

As near as I can tell, the UI Component System

  • Simplifies Layout Handle XML files
  • Moves admin user interface elements from HTML+Javascript to a “pure javascript” custom widget system
  • Is a system for building more complex UI components out of smaller components
  • Pre-renders data for UI components as JSON, binding closely to Magento backend data objects
  • Uses ajax to update component data
  • Introduce a new DSL for creating all of the above

The UI Component system is an ambitious one, and like a lot of things in Magento 2, it’s not quite fully baked. While you might want to stay away from a system that’s not quite done, most core grids and many forms use the UI Component system to render their interface elements, and others use a mix of a traditional block rendering and javascript files. If you want to build a fully featured module, you’ll need to work with the UI Component system.

The rest of this article represents my best understanding of UI Components at this time, (the Magento 2.1 era). Many of the specifics are likely to change in the future, but hopefully the core concepts will stay the same. There’s no great standard course of action for developers who want (or need) to develop backend UI interface elements — as per usual its best to look at what the core team is doing with their own components, mimic that, and keep a close eye on your module/extension code whenever there’s a Magento version update.

Unless you’re interested in complex implementation details, you may want to skip to the end where we use pestle to create a UI Component.

Pure Javascript

If you navigate to Content -> Block in Magento 2’s backend, you’ll see a grid listing of all the CMS Blocks in your Magento system. If you’re not familiar with Blocks, they’re a way to create usable chunks of HTML for your store. Block information is stored in Magento’s backend using CRUD Models.

The listing you see is a UI Component, configured with the following layout handle XML

<!-- File: vendor/magento/module-cms/view/adminhtml/layout/cms_block_index.xml -->
<!-- ... -->
<referenceContainer name="content">
    <uiComponent name="cms_block_listing"/>
</referenceContainer>
<!-- ... -->

If you’re new to Magento’s layout XML, a plain english reading of the above is

Get a reference to the already created container named content, and add the cms_block_listing UI Component to it

If you view the raw source of the HTML page, the <uiComponent/> tag is responsible for rendering the following HTML

<div class="admin__data-grid-outer-wrap" data-bind="scope: 'cms_block_listing.cms_block_listing'">
    <div data-role="spinner" data-component="cms_block_listing.cms_block_listing.cms_block_columns" class="admin__data-grid-loading-mask">
        <div class="spinner">
            <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
        </div>
    </div>
    <!-- ko template: getTemplate() --><!-- /ko -->        
    <script type="text/x-magento-init">
        {"*": {"Magento_Ui/js/core/app": {...very large js object...}}}
    </script>
</div>

If you’ve worked through the Magento 2 Advanced Javascript series, particularly the Javascript Init Scripts tutorial, you know the x-magento-init script tag will invoke the Magento_Ui/js/core/app RequireJS module as a program, passing in the very large js object as a parameter.

Without getting too deep into the implementation details (some of which you can read about in these Stack Exchange answers), this javascript code ends up creating a series of javascript constructor objects that Magento will use as KnockoutJS view models.

The actual rendering of the interface element in the browser is handled by KnockoutJS. The outer div of the skeleton HTML uses Magento’s custom KnockoutJS scope binding to bind a view model that was created by the text/x-magento-init javascript.

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

and then rendering of the UI Component happens via the KnockoutJS “tag-less” template binding

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

The call to getTemplate actually kicks off a number of KnockoutJS nested template renderings — starting with a file named collection.html. You can find all these templates by looking for .html files in your browser’s XHR debugging window. If you’re not familiar with Magento’s extension of KnockoutJS templates to XHR, or any of the other KnockoutJS code, try reading through the KnockoutJS Integration article that’s part of the Magento 2: Advanced Javascript series. Also, keep in mind that Magento’s core team has enhanced KnockoutJS templates with some custom tags and attributes that can be a little disorienting.

In summary, Magento 1 rendered a listing in HTML, and then used javascript to provide the enhanced user interface functionality. Magento 2, while it still uses some skeleton HTML, has shifted most of the rendering of these interface elements to RequireJS modules and KnockoutJS templates.

Sub Components

If you take a closer look at the x-magento-init JSON object, you’ll see there’s a number of nested child javascript objects.

{
    "*": {
        "Magento_Ui/js/core/app": {
            "types": /*...*/
            "components": {
                "cms_block_listing": {
                    "children": {
                        "cms_block_listing": {
                            /*...*/
                            "children": {
                                "listing_top": {
                                    "type": "container",
                                    "name": "listing_top",
                                    "children": {
                                        "bookmarks": {/*...*/},
                                        "columns_controls": {/*...*/},
                                        "fulltext": {/*...*/},
                                        "listing_filters": {/*...*/},
                                        "listing_massaction": {/*...*/},
                                        "listing_paging": {/*...*/}
                                    },

Older developers will be bemused to note the return of nodes named children — a practice we thought was left behind in Magento 1. These child element are each, themselves, fully featured UI Components. The cms_block_listing component is made up of components named listing_top, bookmarks, etc.

As we mentioned earlier, that initial getTemplate call ends up rendering many sub-components. The first KnockoutJS template, collection.html, is so named because its a collection of many different UI Components. Covering this rendering process in full is, unfortunately, not something we have time for today.

One thing we can cover today is how a PHP developer controls what’s rendered in that javascript tree. If we jump back to our <uiComponent/> tag

<!-- #File: vendor/magento/module-cms/view/adminhtml/layout/cms_block_index.xml -->
<uiComponent name="cms_block_listing"/>

Magento uses the uiComponent’s name to look for a new XML file named cms_block_listing.xml.

#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<listing 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="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
            <item name="deps" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">cms_block_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Block</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <!-- ... we'll get to this in a second ... -->
</listing>

These UI Component XML files are a new domain specific language (DSL). The above instructions tell Magento to

  1. Look up a PHP class name and default arguments for the root level listing node
  2. Instantiate that class, using the argument node as constructor arguments.

Magento will look up the PHP class name and default arguments in the following file

#File: vendor/magento/module-ui/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">
    <!-- ... -->
    <listing sorting="true" class="Magento\Ui\Component\Listing">
        <argument name="data" xsi:type="array">
            <item name="template" xsi:type="string">templates/listing/default</item>
            <item name="save_parameters_in_session" xsi:type="string">1</item>
            <item name="client_root" xsi:type="string">mui/index/render</item>
            <item name="config" xsi:type="array">
                <item name="component" xsi:type="string">uiComponent</item>
            </item>
        </argument>
    </listing>    
    <!-- ... -->
</components>

So, when Magento renders <uiComponent name="cms_block_listing"/> as JSON, it starts by running code that (oversimplified) looks like this

$uiComponent = new Magento\Ui\Component\Listing(
    $context, $components, [
        'template'=>'templates/listing/default',
        'save_parameters_in_session'=>'1',
        'client_root'=>'mui/index/render',
        'config'=>[
            'component'=>'uiComponent'
        ],
        'js_config'=>[
            'provider'=>'',
            'deps'=>''
        ],
        'spinner'=>'cms_block_columns',
        'buttons'=>[
            'add'=>[
                'name'=>'add',
                'label'=>'Add New Block',
                'class'=>'primary',
                'url'=>'*/*/new'
            ]
        ],
    ]
)

The data for the arguments above comes from merging the <argument/> nodes together. Each of these parameters has a different effect — but the one we’re interested in is the templates/listing/default parameter. This specifies the XHTML template to render for this UI Component. The templates/listing/default string corresponds to the following template.

#File: vendor/magento//module-ui/view/base/ui_component/templates/listing/default.xhtml
<div
    class="admin__data-grid-outer-wrap"
    data-bind="scope: '{{getName()}}.{{getName()}}'"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../../../../../../Ui/etc/ui_template.xsd">
    <div data-role="spinner" data-component="{{getName()}}.{{getName()}}.{{spinner}}" class="admin__data-grid-loading-mask">
        <div class="spinner">
            <span/><span/><span/><span/><span/><span/><span/><span/>
        </div>
    </div>
    <!-- ko template: getTemplate() --><!-- /ko -->
</div>    

This XHTML template is rendered by a completely different rendering engine than a standard Magento phtml template.

Magento replaces the {{...}} text by calling the PHP method on the UI Component object (getName()), or directly accessing a data property of the same object ({{spinner}}).

The more astute among you may have noticed there’s no x-magento-init listed in the template. Rendering the x-magento-init portion of the UI Component is still handled by the XHTML rendering engine — specifically in the appendLayoutConfiguration method called here

#File: vendor/magento/module-ui/TemplateEngine/Xhtml/Result.php
public function __toString()
{
    try {
        //...
        $this->appendLayoutConfiguration();
        $result = $this->compiler->postprocessing($this->template->__toString());
    } catch (\Exception $e) {
        $this->logger->critical($e->getMessage());
        $result = $e->getMessage();
    }
    return $result;
}
//...
public function appendLayoutConfiguration()
{
    $layoutConfiguration = $this->wrapContent(
        json_encode(
            $this->structure->generate($this->component)
        )
    );
    $this->template->append($layoutConfiguration);
}
//...
protected function wrapContent($content)
{
    return '<script type="text/x-magento-init"><![CDATA['
    . '{"*": {"Magento_Ui/js/core/app": ' . str_replace(['<![CDATA[', ']]>'], '', $content) . '}}'
    . ']]></script>';
}    

Magento will render the structure of the UI Component object as the JSON string, and then append that string to the template.

What is the structure of a UI Component you ask? Remember the we’ll get to the rest in a second hand waving we did here?

#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<?xml version="1.0" encoding="UTF-8"?>
<listing 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="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
            <item name="deps" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">cms_block_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Block</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <!-- ... we'll get to this in a second ... -->
</listing>

If we look at the actual contents of those nodes

#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml

<listingToolbar name="listing_top">
    <argument name="data" xsi:type="array">
        <!-- ... -->
    </argument>    
</listingToolbar>
<columns name="cms_block_columns">
    <argument name="data" xsi:type="array">
        <!-- ... -->
    </argument>    
</columns>

we see more configured UI Components. Any sub-node of a UI Component that’s not named argument is considered a child node of the parent object. i.e. When Magento renders the listing component, it also looks up classes and arguments for listingToolbar, columns, etc. in definitions.xml

#File: vendor/magento/module-ui/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">
    <listingToolbar class="Magento\Ui\Component\Container"><!--...--></listingToolbar>
    <columns class="Magento\Ui\Component\Listing\Columns"><!--...--></columns>
</components>

and the pseudo code we used earlier actually looks more like this

$uiComponent = new Magento\Ui\Component\Listing(...);

$listingToolbar = new Magento\Ui\Component\Container(...);
$columns        = new Magento\Ui\Component\Listing\Columns(...);

$uiComponent->addComponent($listingToolbar);     
$uiComponent->addComponent($columns);

As a (potentially overwhelming) side note, these child components are the ones configured with the RequireJS module names

#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<columns class="Magento\Ui\Component\Listing\Columns">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Ui/js/grid/listing</item>
            <!-- ... -->
        </item>
    </argument>
</columns>

These are the RequireJS modules that Magento turns into KnockoutJS view models. If you lookup the source to these KnockoutJS view models — you’ll usually find the KnockoutJS template configured on the view model constructor.

#File: vendor/magento//module-ui/view/base/web/js/grid/listing.js
define([
    'ko',
    'underscore',
    'Magento_Ui/js/lib/spinner',
    'uiLayout',
    'uiCollection'
], function (ko, _, loader, layout, Collection) {
    'use strict';

    return Collection.extend({
        defaults: {
            template: 'ui/grid/listing',
        }
        //...
    });
});

Data Source Nodes

Finally, there’s one more special UI Component sub-node — the <dataSource/> node.

#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
<listing 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="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
            <item name="deps" xsi:type="string">cms_block_listing.cms_block_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">cms_block_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Block</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <!-- ... -->
    <dataSource name="cms_block_listing_data_source">
        <!-- ... -->
    </dataSource>
</listing>

The nodes named dataSource are still UI Components, but they get special treatment. When Magento renders the JSON for the UI component, dataSource nodes get pulled out of the children structure, and Magento renders them right along side the main, top level component (using the name of the component appended with the string _data_source as an object key).

{
    "*": {
        "Magento_Ui/js/core/app": {
            "types": {/*...*/},
            "components": {
                "cms_block_listing": {
                    "children": {
                        "cms_block_listing": {/*...*/},
                        "cms_block_listing_data_source": {
                            "type": "dataSource",
                            "name": "cms_block_listing_data_source",
                            "dataScope": "cms_block_listing",
                            "config": {
                                "data": {
                                    "items": [],
                                    "totalRecords": 0
                                },
                                "component": "Magento_Ui\/js\/grid\/provider",
                                "update_url": "http:\/\/magento-2-1-0.dev\/admin\/mui\/index\/render\/key\/e628fdf18db9219474935e85ab3f25b445287503a00a230704b4168c566f8059\/",
                                "storageConfig": {
                                    "indexField": "block_id"
                                },
                                "params": {
                                    "namespace": "cms_block_listing"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

The dataSource component is where Magento will look for the actual data that populates your UI Component (i.e. the rendered collection data for a model)

Summary of the UI Component Rendering DSL

OK — that was a bananas-pants amount of information. I just finished writing it and I’m not sure even I followed all of it, so don’t worry if your head is spinning. Here’s a very high level summary.

  1. UI Components render an x-magento-init script that populates a global registry of KnockoutJS view models
  2. UI Components also render skeleton HTML that uses KnockoutJS and the custom scope binding to rendering the DOM nodes that make up a component
  3. The ui_component XML files are a domain specific language for instantiating a nested hierarchy of UI Component objects, which Magento will ultimately use to render the JSON for the x-magento-init script
  4. A ui_component’s XML node name is used to lookup PHP classes to instantiate
  5. Magento uses any sub-<agument/> nodes as constructor arguments for that class
  6. Magento uses any sub-node named <dataSource/> to render the actual data used in a UI component (i.e. grid listing information)
  7. Any other sub-node will be used to render a child UI Component — those child UI Components follow the same rules as their parent
  8. The top level UI node configures an XHTML template, which Magento renders via PHP
  9. UI Component nodes configure the RequireJS module(s) that Magento uses as KnockoutJS view model constructors

As you can see, while the uiComponent tags greatly simplifies Magento 2’s layout handle XML files, they also hide a much more complex UI rendering system that includes both front end and backend Magento systems code, and requires developers to understand Magento’s customizations to RequireJS and KnockoutJS as well.

Creating a Grid Listing with Pestle

As you can see from the above (whether you read it or not), the UI Component system rivals Magento 1’s layout update XML system in both its complexity and the lack of clear guidance on usage. In other words, it’s exactly the sort of place a code generation tool like pestle can make things better for working Magento 2 developers. The most recent versions of pestle include a magento2:generate:ui:grid command for creating UI listings, with more commands to come soon.

We’re going to run through using pestle to create a UI grid. We’re going to assume you’ve worked your way through the CRUD Models for Database Access tutorial and have a working Pulsestorm_ToDoCrud module. We’ll also assume you’ve been able to create an admin endpoint with a layout handle XML file, and have a backend page you can navigate to.

In order to create a grid listing, invoke pestle’s magento2:generate:ui:grid command with the following arguments

$ pestle.phar magento2:generate:ui:grid
Which Module? (Pulsestorm_Gridexample)] Pulsestorm_ToDoCrud
Create a unique ID for your Listing/Grid! (pulsestorm_gridexample_log)] pulsestorm_todo_listing
What Resource Collection Model should your listing use? (Magento\Cms\Model\ResourceModel\Page\Collection)] Pulsestorm\ToDoCrud\Model\ResourceModel\TodoItem\Collection
What's the ID field for you model? (pulsestorm_gridexample_log_id)] pulsestorm_todocrud_todoitem_id

The Which Module? argument tells pestle the Magento module you want to create your grid listing in. This is, generally speaking, the same module as the collection file, but there’s nothing enforcing this convention. We’ve specified the Pulsestorm_ToDoCrud from the previous tutorials

The Create a unique ID for your Listing/Grid! argument is the name we want for our UI Component. This will be the name="" attribute we use in the <uiComponent/> tag, as well as the base file name on disk for the UI Component XML file.

The What Resource Collection Model should your listing use? argument is the class name of the collection model to use. We want our grid listing to display Pulsestorm_ToDoCrud models, so we use the Pulsestorm\ToDoCrud\Model\ResourceModel\TodoItem\Collection collection.

The What’s the ID field for you model? argument is the primary key database column for a model’s database table.

After running the above command, add the following <uiComponent/> to your admin module’s layout handle XML file.

<!-- File: app/code/Pulsestorm/ToDoCrud/view/adminhtml/layout/pulsestorm_admin_todocrud_index_index.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="content">                
            <uiComponent name="pulsestorm_todo_listing"/> 
        </referenceBlock>
    </body>
</page>

With the above in place, clear your cache, and you should have a simple UI Grid that lists the model ID for each individual Pulsestorm\ToDoCrud\Model\ToDoItem model. If you want to add a column for the model’s title attribute, just add the following <column/> node to the generated UI Component XML file

<!-- File: app/code/Pulsestorm/ToDoCrud/view/adminhtml/ui_component/pulsestorm_todo_listing.xml -->
<!-- ... -->
<columns>

    <!-- ... --->

    <column name="title">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="filter" xsi:type="string">text</item>
                <item name="label" xsi:type="string" translate="true">Item Title</item>
                <item name="sortOrder" xsi:type="number">20</item>
            </item>
        </argument>
    </column>

    <!-- ... ---> 

</columns>
<!-- ... -->    

In addition to generating the UI Component pulsestorm_todo_listing.xml file, pestle also generates a “data provider” class, and a “page action” class.

The data provider class wraps the collection resource model

#File: app/code/Pulsestorm/ToDoCrud/Ui/Component/Listing/DataProviders/Pulsestorm/Todo/Listing.php
<?php
namespace Pulsestorm\ToDoCrud\Ui\Component\Listing\DataProviders\Pulsestorm\Todo;

class Listing extends \Magento\Ui\DataProvider\AbstractDataProvider
{    
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        \Pulsestorm\ToDoCrud\Model\ResourceModel\TodoItem\CollectionFactory $collectionFactory,
        array $meta = [],
        array $data = []
    ) {
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
        $this->collection = $collectionFactory->create();
    }
}

and the page action class is responsible for rendering the edit link in the final column.

#File: app/code/Pulsestorm/ToDoCrud/Ui/Component/Listing/Column/Pulsestormtodolisting/PageActions.php
<?php
namespace Pulsestorm\ToDoCrud\Ui\Component\Listing\Column\Pulsestormtodolisting;

class PageActions extends \Magento\Ui\Component\Listing\Columns\Column
{
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource["data"]["items"])) {
            foreach ($dataSource["data"]["items"] as & $item) {
                $name = $this->getData("name");
                $id = "X";
                if(isset($item["pulsestorm_todocrud_todoitem_id"]))
                {
                    $id = $item["pulsestorm_todocrud_todoitem_id"];
                }
                $item[$name]["view"] = [
                    "href"=>$this->getContext()->getUrl(
                        "adminhtml/pulsestorm_todo_listing/viewlog",["id"=>$id]),
                    "label"=>__("Edit")
                ];
            }
        }

        return $dataSource;
    }    

}

While not yet fully featured, the magento2:generate:ui:grid command will get you started with a base grid listing configuration. From there, you should be able to examine Magento’s core grid classes and replicate any functionality you see in a core module.

Originally published July 10, 2016

Magento 2: KnockoutJS Integration

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.

While KnockoutJS bills itself as an MVVM (model, view, view model) framework, PHP developers will find the model portion a little thin. KnockoutJS itself has no native concept of data storage, and like many modern javascript frameworks it was designed to work best with a service only backend. i.e. KnockoutJS’s “Model” is some other framework making AJAX requests to populate view model values.

Something else that might catch you off guard with KnockoutJS is it’s not a “full stack” javascript application framework (and to its credit, doesn’t bill itself as such). KnockoutJS has no opinion on how you include it in your projects, or how you organize your code (although the documentation makes it clear the KnockoutJS team members are fans of RequireJS).

This presents an interesting challenge for a server side PHP framework like Magento. Not only is there a degree of javascript scaffolding that needs to surround KnockoutJS, but Magento 2 is not a service only framework. While the new API features of Magento 2 are making strides in this direction, Magento 2 is not a service only framework. i.e. The backend framework developers also need to build scaffolding to get business object data into KnockoutJS.

Today we’re going to dive into Magento 2’s KnockoutJS integration. By the end of this tutorial you’ll understand how Magento 2 applies KnockoutJS bindings as well as how Magento 2 initializes its own custom bindings. You’ll also understand how Magento has modified some core KnockoutJS behavior, why they’ve done this, and the additional possibilities these changes open for your own applications and modules.

This article is part of a longer series covering advanced javascript concepts in Magento 2. While reading the previous articles isn’t 100% mandatory, if you’re struggling with concepts below you may want to review the previous articles before pointing to your Magento Stack Exchange question in the comments below.

Creating a Magento Module

While this article is javascript heavy, we’ll want our example code to run on a page with Magento’s baseline HTML. This means adding a new module. We’ll do this the same as we did in the first article of this series, and use pestle to create a module with a URL endpoint

$ pestle.phar generate_module Pulsestorm KnockoutTutorial 0.0.1

$ pestle.phar generate_route Pulsestorm_KnockoutTutorial frontend pulsestorm_knockouttutorial

$ pestle.phar generate_view Pulsestorm_KnockoutTutorial frontend pulsestorm_knockouttutorial_index_index Main content.phtml 1column

$ php bin/magento module:enable Pulsestorm_KnockoutTutorial

$ php bin/magento setup:upgrade

These commands should be familiar to anyone who’s worked their way through the Magento 2 for PHP MVC developers series. Once you’ve run the above, you should be able to access the following URL in your system

http://magento.example.com/pulsestorm_knockouttutorial/

and see the rendered app/code/Pulsestorm/KnockoutTutorial/view/frontend/templates/content.phtml template. Pestle isn’t mandatory here — if you have a preferred way of working with a page in Magento, feel free to use it.

RequireJS Initialization

In our previous article, and in the official KnockoutJS tutorials, KnockoutJS initialization is a simple affair.

object = SomeViewModelConstructor();
ko.applyBindings(object);

For tutorial applications, this makes sense. However, if you were to keep all your view model logic, custom bindings, components, etc. in a single chunk of code, KnockoutJS would quickly grow un-manageable.

Instead, Magento’s core team has created the Magento_Ui/js/lib/ko/initialize RequireJS module that, when listed as a dependency, will perform and and all KnockoutJS initialization. You can use this module like this

requirejs(['Magento_Ui/js/lib/ko/initialize'], function(){
    //your program here
});

One interesting thing to note about this RequireJS module is it returns no value. Instead, the sole purpose of listing the RequireJS module as a dependency is to kickoff Magento’s KnockoutJS integration. This might confuse you when you see it in the wild. For example, consider this code from a different Magento RequireJS module.

#File: vendor/magento//module-ui/view/base/web/js/core/app.js
define([
    './renderer/types',
    './renderer/layout',
    'Magento_Ui/js/lib/ko/initialize'
], function (types, layout) {
    'use strict';

    return function (data) {
        types.set(data.types);
        layout(data.components);
    };
});

Three RequireJS dependencies are declared,

#File: vendor/magento//module-ui/view/base/web/js/core/app.js
[
'./renderer/types',
'./renderer/layout',
'Magento_Ui/js/lib/ko/initialize'
]

but only two parameters are used in the resulting function

#File: vendor/magento//module-ui/view/base/web/js/core/app.js
function (types, layout) {
    //...
}

It’s not clear to me if this is a clever bit of programming, or if its something that violates the spirit of RequireJS. Maybe it’s both.

Regardless, the first time you use this library in your own RequireJS based programs Magento will initialize KnockoutJS. Subsequent inclusions will effectively do nothing, as RequireJS caches your modules the first time you load them.

KnockoutJS Initialization

If we take a look at the source of of the Magento_Ui/js/lib/ko/initialize module

#File: vendor/magento//module-ui/view/base/web/js/lib/ko/initialize.js
define([
    'ko',
    './template/engine',
    'knockoutjs/knockout-repeat',
    'knockoutjs/knockout-fast-foreach',
    'knockoutjs/knockout-es5',
    './bind/scope',
    './bind/staticChecked',
    './bind/datepicker',
    './bind/outer_click',
    './bind/keyboard',
    './bind/optgroup',
    './bind/fadeVisible',
    './bind/mage-init',
    './bind/after-render',
    './bind/i18n',
    './bind/collapsible',
    './bind/autoselect',
    './extender/observable_array',
    './extender/bound-nodes'
], function (ko, templateEngine) {
    'use strict';

    ko.setTemplateEngine(templateEngine);
    ko.applyBindings();
});

We see a program that’s relatively simple, but that also includes nineteen other modules. Covering what each of these modules does is beyond the scope of this article. Consider the following a highlight reel.

The ko module is an alias to the knockoutjs/knockout module.

vendor/magento/module-theme/view/base/requirejs-config.js
11:            "ko": "knockoutjs/knockout",
12:            "knockout": "knockoutjs/knockout"

The knockoutjs/knockout module is the actual knockout library file. The knockoutjs/knockout-repeat,knockoutjs/knockout-fast-foreach, and knockoutjs/knockout-es5 modules are KnockoutJS community extras. None of these are formal RequireJS modules.

The modules that start with ./bind/* are Magento’s custom bindings for KnockoutJS. These are formal RequireJS modules, but do not actually return a module. Instead each script manipulates the global ko object to add bindings to KnockoutJS. We’ll discuss the scope binding below, but if you’re the curious type try investigating the implementation details of the other bindings. It’s a useful exercise. Hopefully Magento gets us official documentation soon.

The two extender modules are Magento core extensions to KnockoutJS’s functionality.

The ./template/engine module returns a customized version of KnockoutJS’s template engine, and is the first customization we’ll dive deeply into.

Magento KnockoutJS Templates

To review, in a stock KnockoutJS system, templates are chunks of pre-written DOM/KnockoutJS code that you can use by referencing their id. These chunks are added to the HTML of the page via script tags, with a type of text/html

<script type="text/html" id="my_tempalte">
    <h1 data-bind="text:title"></h1>
</script>

This is a powerful feature, but presents a problem for a server side framework — how do you get the right templates rendered on a page? How can you be sure the template will be there without recreating it every-time? The KnockoutJS solution for this is to use the component binding with a library like RequireJS, but this means your templates are tied to a specific view model object.

Magento’s core engineers needed a better way to load KnockoutJS templates — and they did this by replacing the native KnockoutJS template engine with the engine loaded from the Magento_Ui/js/lib/ko/template/engine RequireJS module.

#File: vendor/magento//module-ui/view/base/web/js/lib/ko/initialize.js
define([
    'ko',
    './template/engine',
    //...
], function (ko, templateEngine) {
    'use strict';
    //...
    ko.setTemplateEngine(templateEngine);
    //...
});

If we take a peek at the Magento_Ui/js/lib/ko/template/engine RequireJS module

#File: vendor/magento//module-ui/view/base/web/js/lib/ko/template/engine.js
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
define([
    'ko',
    './observable_source',
    './renderer'
], function (ko, Source, Renderer) {
    'use strict';

    var RemoteTemplateEngine,
        NativeTemplateEngine = ko.nativeTemplateEngine,
        sources = {};

    //...

    RemoteTemplateEngine.prototype = new NativeTemplateEngine;


    //...
    RemoteTemplateEngine.prototype.makeTemplateSource = function (template) 
    {
        //...        
    }
    //...

    return new RemoteTemplateEngine;
});

we see Magento makes a new object that prototypically inherits from the native KnockoutJS rendering engine, and then modifies a few methods to add custom behavior. If you’re not up on your javascript internals, this means Magento copies the stock KnockoutJS template system, changes it a bit, and then swaps its new template engine in for the stock one.

The implementation details of these modifications are beyond the scope of this article, but the end result is a KnockoutJS engine that can load templates via URLs from Magento modules.

If that didn’t make sense, an example should clear things up. Add the following to our content.phtml file.

#File: app/code/Pulsestorm/KnockoutTutorial/view/frontend/templates/content.phtml    
<div data-bind="template:'Pulsestorm_KnockoutTutorial/hello'"></div>

Here we’ve added a KnockoutJS template binding and passed it the string Pulsestorm_KnockoutTutorial. If we reload our page with the above in place, you’ll see an error like the following in your javascript console

> GET http://magento-2-0-4.dev/static/frontend/Magento/luma/en\_US/Pulsestorm\_KnockoutTutorial/template/hello.html 404 (Not Found)

Magento has taken our string (Pulsestorm_KnockoutTutorial/hello) and used the first portion (Pulsestorm_KnockoutTutorial) to create a base URL to a view resource, and use the second portion (hello) with a prepended template and an appended .html to finish the URL. If we add a KnockoutJS view to the following file

#File: app/code/Pulsestorm/KnockoutTutorial/view/frontend/web/template/hello.html
<p data-bind="style:{fontSize:'24px'}">Hello World</p>

and reload the page, we’ll see Magento has loaded our template from the above URL, and applied its KnockoutJS bindings.

This feature allows us to avoid littering our HTML page with <script type="text/html"> tags whenever we need a new template, and encourages template reuse between UI and UX features.

No View Model

Coming back to the initialize.js module, after Magento sets the custom template engine, Magento calls KnockoutJS’s applyBindings method. This kicks off rendering the current HTML page as a view. If we take a look at that code, something immediately pops out.

#File: vendor/magento//module-ui/view/base/web/js/lib/ko/initialize.js
ko.setTemplateEngine(templateEngine);
ko.applyBindings();

Magento called applyBindings without a view model. While this is a legal KnockoutJS call — telling KnockoutJS to apply bindings without data or view model logic seems pretty useless. What is a view without data going to be good for?

In a stock KnockoutJS system, this would be pretty useless. The key to understanding what Magento is doing here is back up in our KnockoutJS initialization

#File: vendor/magento//module-ui/view/base/web/js/lib/ko/initialize.js
define([
    //...
    './bind/scope',
    //...
],

Magento’s KnockoutJS team created a custom KnockoutJS binding named scope. Here’s an example of using scope — lifted from the Magento 2 homepage.

<li class="greet welcome" data-bind="scope: 'customer'">
    <span data-bind="text: customer().fullname ? $t('Welcome, %1!').replace('%1', customer().fullname) : 'Default welcome msg!'"></span>
</li>

When you invoke the scope element like this

data-bind="scope: 'customer'"

Magento will apply the customer view model to this tag and its descendants.

You’re probably wondering — what the heck’s the customer view model?! If you look a little further down in the home page’s source, you should see the following script tag

<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "customer": {
                    "component": "Magento_Customer/js/view/customer"
                }
            }
        }
    }
}
</script>

As we know from the first article in this series, when Magento encounters a text/x-magento-init script tag with an * attribute, it will

  1. Initialize the specified RequireJS module (Magento_Ui/js/core/app)
  2. Call the function returned by that module, passing in the data object

The Magento_Ui/js/core/app RequireJS module is a module that instantiates KnockoutJS view models to use with the scope attribute. Its full implementation is beyond the, um, “scope” of this article, but at a high level Magento will instantiate a new javascript object for each individual RequireJS module configured as a component, and that new object becomes the view model.

If that didn’t make sense, lets run through an example with the above x-magento-init. Magento looks in the components key, and sees one key/object pair.

"customer": {
    "component": "Magento_Customer/js/view/customer"
}

So, for the customer key, Magento will run code that’s equivalent to the following.

//gross over simplification
var ViewModelConstructor = requirejs('Magento_Customer/js/view/customer');
var viewModel = new ViewModelConstructor;
viewModelRegistry.save('customer', viewModel);

If there’s extra data in a specific component object

"customer": {
    "component": "Magento_Customer/js/view/customer",
    "extra_data":"something"
}

Magento will add that data to the view model as well.

Once the above is done, the view model registry will have a view model named customer. This is the view model Magento will apply for the data-bind="scope: 'customer'" binding.

If we take a look at the implementation of the scope custom binding

#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js
define([
    'ko',
    'uiRegistry',
    'jquery',
    'mage/translate'
], function (ko, registry, $) {
    'use strict';

    //...
        update: function (el, valueAccessor, allBindings, viewModel, bindingContext) {
            var component = valueAccessor(),
                apply = applyComponents.bind(this, el, bindingContext);

            if (typeof component === 'string') {
                registry.get(component, apply);
            } else if (typeof component === 'function') {
                component(apply);
            }
        }
    //...

});

It’s the registry.get(component, apply); line that fetches the named view model from the view model registry, and then the following code is what actually applies the object as a view model in KnockoutJS

#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js

//the component variable is our viewModel
function applyComponents(el, bindingContext, component) {
    component = bindingContext.createChildContext(component);

    ko.utils.extend(component, {
        $t: i18n
    });

    ko.utils.arrayForEach(el.childNodes, ko.cleanNode);

    ko.applyBindingsToDescendants(component, el);
}

The registry variable comes from the uiRegistry module, which is an alias for the Magento_Ui/js/lib/registry/registry RequireJS module.

vendor/magento/module-ui/view/base/requirejs-config.js
17:            uiRegistry:     'Magento_Ui/js/lib/registry/registry',

If a lot of that flew over your head, don’t worry. If you want to peek at the data available in a particular scope’s binding, the following debugging code should steer you straight.

<li class="greet welcome" data-bind="scope: 'customer'">
    <pre data-bind="text: ko.toJSON($data, null, 2)"></pre>            
    <!-- ... -->
</li>

If you’re one of the folks interested in diving into the real code that creates the view models (and not our simplified pseudo-code above), you can start out in the Magento_Ui/js/core/app module.

#File: vendor/magento/module-ui/view/base/web/js/core/app.js
define([
    './renderer/types',
    './renderer/layout',
    'Magento_Ui/js/lib/ko/initialize'
], function (types, layout) {
    'use strict';

    return function (data) {
        types.set(data.types);
        layout(data.components);
    };
});

This module has a dependency named Magento_Ui/js/core/renderer/layout. It’s in this dependency module that Magento initializes the view models, and adds them to the view model registry.

#File: vendor/magento//module-ui/view/base/web/js/core/renderer/layout.js

The code’s a little gnarly in there, but if you need to know how those view models are instantiated, that’s where you’ll find them.

A Component by Any Other Name

One sticky wicket in all this is the word component. This scope binding + x-magento-init system is basically a different take on the native KnockoutJS component system.

By using the same component terminology as KnockoutJS, Magento has opened up a new world of confusion. Even the official documentation seems a little confused on what a component is or isn’t. Such is life on a large software team where the left hand doesn’t know what the right hand is doing — and the rest of the body is freaking out about a third hand growing of of its back.

When discussing these features with colleagues, or asking questions on a Magento forum, it will be important to differentiate between KnockoutJS components, and a Magento components.

Changes in the 2.1 Release Candidate

To wrap up for today, we’re going to talk about a few changes to the above in the Magento 2.1 release candidates. Conceptually, the systems are still the same, but there’s a few changes to the details.

First off, KnockoutJS’s initialization now happens in the Magento_Ui/js/lib/knockout/bootstrap RequireJS module

#File: vendor/magento//module-ui/view/base/web/js/lib/knockout/bootstrap.js
define([
    'ko',
    './template/engine',
    'knockoutjs/knockout-es5',
    './bindings/bootstrap',
    './extender/observable_array',
    './extender/bound-nodes',
    'domReady!'
], function (ko, templateEngine) {
    'use strict';

    ko.uid = 0;

    ko.setTemplateEngine(templateEngine);
    ko.applyBindings();
});

You’ll, all the binding loading has been moved to an individual module Magento_Ui/js/lib/knockout/bindings/bootstrap, defined in

#File: vendor/magento/module-ui/view/base/web/js/lib/knockout/bindings/bootstrap.js

Finally, the “Magento Javascript Component” returned by Magento_Ui/js/core/app has a changed method signature that includes a merge parameter, and the arguments to the layout function make it clear layout’s signature has changed as well.

#File: vendor/magento/module-ui/view/base/web/js/core/app.js
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);
    };
});

Beyond being interesting for folks who who are interested in implementation details, these changes point to the fact that Magento’s javascript modules and frameworks are changing rapidly, and unlike the PHP code, Magento’s RequireJS modules don’t have @api markings to indicate stability.

Unless you absolutely need to, it’s probably best to steer clear of dynamically changing the behavior of these core modules, and keep your own javascript as separate as possible.

Originally published June 18, 2016

KnockoutJS Primer for Magento Developers

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.

Before we can continue our exploration of Magento’s advanced javascript features, we need to take a crash course in KnockoutJS. KnockoutJS bills itself as a javascript MVVM system, and its the dominant DOM manipulation framework in Magento 2.

This is a crash course meant to get a working Magento developer familiar with basic KnockoutJS concepts, with a focus on the features Magento uses. We highly recommend working through the official KnockoutJS tutorials if you plan on building anything of substance with KnockoutJS.

Hello Model, View, View Model

The quickest way to wrap your head around KnockoutJS is a basic example. First, lets create the following HTML page

<!-- File: page.html -->  
<!DOCTYPE html>
<html lang="en">
<head>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
    <script src="https://code.jquery.com/jquery-3.0.0.min.js" integrity="sha256-JmvOoLtYsmqlsWxa7mDSLMwa6dZ9rrIdtrrVYRnDRH0=" crossorigin="anonymous"></script>    
</head>
<body>
<div id="main">
    <h1></h1>
    <p></p>
</div>
</body>
</html>

This page

  1. Loads the KnockoutJS library from the cloudflare CDN
  2. Loads the jQuery library from the jQuery code CDN
  3. Sets up an empty DOM node structure

You don’t need to load jQuery and KnockoutJS from a CDN, but its easiest for this tutorial if you do.

If you load this page in a browser, it will be completely blank. That’s because we need to

  1. Add the javascript code that creates a view model and applies the KnockoutJS bindings
  2. Add the view code to the HTML page that reads from the view model

Tackling the first of those, let’s add a third javascript file to our page. We’ll create a file named ko-init.js with the following contents

//File: ko-init.js
jQuery(function(){
    viewModel = {
        title:"Hello World",
        content:"So many years of hello world"
    };
    ko.applyBindings(viewModel);
});

and then add ko-init.js to our page with a third script tag.

<!-- File: page.html -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<script src="https://code.jquery.com/jquery-3.0.0.min.js" integrity="sha256-JmvOoLtYsmqlsWxa7mDSLMwa6dZ9rrIdtrrVYRnDRH0=" crossorigin="anonymous"></script>    
<script type="text/javascript" src="ko-init.js"></script>

Finally, change the h1 and p tags so they include the following data-bind attributes.

<!-- File: page.html -->
<div id="main">
    <h1 data-bind="text:title"></h1>
    <p data-bind="text:content"></p>
</div>

With the above in place, reload your page and you should see your title and content rendered.

Congratulations! You just created your first KnockoutJS view model and view.

What Just Happened

KnockoutJS bills itself as an “MVVM” system. This stands for Model, View, View Model. Really though, KnockoutJS is better billed as a VVM system, since its agnostic about what sort of model code you use to fetch data. The view is your HTML page. The view model is the javascript object that contains data.

Take a look at the javascript code

//File: ko-init.js
jQuery(function(){
    viewModel = {
        title:"Hello World",
        content:"So many years of hello world"
    }; 
    ko.applyBindings(viewModel);
});

While jQuery isn’t required, KnockoutJS can’t start rendering a view until the entire document/DOM is loaded, and jQuery’s default document ready functionality is a good way to achieve this.

Here we’ve created a view model with simple key/value pairs.

//File: ko-init.js
viewModel = {
    title:"Hello World",
    content:"So many years of hello world"
}; 

Then, we’ve applied the KnockoutJS bindings. Another way to say this is we’ve told KnockoutJS to render the view with our view model. Again, the view is the entire HTML page.

If we look at the important section of our view, we see the data-bind attributes

<!-- File: page.html -->  
<div id="main">
    <h1 data-bind="text:title"></h1>
    <p data-bind="text:content"></p>
</div>

When you call applyBindings, KnockoutJS will scan the entire HTML page for data-bind attributes. When it finds these attributes, it parses the attribute for the binding name and value, and then invokes a set of rules based on the name of the binding.

For example — the binding we invoke above is the text binding. The value we passed to the text binding is title. The set of rules the text binding applies are “use the value passed into the binding to fetch a value from the view model object, and add that value as a text node”. The end result is something that, if written in pure javascript, might look like this

value = viewModel['title'];
textNode = document.createTextNode(value);
h1.appendChild(textNode);

KnockoutJS’s first trick is it gets developers out of the business of directly using javascript to create and update DOM nodes. Instead, developers can write HTML, mark it up with data-bind attributes, and just assign values to an object. You’re not limited to key/value pairs either. Consider this more sophisticated view model.

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.getTitle = function()
        {
            return "Hello Method World";
        }
       this.content = "So many years of hello world";        
    }

    viewModel = new viewModelConstructor;
    ko.applyBindings(viewModel);
});

Here we’ve used a javascript constructor to create a simple object, with a getTitle method. If we change our view to call the getTitle method, you’ll see it works as you’d expect

<!-- File: page.html -->  
<div id="main">
    <h1 data-bind="text:getTitle()"></h1>
    <p data-bind="text:content"></p>
</div>

Another way of thinking about binding parameters is they’re a temporary, limited javascript scope to access your view model’s values and methods.

Other Bindings

While this example is simple, you can start to see how this basic building block could implement far more complicated view logic. The business of updating the DOM is left to the data-bindings, and the business of updating the model is left to pure non-DOM javascript code.

You can start to see the value of this with other bindings. For example, lets add a theValue property in our viewModelConstructor

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.getTitle = function()
        {
            return "Hello World";
        }
       this.content = "So many years of hello world";        
       this.theValue = "2";
    }
    viewModel = new viewModelConstructor;
    ko.applyBindings(viewModel);        
});

and then add an input tag with a new binding.

<!-- File: page.html -->  
<div id="main">
    <h1 data-bind="text:getTitle()"></h1>
    <p data-bind="text:content"></p>
    <input type="text" data-bind="value:theValue"/>        
</div>

Reload the page, and you’ll see an HTML input tag with a value of 2.

Here we’ve used a new (to us) KnockoutJS binding

data-bind="value:theValue"      

We use the value binding to apply a value to the form field. Next, let’s change that input to a select with the same binding

<!-- File: page.html -->  
<div id="main">
    <h1 data-bind="text:getTitle()"></h1>
    <p data-bind="text:content"></p>
    <select data-bind="value:theValue">
        <option value="">-- Choose --</option>
        <option value="1">First</option>
        <option value="2">Second</option>        
        <option value="3">Third</option>                
    </select>      
</div>

If you reload the page, you’ll see the binding has set the value of the select for us.

While this example is simple, the concept behind it is not. Without needing to change any javascript application code, the value binding lets us change our UI.

Observables

So far, what we’ve seen is a powerful parlor trick. Neat, maybe useful, but it only sets the stage for KnockoutJS’s real “knockout” feature — observables.

Again, we’re going to dive in with an example. Change your view so it matches the following

<!-- File: page.html --> 
<div id="main">
    <p data-bind="text:theValue"></p>
    <select data-bind="value:theValue">
        <option value="">-- Choose --</option>
        <option value="1">First</option>
        <option value="2">Second</option>        
        <option value="3">Third</option>                
    </select>    
    <input type="text" data-bind="value:theValue"/>

    <div>
    <br/><br/>
    <button id="button">Do It</button>
    </div>
</div>

and change your view model and binding so they match the following.

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {   
       this.theValue = ko.observable("1");
    }

    window.viewModel = new viewModelConstructor;
    ko.applyBindings(window.viewModel);        
});  

If you reload the page, you’ll see we’ve bound the value of 1 to our <input/> and <p/> tag. So far our view has nothing new — this is the same sort of binding we were doing previously. However, you’ll notice that we’ve done something different in our view model.

//File: ko-init.js
this.theValue = ko.observable("1");

Instead of setting theValue to a hard coded value, or a custom function, we’ve set the value to be something KnockoutJS calls an observable. An observable is a special sort of getter and setter.

If you open up your javascript console, and type the following, you’ll see we can fetch the value of the observable by calling it as a function (viewModel is available via the console since we defined it as a global object on the window object)

> viewModel.theValue()    
> "1"

We can set a value on the observable by passing in a parameter. Here’s how you’d set, and then get the value of, an observable.

> viewModel.theValue("3")
//...
> viewModel.theValue()
> "3"

However, the real power of an observable is in what happens to the DOM nodes we’ve bound that observable to. Try changing the value of the observer via the console and watch the browser window

> viewModel.theValue("3");
> viewModel.theValue("2");
> viewModel.theValue("1");
> viewModel.theValue("10");

As you update the value of the observable, the value of the bound nodes change in real time. Again, as developers, we’re freed from having to worry how the DOM nodes get updated — once we’ve set the value on our model, this value is automatically reflected in the user interface.

While its beyond the scope of this article, you can see how this comes together to form complex javascript applications when our view model includes methods

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {   
        this.theValue = ko.observable("1");
        var that = this;
        this.pickRandomValue = function(){
            var val = Math.floor(Math.random() * (3));
            that.theValue(val);
        };
    }

    window.viewModel = new viewModelConstructor;
    ko.applyBindings(window.viewModel);        
});

and you use KnockoutJS’s event bindings, like click

<!-- File: page.html -->  
<button data-bind="click:pickRandomValue">Do It</button>

We’ll leave parsing through that one as an exercise for the reader

Template Binding

Another binding that’s important to understand is KnockoutJS’s template binding. Consider a view model like this

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {   
        this.first = {
            theTitle:ko.observable("Hello World"),
            theContent:ko.observable("Back to Hello World")
        };
        this.second = {
            theTitle:ko.observable("Goodbye World"),
            theContent:ko.observable("We're sailing west now")            
        };            
    }

    viewModel = new viewModelConstructor;
    ko.applyBindings(viewModel);        
});

Here we’ve created a standard view model, but with nested data objects. If you combine this with a view like this

<!-- File: page.html --> 
<div id="main">
    <div id="one" data-bind="template:{'name':'hello-world','data':first}"></div>

    <div id="two" data-bind="template:{'name':'hello-world','data':second}">
    </div>

    <script type="text/html" id="hello-world">
        <h1 data-bind="text:theTitle"></h1>
        <p data-bind="text:theContent"></p>
    </script>
</div>

you’ll see the following

The template binding accepts a javascript object as a parameter

<!-- File: page.html --> 
<div id="one" data-bind="template:{'name':'hello-world','data':first}"></div>

The data parameter is the property of the view model we want to render the template with. The name of the template is just that — the template name to lookup and render.

The most basic way of adding a named template to the system is adding a <script/> tag with a type of text/html.

<!-- File: page.html --> 
<script type="text/html" id="hello-world">
    <h1 data-bind="text:theTitle"></h1>
    <p data-bind="text:theContent"></p>
</script>   

If you’ve never seen this before it may seem weird/foreign, but many modern javascript frameworks use non-text/javascript <script/> tags as a way to add non-rendered (but DOM accessible) content to a page. A template is just a standard set of HTML nodes with KnockoutJS bindings.

Components

The final binding we’ll at is the component binding. Components are a way to package together a KnockoutJS template, and a KnockoutJS view file. This means you can have a relatively simple view

<!-- File: page.html -->    
<div data-bind="component:'component-hello-world'"></div>

which hides the complexity of a registered component.

//File: ko-init.js
jQuery(function(){    
    var viewModelConstructor = function()
    {   
        this.message = "Hello World";
    }  

    var theTemplate = "<h1 data-bind=\"text:message\"></h1>";    

    ko.components.register('component-hello-world', {
        viewModel:viewModelConstructor,
        template:theTemplate
    });    

    ko.applyBindings();        
});

The register function of the component object expects a name for your component, and then a KnockoutJS component object. A component object is a javascript script object with two properties. The viewModel property expects a view model constructor function, and the template property should be a string with a KnockoutJS template. Once registered, you can use your component by passing the name of the component (as a string) into the binding.

<!-- File: page.html -->  
<div data-bind="component:'component-hello-world'"></div>

If you don’t want to use the data-bind syntax — KnockoutJS gives you the ability to insert a component with a custom tag name based on the component name. Try this in your view/HTML-file

<!-- File: page.html -->  
<component-hello-world></component-hello-world>

This only scratches the surface of what’s possible with KnockoutJS. The official docs have a pretty good overview of the component binding.

Custom Binding

The final KnockoutJS feature we’ll discuss today is the custom binding feature. KnockoutJS gives javascript developers the ability to create their own bindings. For example, here we’re invoking a custom binding named pulseStormHelloWorld, and passing it the value of the message property of our viewModel.

<!-- File: page.html -->  
<div data-bind="pulseStormHelloWorld:message"></div>

Without an implementation, KnockoutJS will ignore our binding. Instead, try the following in ko-init.js

//File: ko-init.js
jQuery(function(){    
    var viewModelConstructor = function()
    {   
        this.message = "Hello World";
    }  

    ko.bindingHandlers.pulseStormHelloWorld = {
        update: function(element, valueAccessor){
            jQuery(element).html('<h1>' + valueAccessor() + '</h1>');
        }
    };    
    ko.applyBindings(new viewModelConstructor);        
});

To add the custom binding to KnockoutJS, all we need to do is add a property to the ko object’s binidngHandlers object. The name of this property is the name of our binding. The handler is a JS object with an update method. KnockoutJS calls the update method whenever a binding is invoked — either during applyBindings, or via an observable.

With the above in place, reload your HTML page and you’ll see the custom binding invoked. This is, of course, a trivial example, but with custom bindings you can make KnockoutJS do anything your programatic mind thinks up.

The KnockoutJS core documentation has a pretty good tutorial on custom bindings if you’re interested in learning more.

Wrap Up

KnockoutJS is a powerful, modern, javascript framework. Its semantics, and disregard for traditional HTML concepts, may make some developers shy away at first, but as a tool of organizing and taming DOM complexity in a modern javascript application KnockoutJS has few peers. Our tour here is incomplete, but hopefully it’s enough to get you interested in working through KnockoutJS’s tutorials and documentation for yourself.

All that said, KnockoutJS by itself isn’t enough to build a full javascript application. Next week we’ll take a look at the framework Magento has built around KnockoutJS — both so you can better understand how the core Magento system works, and also so you can incorporate KnockoutJS into your own modules and applications.

Originally published June 16, 2016