Archive
Statistics
Category : Javascript

Performance problems using Knockout observableArray

I am a fan of Knockout. I recommend using Knockout over jQuery for any non-trivial UI. In Knockout, observable() and observableArray() are two basic objects. observable() enables two-way binding between the UI and the javascript View Model. observableArray() provides array functionality to an observable(). observableArray() can be used to store a collection of View Models.

The natural way to add elements to an observableArray is using the push function:


var collection = ko.observableArray();
for(var index=0; index < sourceCollection.length; index++) {
    var item = sourceCollection[index];
    var model = new ViewModel();
    // copy item in to model
    collection.push(model);
}

The above pattern is very common in Knockout applications. From a source collection, we are iterating over each source element, and creating the corresponding View model for it. We then push the View model into the observableArray.

Though the above works fine, it induces performance problems. For each new push into the observableArray(), the UI is recomputed, the computed observables() based on this observableArray() are recomputed, and so on. In short, the simple push operation is computation intensive and leads to performance problems.

In my scenario, a push of 1000 elements took about 7 seconds. By using the below code, insertion into observableArray() happened within 400 milli-seconds.


var collection = ko.observableArray();
var tmpArray = new Array();
for(var index=0; index < sourceCollection.length; index++) {
    var item = sourceCollection[index];
    var model = new ViewModel();
    // copy item in to model
    tmpArray.push(model);
}
this.collection(tmpArray);

By inserting elements into a temporary array, and later assigning the array to the observableArray(), the Knockout.js performance problem is resolved.

The drawback of Knockout.js

Knockout.js is a declarative way to bind Javascript View Models to HTML views. It is definitely a very good framework for building single page applications. But, it has a few drawbacks. But before going over the drawbacks, I will briefly highlight the problem that it solves in ASP.NET MVC.

Central to the ASP.NET MVC theme is the Model class. The Model class is used to render the view with data. Since Model is specific to a View, we used to create a standard Model (similar to DB entity) and a View Model (specific to the view). With Knockout.js, there is no need to create a View Model within the ASP.NET MVC framework.

Instead, we create Javascript View Models. Knockout.js binds these javascript View Models to the View. Knockout.js alleviates the ViewModel problem for MVC developers by shifting the creation of View Models in scripts, and not in C# server-side presentation layer.

ASP.NET MVC developers can now write Views with Knockout markup, standard Models and controller actions which are AJAX / JSON based. The javascript View Models have functions which can trigger an AJAX postback, receive data in the form of JSON, and fill themselves up. Pretty neat.

But, here is the biggest problem of Knockout.js. If a web application does not have a consistent theme of representing data, then the number of View Models created can increase dramatically.

Consider a Employee model which needs to render in a custom list format and a standard tabular format. It is obvious that we have two different views for rendering the employee data. But, behind the scenes, we have to create two javascript View Models to ensure that the views get bound properly. This is an unexpected problem with Knockout.

The problem exist because javascript do not have a rich programming model. It does not lend itself well to inheritance. It is a bit absurd to create a javascript framework for representing View Models. Though such a task is quite possible, it is not within the realm of average developers. So, we are left with creating multiple View Models for representing the same kind of data in different view formats.

Creating multiple view models (with annoying amounts of similarity) is the biggest drawback of Knockout.js. For the time being, I am using Cut-Paste to create multiple View Models and I am staying away from javascript inheritance.

Facebook type scrolling using jQuery

While using Facebook or Twitter, the application loads more data when we scroll down to the bottom of the page. While this may look a bit tricky, it is not that difficult to accomplish. A few lines of jQuery code can do the work.


$(window).scroll(function() { 
  var docH = $(document).height();
  var winH = $(window).height();
  var scrH = $(window).scrollTop();

  var getMore = (scrH + winH) / docH > 0.9;

  if(getMore) {
    // get more data
  }
});

The above code is self explanatory. We get the height of the document (a), the height of the viewport (window) (b), and the height to which the window has scrolled (c). If a combination of view port height (b) and scrolled height (c) is almost equal to the document height (a), then we can get more data.

Knockout.js and ASP.NET MVC

Knockout.js is a javascript framework for creating MVVM like browser apps. MVVM stands for Model-View-ViewModel. Model represents the data. View represents the UI. ViewModel represents how the model is represented within the View. ViewModel also encapsulates the behaviour of the view. For eg, the rule: for some conditions in the data (model), set a green background to the table cell, is encapsulated within the ViewModel.

Knockout.js works on bindings. Every DOM element (View) is bound to JSON data (Model). Knockout.js provides a framework for creating ViewModels. In this post, we will create a simple ASP.NET MVC application which displays a DropDownList. The DropDownList is bound to JSON data coming from the controller. The example here is an adaptation of the example found in Knockout.js home page.

We will start with familiar territory - an ASP.NET MVC application with HomeController and Index View. The GetTickets() action method returns the model (List of tickets) as JSON data:


    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult GetTickets()
        {
            Ticket[] tickets = new Ticket[]
            {
                new Ticket { Name = "Economy", Price = "$99" },
                new Ticket { Name = "Business", Price = "$199" },
                new Ticket { Name = "First Class", Price = "$399" }
            };

            return this.Json(tickets, JsonRequestBehavior.AllowGet);
        }
    }

We have to pick up this data in our Knockout javascript. The code for creating the ViewModel out of the JSON data is shown below:


<script type="text/javascript">

    function TicketsViewModel() {
        this.tickets = ko.observable();
        this.selectedTicket = ko.observable();
    } 

    $(document).ready(
        function () {
            var ticketsViewModel = new TicketsViewModel();

            $.get("/Home/GetTickets", function (data) {
                ticketsViewModel.tickets(data);
            });

            ko.applyBindings(ticketsViewModel);
        });
        
</script>

TicketsViewModel has two properties - tickets (List of tickets), and selectedTicket (Ticket selected in the dropdown). Both the properties are set to ko.observable(). ko is the knockout global variable, which is ubiquitiously used in Knockout.js (similar to $ in jQuery). ko.observable() ensures that any change in the value of tickets are reflected in the UI and vice-versa.

We then write the javascript for retrieving the model and setting it within the ViewModel. The jQuery AJAX calls the GetTickets() method in the HomeController and sets the JSON data to the ViewModel. Finally, ko.applyBindings() applies all the bindings set in the UI.

A point to note about Knockout.js is that: All Knockout.js code should be written after the HTML elements. Generally Knockout code should be placed at the end of the view.

Finally, the actual view (HTML elements) is shown below: 


<div>
    <select data-bind="options: tickets, optionsText: 'Name', optionsCaption: 'Select...', value: selectedTicket">
    </select>
    <p data-bind="with: selectedTicket">You selected <span data-bind="text: Name"></span>, price <span data-bind="text: Price"></span></p>
</div>

The select HTML tag is the Dropdownlist. The data-bind attribute provides the data binding expressions: options, optionsText, optionsCaption, value are all self-explanatory. The paragraph below the dropdownlist displays the selection made by the user. As you can see, Knockout.js provides custom attributes to DOM elements. The data-bind attribute is widely used. We should all familiarize with some of these attributes to do effective Knockout.js programming.

Finally, the script tags should be placed at the top of the page:


    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.7.1.js")"></script>
    <script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.1.0.js")"></script>

jQuery is not required for Knockout.js. But, as I have used jQuery (for making the AJAX call), I have included that as well. Knockout works well without jQuery. The only requirement from Knockout.js is that all the script should be placed at the bottom of the page, and not in the <head> tag as usually done.

Set RadioButton value using jQuery

Setting RadioButton using jQuery should be simple. But, there is a small trivia about it. Consider a simple radiobutton group called Options which has values of Yes and No.


@Html.RadioButton("Option", "Yes")
<label>Yes</label>
@Html.RadioButton("Option", "No")
<label>No</label>

If you want to set the initial value of Option to No, how would you do it? You would probably use the val function as below:


$(document).ready(
    function() {
        $('input[name=Option]').val('No');   
    });

But, the above code does not work. To avoid this problem, I use to write code using the click method as follows:


$(document).ready(
    function() {
        $('input[value=No]').click();
    });

Though the above code works, it is not elegant. The right way to set the RadioButton value is to pass an array of value(s) to the val() function as follows:


$(document).ready(
    function() {
        $('input[name=Option]').val(['No']);
    });

The above code works well, and according to the jQuery API documentation, the preferred way to set RadioButton values.