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 bind-data="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

Magento 2: Javascript Init Scripts

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 starting a new series that will cover advanced Javascript systems in Magento 2. This series is sponsored by my patreon campaign. If you like what you see here, please consider donating to keep the tutorials coming.

Back in our Magento 2 for PHP MVC series, we stated that RequireJS was the library that underlies nearly every javascript feature built in Magento 2. This is true enough, but RequireJS only scratches the surface of what’s possible with javascript and Magento 2.

Today we’re going to explore the various systems Magento 2 has for kicking off execution of javascript code without an embedded <script type="text/javascript"> tag.

Javascript Init Methods

The Magento javascript init methods we’re going to discuss solve a few different problems.

First, they provide a standard mechanism to discourage directly embedding javascript into a page.

Second, they provide a way to invoke a stand alone RequireJS module (defined with define) as a program.

Third, they provide a way to pass that program a server side generated JSON object.

Fourth, they provide a way to tell that program which (if any) DOM nodes it should operate on.

Keep these four goals in mind. They may help you if you’re struggling with the mental model behind these custom framework features.

Setting up a Module

Toady’s tutorial doesn’t involved much in the way of Magento’s PHP code — you can run these examples from any Magento phtml template that’s rendered on a stock Magento page.

We’re going to use pestle to create a module named Pulsestorm_JavascriptInitTutorial with a single URL endpoint by running the following three commands

$ pestle.phar generate_module Pulsestorm JavascriptInitTutorial 0.0.1

$ pestle.phar generate_route Pulsestorm_JavascriptInitTutorial frontend pulsestorm_javascriptinittutorial

$ pestle.phar generate_view Pulsestorm_JavascriptInitTutorial frontend pulsestorm_javascriptinittutorial_index_index Main content.phtml 1column

$ php bin/magento module:enable Pulsestorm_JavascriptInitTutorial

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

and see the rendered app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml template.

Setting up a RequireJS Module

Now that we’ve setup a Magento module, lets do a quick review and create a RequireJS module. First, create the following file

//File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/web/example.js
define([], function(){
    alert("A simple RequireJS module");
    return {};  
});    

This is a very simple RequireJS module. Due to the module’s location on the file system, and the way Magento loads javascript files, this module’s name/identifier is Pulsestorm_JavascriptInitTutorial/example

Next, change the contents of content.phtml so they match the following.

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml
<script type="text/javascript">
    requirejs(['Pulsestorm_JavascriptInitTutorial/example'],function(example){
        alert("Loaded");
        console.log(example);
    }); 
</script>

Here we’re creating a RequireJS program with a single module dependency. The dependency is our just created module (Pulsestorm_JavascriptInitTutorial/example). Load the

http://magento.example.com/pulsestorm_javascriptinittutorial/

URL in your system, and you should see the alerts.

If any of the above was foreign to you, you may want to review our Magento 2 and RequireJS article.

X-Magento-Init

The first initialization technique we’re going to discuss is the <script type="text/x-magento-init"> method. The most basic version of this initialization method allows you to run a RequireJS module as a program. Change the contents of the content.phtml template so they match the following

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml
<div id="one" class="foo">
    Hello World
</div>
<div id="two" class="foo">
    Goodbye World
</div>    

<script type="text/x-magento-init">
    {
        "*": {
            "Pulsestorm_JavascriptInitTutorial/example":{}
        }
    }        
</script>

Give your page a reload with the above, and you should see the alert statement from our example.js file.

If you’ve never seen this syntax before, it can look a little strange. Let’s take it apart piece by piece.

First is the <script/> tag

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml
<script type="text/x-magento-init">
    //...
</script>

This tag is not a javascript tag. Notice the type="text/x-magento-init" attribute. When a browser doesn’t recognize the value in a script’s type tag, it will ignore the contents of that tag. Magento (similar to other modern javascript frameworks) uses this behavior to its advantage. While it’s beyond the scope of this tutorial, there’s Magento javascript code running that will scan for text/x-magento-init script tags. If you want to explore this yourself, this Stack Exchange question and answer is a good place to start.

The other part of the x-magento-init code chunk we can talk about immediately is the following object

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml
{
    "Pulsestorm_JavascriptInitTutorial/example":{}            
}

Magento will look at the key of this object, and include it (the key) as a RequireJS module. That’s what loading our example.js script.

You’re probably wondering why this is an object with no value. You may also be wondering why the whole thing is a part of another object with a * as a key.

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml
{
    "*": {/*...*/}
}           

Before we can talk about that, we’ll need to talk about javascript components.

Magento Javascript Components

The above example runs our RequireJS module as a program. This works, and Magento itself often uses the x-magento-init method to invoke a RequireJS module as a program. However, the real power of x-magento-init is the ability to create a Magento Javascript Component.

Magento Javascript Components are RequireJS modules that return a function. Magento’s system code will call this function in a specific way that exposes extra functionality.

If that didn’t make sense, try changing your RequireJS module so it matches the following.

//File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/web/example.js
define([], function () {
    var mageJsComponent = function()
    {
        alert("A simple magento component.");
    };

    return mageJsComponent;
});

Here we’ve defined a function and assigned it to a variable named mageJsComponent. Then, we return it.

If you reload the page with the above in place, you should see A Simple Magento Component in an alert box.

This may seem silly — what’s the point of returning a function if all Magento does is call it? You’d be right, but that’s because we left something out. Try changing our phtml template so it matches the following

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml
<div id="one" class="foo">
    Hello World
</div>
<div id="two" class="foo">
    Goodbye World
</div>    

<script type="text/x-magento-init">
    {
        "*": {
            "Pulsestorm_JavascriptInitTutorial/example":{"config":"value"}
        }
    }        
</script>

and changing our RequireJS module so it looks like this

//File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/web/example.js
define([], function () {
    var mageJsComponent = function(config)
    {
        alert("Look in your browser's console");
        console.log(config);
        //alert(config);
    };

    return mageJsComponent;
});

If you reload the page, you should see the changed alert message. If you look in your browser’s javascript console, you should also see the following

> Object {config:"value"}

When we create a Magento Javascript Component, Magento calls the returned function and includes the object from text/x-magento-init.

"Pulsestorm_JavascriptInitTutorial/example":{"config":"value"}         

This is why the RequireJS module name is a key — the value of this object is the object we want to pass to our component.

This may seem like a silly bit of abstraction in these examples. However, in a real module, we’d be generating that JSON with PHP. This system allows us to render the JSON in our phtml template, and then have that passed to Javascript code. This helps avoid the problem of generating Javascript directly with PHP, which can lead to very messy code, and makes it easy to accidentally slip in an error or security problem.

Before we finish up with x-magento-init, there’s one last feature to discuss. You’ll remember we said that x-magento-init

provides a way to pass that program a server side generated JSON object.

provides a way to provide that program with the DOM nodes it should operate on.

We’ve covered how to pass in server side generated JSON — but what about the DOM nodes?

Change your RequireJS module so it looks like the following

//File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/web/example.js
define([], function () {
    var mageJsComponent = function(config, node)
    {       
        console.log(config);
        console.log(node);
        //alert(config);
    };

    return mageJsComponent;
});

What we’ve done here is add a second parameter to out mageJsComponent function. This second parameter will be the DOM node we want our program to operate on. However, if you reload the page with the above in place, you’ll see the following in your console.

> Object {config:"value"}
> false

Magento did pass in a value for node — but that value was false. What gives?

Well, Magento can’t magically know which DOM nodes we want to operate on. We need to tell it! Change your phtml template so it matches the following.

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml

<div id="one" class="foo">Hello World</div>
<div id="two" class="foo">
    Goodbye World
</div>    

<script type="text/x-magento-init">
    {
        "#one": {
            "Pulsestorm_JavascriptInitTutorial/example":{"config":"value"}          
        }
    }        
</script>

Here, we’ve changed the * to a #one. The * we used previously is actually a special case, for programs that don’t need to operate on DOM nodes. The key for this object is actually a CSS/jQuery style selector that tells Magento which DOM nodes the program in Pulsestorm_JavascriptInitTutorial/example should operate on. If we reload our page with the above in place, we’ll see the following in your console

> Object {config: "value"}
> <div id="one" class="foo">Hello World</div>

You’re not limited to IDs — you can uses CSS class identifiers here as well. Change that to

#File: app/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtml
".foo": {
    "Pulsestorm_JavascriptInitTutorial/example":{"config":"value"}          
}

and you’ll see the following in your console.

> Object {"config":"value"}
> <div id="one" class="foo">Hello World</div>
> Object {"config":"value"}
> <div id="one" class="foo">Goodbye World</div>

By building this sort of system, Magento is encouraging developers to avoid hard coding their DOM nodes into their RequireJS modules. The x-magento-init means there’s a system level path forward for building Javascript modules that rely on server side rendered JSON, and operate on any arbitrary DOM node. It’s always been possible for Magento module developers to implement their own systems for this sort of functionality, but Magento 2 provides a standard, built in way to achieve this.

Data-mage-init Attribute

In addition to <script type="text/x-magento-init">, there’s another way to invoke similar functionality on a specific DOM node, and that’s with the data-mage-init attribute. Try replacing the existing phtml template with the following

<div data-mage-init='{"Pulsestorm_JavascriptInitTutorial/example": {"another":"example"}}'>A single div</div>

Reload the page with the above in place, you should see the following in your javascript console.

> Object {another: "example"}  
> <div>This div</div>

Here, we’ve added a data-mage-init attribute to a specific div. This attribute’s value is a JSON object. Similar to x-magento-init, this object’s key is the RequireJS module we want to invoke as a program or Magento Javascript Component, and the value is a nested JSON object to pass into our Magento Javascript Component function as the config parameter.

On a pedantic note — you’ll notice we used single quotes with our attribute

<div data-mage-init='...'>A single div</div>

This is, unfortunately, required. The value of the data-mage-init attribute is parsed by a strict JSON parser, which means the JSON object’s quotes must be double quote — which means we can’t use double quotes for our attribute. While this is technically OK in HTML5, for web programmers of a certain age it brings back some bad Microsoft Frontpage memories.

Wrap Up

Whether you end up using the <script type="text/x-magento-init"> component or a data-mage-init attribute in a specific node, both these techniques provide a standard, system unified way of introducing Javascript entry points onto your page. Many of Magento’s front end and back end UI features rely on this syntax, so even if you personally eschew them, understanding how these systems work is an important part of being a Magento 2 developer.

Between these techniques and the base RequireJS implementation in Magento 2, we’re ready to cover our next topic — Magento 2’s use of the KnockoutJS library.

Originally published June 12, 2016