Using ASP.NET MVC with KnockoutJS to Create a Snappy Shopping Cart UI

Posted by: Gregor Suttie , on 6/5/2013, in Category DNC Magazine
Views: 36815
Abstract: Gregor Suttie introduces KnockoutJS, a JavaScript library that bring tons of richness to your ASP.NET MVC web application with features like ‘Declarative Bindings’, ‘Dependency Tracking’, ‘Templating’ and ‘Observables’. Find out how you can bring all this goodness in your ASP.NET MVC apps with a Shopping Cart UI sample

KnockoutJS (KO) is a JavaScript library written by Steve Sanderson who works for Microsoft and is the author of Pro ASP.NET MVC Framework. It is a JavaScript library that helps build apps conforming to the Model View View-Model pattern. KO makes it easier to create rich, desktop-like user interfaces with JavaScript and HTML.

KO uses observers to make your user interface automatically stay in sync with an underlying data model, along with a powerful and extensible set of declarative bindings to enable productive development. In short, if you want your website to have a snappy, slick user interface, with less code, then Knockout is certainly a good library to use.

This article is published from the DNC .NET Magazine – A Free High Quality Digital Magazine for .NET professionals published once every two months

KnockoutJS Features and Extending KnockoutJS

Knockout works along-side any JavaScript library so you can use jQuery and other JavaScript libraries along with KO as your application starts to grow. We first take a look at the features of KO and then dive into a sample that helps explain these.

Declarative Bindings

KnockoutJS gives us declarative bindings which means we can easily get access to HTML DOM elements on our page and use it with any web framework including PHP, Ruby on Rails and even with ASP. It’s free, open source with no dependencies and supports all mainstream browsers including IE6+, Firefox 2+, chrome, Opera and Safari.

Dependency Tracking

Dependency Tracking comes with KnockoutJS so that you can chain relationships between your model data which lets you transform your data when a part of the relationship change gets updated.

Templating

KnockoutJS has inbuilt templating as well as the ability to use custom templating such as JQuery templating or your very own custom templating. KnockoutJS comes with a number of built-in bindings and these make life really easy and straight forward, these include bindings for controlling text and appearance, control flow, and form fields.

With KnockoutJS you can create your own custom bindings and this really could not be easier, so with KnockoutJS we extend it with using custom bindings and templates and these templates can include JQuery templates or plain old HTML templates. Note that using external templates can slow down the rendering slightly and it’s advisable to try to use inline templating where possible.

Custom Binding

As developers, we are always looking for something that adds that little-bit-more to our websites to make them snappier, have a nicer user interface, with less code. KnockoutJS gives us the ability to load our data for the page and have KnockoutJS bind the data to the user interface using simple elegant bindings.

If the data changes or the user makes a change to the webpage, KnockoutJS has 2-way binding that updates the page and reflect the changes on the user interface.

If you have used JQuery, you might be thinking - hang on JQuery does this for me already! Yes you’d be correct, but KnockoutJS can simplify things even further. With KO you can use a number of bindings for a whole range of things and also extend these to create your own easily. Shown below is how you define a custom binding.

ko.bindingHandlers.myCustomBinding = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
      //init logic
    },
   update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
   //update logic
   }
};

How do I get KnockoutJS?

Microsoft is shipping KO as a part of the ASP.NET project templates in Visual Studio. So any new project will have KO added to it via a NuGet package reference. If you’re not working on a new MVC 4 application, you can add it into any web application by using NuGet as below:-

ko

Or by downloading the .js file from its home on Github https://github.com/SteveSanderson/knockout/downloads , add the knockout .js file to your application and reference it in your webpage and your good to go.

Using KnockoutJS in ASP.NET MVC

When using KnockoutJS within a web application, there are a couple of things you can do to make life easier and these are some best practice points from using it, which I have found useful. If you create a new MVC web application in VS 2012, the solution loads and if you take a look at the contents of the packages.config file, we can see that as part of the solution we are pulling in a number of NuGet packages, one of which is:-

<package id="knockoutjs" version="2.1.0" targetFramework="net45" />

Here we are using NuGet to pull in version 2.1.0 of the KnockoutJS library which at the time of writing this article was the latest version, so straight out of the box our solution has a reference to KnockoutJS and we haven’t had to do anything – were off to a great start. Update: At the time of posting this article, the version is 2.2.1.

The Demo

Our demo application is a simple shopping cart order page where we can update the quantity of items in our shopping basket and see a running total being updated. The page has the data passed in from our MVC Controller Action Method and this is then presented to the page. Demo has a ProductController, a set of entities like CartItem, Category, Model, OrderItems and Product, and the Index.cshtml for the UI. Custom scripts are in the JS folder and we have ajaxservice.js, dataservice.shopping.js, index.js and utils.js.

To start with, when using KnockoutJS, it’s a good idea to create a namespace and use it within your JavaScript to keep things nice and tidy, same as you would within your c# codebase. To create a namespace in JavaScript is as simple as:

var OrdersApp = OrdersApp || {};

The View Model

Next we need to create our ViewModel. It’s in the index.js file. A typical example of the ViewModel we might use for our shopping cart is as follows:-

$(function () {
    OrdersApp.Product = function () {
        var self = this;
        self.id = ko.observable();
        self.price = ko.observable();
        self.category = ko.observable();
        self.description = ko.observable();
    };
   
    // The ViewModel
    OrdersApp.vm = function () {
        products = ko.observableArray([]),
        shoppingCart = ko.observableArray([]),
        addToCart = function (product) {
            // Stub
        },
        removeFromCart = function (cartItem) {
            // Stub
        }
    },
    grandTotal = ko.computed(function () {
        // Stub
    }),
    loadProducts = function () {
        // Stub
    };
    return {
        products: products,
        loadProducts: loadProducts,
        shoppingCart: shoppingCart,
        addToCart: addToCart,
        removeFromCart: removeFromCart,
        grandTotal: grandTotal
    };
    }();

    OrdersApp.vm.loadProducts();
    ko.applyBindings(OrdersApp.vm);
});

This is gist of the complete ViewModel, but what we have here is a pure JavaScript representation of the model data (i.e. products and a shoppingCart) and actions to be performed. The ko.applyBindings() statement is used to tell KnockoutJS to use the object as the ViewModel for the page.

Data Binding and Templating

Products are defined as an observableArray and this means that KnockoutJS will track what’s in this array. For example we can push() and pop() items onto the products observableArray and the front end will automatically show us the updated data due to the 2-way binding KnockoutJS has – you can even use the console in your Chrome browser to manipulate the items in your ViewModel and KnockoutJS will take care of the updating of the user interface for you - it’s that simple.

In order to display a list of Products, we would be able to use the built in for-each binding and display our products as follows.

<ul class="saleItems leftFloat" data-bind="foreach:products, beforeRemove:hideItem, afterAdd: showItem">
<li class="mediumProductSquares" >
  <div>
    <div class="dialogTitleBorder">
     <span class="borderTitleText">Details</span>
    </div>
    <div class="leftFloat">
     <div>
      <span>Make: </span><span data-bind="text: model().brand" class="textValues"></span>
     </div>
     <div>
      <span>Model: </span><span data-bind="text: model().name" class="textValues"></span>
     </div>
     <div>
      <span>Price: </span>
      <span data-bind="text: OrdersApp.formatCurrency(price())" class="textValues">
      </span>
     </div>
    </div>
   <button data-bind="jqButton: { }, click: $root.addToCart">Add Item</button>
  </div>
</li>
</ul>

Note that KO also uses the data-bind tag to specify the type and field to bind to in the ViewModel. The elements inside the foreach data-bind are treated as the ‘row-template’ and repeated for each ‘product’ in the Products collection.

Change Tracking

As we can see above, each of the ‘Add Item’ buttons invoke the $root.addToCart method. However on addition the Total gets updated automatically. If we look at the markup for rendering the cart total, we will see the following

<div class="cartSummaryContainer">
<span>Total Items</span><span data-bind="text:shoppingCart().length"></span>
<span>Total Price</span><span data-bind="text:OrdersApp.formatCurrency(grandTotal())"></span>
<button data-bind="enable: shoppingCart().length > 0, click: $root.placeOrder">Place Order</button>
</div>

As we can see here, Total Items and Total Price is bound to the calculated fields of shoppingCart().length and value returned by the grandTotal() function. In Index.js we will see the grandTotal method is defined as follows.

grandTotal = ko.computed(function () {
var total = 0;
$.each(shoppingCart(), function () {
  total += this.extPrice();
});
return total;
})

Essentially we have defined it as a KO computed value that’s calculated for all the elements in the shopping cart. KO ‘observes’ for the changes in the number of shoppingCart items and on change, computes the grandTotal. Once the grandTotal value is computed, KO updates the UI because of the binding. If we put a breakpoint in the above function and add an item to the cart, we can see how KO call
the computed function automatically.

stack-trace

Thus change tracking and two way data binding provide a rich and responsive behavior where changes in one area of the application gets reflected immediately elsewhere. The demo application to show the basic flow using KnockoutJS code can be found on Github here: http://bit.ly/dncmag-snapko

demoapp

The complete demo shows a shopping cart webpage as shown above where you add products to your basket and can update the totals and a basket total is calculated, you can also remove items from the basket and the totals are all kept in sync using KnockoutJS.

The example code covers use of observables and observable Arrays, computed functions, namespaces in your JavaScript and how to use callbacks.

What KnockoutJS is good at?

KnockoutJS is perfect for creating a user interface that responds immediately to the user and that includes adding and removing data. You don’t have to wait on server postbacks and hack away with viewstate or use any of the older tricks such as Update Panels and similar ones. You set up your ViewModel, then fill it with your data and then the user interface is updated using the 2-way binding done for by KnockoutJS. It’s quick, responds immediately and makes the user experience a whole lot better than before the introduction of KnockoutJS.

KnockoutJS gives us the added benefit of separation of concerns and the fact that it allows for better unit testing of the user interface code – we can now test our JavaScript with a tool such as QUnit allowing us to add into our build server so we can run the user interface unit tests before deployment.

Some gotchas to avoid when using KnockoutJS

Having discussed what KnockoutJS is good at, we should cover what problems you might run into using KnockoutJS - it’s actually something to be careful of within JavaScript.

Scoping in JavaScript is a minefield and can really give you sleepless nights if you’re not careful, so a little discipline is required in order to try to avoid falling into some scoping issues when using JavaScript.

When using JavaScript, there are thankfully a few JavaScript patterns you can use and one is the Module Pattern which is useful for organizing independent, self-containing pieces of JavaScript – you can read more about the Module Pattern here http://elegantcode.com/2011/02/15/basic-javascript-part-10-the-module-pattern/

Be careful when using the ‘this’ keyword in JavaScript as you can run into issues easily with the context of the keyword this depending on how you structure your JavaScript.

For a very good tutorial on how to go about structuring your JavaScript that you will use when working with KnockoutJS as well as other great tips I recommend, you take a look at Rob Connery’s Tekpub course here: http://tekpub.com/productions/knockout

Where can I learn more?

In this article I covered an introduction to KnockoutJS; although we covered a fair amount it hopefully left you wanting to know more. I have listed some additional learning resources over here bit.ly/dncmag-snapko in the Readme section.

Summary

In summary KnockoutJS is a fantastic addition to your arsenal as a web-developer when trying to create a slick user interface that responds immediately. You can use it with any web framework and it’s a one file addition to your solution which makes it easy to update if newer versions come out.

With KnockoutJS, you get superb tutorials and there are more and more articles and tutorials popping up all the time. There is no reason not to give it a try as its super easy to get going with, just be mindful of scoping. Also try to refactor your JavaScript code at all times. If you find yourself writing a lot of JavaScript when using KnockoutJS, the chances are there is a better cleaner way (normally methods are only a couple of lines).

Spend a couple of hours using KnockoutJS and you’ll wonder why you’re not using it on every web project – you can even go back and add it into your old web applications and improve the user experience with ease.

Download the entire source code of this article (Github)

Gregor Suttie is a developer who has been working on mostly Microsoft technologies for the past 14 years, he is 35 and from near Glasgow, Scotland. You can Follow him on twitter @gsuttie and read his articles at bit.ly/z8oUjM

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by les on Friday, June 21, 2013 6:01 AM
Excellent example. Shows the power of knockout. Got me reading more. Thanks.

Noticed the animation methods hideItem/show in the ViewModel  are not getting called. After some further reading eventially got solution at http://knockoutjs.com/documentation/foreach-binding.html (Parameters section).


In the Index.cshtml page I changed the foreach calls for products and shoppingCart to

<ul class="saleItems leftFloat" data-bind='foreach: { data: products, beforeRemove: hideItem, afterAdd: showItem }'>
<tbody data-bind="{ name:'cartItems', foreach: {data: shoppingCart, beforeRemove:hideItem, afterAdd: showItem}}">  

respectively and it worked like a charm.
Comment posted by Manuel on Friday, November 8, 2013 9:27 AM
I'm using your shopping cart, but i'm trying to saves my selection in other controller with this:

placeOrder = function () {
            var params = ko.toJSON(ko.utils.unwrapObservable(shoppingCart()));
            
            
            console.log(params);
            alert(params)
            
          
           $.ajax('/Orden/Create', {
                data: params,
                type: "POST",
                contentType: "application/json",
                dataType: "json",
                error: function (xhr, status, error) {
                    console.log(error);
                },
                success: function (response) { alert('hola'); }
            });


        };


But some parameter in the /Orden/Create who is receiving the data, are not working right... what can i do?
Comment posted by madugundu krishna on Wednesday, April 23, 2014 1:45 AM
very useful

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel