Thanks for visiting my blog!
One of the things I wanted to be able to do is to build longer-lived pages where it made sense without having to resort to post-backs or hacked together JavaScript. In fact, what I really wanted was a client-side ViewModel (e.g. MVVM) for my web page. After looking at several of the existing frameworks for the job, I settled on KnockoutJS as a great solution. The currently released version (1.21) is a good solution, but the developers of the framework have released a Beta (1.3Beta) version that greatly simplifies what I needed. If you want to follow along, make sure you download that version here.
To get started, I simply have a database of Xbox games (yeah, I know). I am using ASP.NET MVC3 for the web part. I’ve got a single page that contains a drop down of genres and a REST-call from the MVC Controller that returns the games for a particular genre. You can look at the code if you’re interest in that part of the story. I am going to focus on the client-side JavaScript.
The first thing I needed was to add the KnockoutJS file into my project. If you go to this blog entry, you can download the JavaScript file to include in your project:
Note that there is a KnockoutJS Nuget package but it includes the 1.21 version currently. For this example, I am using the 1.3 version instead so you’ll need to get it manually.
Next I created a simple view and included Knockout on the page:
<script type="text/javascript" src="/Scripts/knockout-1.3.0beta.js"></script>
I also wanted to separate the JavaScript from my view (note the separation, like I am used to in Silverlight and a great suggestion by Dave Ward). So I created a separate JavaScript file and included it:
<script type="text/javascript" src="/Scripts/view.js"></script>
In the Controller, I am retrieving a list of genres and then simply creating a DropDownList with the results of the Genres:
<div>
@Html.DropDownList("genres",
Model.Select(g =>
new SelectListItem()
{
Text = g.Name,
Value = g.GenreID.ToString()
}),
"(none)")
</div>
This is all pretty standard ASP.NET MVC so I won’t belabor explaining it.
What I wanted was to have a list of games show up when the user picked one of the genres. I can accomplish this by using KnockoutJS and jQuery
KnockoutJS is a framework that allows me to use HTML-based data binding markup to describe my UI, CSS to describe what the design looks like and JavaScript to tie the data to the data binding. The is chiefly accomplished through the concept of observable objects. For example, I created a new JavaScript ‘class’ called gameModel in my view.js by creating members using the observable method on the knockout (e.g. ko) object like so:
// view.js
$(document).ready(function () {
function gameModel() {
this.name = ko.observable();
this.id = ko.observable();
this.genre = ko.observable();
this.releaseDate = ko.observable();
this.price = ko.observable();
this.imageUrl = ko.observable();
};
...
});
The observable call returns an object that not only can store a property, but let the KnockoutJS binding stack know when the property changes (two way binding). In order to use the gameModel ‘class’, I created a view model to store a collection of gameModels like so:
// Define Main ViewModel
var theViewModel = {
games: ko.observableArray([]),
...
};
The games property of the view models ‘class’ will hold the current list of games that are shown in the UI. The **observableArray **object is like the **observable **object but it notifies the data binding stack when a collection changes. The goal here is to have the view model load the games from the REST service and as the collection changes, the HTML should change to react to that. No more manually creating/destroying parts of the markup.
In order to make this work, we must use the data binding syntax in the HTML code:
<div data-bind="foreach: games">
<div class="game-block">
<div>
<img data-bind="attr: { src: imageUrl, alt: name }" /></div>
<div class="game-name" data-bind="text: name">
</div>
</div>
</div>
KnockoutJS uses an attribute called “data-bind” to specify the data binding for a particular element. The syntax is fairly straightforward but let’s look at it from the inside-out:
<div class="game-name"
data-bind="text: name">
</div>
The “text” tells KnockoutJS the type of binding you want. In this case, we want to fill the text of the div with the name property (e.g. the observable object) on the model (gameModel in this case). There are several built-in binding types but it is extensible if you need something custom. These include (but these aren’t all of the binding types):
- visible: Expects the value to be Boolean as to whether an item is visible or not.
- text: Sets the text of the element (logically…e.g. innerText for divs; value for input).
- html: Displays the HTML as the inner part of an element.
- css: Sets the element’s CSS class.
- style: Sets specific CSS style properties on the element.
- attr: Sets attributes on the element.
For example, the attr binding is used to set attributes of the image based on properties of the model:
<div>
<img data-bind="attr: { src: imageUrl, alt: name }" />
</div>
In this example, the src and alt attributes of the img tag are set to properties of the model.
We now have data binding based on the model class, but what about the collection? This is where the data binding syntax of KnockoutJS (specifically 1.3 and later) is so simple and powerful. There is a data binding called foreach. This data binding will repeat what is inside an element based on a collection:
<div data-bind="foreach: games">
<div class="game-block">
<div>
<img data-bind="attr: { src: imageUrl, alt: name }" /></div>
<div class="game-name" data-bind="text: name">
</div>
</div>
</div>
In this case, the entire game-block is repeated for each item in the games collection of the view model. If there are no items in the collection, the element will be empty. To tie the view model to the view, back in the JavaScript (view.js), you simply need to call the KnockoutJS applyBinding method:
// Bind it to the UI
ko.applyBindings(theViewModel);
So you might be wondering about how you get the change of the DropDownList to work? KnockoutJS does have a way to data bind events, but depending on the style of JavaScript you like – you may choose that. I find that the simple jQuery event handling is just simpler:
$('#genres').change(function (event) {
var genreId = $("#genres option:selected").first().val();
if (genreId !== null) {
theViewModel.loadGames(genreId);
}
});
In this case, we’re just using jQuery to wire to the change event and if the genreId isn’t null (e.g. the user picked something) we call the loadGames method of the ViewModel. Even though I’m using jQuery for the event, we’re still leaving the actual work in the view model as that helps centralize it for me. But you may be wondering about the loadGames method since I haven’t shown it yet. Here’s the view model:
// Define Main ViewModel
var theViewModel = {
games: ko.observableArray([]),
loadGames: function (genreId) {
var url = "/Sample/GetGamesByGenre?id=" + genreId;
$.ajax({
url: url,
success: function (response) {
if (response.success) {
theViewModel.games.removeAll();
$.each(response.results, function (x, game) {
theViewModel.games.push(new gameModel()
.id(game.Id)
.name(game.Name)
.releaseDate(game.ReleaseDate)
.price(game.Price)
.imageUrl(game.ImageUrl)
.genre(game.Genre));
});
} else {
alert(response.message);
}
},
error: function (response) {
alert("Failed to get games...");
}
});
}
};
The loadGames just uses a jQuery ajax call to get the data from REST service. The response (if successful) is just a collection of objects. I first call the removeAll method on the collection to make sure it’s clear before we fill it, then push a new gameModel ‘class’ onto the colleciton for each element of the results. Once convention you’ll have to get used to with observable is that you don’t assign the values, but you execute the function that is the property to assign the new value:
// do
game.id(1);
// not
game.id = 1;
Assignment supports chainging (e.g. fluent syntax) so creating a new instance of the gameModel class is like so:
new gameModel()
.id(game.Id)
.name(game.Name)
.releaseDate(game.ReleaseDate)
.price(game.Price)
.imageUrl(game.ImageUrl)
.genre(game.Genre));
The current version of KnockoutJS (1.2.1) works great too, but the templating syntax described here (e.g. foreach) isn’t in that build. Instead they rely on jQuery Templates so the examples you see out there will look a little different than this.
KnockoutJS has some great links that I want to point you to:
You can get the full source (including the database) here: