It has been quite here for some month as I was very busy on some critical projects and I also got used to be a father to one wonderful baby. Now it’s time to get back to community stuff and this time I will dig to fancy JavaScript library called Knockout. This was one of interesting journeys I have had together with fellow ASP.NET MVP Hajan Selmani.
How I got to Knockout
Knockout was decision that was to be made in one project where UI is making heavy use of AJAX. Although there were other ways how to go we got some technical decisions that were not adequate and that left not much room for us.
As we had business logic implemented in database level we built WCF service so UI can communicate with database and we can also solve some issues in server side code so we don’t affect UI with every change we make.
This is how the communication between layers was organized in big picture. We had to find something to make communication between UI and server as painless as the choice was knockout. Actually Hajan suggested it at first place and it take me a little time to get to this idea but now I like the idea, of course.
What is Knockout?
Knockout is JavaScript library that allows to implement declarative model binding and use observable variables and collections. This is like client side implementation of MVVM but the engine that it is running on is JavaScript. I hope this explanation is simple enough to get the idea.
Coding with Knockout is as follows:
- write code to get data from server,
- write templates for repeated data,
- add data binding attributes to HTML elements,
- bind data to page.
Here is fragment on page that uses Knockout to show data. data-bind attribute is the one that Knockout checks. In this attribute you tell to Knockout all it has to do with element. On this example you can see foreach, click, text and clickBubble attributes.
<ul data-bind="foreach: $root.Products()">
<li class="ui-state-default"
data-bind="click: function(data){$root.SetCurrentProduct(data)">
<span data-bind="text: ProductName"></span>
<div>
<span class="moveUp"
data-bind="click: function(data){MoveProductUp(data)},
clickBubble: false"></span>
<span class="moveDown"
data-bind="click: function(data){MoveProductDown(data)},
clickBubble: false"></span>
</div>
</li>
</ul>
Syntax of attributes may feel weird at first but still it’s better to have one HTML-attribute to avoid possible clashes with other libraries.
Getting data from server
If you are building rich user interface you probably have enough calls for data to server that you need some service classes.
function ProductService() {
}
ProductService.prototype.GetProductsForUser = function (categoryId, callback) {
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: "ProductService.svc/GetProductsForUser",
data: { "categoryId": categoryId },
success: function (response) {
if (typeof callback === "function") callback(response);
},
error: function () {
if (typeof callback === "function") callback("error");
}
});
};
Classes like these help us divide service calls to special service classes on UI side so our code remains organized and clean.
View models
As a next thing we need view models for our pages. View models provide data and operations to HTML page and bindings are controlled by Knockout. If we are using collections that users may change through UI code then we can use observable collections that reflect changes through bindings automatically back to HTML DOM.
Using of view models like this is optional – you can organize your code the way you like. As we had some view specific logics we started using view models so we have view model for each HTML page and we can go on with object-oriented code that we love usually most.
function ProductViewModel() {
var self = this;
self.Products = ko.observableArray();
}
ProductViewModel.prototype.Initialize = function () {
ko.applyBindings(this, $('#ProductForm')[0]);
}
ProductViewModel.prototype.LoadProducts = function () {
this.Products.splice(0, this.Products().length);
productService.LoadProducts(null, function (data) {
if (data == 'error') {
console.error('error loading products');
return;
}
for (var i = 0; i < data.LoadProductsResult.length; i++) {
var product = data.LoadProductsResult[i];
product.Categories = ko.observable(product.Categories);
productModel.Products.push(product);
}
});
}
When view model is initialized then Knockout is binding view model to products form element. After that products are loaded from server and products collection is filled with products. As products collection is array observed by Knockout then products list on screen is filled automatically. We can use templates for single products and keep this way our forms simpler to read.
What were main wins using Knockout?
As I am not big JavaScript guru and we had most forms not very simple then my biggest wins are these:
- no need for long-long code to control the UI,
- no need for long code to bind data to form elements,
- HTML got way cleaner and shorter when using Knockout,
- Knockout worked also in more complex situations where more than one view model per page was needed.
Practically Knockout solved almost all nightmares I was afraid of to happen.
How to get started?
I got started using Knockout home page. They have pretty good tutorial and also good examples:
I started from these pages and got on my feet with Knockout quickly.
Conclusion
Knockout is easy to learn although it takes some time to find out all powerful features it has and find answers to questions when working with more complex scenarios. Although materials at Knockout site are very good they don’t explain all things but using search engines I was able to solve all problems I faced. I also had some problems when Knockout got confused but mostly these were simple issues. After week or two with Knockout I found it to be extremely valuable library to use even in more complex scenarios.
View Comments (1)
I have found Knockout to be a great tool as well. I began with an architecture identical to what you have outlined here. Eventually, I found the need to add one more piece.
I now add a JavaScript view file to complement my HTML view. This is similar to what we find in WPF as a code behind file to complement a XAML view. All JavaScript that directly manipulates the view DOM (e.g. animations, UI control setup and interaction, "$('mySelector').*", etc) belongs in this view file.
Your initialize routine would get moved to the view file, for instance. The view model is then able to follow strict MVVM by not including any view specific logic, but only supplying data for the view.
Knockout can make web development quite enjoyable. Have fun!