Tutorials‎ > ‎

[JavaScript] MVVM Implementation Using Knockout.js

posted Mar 15, 2017, 5:37 AM by Benedictus Jason Reinhart

Introduction

MVVM consists of 3 layers, the Model, View and ViewModel. The model layer represents the data of your application, the view layer represents the UI (user interface) or what users see on your application, and lastly the view model does not represent anything in particular, it handles your data binding (model) to your application view. Designing application using the MVVM pattern is great when your application requirement is not really big. Binding data model to your view makes your code more readable, but does not scale really well on a bigger system. It becomes more confusing on which bind on what. It also is an "overkill" for a simple UI or application operation logic when the operation can actually be executed in a simple steps.

Knockout.js is a simple library that helps developers to build a simple page with MVVM structure. With only 25 kb (min+gz), Knockout.js able to do data bindings to make your UI automatically refreshes as your data change while maintaining dependencies by tracking it with chains of relationships between model data. Additionally, Knockout.js does not have any dependency. That means you can use Knockout.js without worrying if it will interfere with your existing libraries, e.g. using Knockout.js with jQuery.

You can download Knockout.js in their official site.

Getting Started

Suppose that we want to build a "Product page" with a shopping cart in it. In order to show the products, we need to pull the data from a server, usually using AJAX request. Knockout.js does not handle these kind of things as it is not the part of the presentation layer, so you probably want to handle these tasks using things like Fetch, jQuery AJAX, etc. The main focus of using Knockout is building your view to present data and while handling any changes that may occur while user interacts with your application.

To start, let's build a simple plain HTML page. Make sure you have downloaded Knockout.js (or install it via npm, whichever way you like the most).
index.html:
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <title></title>  
  5.     <script src="knockout-3.4.1.js"></script>  
  6.     <script src="script.js"></script>  
  7. </head>  
  8. <body>  
  9.     <div align="center">  
  10.         <h1>MVVM Shop</h1>  
  11.         <table border="1" cellspacing="0" cellpadding="10">  
  12.             <tr>  
  13.                 <th>Product Name</th>  
  14.                 <th>Product Price</th>  
  15.                 <th>Add to Cart</th>  
  16.             </tr>  
  17.         </table>  
  18.   
  19.         <br><br>  
  20.   
  21.         <h2>My Cart</h2>  
  22.         <table border="1" cellspacing="0" cellpadding="5">  
  23.             <tr>  
  24.                 <th>Name</th>  
  25.                 <th>Price</th>  
  26.                 <th>Quantity</th>  
  27.                 <th>Subtotal</th>  
  28.                 <th>Action</th>  
  29.             </tr>  
  30.             <tr>  
  31.                 <td colspan="3" align="right"><b>Grand Total</b></td>  
  32.                 <td colspan="1" align="right"></td>  
  33.                 <td></td>  
  34.             </tr>  
  35.             <tr>  
  36.                 <td colspan="5" align="center">  
  37.                     <button>Checkout / Clear Cart</button>  
  38.                 </td>  
  39.             </tr>  
  40.         </table>  
  41.     </div>  
  42. </body>  
  43. </html>  


First, we load the Knockout.js library and a script file for our code. We create a table as a container for our products and a cart to store items that users choose. User can choose the quantity of an item they wish to add to the cart, and the cart will dynamically refresh as the user interacts with it. The checkout button is used to clear the cart only, nothing special will happen (no sending data, as it is not our main topic here).

Moving to script.js, we will code everything in our onload function to make sure our DOM is ready. Here, I make a Product "class" to wrap what each of our product has.
  1. window.onload = function() {  
  2.     var Product = function Product(name, price) {  
  3.         this.name = name;  
  4.         this.price = price;  
  5.         this.quantity = 0;  
  6.     }  


We will use the quantity attribute to keep track the number of the product inserted into the cart. 

Bindings

In Knockout.js, we need to make a ViewModel (the VM in MVVM) as the representation of our UI model. The view model will hold our product data, which we will later bind it to the UI using data-bind attribute in HTML and let Knockout.js do the rest. Add the ViewModel, then immediately bind it to our view:
  1. var ViewModel = function ViewModel(name) {    
  2.     this.products = ko.observableArray([    
  3.         new Product('Potato', 50),    
  4.         new Product('Carrot', 40),    
  5.         new Product('Broccoli', 55),    
  6.         new Product('Tomato', 53),    
  7.     ]);    
  8. };    
  9.   
  10. var viewModel = new ViewModel();  
  11. ko.applyBindings(viewModel);  

Our ViewModel will have products (an observable array, special type that lets Knockout.js to bind data to view) with 4 items. To show the products array, modify your code in your HTML:
  1. <table border="1" cellspacing="0" cellpadding="10">  
  2.     <tr>  
  3.         <th>Product Name</th>  
  4.         <th>Product Price</th>  
  5.         <th>Add to Cart</th>  
  6.     </tr>  
  7.     <!-- ko foreach: products -->  
  8.     <tr>  
  9.         <td><span data-bind="text: name"></span></td>  
  10.         <td><span data-bind="text: price"></span></td>  
  11.         <td>  
  12.             <input style="width: 40px;" type="number" data-bind="value: $data.quantity">  
  13.             <button data-bind="click: $parent.addToCart.bind($data)">Add to Cart</button>  
  14.         </td>  
  15.     </tr>  
  16.     <!-- /ko -->  
  17. </table>


To loop in your observable array Knockout.js does it in a special syntax, comment:
<!-- ko foreach: products -->
will loop the products attribute in our ViewModel (which currently has 4 elements). The loop will repeat everything inside the <!-- ko ... --> until it meets <!-- /ko -->. We bind the name of the current element in the products to the span. User will see the value of the name and price attribute inside the product, because we bind it to the text.

Notice the input of the cart: 
<input style="width: 40px;" type="number" data-bind="value: $data.quantity">

We want to make sure that every time the input element changes, the value of quantity inside the product will also change. We can use the data-bind attribute to bind the value of quantity of the product to the value of the input element.

On the button element, we have something different:
<button data-bind="click: $parent.addToCart.bind($data)">Add to Cart</button> 

We can bind events like click event on a button to the ViewModel. In this case, we bind the click event to the parent of the product, which is the ViewModel itself. Simply said, products is a child of ViewModel, so if we access the parent of products, we would get ViewModel. In the code above, the context of the event binding is still in products, so calling $parent will give us access to ViewModel. Now, when we click on the button, the addToCart function of the ViewModel will be called within the context of $data, the product itself. However, we still do not have the addToCart function so we need to create it:
  1. var ViewModel = function ViewModel(name) {  
  2.     // ...  
  3.     // ...  
  4.     this.cart = ko.observableArray([]);  
  5.   
  6.     this.addToCart = function(product) {  
  7.         var p = Object.assign({}, product);  
  8.         this.cart.push(p);  
  9.         this.calculateGrandTotal();  
  10.     }.bind(this);  
  11.   
  12.     this.grandTotal = ko.observable(0);  
  13.   
  14.     this.calculateGrandTotal = function() {  
  15.         var t = 0;  
  16.         for (var item of this.cart()) {  
  17.             t += item.price * item.quantity;  
  18.         }  
  19.         this.grandTotal(t);  
  20.     }.bind(this);  
  21. };


Now to show the content of the cart, add the following code to your HTML:
  1. <h2>My Cart</h2>  
  2. <table border="1" cellspacing="0" cellpadding="5">  
  3.     <tr>  
  4.         <th>Name</th>  
  5.         <th>Price</th>  
  6.         <th>Quantity</th>  
  7.         <th>Subtotal</th>  
  8.         <th>Action</th>  
  9.     </tr>
  10.     <!-- ko foreach: cart -->  
  11.     <tr>  
  12.         <td data-bind="text: name"></td>  
  13.         <td data-bind="text: price"></td>  
  14.         <td data-bind="text: quantity"></td>  
  15.         <td data-bind="text: (quantity * price)" align="right"></td>  
  16.         <td>  
  17.             <button data-bind="click: $parent.removeFromCart.bind($data)">Remove</button>  
  18.         </td>  
  19.     </tr>  
  20.     <!-- /ko -->  
  21.     <tr>  
  22.         <td colspan="3" align="right"><b>Grand Total</b></td>  
  23.         <td colspan="1" align="right" data-bind="text: grandTotal"></td>  
  24.         <td></td>  
  25.     </tr>  
  26.     <tr>  
  27.         <td colspan="5" align="center">  
  28.             <button data-bind="click: checkout">Checkout / Clear Cart</button>  
  29.         </td>  
  30.     </tr>  
  31. </table>


Here we show all the element inside the cart using foreach loop like how we show the products data. Note that on the checkout button, we do not need to access the parent because we are already in the context of the ViewModel while in the foreach loop, we have to access the $parent because the context is in the product.

We need to add a few more functions that are called above:
  1. this.checkout = function() {  
  2.     this.cart.removeAll();  
  3.     this.calculateGrandTotal();  
  4. }.bind(this);  
  5.   
  6. this.removeFromCart = function(product) {  
  7.     this.cart.remove(product);  
  8.     this.calculateGrandTotal();  
  9. }.bind(this);  


That's it, building an MVVM page is really easy using Knockout.js, and the result is really amazing. Data binding becomes very easy as Knockout.js handles it really nicely, making your page really dynamic and responsive to user's interaction. There are still more in Knockout.js, but this tutorial will only cover on the basic.
ċ
knockout tutorial.rar
(23k)
Benedictus Jason Reinhart,
Mar 15, 2017, 5:39 AM
Comments