Categories


Archives


Recent Posts


Categories


KnockoutJS Primer for Magento Developers

astorm

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.

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

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
Series Navigation<< Magento 2: Javascript Init ScriptsMagento 2: KnockoutJS Integration >>

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 16th June 2016