View Live Demo
Introduction
Knockout is a JavaScript library which dynamically binds data between different HTML contents and follows the Model-View-View-Model (MVVM) design pattern. One of its core features is Observable, which can detect changes in the view model and updates the model according to that change. It’s a special JavaScript object that can detect dependencies among controls automatically. In this article, we will mostly use observable to bind controls and update data dynamically.
Problem
Consider a metro tile where each tile has a title and description. It has a background color as well. There can be multiple tiles of the same type. Our target is to create tiles dynamically and with every tile, there would be an edit panel where user can edit its title, description and background color. If you are confused about this scenario, then I recommend you to view demo first.
Solution
One of the main features of Knockout is, it can track each element of UI and can update automatically. We will use this feature to bind our metro tiles with its edit panel and when user will change in edit panel, tiles of that panel will change automatically. Knockout will track edit panel with its correspondent tile. For this, first we have to take a JavaScript object where all the property of a metro tile is defined. Here, we have title, description and color as an observable. This observable will detect dependencies between each tile and its edit panel and it will keep each tile object updated automatically. Here is a how Tile
object looks:
var Tile = function (title, description, color) {
var self = this;
self.title = ko.observable(title);
self.description = ko.observable(description);
self.tileColor = ko.observable(color);
self.editPanel = ko.observable(false);
self.expandEdit = function () {
self.editPanel(true);
};
self.collapseEdit = function () {
self.editPanel(false);
};
};
Another additional observable is taken named editPanel
which will detect visibility of its tile’s edit panel. expandEdit
and collapseEdit
will make editPanel true
or false
on click event. Here, ObservableArray
is taken to store all tiles. addTile
and removeTile
functions will add new tile on the view and remove tile will delete its corresponding tile from the view and from the observableArray
as well.
var TileModel = function () {
var self = this;
self.lines = ko.observableArray([
new Tile("Batman", "The Dark Night Rises", "#525252"),
new Tile("Transformer", "Revenge of the Fallen", "#DA532C")
]);
self.addTile = function () {
self.lines.push(new Tile("Title", "Description", RandomColor()))
};
self.removeTile = function (line) {
self.lines.remove(line) };
};
ko.applyBindings(new TileModel());
Also, two tiles are pushed into the array on load and observable will bind them with the HTML control automatically. Then, finally we’ve binded the full model with Knockout with ko.applyBindings()
.
Now let’s take a look at HTML controls which are binded with observable object:
<a id="aCreate" class="btn-add-tile" data-bind='click: addTile'>Add Tile</a>
<div class="tile-container" data-bind='foreach: lines'>
<div class="tile-area">
<div class="tile-elem" data-bind="style: { background: tileColor }">
<p class="tile-description" data-bind="text: description"></p>
</div>
<span class="tile-title" data-bind="text: title" style=""></span>
<div class="bottom-bar">
<span class="arrow" data-bind='click: expandEdit, ifnot:editPanel'>
<img src="Styles/images/MetroDown.png" alt="Open edit panel" /></span>
<span class="arrow" data-bind='click: collapseEdit, if:editPanel'>
<img src="Styles/images/MetroUp.png" alt="Close edit panel" /></span>
<span class="close-btn" data-bind='click: $parent.removeTile'>
<img src="Styles/images/MetroClose.png" alt="Remove tile" /></span>
</div>
<div data-bind="if: editPanel">
<div class="edit-panel">
<label>Title:</label>
<input class="text-title" data-bind="value: title,
valueUpdate: 'afterkeydown'" maxlength="12" />
<label>Description:</label>
<textarea class="txtdesc" rows="2"
data-bind="value: description,
valueUpdate: 'afterkeydown'"></textarea>
</div>
</div>
</div>
</div>
Here, Knockout will search for data-bind
attribute to bind data with its model. Now, on clicking Add Tile button, we are calling addTile
function from TileModel
which adds a new object to lines. With foreach
, Knockout is generating all the tile
objects stored in lines with a loop. Every time a tile is added or removed, it tracks the changes in the model and binds tiles dynamically. Now edit panel’s <textarea>
is binded with <p>
of tile
element with description object. <p>
is updated when <textarea>
has an input character. We are updating the description object after key down. Title will update in the same way like description. So the main thing is all the observables track dependencies among the HTML controls and it updates automatically.
Why Knockout?
In situations like this, where we have to bind data dynamically and need to track all the dependencies among controls, then Knockout is the best choice. We will bind observable with UI controls and observable will notify about changes and it will update automatically. Another good feature is, it can update data very frequently, like when we bind afterkeydown
method, observable detects change and updates the correspondent element at that moment.
Run Source Code
Download the zip file, extract it, open KnockoutTiles.sln file and hit F5. It’s an ASP.NET web project written in Visual Studio 2012 and used knockout.js version 2.2.0.
Conclusion
Knockout follows the MVVM pattern where observable tracks dependencies between model and view model and updates view automatically. This article gives a very basic idea about observable. I recommend you download the source file and see for yourself how it actually works.
Reference
CodeProject