Knockout Observables for Javascript Programmers

This entry is part 5 of 5 in the series Magento 2: Advanced Javascript. Earlier posts include Magento 2: Javascript Init Scripts, KnockoutJS Primer for Magento Developers, Magento 2: KnockoutJS Integration, and The Curious Case of Magento 2 Mixins. This is the most recent post in the series.

The concept of “observables” can be a little tricky to wrap your head around in Knockout.js. Today we’re going to have a quick tutorial on how observables work outsidethe context of a normal Knockout.js view. We need to do this because Magento 2’s javascript frameworks make heavy use of observables that goes above and beyond what a normal front end developer needs to be aware of.

If you’re trying to think rationally about your Magento systems, you’ll not only need to understand how observables work, but you’ll often need to know how the internals are implemented.

What are Observables

Observables are stand-alone setter/getter objects. From a Magento bootstrapped page, run the following code in your browser’s javascript console. You should also be able to do this outside of Magento in systems that use a global ko variable instead of Magento’s special RequireJS module.

//load the Knockout.js library -- normally you'd do this in a `define`
ko = requirejs('ko');

//create the observable object with a default value
var objectToHoldMyValue = ko.observable('default value');

//fetch that value by calling the observable as a function
console.log( 'Value: ' + objectToHoldMyValue() );
"default value"

//set a new value by calling the observable as a function with an argument
objectToHoldMyValue('new value')

//fetch that value by calling the observable as a function
console.log( 'Value: ' + objectToHoldMyValue() );
"new value"    

As you can see from the above code and comments, the first job of an observable object is to store a value, return a value, and change a stored value. The syntax may be a little weird if you’re not used to the “objects can be anonymous functions” nature of javascript, but this is nothing too crazy. Also — nothing too necessary either, until you consider subscribers.

//subscribe to a change
objectToHoldMyValue.subscribe(function(newValue){
    console.log("The subscriber sees: " + newValue);
});     

The above code sets up a callback that is, in other terms, an event listener (i.e. you’re subscribing to an event). The event you’re subscribing to? A change in value of the observable. If you run the value setting code again.

objectToHoldMyValue('a second new value')
The subscriber sees: a second new value

you’ll see Knockout calls your subscriber method.

Important: Subscribers are only called when a value changes. If you pass in the observable’s current value, Knockout will not call subscriber callbacks

objectToHoldMyValue('a third new value')
The subscriber sees: a third new value

objectToHoldMyValue('a third new value')
[no output, because the subscriber was not called]

While our example is a little silly, in a real program observables let you take actions whenever the value of a variable changes. That’s an incredibly powerful feature.

The Importance of Observables

Observables are what enables Knockout’s “update the model, automatically update the UI” behavior. If you consider a simple Knockout.js data binding (from the official intro tutorial)

<input data-bind="value: firstName" ... />

Behind the scenes, the value data-binding will check if firstName is an observable. IffirstName is an observable, the value binding implementation will setup a subscriber that updates the <input/>. This means whenever a programmer updates the value stored infirstName, the binding’s subscriber runs, and the <input/>‘s value is updated.

Knockout.js does all this behind the scenes. Even if you create a custom binding, Knockout handles setting up the subscriber, and your binding’s update method gets called. There’s no need for you, as a binding developer, to know about subscribers.

The subscribe method feels like something that should be a private API, but since this is javascript and everything’s public by default, developers can and will setup their own subscribers for observables.

Something else that may cause you, as a javascript or PHP programmer, a bit of cognitive dissonance is the lack of empty parameter () parenthesis when someone uses an observable in a data binding

<input data-bind="value: firstName" ... />

When I first started with Knockout.js, the lack of any clear distinction between a regular object property and an observable — at the template level — was a little confusing. Once you understand that observables are just callable javascript objects, and understand that the binding needs to receive this object and not its stored value, things start to make a little more sense. Developers from a civilized language (like ruby), where you don’t need parenthesis to call a method, are now free to laugh.

For Magento 2 Developers

As a Knockout.js developer, you can live a life that’s mostly ignorant of how observables are implemented. Magento 2 developers don’t have this luxury. The UI Component systems make heavy use of observable properties, and also setup their own subscribers.

The good news is: When you see something like

//...
someProp: ko.observable('default value')
//...

you don’t need to panic. The program is just using someProp to store a value.

The bad news is — that observable may have a number of subscribers. These subscribers may come from a Knockout.js template’s data-bind attributes. They may come from Magento core code setting up their own subscribers. You can view the number of callbacks an observer has via the _subscriptions property

console.log(objectToHoldMyValue._subscriptions);
Object
    change: Array[3]
        0: ko.subscription
        1: ko.subscription
        2: ko.subscription

Or peek at a particular callback like this

console.log(
    objectToHoldMyValue._subscriptions.change[1].callback
);

However — you’re at the mercy of your debugger w/r/t to how this information is displayed, and there’s no easy way to tell where a particular subscriber comes from. Also, we’re deep into Knockout.js internals at this point, and relying on this sort of code for anything other than debugging introduces enormous potential for instabilities.

The Curious Case of Magento 2 Mixins

We’re back, with an unexpected fourth article in our Advanced Javascript series.

At the end of our UI Component series, we said

The same applies to system integrators, but if you’re the one building and maintaining a specific Magento instance I’d say it’s safe to also use RequireJS’s selective “monkey patching” abilities to modify the behavior of core javascript objects. Keep an eye on Magento Quickies for an upcoming article on how to do this with Magento’s specialrequirejs-config.js files.

However, when I started researching RequireJS monkey patching and the best way to apply it in Magento 2, I was in for a bit of a surprise. Joe Constant pointed me towards a feature Magento 2 erroneously calls mixins, and all sorts of confusion resulted.

Our end goal today is to teach you a technique for applying a safe method rewrite technique using Magento 2 javascript techniques — one that goes beyond the usual RequireJS tools. To get there though, we’ll need to wade through some very puzzling choices made by Magento’s product and engineering teams.

We’re going to start with a quick programming lesson, continue with some related feature highlights of Magento’s javascript system, and then fall into discussing Magento’s weirdly named mixins system.

What is a Mixin?

A “mixin” is, from one point of view, an alternative to traditional class inheritance. Mixins date back to lisp programming of the early/mid 1980s.

In “classical OOP”, you might define three classes like this

class A
{
    public function foo()
    {
    }
}

class B extends A
{
    public function bar()
    {
    }
}

class C extends B
{
    public function baz()
    {
    }
}

$object = new C;

This hierarchy chain means your instantiated $object will have a baz method, a barmethod, and a foo method.

Mixins offer a different approach. With mixins, you get to pick and choose where you class’s (or object’s) method(s) come from. With an imaginary mixin based language, the above might look like

class A
{
    public function foo()
    {
    }
}

class B
{
    public function bar()
    {
    }
}

class C
{
    mixin A;
    mixin B;

    public function baz()
    {
    }
}
$object = new C;

Notice no classes inherit from one another. Instead, the programmer indicates that class Cshould get methods from class A and class B. The result is more flexibility, at the expense of some ambiguity about how conflicts between different mixins should work, or what the language syntax should be for specifying mixins.

If you’ve ever used PHP Traits, you’ve used a simplified mixin system. In PHP, you need to define explicit traits, and then can combine those traits in your classes. Traits, by themselves, can’t be instantiated as objects. PHP classes, by themselves, can’t be used as traits.

Contrast this with ruby, which allows one module to completely “include” (or “mix in”) another module’s methods.

You’ll also see the idea of multiple inheritance thrown about in mixin discussions. With multiple inheritance, classes still extend other classes, but you’re allowed to have a single class extend more than one class.

Javascript and Mixins, Sitting in a Tree

Unlike classes, there doesn’t seem to be a consensus on how mixin syntax should work across languages. Some languages have explicit mixins while other languages have de-facto mixins due to the nature of their object system.

Javascript is an example of the later. Javascript doesn’t have any native classes. In javascript, you define methods by attaching functions to objects

var foo = {};

foo.someMethod = function(){
    //...
};

Since objects can be easily reflected into, Javascript is a fertile enviornment for developers who want to build systems for creating mixin like objects. One library that offers this sort of functionality (although the word mixin isn’t used) is underscore.js.

Using the extend method in underscore.js, you can have a de-facto mixin-like behavior. Consider the following

var a = {
    foo:function(){
        //...
    }
};

var b = {
    bar:function(){
        //...
    }

}

c = _.extend(a, b);    

The object in c will end up having both a foo, and a bar method. The _.extend method lets us say

Hey, javascript, create a new object with stuff from these other objects

Confusingly, underscore.js has an actual method named mixin, but this method is for adding methods to the underscore JS object itself. Something something cache invalidation and naming things.

Magento uiClass Objects

If you’ve worked your way through the UI Component series, you’re already familiar withMagento’s uiClass objects. These objects also have an extend method. This method looks similar to the underscore.js method

var b = {
    bar:function(){
        //...
    }

}
UiClass = requirejs('uiClass');

// class NewClass extends uiClass
var NewClass = UiClass.extend(b);

// class AnotherNewClass extends NewClass
var AnotherNewClass = NewClass.extend({});

var object = new NewClass;
object.bar();

However, the uiClass extend method is used for something slightly different. The purpose of the uiClass.extend is to create a new javascript constructor function that’s based on an existing javascript constructor function. Above, NewClass won’t get a bar method, but objects instantiated from it will.

While this feels more like straight inheritance, there might be some folks who would call this a mixin due to the uiClass‘s implementation details.

We’re now going to jump to a completely different topic, but keep all of the above in mind.

Magento 2 RequireJS Mixins

Magento 2’s requirejs-config.js files have a feature that’s labeled as a “mixin”. This feature has (almost) nothing to do with traditional computer science mixins, so we’ll continue to refer to them with the “skeptical quotes”.

Despite all the shade we’re throwing at the feature’s name, it’s actually a very good and important feature. A Magento 2 RequireJS “mixin” allows you to programmatically listen for the initial instantiation of any RequireJS module and manipulate that module before returning it.

If that didn’t make sense, a quick sample module should make things clearer. First, create and enable a blank module with the following pestle commands (or use your own module creating methodology).

$ pestle.phar generate_module Pulsestorm RequireJsRewrite 0.0.1
$ php bin/magento module:enable Pulsestorm_RequireJsRewrite
$ php bin/magento setup:upgrade    

If you’re interested in creating a module by hand, or curious what the above pestle command is actually doing, take a look at our Introduction to Magento 2 — No More MVCarticle.

Once that’s complete, create the following requirejs-config.js file

//File: app/code/Pulsestorm/RequireJsRewrite/view/base/requirejs-config.js
var config = {
    'config':{
        'mixins': {
            'Magento_Customer/js/view/customer': {
                'Pulsestorm_RequireJsRewrite/hook':true
            }
        }
    }
};    

and the following RequireJS module/file.

//File: app/code/Pulsestorm/RequireJsRewrite/view/base/web/hook.js
define([], function(){
    'use strict';    
    console.log("Called this Hook.");
    return function(targetModule){
        targetModule.crazyPropertyAddedHere = 'yes';
        return targetModule;
    };
});

With the above in place, if you load the Magento homepage (or any page which uses theMagento_Customer/js/view/customer RequireJS module) you should see the

Called this Hook   

message output to your javascript console. Also, if you examine theMagento_Customer/js/view/customer module via the console, you’ll see it has an extracrazyPropertyAddedHere property

> module = requirejs('Magento_Customer/js/view/customer');
> console.log(module.crazyPropertyAddedHere)
"yes"

With the above code, we changed the object returned by theMagento_Customer/js/view/customer module. If used judiciously, this is an incredibly powerful feature.

What Just Happened?

Before we jump to the implications of this feature, lets talk about the code we just wrote.

//File: app/code/Pulsestorm/RequireJsRewrite/view/base/requirejs-config.js
var config = {
    'config':{
        'mixins': {
            'Magento_Customer/js/view/customer': {
                'Pulsestorm_RequireJsRewrite/hook.js':true
            }
        }
    }
};    

If you’re not familiar with these requirejs-config.js files, they allow individual Magento modules to provide configuration values for RequireJS. You can read more about them in our Magento 2 and RequireJS article.

If you are familiar with them, you may be confused by the mixins configuration key. This is not a part of standard RequireJS. This is a special configuration flag Magento introduced to their RequireJS system. Don’t let the word mixins confused you. As we previously mentioned, this has (almost) nothing to do with the programming concept we discussed earlier. It’s just a poorly chosen name.

The mixins property should be an object of key values pairs. The key (Magento_Customer/js/view/customer) is the object whose creation you want to listen for. The value is another object

{
    'Pulsestorm_RequireJsRewrite/hook':true
}

The key of this second object is the RequireJS module that’s going to be listening. We named this module hook, but that’s not required. You can use any module name you like here.

Next, we defined our RequireJS module.

//File: app/code/Pulsestorm/RequireJsRewrite/view/base/web/hook.js
define([], function(){
    'use strict';    
    console.log("Called this Hook.");
    return function(targetModule){
        targetModule.crazyPropertyAddedHere = 'yes';
        return targetModule;
    };
});

If you’re not familiar with how Magento resolves RequireJS module names to URLs, you may want to read through the front end articles in our Magento 2 for PHP MVC Developersseries.

These “listener/hook” modules are standard RequireJS modules. They should return a callable object (i.e. a javascript function). This is the function Magento will call after loading a RequireJS module. It has a single parameter (targetModule) above. This variable will be a reference to whatever the spied on RequireJS module (Magento_Customer/js/view/customer in our example) returns.

Whatever this callback function returns will be treated by the rest of the system as the actual module. That’s why Magento_Customer/js/view/customer had our extracrazyPropertyAddedHere property.

Class Rewrites for Javascript

As we’ve mentioned a few times, this is an incredibly powerful feature. One thing you could do with it os replace method implementations on RequireJS modules that return objects

define([], function(){
    'use strict';    
    console.log("Called this Hook.");
    return function(targetModule){
        targetModule.someMethod = function(){
            //replacement for `someMethod
        }
        return targetModule;
    };
});

Also, if the module in question returns a uiClass based object? You could use uiClass‘sextend method to return a different class that extended the method, but used uiClass‘s_super() feature to call the parent method.

define([], function(){
    'use strict';    
    console.log("Called this Hook.");
    return function(targetModule){
        //if targetModule is a uiClass based object
        return targetModule.extend({
            someMethod:function()
            {
                var result = this._super(); //call parent method

                //do your new stuff

                return result;
            }
        });
    };
});

These are powerful techniques that allow a careful developer, with a small amount of code, to change existing system behavior in exactly the way she wants to. However, like class rewrites in Magento 1, and the class <preference> feature in Magento 2, the above examples are a winner take all situation. While multiple developers can all safely setup their own hooks, they can’t all redefine the same method or function. One person’s modifications will win out over the other person’s.

Fortunately, Magento 2 has a solution for that in the mage/utils/wrapper module.

Wrapping Function Calls

The mage/utils/wrapper module allows for functionality similar to a Magento 2 backend around plugin. Here’s a simple example that should demonstrate what it means to “wrap” a function.

var example = {};
example.foo = function (){
    console.log("Called foo");
}

var wrapper = requirejs('mage/utils/wrapper');

var wrappedFunction = wrapper.wrap(example.foo, function(originalFunction){        
    console.log("Before");
    originalFunction();
    console.log("After");
});

//call wrapped function
wrappedFunction();

//change method definition to use wrapped function
example.foo = wrappedFunction;

If you run the above code, you’ll see the following output

Before
Called foo
After

The wrap method accepts two arguments. The first is the original function you want to wrap. The second is the function you want to wrap it with. The originalFunctionparameter will be a reference to the function you’re trying to wrap (example.foo above). The wrap method returns a function that, when called, will call your function. Your function can, if you desire, call the original function.

The point of the wrapper module is to wrap an existing function call with new code without needing to edit the original function. It’s another powerful technique that javascript’s flexible object system enables. Another great thing about wrapping is, multiple people can do it. Try running the following after running the above code.

var wrappedFunction2 = wrapper.wrap(wrappedFunction, function(originalFunction){        
    console.log("Before 2");
    originalFunction();
    console.log("After 2");
});   

wrappedFunction2();

This means if you’re using it to replace method definitions, you can avoid the winner take all situation we described earlier. Consider the following “mixin” hook.

define(['mage/utils/wrapper'], function(wrapper){
    'use strict';    
    console.log("Called this Hook.");
    return function(targetModule){

        var newFunction = targetModule.someFunction;
        var newFunction = wrapper.wrap(newFunction, function(original){
            //do extra stuff

            //call original method
            var result = original();    

            //do extra stuff                

            //return original value
            return result;
        });

        targetModule.someFunction = newFunction;
        return targetModule;
    };
});    

Here we’ve replaced the definition of someFunction with our wrapped function. This technique also has the advantage of working with RequireJS modules that return functions instead of objects.

Why Call this a Mixin

All this still leaves the question of why Magento calls this feature a mixin. A developer can certainly use this feature to implement mixin like behavior, but the feature itself is more of a listener/hook and has nothing to do with adding methods to objects.

I’m sure, to a non-technical user, all this terminology seems like interchangeable jargon but — words still mean things. When a junior, or even intermediate developer, encounters this feature they’re going to expect some sort of real mixin functionality. They’ll spend hours, possibly days, spinning their wheels trying to make it work until they give up.

When a senior developer encounters this they’re going to wonder why something a simple peer/code-review should have caught made it through engineering, through product, and into a production system. Like so much of Magento 2, it feels like we’re looking at the skeleton of a great new building, but being sold office space.

Regardless, this not really a mixin functionality is powerful, and is the perfect mechanism for changing your system’s behavior. As an extension developer, it’s still a crap shoot as to whether a particular RequireJS module will still be around version to version, but for system integrators who own a particular Magento system (and can adjust customizations over time) not really a mixin customizations will be a boon.

Magento 2: KnockoutJS Integration

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

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 datainto 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 yourMagento 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 theMagento 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 renderedapp/code/Pulsestorm/KnockoutTutorial/view/frontend/templates/content.phtmltemplate. 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/initializeRequireJS module that, when listed as a dependency, will perform any 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. Theknockoutjs/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 thescope 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_template">
    <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 ourcontent.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 stringPulsestorm_KnockoutTutorial/hello. 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 theMagento_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 theMagento_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 out 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 theMagento_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();
});

Notice that Magento’s core developers moved all the binding loading 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 thelayout 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.

KnockoutJS Primer for Magento Developers

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 withdata-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 getTitlemethod. 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 theValueproperty 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 anyjavascript 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 koobject’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.

Magento 2: Javascript Init Scripts

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 withdefine) 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 namedPulsestorm_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 theMagento 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 renderedapp/code/Pulsestorm/JavascriptInitTutorial/view/frontend/templates/content.phtmltemplate.

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 isPulsestorm_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.phtmltemplate 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 ourexample.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 phtmltemplate 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 andincludes 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 ourphtml 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 our 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 inPulsestorm_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-initattribute. 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>A single 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 configparameter.

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 astrict 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 adata-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.