I am the captain of my own starship.

Fly with me, "boldly go where no man has gone before!"

KnockOut.js Template Tutorial

Sample Screenshot

This article was published to codeproject.com on 1/5/2018.

Introduction

There are two ways to create a template and use it with KnockOut JS. The first one is very easy to do, define a template in the same JavaScript section, then simply use it. The second one is a little more complicated. This second approach allows the template to be splitted out into separated files, and loaded into the main page for use.

The first approach, although it is simple, it is useless. The second approach promotes resuability, but it is slightly more complicated, and more fun to discuss. It involves the use of require.js and text.js. Even though this is a bit complicated, the official documentation provided by KnockOut.js sufficiently described how to accomplish this. This tutorial will provide working example and the way to test using Jetty Web Server.

Using KnockOut JS

One thing that is cool about KnockOut JS is that it is just one js file that you need to included in the main page before all other javascript files. Then in the main page, KnockOut Js functionality can be freely used.

Using a simple example to show this. Assuming I have a page with a edit box and I want to enter some text in it, then miraculously, the value would show in a paragraph (using <p>some text entered</p>). Here is the code that does all these using KnockOut JS:

<html>
<head>
<title>Knockout Test</title>
</head>
<body>
   <input type="text" data-bind="textInput: helloValue">
   <br/>
   <p>Hello <span data-bind="text: helloValue"></span></p>

   <script type="text/javascript" src="scripts/knockout-3.4.2.js"></script>
   <script type="text/javascript">
      var helloDataModel = function() {
         helloValue = ko.observable("")
      };
      ko.applyBindings(new helloDataModel());
   </script>   
</body>
</html>

After downloading and unzipping the sample application (attached zip sample), in the unzip folder, you should find a file called sample1.html. If you run this in the browser, you will see a text box. And if you enter some text (i.e. "Jimmy") in it, you will see the second line displaying "Hello Jimmy". If there is any problem, check the Developer console of the web browser and see if there is any error regarding the JavaScript configuration.

Approach #1 of Creating Template

Note: from this point on ward, I use function and method interchangably. They meant the same thing.

The first approach of creating a template, then use it with KnockOut JS is very straight forward. In my sample application, you will find a file called "template-test1.html", which shows how straight forward it is to create a template and use it on the web page. Here is the content of this file:

<html>
<head>
<title>Knockout Test</title>
</head>
<body>

   <div data-bind="component: 'view-count'"></div>

   <script type="text/javascript" src="scripts/knockout-3.4.2.js"> </script>
   <script type="text/javascript">
      var myDataModel = function () {
         var self = this;
         
         self.viewCount = ko.observable(1);
         self.clickView = function () {
            var currCount = self.viewCount();
            self.viewCount(currCount + 3);
         };
      };
      
      ko.components.register('view-count', {
         viewModel: myDataModel,
         template: "<p data-bind='text: viewCount'></p><button data-bind='click: clickView'>test</button>"
      });
      ko.applyBindings();
   </script>
</body>
</html>

The above html file would show a paragraph (<p></p>) that shows a number, which is set to 1 initially. Underneath it, there is a button. When the user clicks on the button, the displayed number will increment by 3. Run it, and see if it works for you.

Here is how it works: In the JavaScript code section, you will see the following segment:

ko.components.register('view-count', {
   viewModel: myDataModel,
   template: "<p data-bind='text: viewCount'></p><button data-bind='click: clickView'>test</button>"
});
ko.applyBindings();

What this piece does is first register a component. It is done by the invocation of KnockOut components register method: ko.components.register(...). What this does is add the custom component to the KnockOut registered component list. Inside the method invocation:

  • component name is set to "view-count".
  • After the name, an object with two properties is set, the first property is called viewModel, and is set to function reference called "myDataModel".
  • The next property of the object is called template, it is set with a string of html code, which defines the html component that will be displayed.

The last line is applying the data binding to KnockOut.

The first part of the JavaScript code is to define the function/object that would serve the data model for the view:

var myDataModel = function () {
   var self = this;
   
   self.viewCount = ko.observable(1);
   self.clickView = function () {
      var currCount = self.viewCount();
      self.viewCount(currCount + 3);
   };
};

The view model is called myDataModel, which is referenced by ko.components.register(...). The first thing I did is to get a referece to the this reference and save it in a local variable called self. For this data model, there is a counter that keep tracking the times it was clicked, it is called self.viewCount. It is set to a function called ko.observable(). This is very important to realize. Because as an observable property to a view data model, you can't simply use the equal sign to assign new value or retrieve the value stored. Instead, you have to use it as a method.

The second property in the view model references a function called self.clickView. This function is used by the template "view-count", in the string that defines the html code to be display for the template, there is a button that uses this clickView to increment the click count. Everytime the button is clicked, the current count is incremented by 3. The way this self.clickView works:

  • First it defines a local variable called currCount. And it is assigned to the value by calling self.viewCount. Like I said, you can't assign self.viewCount as a property. It is initialized as ko.observable(), so you can only use it as a method.
  • Once gotten the current value of the self.viewCount(), it is incremented by 3. Then use self.viewCount() as a method to set the new value. This is how to update the view count on the data model.

As you can see, it is pretty easy to create a template and use it in a page with KnockOut JS. But, it is not very useful because it is tight coupled to a specific page. Now that we know how to create a template, the next step is to separate this out into files and integrate into pages when it is needed. It will be more loose coupled and more reusable.

Approach #2 of Creating Template

KnockOut JS itself does not provide out-of-box support for separating the template, and the view model code out into separated files. According to the documentation, one must integrate some third party library to do this. Some further digging shows that I need to use require.js and a sub component of require.js called text.js. "require.js" can be used to include required javascript code. But it does not support loading text resources directly. That is why a subcomponent called "text.js" has to be included as well. Just to be clear, I am not a big expert with "require.js" and "text.js", just enough to get this demo to work. The last thing to mention here is that you can't test the test page without a web server. That it, this sample web page application has to be hosted with a web server. In the next section, I will discuss the testing process.

First, let me show you the code for the main page:

<html>
<head>
<title>Knockout Test</title>
</head>
<body>

   <script type="text/javascript" data-main="scripts/main.js" src="scripts/require.js"></script>
   <div data-bind="component: 'view-count'"></div>
</body>
</html>

There are only two lines in the <body>...</body>. The first line imports "require.js". As you can see, there is no direct import of "knockout-3.4.2.js" into this main html file. However, there is this:

data-main="scripts/main.js"

Basically, this specifies the main entry of the JavaScript program, which is defined in the file called "main.js". The content of this "main.js" looks like this:

require(['knockout-3.4.2'], function(ko) {
   ko.components.register('view-count', {
      viewModel: { require: "test-template" },
      template: { require: "text!../templates/test-template.html" }
   });
   ko.applyBindings();
});

The first line of the file uses a method called require(). Obviously, this is from require.js. The first parameter is an array, which contains a list of comma separated strings, that defines the javascript files that the rest of this script depends on. This is a form of dependency injection. The second parameter is a function that the method require() will execute.

The function that was passed in, what it does is calling the ko.components.register() to register the customized template. Again, you will see viewModel as property #1, and template as property #2. These two properties are assigned with two objects. Both have just one property called require. The values of these require are files that can be dynamically loaded via require.js. The string values are file names without extension. Notice that the require property for template looks a bit strange? It has a prefix of "text!" before it. This is basically using text.js to load the text string from a file from the remote server. Then the relative file path without the file extension would specify the html file that contains the template.

The code for the javascript file test-template.js looks like this:

define(['knockout-3.4.2'], function(ko) {
   var myDataModel = function () {
      var self = this;
      
      self.viewCount = ko.observable(1);
      self.clickView = function () {
         var currCount = self.viewCount();
         self.viewCount(currCount + 3);
      };
   };
   
   return myDataModel;
});

In this one, instead of calling the require() method, it was the define() method from require.js called. The way is similar to the invocation of require() in the main.js. The first parameter defines the dependent javascript files. And the second one is a function that would define function, called myDataModel, that would create the view model object. The code for this myDataModel is the same as the myDataModel in the previous section.

Finally, we have the template used by KnockOut. It is defined in a file called "test-template.html". Here it is:

<p data-bind="text: viewCount">
</p>
<button data-bind="click: clickView">Test</button>

As you can see, the code is also identical to the code from the previous section. With the help of require.js, and its sub component text.js. I was able to slice the orginal file into 4 different files. Although it looks slightly more complicated, the template, and its view model can be separated and potentially reused in a different application. However, I don't like tthe use of require.js and its sub component text.js. Using these basically tight couples my component and its behavior to this require.js, which to my opinion is a terrible thing to do.

Testing Both Approaches Using Jetty Web Server

This second approach of defining the component cannot be tested by double click on the html file called "template-test2.html". If you do, you won't be able to see the action in the web browser. Technically you can with FireFox, but it fails with Chrome and IE. So I use Jetty Web Server to test it. I wrote another article for CodeProject that discuss the approaches of using Jetty Web Server to serve static web content. For testing this demo application, I used the second approach described in that article.

Here is what you do:

  • Download the Jetty Web Server, and unzip it.
  • In the base directory of the Jetty Web Server, find the webapps folder. In it, create a sub folder called "knockout1".
  • In this sub folder (knockout1) "knockout1", create another sub folder called "WEB-INF".
  • In this sub folder (WEB-INF), put in a file called web.xml. I will show you the content of web.xml.
  • Go up one level, in the sub folder "knockout1", copy the entire demo app in it. So that in this sub folder you will have: 1) folder scripts, 2) folder templates, 3) sample1.html, 4) template-test1.html, and 5) template-test2.html.
  • In the folder "scripts", rename all the ".js.txt" files to just ".js" files.
  • Run Jetty Web Server with command java -jar start.jar.

Once the web server started up. you can navigate to:

http://localhost:8080/knockout1/sample1.html
http://localhost:8080/knockout1/template-test1.html
http://localhost:8080/knockout1/template-test2.html

The last url should demonstrate approach #2.

Summary

Well, this is it, another summary of another article on JavaScript. I started out this article as a way to show that I know a little about KnockOut.js. Well, I do. In additional, I learned a little of require.js and its sub component called text.js. It is amazing that the two work well together.

After learning all these, I have to say I am unimpressed with KnockOut.JS. I could imagine that this framework started out with good intentions, and had great promises. Its simplicity is most impressive. However, it lacks a lot of features. It lacks the support of page navigation, which is supported in Angular JS, and also in Ember JS; although not in Vue.JS either. The lack of out of box support of separating the template from main code is somewhat frustrating, and use require.js and its sub components to achieve this is just a hack, and not an ideal one. It creates a tight couple of the two. I just don't like this.

If you were asking me, what would be a great way of using this framework. My answer would be, not using the framework to create simple page web applications, but as a supporting framework for a server-side MVC application.

History